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来强制执行约束.
Jos*_*lio 140
您可以使用WHERE
子句创建一个接受多个NULL的唯一索引.请参阅以下答案.
您不能创建UNIQUE约束并允许NULL.您需要设置NEWID()的默认值.
在创建UNIQUE约束之前,将现有值更新为NEWID(),其中为NULL.
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
(其中IssueID
为PRIMARY 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
当我应用下面的唯一索引时:
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)
但我没有测试过这个
可以在聚集索引视图上创建唯一约束
您可以像这样创建视图:
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)
创建一个仅选择非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)
小智 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)
这样,您就不必担心列是唯一的且可为空的。如果一个人没有借书卡,他们在借书卡表中就不会有记录。此外,如果还有关于图书卡的其他属性(可能是到期日期或其他属性),您现在就有了一个放置这些字段的合理位置。