I have a table which I use to define default and custom options for a client. If the custom_id
field has a value then it represents a record for a unique custom job. If it is empty then the record represents the default option for the client.
My issue is that I would like to enforce uniqueness in two situations:
custom_id
, client
and option
are all non-null client
and option
are non-null, but custom_id
is null The table definition below works in the first case, but not in the second, since null is not treated as a value. Is there a way to make it so that null is treated as a value?
class OptionTable(Base):
__tablename__ = "option_table"
__table_args__ = (
UniqueConstraint("custom", "client", "option", name="uix_custom_client_option"),
)
id = Column(Integer, primary_key=True)
custom_id = Column(Integer, ForeignKey("custom.id"), nullable=True)
client = Column(String, nullable=False)
option = Column(String, nullable=False)
Here is some sample data and the result when they are added in order:
+----+----------+----------+--------+---------------------------------------------+
| id | CustomID | Client | Option | result |
+----+----------+----------+--------+---------------------------------------------+
| 1 | 123 | MegaCorp | Apple | OK |
| 2 | 123 | MegaCorp | Apple | not unique |
| 3 | NULL | MegaCorp | Apple | OK |
| 4 | NULL | MegaCorp | Google | OK |
| 5 | NULL | MegaCorp | Google | this one should fail, but currently doesn't |
+----+----------+----------+--------+---------------------------------------------+
This related answer does what I'm looking for, using MySQL. The ideal solution would be to do this using sqlalchemy.
I would do
CREATE UNIQUE INDEX ON atable
(client, option, coalesce(custom_id, -42));
where -42
is a value that cannot occur for custom_id
.
How does it work?
If there are two rows with identical client
, option
and custom_id
, all of which are NOT NULL
, it will work just like a regular unique index and will prevent the second row from being added.
If there are two rows with identical client
and option
that both have custom_id IS NULL
, then the index will prevent the second row from being added, because it indexes -42
instead of NULL
, and the two index tuples would be identical.
Based on the approach recommended in this answer , the solution is to create two partial indexes .
Using sqlalchemy for the example in the question, this looks like:
class OptionTable(Base):
__tablename__ = "option_table"
id = Column(Integer, primary_key=True)
custom_id = Column(Integer, ForeignKey("custom.id"), nullable=True)
client = Column(String, nullable=False)
option = Column(String, nullable=False)
__table_args__ = (
Index(
"uix_custom_client_option",
"custom_id",
"client",
"option",
unique=True,
postgresql_where=custom_id.isnot(None)
),
Index(
"uix_client_option",
"client",
"option",
unique=True,
postgresql_where=custom_id.is_(None)
),
)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.