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


618

我想在要用GUID填充的列上具有唯一约束。但是,我的数据包含此列的空值。如何创建允许多个空值的约束?

这是一个示例方案。考虑以下模式:

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值,同时仍检查实际数据的唯一性?


投票以获取标准兼容性的连接问题以进行投票:connect.microsoft.com/SQLServer/Feedback/Details/299229
Vadzim,2015年


UNIQUE约束并允许NULL。?这是常识。这是不可能的
flik

13
@flik,最好不要引用“常识”。那不是有效的论点。特别是在考虑这null不是价值而是缺乏价值时。根据SQL标准,null不视为null。那么为什么倍数null应该是唯一性违背呢?
弗雷德里克

Answers:


144

SQL Server 2008以上

您可以创建一个唯一的索引,该索引通过一个WHERE子句接受多个NULL 。请参阅下面答案

在SQL Server 2008之前

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

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


2
这将向现有行追溯添加值,如果是这样,这就是我需要做的,谢谢?
Stuart

1
您将需要运行UPDATE语句以将现有值设置为NEWID(),其中现有字段为NULL
Jose Basilio

54
如果您使用的是SQL Server 2008或更高版本,请参阅下面的答案,其中包含100多个投票。您可以在唯一约束中添加WHERE子句。
达伦·格里菲思

1
这个问题也对ADO.NET DataTables造成了影响。因此,即使我可以使用此方法在后备字段中允许空值,DataTable也不会让我首先将空值存储在唯一列中。如果有人知道解决方案,请在此处发布
dotNET

6
伙计们确保您向下滚动并阅读600票赞成票。它不再仅仅超过
发光

1286

您要查找的内容确实是ANSI标准SQL:92,SQL:1999和SQL:2003的一部分,即UNIQUE约束必须禁止重复的非NULL值,但接受多个NULL值。

但是,在Microsoft 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来强制执行约束。


3
这可能是做到这一点的最佳方法。不确定是否会对性能产生影响?任何人?
Simon_Weaver

3
我正在尝试在SQL Server 2008 Express版中完全做到这一点,并收到如下错误:在[SLS-CP] .dbo.MasterFileEntry(MailingId)上创建唯一的非索引UC_MailingId,其中MailingId不为空结果导致:消息156,级别15,状态1,第3行关键字“ WHERE”附近的语法错误。如果删除where子句,则DDL可以正常运行,但当然不会执行我需要的操作。有任何想法吗?
肯尼斯·巴尔特里尼克

4
除非我没有记错,否则您无法像创建唯一约束一样在唯一索引上创建外键。(当我尝试时,至少SSMS向我抱怨。)能够有一个始终为唯一(当不为null时)唯一的可为空的列作为外键关系的来源将是很好的。
瓦卡诺2011年

8
确实是一个很好的答案。太糟糕了,它被一个被接受的答案隐藏了。这个解决方案几乎没有引起我的注意,但是现在我的实现就像奇迹一样。
珊瑚母鹿

2
SQL 2005及以下版本的另一种替代方法是“计算列”(又称“ Nullbuster”)技巧。stackoverflow.com/a/191729/132461它使您免于使用另一个视图使数据库混乱,您只需拥有另一列-如果ColumnA是您希望成为ANSI可为空的UNIQUE列,则通常命名为ColumnA-Nullbuster。在ColumnA-Nullbuster上放置一个唯一索引(或表达业务意图的约束),它将对ColumnA实施唯一性
DanO 2013年

34

SQL Server 2008及更高版本

只需过滤一个唯一索引:

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

在较低版本中,仍然不需要实例化视图

对于SQL Server 2005和更早版本,您可以在不使用视图的情况下进行操作。我只是添加了一个独特的约束,就像您要的是我的一张桌子一样。鉴于我想要column的唯一性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,现在也无所谓。

请注意,包含计算列的索引将导致每个表达式结果与表中的其他数据一起保存到磁盘,这确实占用了额外的磁盘空间。

请注意,如果您不希望使用索引,您仍然可以通过PERSISTED在列表达式定义的末尾添加关键字,使表达式预先计算到磁盘上,从而节省CPU 。

在SQL Server 2008及更高版本中,如果可以的话,绝对可以使用过滤的解决方案!

争议

请注意,某些数据库专业人员会将其视为“代理NULL”的情况,它们肯定存在问题(主要是由于围绕尝试确定某物何时是真实值丢失数据代理值而引起的问题;也可能存在问题与非NULL替代值的数量相乘就疯狂了)。

但是,我相信这种情况是不同的。我要添加的计算列将永远不会用于确定任何内容。它本身没有意义,也不会编码在其他正确定义的列中尚未单独找到的信息。永远不要选择或使用它。

所以,我的故事是,这不是替代NULL,我坚持使用!由于除了欺骗UNIQUE索引以忽略NULL之外,我们实际上不希望出于任何目的使用非NULL值,因此我们的用例不存在正常的替代NULL创建所引起的问题。

综上所述,我使用索引视图没有问题,但是它带来了一些问题,例如使用的要求SCHEMABINDING。祝您在基表中添加新列(您至少必须删除索引,然后删除视图或更改视图以使其不受模式约束)很有趣。请参见在SQL Server(2005)(以及更高版本)(2000)中创建索引视图的完整(完整)要求列表

更新资料

如果您的列是数字,则可能存在确保使用唯一约束Coalesce不会导致冲突的挑战。在这种情况下,有一些选择。一种可能是使用负数,将“代理NULL”仅放在负数范围内,而将“实际值”仅放在正数范围内。或者,可以使用以下模式。在表Issue(其中IssueIDPRIMARY KEY),有可能会或可能不会是一个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。


16

对于正在使用Microsoft SQL Server Manager并想要创建唯一但可为空的索引的用户,您可以像通常那样创建唯一索引,然后在新索引的索引属性中,从左侧面板中选择“过滤器”,然后输入您的过滤器(这是您的where子句)。它应显示如下内容:

([YourColumnName] IS NOT NULL)

这适用于MSSQL 2012


如何使在Microsoft SQL Server Management Studio中筛选的索引这里描述和完美的作品:msdn.microsoft.com/en-us/library/cc280372.aspx

9

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

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

每个非null更新和插入均失败,并显示以下错误:

UPDATE失败,因为以下SET选项具有错误的设置:'ARITHABORT'。

我在MSDN上找到了

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

因此,为了使其正常工作,我这样做了

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

我相信可以在代码中使用

ALTER DATABASE "DBNAME" SET ARITHABORT ON

但我还没有测试


6

创建一个仅选择非NULL列的视图,然后UNIQUE 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

所以我需要更改我的dal以插入视图吗?
Stuart

1
您可以创建触发器INSTEAD OF INSERT。
Quassnoi


4

可以在聚簇索引视图上创建唯一约束

您可以这样创建视图:

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)

2

也许考虑一个“ INSTEAD OF”触发器,然后自己检查一下?在列上具有非聚集(非唯一)索引以启用查找。


1

如前所述,SQL Server涉及时未实现ANSI标准UNIQUE CONSTRAINT。自2007年以来,Microsoft Connect上已经有一张票。如此处和此处所述,到目前为止,最好的选择是使用另一个答案中列出的过滤索引或计算列,例如:

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])
)

1

您可以创建一个INSTEAD OF触发器来检查特定条件和错误(如果满足)。在较大的表上创建索引可能会非常昂贵。

这是一个例子:

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

您不能在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

-1
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

如果您使用textBox填写注册表并使用insert,而您的textBox为空并且您单击Submit按钮,则此代码。

CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.