为什么UNIQUE约束只允许一个NULL?


Answers:


52

为什么这样工作?因为可以追溯到那时,有人在不知道或不关心标准说什么的情况下做出了一个设计决策(毕竟,我们确实有各种带有NULLs 的怪异行为,并且可以随意强迫不同的行为)。在这种情况下,该决定要求NULL = NULL

这不是一个非常明智的决定。他们应该做的是使默认行为符合ANSI标准,如果他们确实想要这种特殊行为,请通过DDL选项(例如WITH CONSIDER_NULLS_EQUAL或)允许它WITH ALLOW_ONLY_ONE_NULL

当然,事后看来是20/20。

无论如何,即使不是最干净或最直观的解决方案,我们也都有解决方法。

通过创建唯一的筛选索引,可以在SQL Server 2008及更高版本中获得适当的ANSI行为。

CREATE UNIQUE INDEX foo ON dbo.bar(key) WHERE key IS NOT NULL;

这允许一个以上的NULL值,因为这些行被完全排除在重复检查之外。作为额外的好处,如果NULL允许多个s,则最终它的索引将小于包含整个表的索引(特别是当它不是索引中的唯一列,具有INCLUDE列等时)。但是,您可能需要了解筛选索引的其他一些限制:


8

正确。在sql server中,唯一约束或索引的实现只允许一个NULL。还要更正这一点,从技术上讲,它与NULL的定义不符,但这是他们做的事情之一,即使它在“技术上”不正确,也使其更有用。请注意,PRIMARY KEY(也是唯一索引)不允许使用NULL(当然)。


1
这种(SQL Server的)技术性也不符合SQL标准。关于此问题有7年的Connect项目
ypercubeᵀᴹ

@ypercube是的。这就是为什么我说这只是实现而与NULL的定义不符。我没有考虑过过滤后的唯一索引(尽管我已将其用于其他用途。)
Kenneth Fisher

3

首先-停止使用短语“空值”,它只会使您误入歧途。而是使用短语“空标记”(null marker)-列中的标记,指示该列中的实际值缺失或不适用(但请注意,该标记并未说明实际上是哪种选择¹)。

现在,想象以下情况(数据库不完全了解建模情况)。

Situation          Database

ID   Code          ID   Code
--   -----         --   -----
1    A             1    A
2    B             2    (null)
3    C             3    C
4    B             4    (null)

我们正在建模的完整性规则是“代码必须唯一”。现实情况违反了这一点,因此数据库不应同时将表2和表4都放在表中。

最安全,最不灵活的方法是在“代码”字段中禁止使用空标记,因此不可能出现数据不一致的情况。最灵活的方法是允许多个空标记,并在输入值时担心唯一性。

Sybase程序员采用了一种比较安全,不太灵活的方法,即只允许在表中使用一个空标记-此后,评论员一直在抱怨。我猜微软是为了保持向后兼容性而继续这种行为。


¹我确信我读过某个地方,科德考虑实现两个空标记-一个用于未知,一个用于不适用-但拒绝了它,但我找不到参考。我记得正确吗?

PS我最喜欢的关于null的报价是:Louis Davidson,“ Professional SQL Server 2000数据库设计”,Wrox出版社,2001,第52页。


1
允许一个人null也不能实现这个目标。因为丢失的值可能与其他行之一中的值相同。
马丁·史密斯

1
@MartinSmith说了什么。如果您有检查约束CHECK (Value IN ('A','B','C','D'))怎么办?然后,SQL-Server的实现和SQL标准都允许表具有5行(每个值一行,外加1个NULL)。然后,可以说,虽然数据库与其约束一致,但与设计者的意图不一致。该表格最多包含4行。没有可以将NULL更改为不会违反约束的值,除非删除一个或多个行。
ypercubeᵀᴹ

1
标准将允许6行甚至是106行而不是5行的事实并没有改变它们在这种情况下都以某种方式失败的事实。
ypercubeᵀᴹ

@Martin Smith,它可能会,但是再一次,它可能不会-数据库服务器无法告知,因此它不会冒风险并采取了安全的路线。那是Sybase(我认为)程序员决定的,从那以后引起了烦恼(至少可以追溯到Inside SQL Server 6.5,这是我书架上最古老的书,Ron Soukup在其中做出的评论与Aaron Bertrand所做的评论大致相同) 。我猜可能会更糟-他们本来可以不强制使用null标记。:-)
Greenstone Walker

2
@GreenstoneWalker-它不采取“安全”的路线。它假定缺失值不会冲突。CREATE TABLE #T(A INT NULL UNIQUE);INSERT INTO #T VALUES (1),(NULL);UPDATE #T SET A = 1 WHERE A IS NULL;会引发错误。根据您的设计动机理论,应该NULL在第一种情况下避免插入-因为知识的不完整意味着无法保证其价值是不同的。
马丁史密斯

2

从技术上讲这可能不准确,但从哲学上讲,它可以帮助我晚上入睡。

就像其他人已经说过或暗示过的,如果您认为NULL是未知的,则无法确定一个NULL值实际上是否等于另一个NULL值。以这种方式考虑,表达式NULL == NULL应该计算为NULL,表示未知。

唯一性约束将需要确定的值来比较列值。换句话说,当使用相等运算符将单个列值与任何其他列值进行比较时,它必须评估为false才有效。尽管未知通常被认为是虚假的,但它并不是真的错误。两个NULL值可以相等,也可以不相等...根本无法确定。

这有助于将唯一约束视为可以确定为彼此不同的限制值。我的意思是,如果您运行的SELECT看起来像这样:

SELECT * from dbo.table1 WHERE ColumnWithUniqueContraint="some value"

鉴于存在唯一约束,大多数人会期望得到一个结果。如果在ColumnWithUniqueConstraint中允许多个NULL值,则不可能使用NULL作为比较值从表中选择单个不同的行。

鉴于此,我相信,不管是否按照NULL的定义正确实现了它,在大多数情况下,它绝对比允许多个NULL值实际得多。


如果存在唯一性约束(在任何实现中,不仅是SQL-Server),您的Select都将给出1个结果。你想说啥?
ypercubeᵀᴹ

-3

UNIQUE约束的主要目的之一是防止重复记录。如果需要一张表,其中可以有多个记录的值是“未知”,但不允许两个记录具有相同的“已知”值,那么在对未知值进行分配之前,应该为其分配人工唯一标识符添加到表格中。

在极少数情况下,列具有UNIQUE约束并且包含单个null值;例如,如果一个表包含列值和本地化文本描述之间的映射,则使用一行NULL可以定义当其他表中的该列为时应显示的描述NULL。的行为NULL允许该使用情况。

否则,我看不到UNIQUE在任何列上都有约束以允许存在许多相同记录的数据库的基础,但是在允许键值不可区分的多个记录时,我看不到任何防止这种情况的方法。声明NULL不等于自身不会使NULL值彼此区分。


3
人工唯一标识符是一个笑话,对不起。对于VIN,您将如何做?如果您不知道这是什么,为什么要化妆呢?只是为了占用额外的磁盘空间?似乎无济于事,可以解决其他一些问题(例如,不想以优雅地处理NULL的方式编写应用程序)。如果您绝对需要知道为什么某物为NULL(例如,存在但未知vs.知道它不存在vs.不知道或不在乎它是否存在),则添加某种状态列。令牌只会导致笨拙的trick流代码来处理它们。
亚伦·伯特兰

很大程度上取决于唯一性约束的目的。如果将字段用作标识符,则不应为null。在业务规则建议某项出现两次时(例如VIN)的情况下,其中一项必定是错误的,但是某些项可能是“不知道”的,唯一性约束就不像是正确的方法。如果一个人的车辆具有已知的VIN,并且与数据库中的另一个VIN发生冲突,则可能知道至少一个VIN是错误的,但是最好让数据库报告两个记录的可信值,而不是猜测那是对的。
2014年

@AaronBertrand:在某些情况下,可能无法为字段填充之前必须先建立可能为空的unique-if-not-null字段(例如“配偶ID”),否则无法建立代理键(例如“配偶ID”)一个“独特的”约束将是不够的;如果X.Spouse不为空,则X.Spouse.Spouse = X很有必要。顺便说一句,诸如“配偶”之类的事情也可以这样处理:未婚者的记录不应以配偶为“ NULL”,而是其自己的ID,在这种情况下,X.spouse.spouse = X规则可以适用于每个人。
2014年
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.