2

This is a simply question. I have been playing with SQLAlchemy and I'm now at the point of dealing with Unique Constraints in a Table. So, when a Unique Constraint is violated, an IntegrityError exception is raised. If I have multiple Unique Constraints defined for a Table, how can I distinguish between them through the raised exception?

EDIT: I'm using sqlite3 underneath.

FINAL EDIT: AVC answer provides an alternative solution, by checking in the message error for the columns that violated the constraint. It's not an elegant solution but it gets the job done.

  • See [How to get errors details from android.database.SQLException?](http://stackoverflow.com/questions/35637570/how-to-get-errors-details-from-android-database-sqlexception) – CL. Jan 06 '17 at 07:50
  • Not helpful. I'm not using Android nor Java. – Carlos Ferreira Jan 06 '17 at 15:10

1 Answers1

1

The integrity error will tell you which column has violated the constraint. For example:

IntegrityError: (IntegrityError) insert or update on table "my_table" 
            violates foreign key constraint "my_table_some_column_fkey"

In your case you are declaring your constraint like this:

UniqueConstraint('datapath_port_id', 'datapath_id',
    'controller_id', name='unique_registration')

What you are doing here is not declaring multiple uniqueness constrains. That would look something like this:

Column('datapath_port_id', unique=True)
Column('datapath_id', unique=True)
Column('controller_id', unique=True)

Instead you have created a multicolumn or composite uniqueness constraint which means that the combination of all three columns must be unique.

So if you had a database row A, B, C with your multicolumn uniqueness constraint you could still insert A, A, A even though there is already the value A in the first row. Similarly, B, B, B and C, C, C would also be valid inserts. What would fail though is trying to insert A, B, C again. That fails because all the columns match an existing row, which is why the uniqueness error you're seeing lists all the columns.

Here is an example with code:

from sqlalchemy import create_engine
from sqlalchemy import Column, Integer
from sqlalchemy import UniqueConstraint
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base()


class Test(Base):
    __tablename__ = 'test'
    __table_args__ = (
        UniqueConstraint('a', 'b', name='ab_constraint'),
        UniqueConstraint('b', 'c', name='bc_constraint'))
    id = Column(Integer, primary_key=True)
    a = Column(Integer)
    b = Column(Integer)
    c = Column(Integer)


Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

session.add(Test(a=1, b=1, c=1))
session.add(Test(a=2, b=2, c=2))

From there we can try to violate the different uniqueness constraints to see what happens. If try to run this command:

session.add(Test(a=1, b=1, c=2))
session.commit()

We get:

sqlalchemy.exc.IntegrityError: (IntegrityError) UNIQUE constraint failed: test.a, test.b u'INSERT INTO test (a, b, c) VALUES (?, ?, ?)' (1, 1, 2)

And then if we try to violate the second constraint:

session.add(Test(a=1, b=2, c=2))
session.commit()

We instead get:

sqlalchemy.exc.IntegrityError: (IntegrityError) UNIQUE constraint failed: test.b, test.c u'INSERT INTO test (a, b, c) VALUES (?, ?, ?)' (1, 2, 2)

With the first case specifying that test.a and test.b violated the constraint and the second one specifying test.b and test.c.

The names I chose for those constraints (ab_constraint and bc_constraint) are however are not included in the response. They are however present in at the database level. We can see this by echoing the database creation commands:

CREATE TABLE test (
    id INTEGER NOT NULL,
    a INTEGER,
    b INTEGER,
    c INTEGER,
    PRIMARY KEY (id),
    CONSTRAINT ab_constraint UNIQUE (a, b),
    CONSTRAINT bc_constraint UNIQUE (b, c)
)

However, even at the database level (I'm using SQLite) the constraint name is not used in the error message:

sqlite> INSERT INTO test (a, b, c) VALUES (1, 1, 1);
sqlite> INSERT INTO test (a, b, c) VALUES (2, 2, 2);
sqlite> INSERT INTO test (a, b, c) VALUES (1, 1, 2);
Error: UNIQUE constraint failed: test.a, test.b
Community
  • 1
  • 1
ACV
  • 1,895
  • 1
  • 19
  • 28
  • This is weird because the result for my constraint, did not returned that to me. I forgot to mention that i'm using sqlite3 underneath. This was the result of the Unique Constraint. `(sqlite3.IntegrityError) UNIQUE constraint failed: hosts.datapath_port_id, hosts.datapath_id, hosts.controller_id [SQL: 'INSERT INTO hosts (host_mac, datapath_port_id, datapath_id, controller_id, registration_date) VALUES (?, ?, ?, ?, ?)'] [parameters: ('AA-BB-CC-DD-EE-FF', 1, 1, 1, '2017-01-06 15:06:38.429267')] ` – Carlos Ferreira Jan 06 '17 at 15:07
  • The constaint is declared as `UniqueConstraint('datapath_port_id', 'datapath_id', 'controller_id', name='unique_registration'),` – Carlos Ferreira Jan 06 '17 at 15:08
  • I think I understand the source of your confusion, I've updated my answer, let me know if it helps. – ACV Jan 06 '17 at 17:17
  • Sort of. When I declared the Uniqueness, I really wanted the uniqueness to be the combination of the three columns. I really want the database to reject multiple `A,B,C` entries. The thing is, when I gave the `UniqueConstraint` a name, I wanted the exception `IntegrityError` to report its name. If I had two multi-column `UniqueConstrains` for the table (ex: `UniqueConstraint(A,B)` and `UniqueConstraint(C,D)`), how would I distinguish between them if a constraint violation would occur? – Carlos Ferreira Jan 06 '17 at 22:58
  • So, in other words, there is no way to know which one of the constraints was violated. I just have to go around my code and structure it in a way, that I can know for sure which one of the constraints was violated. – Carlos Ferreira Jan 08 '17 at 02:54
  • The error will tell you which columns constraint were violated, it just doesn't use the constraint name. – ACV Jan 08 '17 at 21:03
  • Yep, I noticed that. I'm gonna have to work with it. Thank you for your support. – Carlos Ferreira Jan 09 '17 at 12:19