我想在一个列上有一个唯一的约束,我将用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);

最后一个语句失败,并返回一条消息:

违反唯一键约束'UQ_People_LibraryCardId'。不能在对象'dbo.People'中插入重复的键。

我如何改变我的模式和/或唯一性约束,使它允许多个NULL值,同时仍然检查对实际数据的唯一性?


当前回答

如前所述,SQL Server在UNIQUE约束方面没有实现ANSI标准。从2007年开始,微软连接就有这样的罚单。正如这里和这里所建议的,到目前为止,最好的选择是使用另一个答案或计算列中所述的过滤索引,例如:

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

其他回答

可以在群集索引视图上创建唯一的约束

你可以像这样创建视图:

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)

根据我的经验——如果你认为一个列需要允许null,但也需要为它们存在的值设置UNIQUE,那么你可能对数据建模不正确。这通常表明您在同一个表中创建了一个单独的子实体作为一个不同的实体。将这个实体放在第二个表中可能更有意义。

在提供的例子中,我将把LibraryCardId放在一个单独的LibraryCards表中,并在People表中添加一个唯一的非空外键:

CREATE TABLE People (
  Id INT CONSTRAINT PK_MyTable PRIMARY KEY IDENTITY,
  Name NVARCHAR(250) NOT NULL,
)
CREATE TABLE LibraryCards (    
  LibraryCardId UNIQUEIDENTIFIER CONSTRAINT PK_LibraryCards PRIMARY KEY,
  PersonId INT NOT NULL
  CONSTRAINT UQ_LibraryCardId_PersonId UNIQUE (PersonId),
  FOREIGN KEY (PersonId) REFERENCES People(id)
)

这样,您就不需要为列既唯一又可为空而烦恼了。如果一个人没有借书证,他就不会在借书证表上有记录。此外,如果有关于借书证的其他属性(可能是Expiration Date或其他属性),那么现在就有了一个放置这些字段的逻辑位置。

SQL Server 2008 +

您可以使用WHERE子句创建接受多个null的唯一索引。请看下面的答案。

在SQL Server 2008之前

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

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

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

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

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

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

我在MSDN上找到的

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

为了让它正确工作,我这样做了

右键单击[数据库]——>属性——>选项——>其他 选项——> miscellaneous——>算术中止Enabled——>true

我相信可以在代码中使用

ALTER DATABASE "DBNAME" SET ARITHABORT ON

但我还没有测试过

也许可以考虑使用“代替”触发器,然后自己检查?使用列上的非聚集(非唯一)索引来启用查找。