我如何创build一个也允许空值的唯一约束?

我想对我将要用GUID填充的列有一个唯一的约束。 但是,我的数据包含这些列的空值。 如何创build允许多个空值的约束?

这是一个示例场景 。 考虑这个模式:

CREATE TABLE People ( Id INT CONSTRAINT PK_MyTable PRIMARY KEY IDENTITY, Name NVARCHAR(250) NOT NULL, LibraryCardId UNIQUEIDENTIFIER NULL, CONSTRAINT UQ_People_LibraryCardId UNIQUE (LibraryCardId) ) 

然后看看我想要实现的代码:

 -- This works fine: INSERT INTO People (Name, LibraryCardId) VALUES ('John Doe', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA'); -- This also works fine, obviously: INSERT INTO People (Name, LibraryCardId) VALUES ('Marie Doe', 'BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB'); -- This would *correctly* fail: --INSERT INTO People (Name, LibraryCardId) --VALUES ('John Doe the Second', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA'); -- This works fine this one first time: INSERT INTO People (Name, LibraryCardId) VALUES ('Richard Roe', NULL); -- THE PROBLEM: This fails even though I'd like to be able to do this: INSERT INTO People (Name, LibraryCardId) VALUES ('Marcus Roe', NULL); 

最后的声明失败并带有一条消息:

违反UNIQUE KEY约束“UQ_People_LibraryCardId”。 不能在对象'dbo.People'中插入重复的键。

我怎样才能改变我的模式和/或唯一性约束,以便它允许多个NULL值,同时仍然检查实际数据的唯一性?

SQL Server 2008 +

您可以使用WHERE子句创build一个接受多个NULL的唯一索引。 请参阅下面的答案 。

在SQL Server 2008之前

您不能创build一个UNIQUE约束并允许NULL。 您需要设置NEWID()的默认值。

在创buildUNIQUE约束之前将现有的值更新为NEWID(),其中NULL。

你正在寻找的是ANSI标准SQL:92,SQL:1999和SQL:2003的一部分,即UNIQUE约束必须禁止重复的非NULL值,但接受多个NULL值。

然而,在SQL Server的微软世界中,只允许一个NULL,但多个NULL不是…

SQL Server 2008中 ,您可以基于排除NULL的谓词定义唯一的已过滤索引:

 CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull ON YourTable(yourcolumn) WHERE yourcolumn IS NOT NULL; 

在早期版本中,可以使用NOT NULL谓词来执行VIEWS来强制约束。

SQL Server 2008和以上

只需过滤一个独特的索引:

 CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName ON dbo.Party(SamAccountName) WHERE SamAccountName IS NOT NULL; 

在较低版本中,物化视图仍然不是必需的

对于SQL Server 2005及更早版本,您可以在没有视图的情况下执行此操作。 我只是添加了一个独特的约束,就像你要求我的一个表。 鉴于我希望列SamAccountName唯一性,但我想要允许多个NULL,我使用物化列而不是物化视图:

 ALTER TABLE dbo.Party ADD SamAccountNameUnique AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID))) ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName UNIQUE (SamAccountNameUnique) 

只需要在计算列中放入一些东西,当实际需要的唯一列为NULL时,这些东西在整个表中保证是唯一的。 在这种情况下, PartyID是一个标识列,数字将永远不会匹配任何SamAccountName ,所以它为我工作。 你可以尝试你自己的方法 – 确保你了解你的数据的领域,以便不可能与真实数据相交。 这可以像预先设定一个差异性字符一样简单:

 Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID)) 

即使PartyID在某一天变成非数字,并且可能与SamAccountName一致,现在也不重要。

尽pipe如此,桌面上的空间可能并不是最好的。 在SQL Server 2008中,绝对使用过滤的解决scheme!

请注意,如果您不想使用索引,但希望使用一些磁盘空间预先计算expression式以便稍后保存CPU,则还可以将关键字PERSISTED添加到列定义的末尾。

争议

请注意,一些数据库专业人士将这看作是“代理空值”的情况,这肯定会有问题(主要是由于围绕试图确定什么时候是真正的价值缺失数据替代价值的问题;还可能存在问题与非NULL替代值的数量疯狂地相乘)。

不过,我相信这个情况是不一样的。 我添加的计算列永远不会被用来确定任何东西。 它本身没有意义,并且没有编码在其他正确定义的列中没有单独find的信息。 它不应该被选中或使用。

所以,我的故事是,这不是代理NULL,我坚持! 由于除了欺骗UNIQUE索引忽略NULL以外,实际上我们不需要非NULL值,所以我们的用例没有任何正常的代理NULL创build出现的问题。

所有这一切,我没有问题,而是使用索引视图 – 但它带来了一些问题,如需要使用SCHEMABINDING 。 在您的基本表中添加一个新的列会很有趣(至less您必须删除索引,然后删除视图或更改视图以避免模式绑定)。 请参阅在SQL Server(2005) (也是更高版本) (2000)中 创build索引视图的完整(长) 要求列表 。

更新

如果您的列是数字,那么确保使用Coalesce的唯一约束不会导致冲突可能会遇到一些挑战。 在这种情况下,有一些select。 一个可能是使用一个负数,把“替代空值”仅放在负值范围内,而“真实值”只在正值范围内。 或者,可以使用以下模式。 在表Issue (其中IssueIDPRIMARY KEY )中,可能有也可能不是TicketID ,但是如果有TicketID ,则它必须是唯一的。

 ALTER TABLE dbo.Issue ADD TicketUnique AS (CASE WHEN TicketID IS NULL THEN IssueID END); ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull UNIQUE (TicketID, TicketUnique); 

如果IssueID 1具有票证123,则UNIQUE约束将位于值(123,NULL)上。 如果IssueID 2没有票证,它将在(NULL,2)上。 有人认为这个约束不能重复表中的任何行,并且仍然允许多个NULL。

对于正在使用Microsoft SQL Server Manager并希望创build唯一但可为空索引的用户,您可以像通常那样在索引属性中为您的新索引创build唯一索引,请从左侧面板中select“filter”,然后input你的filter(这是你的where子句)。 它应该读取像这样的东西:

 ([YourColumnName] IS NOT NULL) 

这适用于MSSQL 2012

当我应用下面的唯一索引时:

 CREATE UNIQUE NONCLUSTERED INDEX idx_badgeid_notnull ON employee(badgeid) WHERE badgeid IS NOT NULL; 

每个非空更新和插入失败,错误如下:

更新失败,因为下列SET选项有不正确的设置:'ARITHABORT'。

我在MSDN上find了这个

在创build或更改计算列或索引视图上的索引时,SET ARITHABORT必须为ON。 如果SET ARITHABORT为OFF,则在计算列或索引视图上具有索引的表上的CREATE,UPDATE,INSERT和DELETE语句将失败。

所以要做到这一点,我做到了这一点

右键单击[数据库] – >属性 – >选项 – >其他选项 – >其他 – >算术中止已启用 – > true

我相信可以在代码中使用这个选项

 ALTER DATABASE "DBNAME" SET ARITHABORT ON 

但是我没有testing过这个

创build一个仅select非NULL列并在视图上创buildUNIQUE INDEX的视图:

 CREATE VIEW myview AS SELECT * FROM mytable WHERE mycolumn IS NOT NULL CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn) 

请注意,您需要在视图而不是表格上执行INSERTUPDATE

你可以用INSTEAD OF触发器来做:

 CREATE TRIGGER trg_mytable_insert ON mytable INSTEAD OF INSERT AS BEGIN INSERT INTO myview SELECT * FROM inserted END 

可以在聚集索引视图上创build唯一的约束

您可以像这样创build视图:

 CREATE VIEW dbo.VIEW_OfYourTable WITH SCHEMABINDING AS SELECT YourUniqueColumnWithNullValues FROM dbo.YourTable WHERE YourUniqueColumnWithNullValues IS NOT NULL; 

和这样的独特约束:

 CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_OFYOURTABLE ON dbo.VIEW_OfYourTable(YourUniqueColumnWithNullValues) 

也许考虑一个“ INSTEAD OF ”触发器并自己检查一下? 使用列上的非群集(非唯一)索引启用查找。

如前所述,SQL Server在UNIQUE CONSTRAINT中没有实现ANSI标准。 自2007年以来,Microsoft Connect上有一张票据 。正如现在所build议的那样,现在最好的select是使用另一个答案或计算列中所述的过滤索引,例如:

 CREATE TABLE [Orders] ( [OrderId] INT IDENTITY(1,1) NOT NULL, [TrackingId] varchar(11) NULL, ... [ComputedUniqueTrackingId] AS ( CASE WHEN [TrackingId] IS NULL THEN '#' + cast([OrderId] as varchar(12)) ELSE [TrackingId_Unique] END ), CONSTRAINT [UQ_TrackingId] UNIQUE ([ComputedUniqueTrackingId]) ) 

它也可以在devise者中完成

右键单击索引> 属性来获得这个窗口

捕获

你不能用UNIQUE约束来做到这一点,但你可以在触发器中做到这一点。

  CREATE TRIGGER [dbo].[OnInsertMyTableTrigger] ON [dbo].[MyTable] INSTEAD OF INSERT AS BEGIN SET NOCOUNT ON; DECLARE @Column1 INT; DECLARE @Column2 INT; -- allow nulls on this column SELECT @Column1=Column1, @Column2=Column2 FROM inserted; -- Check if an existing record already exists, if not allow the insert. IF NOT EXISTS(SELECT * FROM dbo.MyTable WHERE Column1=@Column1 AND Column2=@Column2 @Column2 IS NOT NULL) BEGIN INSERT INTO dbo.MyTable (Column1, Column2) SELECT @Column2, @Column2; END ELSE BEGIN RAISERROR('The unique constraint applies on Column1 %d, AND Column2 %d, unless Column2 is NULL.', 16, 1, @Column1, @Column2); ROLLBACK TRANSACTION; END END 
 CREATE UNIQUE NONCLUSTERED INDEX [UIX_COLUMN_NAME] ON [dbo].[Employee]([Username] ASC) WHERE ([Username] IS NOT NULL) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF, MAXDOP = 0) ON [PRIMARY]; 

这个代码如果你做一个registry单与文本框,并使用插入和你的文本框是空的,你点击提交button。

 CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column] ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;