【问题标题】:Treating `null` as a distinct value in a table unique constraint将“null”视为表唯一约束中的不同值
【发布时间】:2019-08-25 13:43:27
【问题描述】:

我有一个表格,用于为客户定义默认和自定义选项。如果custom_id 字段有一个值,那么它代表一个独特的自定义作业的记录。如果为空,则记录代表客户端的默认选项。

我的问题是我想在两种情况下强制执行唯一性:

  1. custom_idclientoption 都是非空的
  2. clientoption 不为空,但 custom_id 为空

下面的表定义适用于第一种情况,但不适用于第二种情况,因为 null 不被视为值。有没有办法让 null 被视为一个值?

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)

这是一些示例数据以及按顺序添加时的结果:

+----+----------+----------+--------+---------------------------------------------+
| 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 |
+----+----------+----------+--------+---------------------------------------------+

这个related answer 使用MySQL 完成了我正在寻找的工作。理想的解决方案是使用 sqlalchemy。

【问题讨论】:

  • 您能否提供示例数据来帮助您解释您的目标?
  • documentation 表明这是标准 SQL 行为; NULL 不能违反唯一性约束。您需要一个实际值来指示缺少自定义 ID。
  • @chepner 是的,这绝对是标准的 SQL 行为。我在问是否有办法使用 sqlalchemy 解决它
  • 有一个相关的问题,它在 MySQL 的插入和更新语句之前实现了我正在寻找的内容。添加到问题中。
  • 问题是您的数据库本身(SQLAlchemy 只是一个接口)允许第 5 行。我建议使用不可为空的布尔值“has_custom”列而不是自定义 ID 本身在唯一性约束中。 (当然,问题就变成了阻止记录具有两列的 True/Null 组合。)

标签: python postgresql sqlalchemy


【解决方案1】:

根据this answer推荐的方法,解决方案是创建两个partial indexes

对问题中的示例使用 sqlalchemy,如下所示:

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)
        ),
    )

【讨论】:

    【解决方案2】:

    我愿意

    CREATE UNIQUE INDEX ON atable
       (client, option, coalesce(custom_id, -42));
    

    其中-42custom_id 不能出现的值。

    它是如何工作的?

    如果有两行具有相同的clientoptioncustom_id,它们都是NOT NULL,它将像常规唯一索引一样工作,并且会阻止添加第二行。

    如果有两个具有相同clientoption 的行都具有custom_id IS NULL,则索引将阻止添加第二行,因为它索引-42 而不是NULL,并且两个索引元组将是相同的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-20
      • 2021-01-22
      相关资源
      最近更新 更多