【问题标题】:SQL can I have a "conditionally unique" constraint on a table?SQL 我可以对表有“条件唯一”约束吗?
【发布时间】:2011-03-18 00:51:18
【问题描述】:

在我的职业生涯中,我曾多次遇到过这个问题,但我当地的同行似乎都无法回答。假设我有一个表,其中有一个“描述”字段,它是一个候选键,除了有时用户会在过程中途停止。因此,对于可能 25% 的记录,此值为空,但对于所有非空的,它必须是唯一的。

另一个例子可能是一个表,它必须维护一个记录的多个“版本”,并且一个位值指示哪个是“活动”的。所以“候选密钥”总是被填充,但可能存在三个相同的版本(有效位为 0),只有一个有效(有效位为 1)。

我有解决这些问题的替代方法(在第一种情况下,在存储过程或业务层中强制执行规则代码,在第二种情况下,使用触发器填充存档表并在需要时对表进行 UNION历史)。我不想要替代品(除非有明显更好的解决方案),我只是想知道是否有任何风格的 SQL 可以以这种方式表达“条件唯一性”。我正在使用 MS SQL,所以如果有办法做到这一点,那就太好了。我主要只是在学术上对这个问题感兴趣。

【问题讨论】:

  • 执行唯一索引或检查约束无法处理的条件规则的另一种方法是使用触发器。
  • 感谢计算机指针。它绝对适用于情况 2。我在发布之前进行了搜索,但我实际上并没有搜索我最终在标题中使用的关键字,否则我会找到它!您发布的链接并没有完全解决情况#1,这在 2005 年和 2008 年通过 Tom H. 的建议和 Arthur 的建议解决了。gbn:我使用的是 2005,这就是为什么我不知道索引过滤器的原因2008.
  • 有没有办法让我接受两个答案?

标签: sql sql-server sql-server-2005 unique-constraint check-constraints


【解决方案1】:

感谢 cmets,这个答案的初始版本是错误的。

这是一个使用计算列的技巧,它有效地允许 SQL Server 中的可为空的唯一约束:

create table NullAndUnique 
    (
    id int identity, 
    name varchar(50),
    uniqueName as case 
        when name is null then cast(id as varchar(51)) 
        else name + '_' end,
    unique(uniqueName)
    )

insert into NullAndUnique default values
insert into NullAndUnique default values -- Works
insert into NullAndUnique default values -- not accidentally :)
insert into NullAndUnique (name) values ('Joel')
insert into NullAndUnique (name) values ('Joel') -- Boom!

name 为空时,它基本上使用id+ '_' 是为了避免名称可能是数字的情况,例如1,这可能与id 发生冲突。

【讨论】:

  • 真的吗?来自 MSDN:“在唯一索引中使用的列应设置为 NOT NULL,因为在创建唯一索引时将多个空值视为重复。”
  • 不,SQL Server NOT 允许在唯一约束覆盖的列中存在多个空值。 SQL 标准规定应该允许这样做(因为 NULL 甚至不等于它自己),但是微软的 SQL Server 在这一点上没有遵循标准。
【解决方案2】:

如果您使用的是 SQL Server 2008,索引过滤器可能是您的解决方案:

http://msdn.microsoft.com/en-us/library/ms188783.aspx

这就是我强制使用多个 NULL 值的唯一索引的方式

CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL

【讨论】:

  • 啊 - 基于函数的索引... DBA 讨厌那些
  • @OMG Ponies 虽然对 DBA 来说很糟糕,但这是唯一可行的解​​决方案
  • @msarchet:工作阻碍了 ATM 的工作,但 OP 中描述的表格设置对我来说并不理想。但享受与 DBA 战斗 =)
  • @OMG 小马:嗯?它是一个过滤索引,就像其他 DBMS 已经并最终添加到 SQL Server 2008...
  • 为什么 GUI 不支持这个?确定在“过滤器”部分中指定 WHERE 条件很简单吗?就目前而言,没有人一眼就能看出过滤器的存在。
【解决方案3】:

甲骨文可以。在 Oracle 的索引中,B树不会对完全空键进行索引,Oracle 使用 B树索引来强制执行唯一约束。

假设一个希望基于 ACTIVE_FLAG 设置为 1 的 ID_COLUMN 版本:

CREATE UNIQUE INDEX idx_versioning_id ON mytable 
  (CASE active_flag WHEN 0 THEN NULL ELSE active_flag END,
   CASE active_flag WHEN 0 THEN NULL ELSE id_column   END);

【讨论】:

    【解决方案4】:

    对于尚未完成的描述,我不会将它们与最终描述放在同一个表中。然后,最终表将在描述上具有唯一索引或主键。

    在活动/非活动的情况下,我可能有单独的表,就像您对“存档”或“历史”表所做的那样,但至少在 MS SQL Server 中执行此操作的另一种可能方法是通过使用索引视图:

    CREATE TABLE Test_Conditionally_Unique
    (
        my_id   INT NOT NULL,
        active  BIT NOT NULL DEFAULT 0
    )
    GO
    CREATE VIEW dbo.Test_Conditionally_Unique_View
    WITH SCHEMABINDING
    AS
        SELECT
            my_id
        FROM
            dbo.Test_Conditionally_Unique
        WHERE
            active = 1
    GO
    CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id)
    GO
    
    INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
    VALUES (1, 0)
    INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
    VALUES (1, 0)
    INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
    VALUES (1, 0)
    INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
    VALUES (1, 1)
    INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
    VALUES (2, 0)
    INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
    VALUES (2, 1)
    INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
    VALUES (2, 1)    -- This insert will fail
    

    您也可以对 NULL/Valued 描述使用相同的方法。

    【讨论】:

    • 谢谢,这适用于 2005 年,实际上我今天只是在处理另一个问题的索引视图。这是一个非常棒的应用程序!
    【解决方案5】:

    我不完全了解您的预期用途或表格,但您可以尝试使用一对一的关系。将这个“有时”唯一的列拆分到一个新表中,在新表中的该列上创建唯一索引,并使用原始表 PK FK 回到原始表。当“唯一”数据应该存在时,只有在这个新表中有一行。

    旧表:

    TableA
    ID    pk
    Col1  sometimes unique
    Col...
    

    新表:

    TableA
    ID
    Col...
    
    TableB
    ID   PK, FK to TableA.ID
    Col1 unique index
    

    【讨论】:

      猜你喜欢
      • 2013-08-20
      • 2021-07-22
      • 2020-07-24
      • 2010-10-26
      • 2014-02-14
      • 1970-01-01
      • 2013-02-21
      • 1970-01-01
      相关资源
      最近更新 更多