ORACLE不允许在任何包含主键的列中使用NULL值。似乎大多数其他“企业级”系统也是如此。
同时,大多数系统还允许在可为空的列上使用唯一约束。
为什么唯一约束可以具有NULL但主键不能具有NULL?是否有根本的逻辑原因,还是更多的技术限制?
ORACLE不允许在任何包含主键的列中使用NULL值。似乎大多数其他“企业级”系统也是如此。
同时,大多数系统还允许在可为空的列上使用唯一约束。
为什么唯一约束可以具有NULL但主键不能具有NULL?是否有根本的逻辑原因,还是更多的技术限制?
Answers:
主键用于唯一标识行。这是通过将键的所有部分与输入进行比较来完成的。
根据定义,NULL不能成为成功比较的一部分。甚至与自己进行比较(NULL = NULL
)也会失败。这意味着包含NULL的键将不起作用。
另外,外键中允许使用NULL来标记可选关系。(*)在PK中也允许它会破坏这一点。
(*)注意:具有可空的外键并不是干净的关系数据库设计。
如果有两个实体A
,B
并且A
可以选择与关联B
,那么干净的解决方案是创建一个解析表(假设AB
)。该表将连接A
用B
:如果是一个关系那么它将包含一个记录,如果不是那就不是。
主键为每个定义一个唯一的标识符表中的行:当表具有主键时,您可以采用保证的方式从表中选择任何行。
唯一约束不一定标识每行;它只是指定如果一行的列中有值,则它们必须是唯一的。这不足以唯一地标识每一行,这是主键必须执行的操作。
从根本上说,多列主键中的NULL没什么不对。但是,具有影响力的设计人员可能没有想到,这就是为什么许多系统在尝试这种方法时都会引发错误。
考虑存储为一系列字段的模块/软件包版本的情况:
CREATE TABLE module
(name varchar(20) PRIMARY KEY,
description text DEFAULT '' NOT NULL);
CREATE TABLE version
(module varchar(20) REFERENCES module,
major integer NOT NULL,
minor integer DEFAULT 0 NOT NULL,
patch integer DEFAULT 0 NOT NULL,
release integer DEFAULT 1 NOT NULL,
ext varchar(20),
notes text DEFAULT '' NOT NULL,
PRIMARY KEY (module, major, minor, patch, release, ext));
主键的前5个元素是发行版本中定期定义的部分,但是某些软件包具有定制的扩展名,通常不是整数(例如“ rc-foo”或“ vanilla”或“ beta”或其他用于谁四个场是不够的可能梦想)。如果软件包没有扩展名,则在上述模型中为NULL,以这种方式放置东西不会造成任何伤害。
但是什么是 NULL?它应该表示缺乏信息,一个未知数。也就是说,也许这更有意义:
CREATE TABLE version
(module varchar(20) REFERENCES module,
major integer NOT NULL,
minor integer DEFAULT 0 NOT NULL,
patch integer DEFAULT 0 NOT NULL,
release integer DEFAULT 1 NOT NULL,
ext varchar(20) DEFAULT '' NOT NULL,
notes text DEFAULT '' NOT NULL,
PRIMARY KEY (module, major, minor, patch, release, ext));
在此版本中,元组的“ ext”部分不是NOT NULL,但默认为空字符串-在语义(和实际上)上与NULL不同。NULL是未知数,而空字符串是“不存在的内容”的故意记录。换句话说,“空”和“空”是不同的东西。它与“我在这里没有价值”和“我不知道这里的价值”之间的区别。
当您注册缺少版本扩展名的软件包时,您会知道它缺少扩展名,因此,空字符串实际上是正确的值。仅当您不知道它是否具有扩展名,或者您知道它具有扩展名但不知道它是什么时,NULL才是正确的。在以字符串值为标准的系统中,这种情况更容易处理,因为除了插入0或1之外,没有其他方法可以表示“空整数”,这将在以后进行的任何比较中汇总(本身的含义)*。
顺便说一句,这两种方法在Postgres中都是有效的(因为我们正在讨论“企业” RDMBS),但是当您将NULL放入混合中时,比较结果可能会有很大不同-因为NULL ==“不知道”,所以所有涉及NULL的比较结果最终为NULL,因为您无法了解未知的内容。危险!请仔细考虑:这意味着NULL比较结果将通过一系列比较传播。排序,比较等时,这可能是一些细微错误的来源。
Postgres假设您已经成年,可以自己做出决定。Oracle和DB2假定您没有意识到自己在做愚蠢的事情并抛出错误。通常这是正确的事情,但并非总是如此- 在某些情况下,您可能实际上并不知道并且为NULL,因此,将行与未知元素放在同一行中是不可能的,这是正确的行为。
无论如何,您都应该努力消除整个模式中允许的NULL字段的数量,而且要加倍处理涉及主键一部分的字段。在绝大多数情况下,NULL列的存在表示未规范化(与故意取消规范化相反)的架构设计,在接受之前应进行认真思考。
[* 注意:可以创建一个自定义类型,该类型是整数的结合,而一个“底部”类型的语义上表示“空”,而不是“未知”。不幸的是,这在比较操作中引入了一些复杂性,通常,真正地正确键入类型在实践中不值得付出努力,因为一开始根本不应该允许您使用很多NULL
值。话虽如此,RDBMS BOTTOM
除了要NULL
包含习惯性地将“无值”的语义与“未知值”的语义混为一谈之外,如果还包括默认类型,那就太好了。]
NULL == NULL-> false(至少在DBMS中)
因此,即使具有实际值的其他列也无法使用NULL值检索任何关系。
where pk_1 = 'a' and pk_2 = 'b'
普通值,并where pk_1 is null and pk_2 = 'b'
在存在空值时切换到该值。
where (a.pk1 = b.pk1 or (a.pk1 is null and b.pk1 is null)) and (a.pk2 = b.pk2 or (a.pk2 is null and b.pk2 is null))
/
托尼·安德鲁斯(Tony Andrews)的回答很不错。但是真正的答案是,这已经是关系数据库社区所使用的约定,并不是必须的。也许这是一个很好的约定,也许不是。
将任何内容与NULL进行比较都会得出UNKNOWN(第三个真值)。因此,正如已经被废除的那样,所有关于平等的传统智慧都被排除在外了。乍一看就是这样。
但是我认为并非一定如此,甚至SQL数据库也认为NULL不会破坏所有进行比较的可能性。
在数据库中运行查询SELECT * FROM VALUES(NULL)UNION SELECT * FROM VALUES(NULL)
您看到的只是一个具有一个值为NULL的属性的元组。因此,联合在这里将两个NULL值视为相等。
将具有3个组成部分的组合键与具有3个属性(1、3,NULL)=(1、3,NULL)的元组进行比较时== 1 = 1 AND 3 = 3 AND NULL = NULL结果为UNKNOWN 。
但是我们可以定义一种新型的比较运算符,例如。==。X == Y <=> X = Y或(X是NULL并且Y是NULL)
拥有这种相等运算符将使具有空成分的复合键或具有空值的非复合键变得毫无问题。
我仍然相信这是技术性带来的基本/功能缺陷。如果您具有一个可选字段来标识客户,那么现在您必须在其中输入虚拟值,仅仅是因为NULL!= NULL,不是特别优雅,但这是“行业标准”