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

Stu*_*art 584 t-sql sql-server

我希望对我将使用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)
)
Run Code Online (Sandbox Code Playgroud)

然后看看我正在尝试实现的代码:

-- 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);
Run Code Online (Sandbox Code Playgroud)

最终语句失败并显示一条消息:

违反UNIQUE KEY约束'UQ_People_LibraryCardId'.无法在对象'dbo.People'中插入重复键.

如何更改我的架构和/或唯一性约束,以便它允许多个NULL值,同时仍然检查实际数据的唯一性?

Vin*_*uck 1244

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

但是,在SQL Server的Microsoft世界中,允许单个NULL,但是多个NULL不是......

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

CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull
ON YourTable(yourcolumn)
WHERE yourcolumn IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

在早期版本中,您可以使用带有NOT NULL谓词的VIEWS来强制执行约束.

  • 真的是一个很好的答案.太糟糕了,被接受的答案隐藏了.这个解决方案几乎没有引起我的注意,但它现在的实现就像奇迹一样. (7认同)
  • 除非我弄错了,否则你不能像独特的约束一样创建一个独特索引的外键.(当我尝试时,至少SSMS向我抱怨.)能够拥有一个始终唯一(当不为空)的可为空的列是外键关系的来源将是很好的. (4认同)
  • 我试图在SQL Server 2008 Express版本中做到这一点,我得到一个错误如下:创建UNIQUE NONCLUSTERED索引UC_MailingId ON [SLS-CP] .dbo.MasterFileEntry(MailingId)WHERE MailingId IS NOT NULL结果:Msg 156, Level 15,State 1,Line 3关键字'WHERE'附近的语法不正确.如果我删除了where子句,DDL运行正常,但当然,不能做我需要它.有任何想法吗? (3认同)
  • 这可能是最好的方法.不确定是否有任何性能影响?任何人? (2认同)
  • @Simon_Weaver“创建 UNIQUE 约束和创建独立于约束的唯一索引之间没有显着差异。” http://msdn.microsoft.com/en-us/library/ms187019.aspx (2认同)
  • SQL 2005及以下版本的另一个替代方案是计算列,即"Nullbuster"技巧.http://stackoverflow.com/a/191729/132461它可以避免您使用另一个视图混乱数据库,而只需要另一列 - 通常命名为ColumnA-Nullbuster,如果ColumnA是您想要的ANSI可为空的UNIQUE.在ColumnA-Nullbuster上放置一个独特的索引(或约束来表达业务意图),它将强制ColumnA上的唯一性 (2认同)
  • @Stuart您可以接受此答案吗? (2认同)
  • 连接标准兼容性问题投票:https://connect.microsoft.com/SQLServer/Feedback/Details/299229 (2认同)

Jos*_*lio 140

SQL Server 2008 +

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

在SQL Server 2008之前

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

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

  • 如果您使用的是SQL Server 2008或更高版本,请参阅下面的答案,其中包含100多个upvotes.您可以将WHERE子句添加到Unique Constraint. (50认同)
  • 伙计们确保向下滚动并阅读600个upvotes答案.它不再只是超过100. (6认同)
  • 这将追溯性地将值添加到现有行,如果是这样,这就是我需要做的,谢谢? (2认同)

Eri*_*ikE 33

SQL Server 2008及以上版本

只需过滤一个唯一索引:

CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

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

对于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)
Run Code Online (Sandbox Code Playgroud)

您只需在计算列中放置一些内容,当实际所需的唯一列为NULL时,该列将在整个表中保证唯一.在这种情况下,PartyID是一个标识列,并且数字将永远不会匹配任何SamAccountName,所以它对我有用.您可以尝试自己的方法 - 确保您了解数据的域,以便不可能与实际数据交叉.这可以像预先设置这样的区分字符一样简单:

Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))
Run Code Online (Sandbox Code Playgroud)

即使PartyID有一天变得非数字并且可能与a重合SamAccountName,现在也没关系.

请注意,包含计算列的索引的存在会隐式地使每个表达式结果与表中的其他数据一起保存到磁盘,这会占用额外的磁盘空间.

请注意,如果您不想要索引,则仍可以通过将关键字添加PERSISTED到列表达式定义的末尾来将表达式预先计算到磁盘来节省CPU .

在SQL Server 2008及更高版本中,如果可能,请务必使用已过滤的解决方案!

争议

请注意,一些数据库专业人员会将此视为"代理NULL"的情况,这肯定存在问题(主要是由于试图确定何时某些内容是真实值缺失数据代理值的问题;也可能存在问题与非NULL代理值的数量相乘疯狂).

但是,我认为这种情况有所不同.我正在添加的计算列永远不会用于确定任何内容.它没有任何意义,并且没有编码在其他正确定义的列中未单独找到的信息.永远不应该选择或使用它.

所以,我的故事是,这不是代理NULL,我坚持它!由于我们实际上并不希望将非NULL值用于除了欺骗UNIQUE索引以忽略NULL 之外的任何目的,因此我们的用例没有出现正常代理NULL创建的问题.

所有这一切,我没有使用索引视图的问题 - 但它带来了一些问题,如使用的要求SCHEMABINDING.在基表中添加新列很有趣(您至少必须删除索引,然后删除视图或将视图更改为不受模式限制).请参阅在SQL Server(2005)(以及更高版本),(2000)中创建索引视图的完整(长)要求列表.

更新

如果您的列是数字,则可能存在确保使用唯一约束Coalesce不会导致冲突的挑战.在这种情况下,有一些选择.一种可能是使用负数,将"代理空值"仅设置在负范围内,将"实际值"仅设置在正范围内.或者,可以使用以下模式.在表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);
Run Code Online (Sandbox Code Playgroud)

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


How*_*ard 16

对于使用Microsoft SQL Server管理器且想要创建唯一但可以为空的索引的人,您可以像通常在新索引的索引属性中那样创建唯一索引,从左侧面板中选择"过滤器",然后输入你的过滤器(这是你的where子句).它应该是这样的:

([YourColumnName] IS NOT NULL)
Run Code Online (Sandbox Code Playgroud)

这适用于MSSQL 2012


Mik*_*lor 9

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

CREATE UNIQUE NONCLUSTERED INDEX idx_badgeid_notnull
ON employee(badgeid)
WHERE badgeid IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

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

UPDATE失败,因为以下SET选项具有不正确的设置:'ARITHABORT'.

我在MSDN上找到了这个

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

所以为了让这个工作正常,我做到了这一点

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

我相信可以在代码中使用设置此选项

ALTER DATABASE "DBNAME" SET ARITHABORT ON
Run Code Online (Sandbox Code Playgroud)

但我没有测试过这个


Lie*_*ers 7

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

您可以像这样创建视图:

CREATE VIEW dbo.VIEW_OfYourTable WITH SCHEMABINDING AS
SELECT YourUniqueColumnWithNullValues FROM dbo.YourTable
WHERE YourUniqueColumnWithNullValues IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)

和这样的唯一约束:

CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_OFYOURTABLE 
  ON dbo.VIEW_OfYourTable(YourUniqueColumnWithNullValues)
Run Code Online (Sandbox Code Playgroud)


Qua*_*noi 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)
Run Code Online (Sandbox Code Playgroud)

请注意,您需要在视图而不是表上执行INSERT's和UPDATE's.

您可以使用INSTEAD OF触发器执行此操作:

CREATE TRIGGER trg_mytable_insert ON mytable
INSTEAD OF INSERT
AS
BEGIN
        INSERT
        INTO    myview
        SELECT  *
        FROM    inserted
END
Run Code Online (Sandbox Code Playgroud)


小智 6

也可以在设计器中完成

右键单击索引> 属性以获取此窗口

捕获


小智 5

根据我的经验 - 如果您认为列需要允许 NULL,但也需要对于存在的值来说是唯一的,那么您可能会错误地对数据进行建模。这通常表明您正在同一个表中创建一个单独的子实体作为不同的实体。将此实体放在第二个表中可能更有意义。

在提供的示例中,我将 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)
)
Run Code Online (Sandbox Code Playgroud)

这样,您就不必担心列是唯一的且可为空的。如果一个人没有借书卡,他们在借书卡表中就不会有记录。此外,如果还有关于图书卡的其他属性(可能是到期日期或其他属性),您现在就有了一个放置这些字段的合理位置。

  • 严重不同意你的第一个说法。在澳大利亚,每个员工都有一个称为税号的东西,这当然是唯一的。根据法律,您无需将其提供给您的员工。这意味着该列可以为空,但否则应该是唯一的。在这种情况下,附加表可能被视为过度设计。 (9认同)