在我的职业生涯中,我已经有过几次这样的事情,我的当地同行似乎都没有能够回答这个问题。假设我有一个具有“描述”字段的表,该字段是候选键,除了有时用户将在该过程的中途停止。因此,对于大约25%的记录,此值为null,但对于所有非NULL的记录,它必须是唯一的。
另一个例子可能是一个必须保持记录的多个“版本”的表,而一个位值表示哪一个是“活动的”。因此总是填充“候选键”,但可能有三个版本相同(活动位为0),只有一个有效(活动位为1)。
我有替代方法来解决这些问题(在第一种情况下,在存储过程或业务层中强制执行规则代码,在第二种情况下,使用触发器填充存档表,并在需要时使用UNION填充表历史)。我不想要替代方案(除非有明显更好的解决方案),我只是想知道SQL的任何风格是否能以这种方式表达“条件唯一性”。我正在使用MS SQL,所以如果有办法在那里做,那很好。我大多只是在学术上对这个问题感兴趣。
答案 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