主键中允许 NULL - 为啥以及在哪个 DBMS 中?

Posted

技术标签:

【中文标题】主键中允许 NULL - 为啥以及在哪个 DBMS 中?【英文标题】:NULL permitted in Primary Key - why and in which DBMS?主键中允许 NULL - 为什么以及在哪个 DBMS 中? 【发布时间】:2011-04-23 20:05:44 【问题描述】:

关于我的问题"Why to use ´not null primary key´ in TSQL?"...

据我从其他讨论中了解到,某些 RDBMS(例如 SQLite、mysql)允许在主键中使用“唯一”NULL。

为什么允许这样做,它有什么用处?

背景:我认为了解基本概念、方法及其在不同 DBMS 中的实现的差异对于与同事和数据库专业人员的交流是有益的。

注意事项

MySQL 已修复并返回到“NOT NULL PK”列表。 SQLite 已添加到“NULL PK”列表中(感谢 Paul Hadfield):

为了确定主键值的唯一性,NULL 值被认为不同于所有其他值,包括其他 NULL。

如果 INSERT 或 UPDATE 语句尝试修改表内容,以便两行或多行具有相同的主键值,则违反约束。根据 SQL 标准,PRIMARY KEY 应始终暗示 NOT NULL。不幸的是,由于长期的编码疏忽,SQLite 并非如此。

除非列是 INTEGER PRIMARY KEY SQLite 允许在 PRIMARY KEY 列中使用 NULL 值。我们可以更改 SQLite 以符合标准(我们将来可能会这样做),但是当疏忽被发现时,SQLite 已被广泛使用,以至于我们担心如果我们解决了问题会破坏遗留代码。

所以现在我们选择继续在 PRIMARY KEY 列中允许 NULL。但是,开发人员应该知道,我们将来可能会更改 SQLite 以符合 SQL 标准,并应相应地设计新程序。

——SQL As Understood By SQLite: CREATE TABLE

【问题讨论】:

我不知道旧版本的 MySQL 是否对此有所不同,但从现代版本开始,主键必须在非空列上。请参阅dev.mysql.com/doc/refman/5.1/en/create-table.html:“PRIMARY KEY 是一个唯一索引,其中所有键列都必须定义为 NOT NULL。如果它们没有显式声明为 NOT NULL,MySQL 会隐式(并且静默地)声明它们。” @Hammerite,这绝对不仅仅是评论,你为什么不发布答案?我有相当长的 MySQL 教授的帖子列表,确定 NULL 在 PK 中的使用,我真的应该给吗? 好的,我做了一个回答说。我不知道你的第二个问题是什么意思。这些人对空键和主键有什么看法? 仅仅因为 RDBMS 的默认行为可能允许这种异常,并不意味着您也必须允许它。只需在您的设计中的列上放置一个 NOT NULL 约束,瞧。它按照上帝的意图运作。 【参考方案1】:

假设您有一个包含可空列 Kn 的主键。

如果您想要拒绝第二行,因为在该第二行中,Kn 为空并且表中已经包含 Kn 为空的行,那么您实际上要求系统将比较“row1. Kn = row2.Kn" 给出 TRUE(因为您希望系统检测到这些行中的键值确实相等)。但是,这种比较归结为“null = null”比较,并且标准已经明确规定 null 不等于任何东西,包括它自己。

为了满足您的需求,这将相当于 SQL 偏离了它自己关于处理 null 的原则。 SQL 中有无数的不一致之处,但这个特定的不一致从未通过委员会。

【讨论】:

“允许你想要什么” - 你回答了我没有问过的问题。我问为什么有些 DBMS 有,而不是为什么不应该 总结:因为 null != null(根据定义)PK 中的所有列都必须为非 NULL,以便它们具有可比性。 很抱歉来晚了,但我只是偶然发现了这个。我不同意您所说的,但是您可以拥有一个允许空值的唯一索引字段,并且不允许多行在该字段中包含空值。这需要比较 (null==null)=>true。事实上,在 MS SQL 中,您可以忽略比较中的空值,然后您就可以在该列中拥有多行具有空值的行。 您对MySQL、postgres 和Oracle 允许唯一键列有多个NULL 有何评论?我猜SQL标准只允许单个NULL,对吧?但是多个 NULL 并不是那么好。对此有何看法? 该评论不适合此处 cmets 允许的空间。您提到的那些产品可以允许这样做并且仍然声称符合 SQL 标准的精神,因为键只是一个约束,并且如果涉及任何 NULL,即永远不会违反约束(即始终认为满足),即如果事实约束表达式的值计算为 UNKNOWN。请注意,任何此类带有空值的行都将始终被接受,这与我的假设“如果您希望以...为由拒绝此类行”的假设相冲突。【参考方案2】:

我不知道旧版本的 MySQL 是否在这方面有所不同,但从现代版本开始,主键必须在非空列上。请参阅the manual page on CREATE TABLE:“PRIMARY KEY 是一个唯一索引,其中所有键列必须定义为NOT NULL。如果它们没有显式声明为NOT NULL,MySQL 会隐含地(并且默默地)声明它们。”

【讨论】:

感谢您让我走上正轨。好吧,我似乎提出了愚蠢的问题,因为我阅读了太多 ***。这是因为我相信***.com/questions/3876785/…和其他人的cmets。好吧,我检查了 MySQL 中的 PK 不允许 NULL。【参考方案3】:

就关系数据库理论而言:

表的主键用于唯一标识表中的每一行 列中的 NULL 值表示您不知道该值是什么 因此,切勿使用“我不知道”的值来唯一标识表中的一行。

根据您要建模的数据,可以使用“合成”值代替 NULL。我使用了 0、“N/A”、“1980 年 1 月 1 日”和类似的值来表示“已知丢失”的虚拟数据。

大多数(如果不是全部)数据库引擎确实允许 UNIQUE 约束或索引,这确实允许 NULL 列值,尽管(理想情况下)只有一行可以被分配值 null(否则它不会是唯一的价值)。这可以用来支持不完全符合关系理论的令人恼火的务实(但偶尔是必要的)情况。

【讨论】:

请原谅我 [你永远不应该使用“我不知道”的价值]!我使用 SQL Server!我询问了其他人和其他 RDBMS 这个概念不是特定于平台的,它是关系数据库系统规范的一部分。 SQL、Oracle、MySQL、Postgres 等都只是这些规范的实现。它们的正确性和/或准确性的问题已经在互联网上引发了许多近乎宗教的火焰战争。 使用远远超出有效值范围的虚假值可能会破坏基数计算,并且通常是一个坏主意。 NULL 表示 NULL,JAN 1, 1990 不表示 NULL。 @Stephanie,对于日期,我同意。对于状态码之类的东西,使用“0 = 尚未分配状态”之类的东西比将其保留为空要好。 (基数不会受到影响,因为无论是 null 还是 0,您仍然会有 N 行。) @Philip,我没有说基数受到影响,我说基数计算。一些 RDBMS 存储最小和最大列值,并假设两者之间的值分布均匀(在没有直方图的情况下)。如果其余的键是 1,2,3,4,5... 你会没事的。如果它们是 1000,1001,1002...,优化器将假定您有 1/1000 的行用于 ID = n 的谓词。在您的示例中,这看起来像一个 FK,在这种情况下,这无关紧要,因为您将过滤 Code = 'Status not yet assigned' 而不是 Code_ID = 0。您只会加入那个0【参考方案4】:

嗯,它可以让您在数据库中本地实现Null Object Pattern。因此,如果您在代码中使用与 DB 交互非常密切的类似代码,您可以只查找与键对应的对象,而无需对空值进行特殊情况检查。

现在我不确定这是否值得的功能,但这确实是一个问题,即在绝对所有情况下禁止 null pkeys 的优点是否大于阻碍(无论好坏)实际上想要使用 null 的人的缺点键。仅当您能够证明一些重要的改进(例如更快的密钥查找)能够保证密钥为非空时,这才是值得的。一些数据库引擎会显示这一点,而其他的可能不会。如果强制没有任何真正的好处,为什么要人为地限制你的客户?

【讨论】:

【参考方案5】:

正如在其他答案中所讨论的,NULL 的意思是“本列中应该包含的信息是未知的”。但是,经常用来表示“此属性不存在”的另一种含义。当查看被解释为某个特定事件发生的时间的时间戳字段时,这是一种特别有用的解释,在这种情况下,NULL 通常用于表示该事件尚未发生。

SQL 不能很好地支持这种解释是一个问题——为了让它正常工作,它确实需要一个单独的值(类似于“never”),它的行为不像 null 那样(“从不”应该等于“从不”,并且应该比所有其他值都高)。但是由于 SQL 缺少这个概念,并且没有方便的方法来添加它,因此为此目的使用 null 通常是最佳选择。

这留下了一个问题,即当可能尚未发生的事件的时间戳应该是表的主键的一部分时(一个常见的要求可能是在使用软删除时使用自然键和删除时间戳要求能够在删除后重新创建项目)您确实希望主键具有可为空的列。唉,这在大多数数据库中是不允许的,相反,您必须求助于人工主键(例如行序列号)和 UNIQUE 约束,否则应该是您的实际主键。

一个示例场景,为了澄清这一点:我有一个users 表。因为我要求每个用户都有一个不同的用户名,所以我决定使用username 作为主键。我想支持用户删除,但由于出于审计目的我需要跟踪用户历史上的存在,所以我使用软删除(在架构的第一个版本中,我向用户添加了一个“已删除”标志,并确保删除的在所有只需要活跃用户的查询中检查标志)。

但是,另一个要求是,如果用户名被删除,新用户应该可以注册该用户名。实现此目的的一种有吸引力的方法是将已删除标志更改为可为空的时间戳(其中空值表示用户尚未被删除)并将其放入主键中。如果主键允许可以为空的列,这将产生以下效果:

当用户的deleted 列为空时,使用现有用户名创建新用户将被拒绝作为重复键条目 删除用户会更改其键(这需要更改级联到引用用户的外键,这是次优的,但如果删除很少,则可以接受),因此deleted 列是删除发生时间的时间戳 现在可以成功创建新用户(deleted 时间戳为空)。

但是,这实际上无法通过标准 SQL 实现,因此必须使用不同的主键(在这种情况下可能是生成的数字用户 ID)并使用 UNIQUE 约束来强制 (username,@ 987654327@).

【讨论】:

【参考方案6】:

在某些情况下,主键为 null 可能是有益的。在我的一个项目中,我在数据库同步期间使用了此功能:一个在服务器上,许多在不同的用户设备上。考虑到并非所有用户都可以一直访问 Internet,我决定只有主数据库才能为我的实体提供 ID。 SQLite 有自己的行编号机制。如果我使用额外的 id 字段,我会使用更多带宽。将 null 作为 id 不仅通知我在客户端设备上创建了一个实体,而他没有访问 Internet,而且还降低了代码复杂性。唯一的缺点是在客户端设备上我无法通过它的 id 获取实体,除非它之前与主数据库同步。但这不是问题,因为我的用户关心实体的参数,而不是它们的唯一 ID。

【讨论】:

以上是关于主键中允许 NULL - 为啥以及在哪个 DBMS 中?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 python 切片中允许非整数内置类型?

为啥在 JavaScript 的 IF 语句中允许重新声明变量

在 jquery ui 自动完成中允许 null

为啥Java中允许受保护的静态? [关闭]

为啥是 ”。” Access中的查询字段名称中允许但表字段名称中不允许?

为啥在未计算的操作数中不允许使用 lambda 表达式,但在常量表达式的未计算部分中允许使用 lambda 表达式?