SQL我可以在表上有一个“条件唯一”约束吗?

时间:2010-07-19 17:21:38

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

在我的职业生涯中,我已经有过几次这样的事情,我的当地同行似乎都没有能够回答这个问题。假设我有一个具有“描述”字段的表,该字段是候选键,除了有时用户将在该过程的中途停止。因此,对于大约25%的记录,此值为null,但对于所有非NULL的记录,它必须是唯一的。

另一个例子可能是一个必须保持记录的多个“版本”的表,而一个位值表示哪一个是“活动的”。因此总是填充“候选键”,但可能有三个版本相同(活动位为0),只有一个有效(活动位为1)。

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

5 个答案:

答案 0 :(得分:30)

如果您使用的是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

答案 1 :(得分:2)

如果描述尚未完成,我将不会在最终描述的同一表格中。然后,最终表将在描述中具有唯一索引或主键。

在活动/非活动的情况下,我可能会像使用“存档”或“历史”表一样使用单独的表,但在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描述使用相同的方法。

答案 2 :(得分:1)

Oracle确实如此。 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);

答案 3 :(得分:0)

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

这是一个使用计算列的技巧,它有效地允许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!

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

答案 4 :(得分:0)

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

旧桌子:

TableA
ID    pk
Col1  sometimes unique
Col...

新表:

TableA
ID
Col...

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