How do I create a unique constraint that also allows nulls?
我想在一列上有一个唯一的约束,我将用guid填充该列。但是,我的数据包含此列的空值。如何创建允许多个空值的约束?
下面是一个示例场景。考虑此架构:
1 2 3 4 5 6 | 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) ) |
然后,请参阅此代码,了解我要实现的目标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | -- 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); |
最终语句失败,并显示一条消息:
Violation of UNIQUE KEY constraint 'UQ_People_LibraryCardId'. Cannot insert duplicate key in object 'dbo.People'.
如何更改模式和/或唯一性约束,使其允许多个
您要查找的实际上是ANSI标准SQL:92、SQL:1999和SQL:2003的一部分,即唯一约束必须不允许重复的非空值,但接受多个空值。
然而,在SQL Server的Microsoft世界中,只允许一个空值,但不允许多个空值…
在SQL Server 2008中,可以基于不包含空值的谓词定义唯一的筛选索引:
1 2 3 | CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull ON YourTable(yourcolumn) WHERE yourcolumn IS NOT NULL; |
在早期版本中,可以使用带有非空谓词的视图来强制约束。
SQL Server 2008+
您可以使用
不能创建唯一约束并允许空值。您需要设置newID()的默认值。
在创建唯一约束之前,将现有值更新为newid(),其中为空。
SQL Server 2008及更高版本
只需筛选唯一索引:
1 2 3 | CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName ON dbo.Party(SamAccountName) WHERE SamAccountName IS NOT NULL; |
在较低版本中,仍然不需要物化视图
对于SQL Server 2005及更早版本,您可以在没有视图的情况下执行此操作。我刚在我的一个表中添加了一个独特的约束,就像你要求的那样。鉴于我希望在列
1 2 3 4 | ALTER TABLE dbo.Party ADD SamAccountNameUnique AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID))) ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName UNIQUE (SamAccountNameUnique) |
您只需在计算列中放入一些内容,当实际需要的唯一列为空时,这些内容将保证整个表的唯一性。在这种情况下,
1 | Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID)) |
即使有一天
请注意,包含计算列的索引的存在隐式地导致每个表达式结果与表中的其他数据一起保存到磁盘,这确实会占用额外的磁盘空间。
请注意,如果不需要索引,您仍然可以通过将关键字
在SQL Server 2008及更高版本中,如果可能的话,一定要使用筛选后的解决方案!
争议
请注意,一些数据库专业人员会将此视为"代理空值"的情况,这肯定有问题(主要是由于在试图确定某个值是真实值还是丢失数据的代理值时出现的问题;也可能是非空代理值的数量与疯狂值相乘的问题)。
不过,我相信这个案子是不同的。我要添加的计算列永远不会用于确定任何内容。它本身没有意义,也不编码在其他正确定义的列中未单独找到的信息。不得选择或使用。
所以,我的故事是,这不是一个代理无效,我坚持它!由于我们实际上不希望非空值用于除欺骗
尽管如此,我并不反对使用索引视图,但是它带来了一些问题,比如使用
更新
如果您的列是数字列,则可能存在确保使用
1 2 3 4 | 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,那么
对于使用Microsoft SQL Server Manager并希望创建唯一但可以为空的索引的用户,可以像通常那样创建唯一索引,然后在新索引的索引属性中,从左侧面板中选择"筛选器",然后输入筛选器(这是WHERE子句)。它应该是这样的:
1 | ([YourColumnName] IS NOT NULL) |
这适用于MSSQL 2012
当我应用以下唯一索引时:
1 2 3 | CREATE UNIQUE NONCLUSTERED INDEX idx_badgeid_notnull ON employee(badgeid) WHERE badgeid IS NOT NULL; |
每个非空更新和插入都失败,错误如下:
UPDATE failed because the following SET options have incorrect settings: 'ARITHABORT'.
我在msdn上找到这个
SET ARITHABORT must be ON when you are creating or changing indexes on computed columns or indexed views. If SET ARITHABORT is OFF, CREATE, UPDATE, INSERT, and DELETE statements on tables with indexes on computed columns or indexed views will fail.
为了让这个正常工作,我做了这个
Right click [Database]-->Properties-->Options-->Other
Options-->Misscellaneous-->Arithmetic Abort Enabled -->true
我相信可以在代码中使用
1 | ALTER DATABASE"DBNAME" SET ARITHABORT ON |
但我没有测试过这个
创建只选择非
1 2 3 4 5 6 7 | CREATE VIEW myview AS SELECT * FROM mytable WHERE mycolumn IS NOT NULL CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn) |
请注意,您需要在视图上执行
您可以使用
1 2 3 4 5 6 7 8 9 | CREATE TRIGGER trg_mytable_insert ON mytable INSTEAD OF INSERT AS BEGIN INSERT INTO myview SELECT * FROM inserted END |
也可以在设计师那里完成
右键单击索引>属性以获取此窗口
可以在聚集索引视图上创建唯一约束
您可以这样创建视图:
1 2 3 | CREATE VIEW dbo.VIEW_OfYourTable WITH SCHEMABINDING AS SELECT YourUniqueColumnWithNullValues FROM dbo.YourTable WHERE YourUniqueColumnWithNullValues IS NOT NULL; |
唯一的约束如下:
1 2 | CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_OFYOURTABLE ON dbo.VIEW_OfYourTable(YourUniqueColumnWithNullValues) |
也许考虑一个"
您可以创建一个instead-of-trigger来检查是否满足特定条件和错误。在较大的表上创建索引的成本可能很高。
下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | CREATE TRIGGER PONY.trg_pony_unique_name ON PONY.tbl_pony INSTEAD OF INSERT, UPDATE AS BEGIN IF EXISTS( SELECT TOP (1) 1 FROM inserted i GROUP BY i.pony_name HAVING COUNT(1) > 1 ) OR EXISTS( SELECT TOP (1) 1 FROM PONY.tbl_pony t INNER JOIN inserted i ON i.pony_name = t.pony_name ) THROW 911911, 'A pony must have a name as unique as s/he is. --PAS', 16; ELSE INSERT INTO PONY.tbl_pony (pony_name, stable_id, pet_human_id) SELECT pony_name, stable_id, pet_human_id FROM inserted END |
如前所述,当涉及到
1 2 3 4 5 6 7 8 9 10 11 | 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]) ) |
如果您使用文本框创建一个注册表,并使用insert和ur文本框,则此代码为空,然后单击Submit按钮。
1 2 | CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column] ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`; |
1 2 3 4 5 | 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]; |
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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 |