15

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

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

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

4

5 回答 5

36

如果您使用的是 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
于 2010-07-19T17:36:23.770 回答
3

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

在活动/非活动的情况下,我可能有单独的表,就像你对“存档”或“历史”表所做的那样,但至少在 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 描述使用相同的方法。

于 2010-07-19T18:30:53.970 回答
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!

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

于 2010-07-19T17:27:59.000 回答
0

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

旧表:

TableA
ID    pk
Col1  sometimes unique
Col...

新表:

TableA
ID
Col...

TableB
ID   PK, FK to TableA.ID
Col1 unique index
于 2010-07-19T18:44:04.227 回答
-1

甲骨文可以。在 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);
于 2010-07-19T18:01:12.473 回答