复合主键中的可空列有什么问题?

13 浏览
0 Comments

复合主键中的可空列有什么问题?

Oracle不允许在构成主键的任何列中使用NULL值。看起来大多数其他“企业级”系统也是如此。

与此同时,大多数系统也允许在可为空的列上设置唯一约束。

为什么唯一约束可以包含NULL值,而主键却不行?这是基于逻辑上的根本原因,还是更多的技术限制?

0
0 Comments

在复合主键中使用可空列有一些问题,这是因为设计者可能没有意识到这一点,所以许多系统在尝试这样做时会报错。

考虑一下作为一系列字段存储的模块/包版本的情况:

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"或其他一些对于4个字段不够的人可能会想到的东西)。如果一个包没有扩展名,那么在上述模型中它是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"是不同的东西。它们之间的区别是"我这里没有一个值"和"我不知道这里的值是什么"。

当你注册一个没有版本扩展名的包时,你知道它没有扩展名,所以空字符串实际上是正确的值。只有在你不知道它是否有扩展名,或者你知道它确实有,但不知道是什么时,NULL才是正确的。如果字符串值是正常的,处理这种情况会更容易,因为除了插入0或1之外,没有其他方法表示"空整数",而这些值在后续的比较中会被忽略(这有它自己的问题)。

顺便说一句,Postgres两种方式都是有效的(因为我们讨论的是"企业"关系数据库),但是当你将NULL放入比较中时,比较结果可能会有很大的不同——因为NULL == "don't know",所以涉及NULL的比较的所有结果都会是NULL,因为你不能知道一个未知的东西。危险!仔细考虑一下:这意味着NULL比较结果会在一系列比较中传播。这在排序、比较等方面可能会导致难以察觉的错误。

Postgres假设你是一个成年人,可以为自己做出这个决定。Oracle和DB2则假设你没有意识到自己在做一些愚蠢的事情,并会抛出错误。这通常是正确的,但并不总是正确的——你可能真的不知道,并且在某些情况下拥有一个NULL,因此留下一个具有未知元素的行是正确的行为。

无论如何,你应该努力减少在整个模式中允许的NULL字段的数量,尤其是在作为主键的字段中。在绝大多数情况下,存在NULL列意味着模式设计不规范(与有意地去范式化相反),在接受之前应该仔细考虑。

[注意:事实上,你可以创建一个自定义类型,它是整数和一个"bottom"类型的并集,这个类型在语义上意味着"空",而不是"未知"。不幸的是,这会在比较操作中引入一些复杂性,并且通常在实践中,真正的类型正确性不值得付出努力,因为你根本不应该允许许多NULL值。也就是说,如果关系数据库管理系统除了NULL之外还包括一个默认的BOTTOM类型,以防止随意混淆"没有值"的语义和"未知值"的语义,那将是很好的。]

这是一个非常好的答案,解释了关于NULL值及其在许多情况下的影响的很多内容。先生,我对您表示尊敬!即使在大学里,我也没有得到过关于数据库中NULL值如此好的解释。谢谢!

我支持这个答案的主要观点。但是像"应该表示的是缺乏信息、未知"、"在语义上(和实际上)与NULL是不同的"、"NULL是未知的"、"空字符串是'某个东西不存在'的一个明确记录"、"NULL == '不知道'"等写法都是模糊、误导性的,实际上只是缺少关于NULL或任何值是如何或可以或应该被使用的陈述的模拟,这些陈述必须解释和驳斥。

0
0 Comments

为什么在复合主键中使用可空列会出现问题?出现问题的原因是根据定义,NULL不能作为成功比较的一部分。即使是与自身的比较(NULL = NULL),也会失败。这意味着包含NULL的主键将无法正常工作。此外,允许在外键中使用NULL可以标记可选关系。如果在主键中也允许使用NULL,将打破这个关系。

解决方法是在规范的关系数据库设计中,不应该使用可空的外键。如果存在两个实体A和B,其中A可以选择与B相关联,清晰的解决方案是创建一个解析表(例如AB)。该表将A与B关联起来:如果存在关系,则会包含一条记录,如果不存在关系,则不会包含记录。

在非规范的数据库设计中,使用可空的外键可能会带来问题,特别是在需要添加新功能时。在非规范化的数据库设计中,可能会出现这样的问题。一个例子是在版本化的模式中,使用NULL FK而不是缺少行。

ANSI标准是不允许将可空列作为主键的一部分。大多数其他企业级数据库也遵循相同的限制。这是大多数数据库遵循这种约定的原因。

在复合主键中使用可空列会导致比较失败,因为NULL不能作为成功比较的一部分。为了解决这个问题,规范的数据库设计中不应该使用可空的外键。而在非规范化的数据库设计中,使用可空的外键可能会导致问题。

0
0 Comments

可空列在复合主键中的问题出现的原因是,当一个表具有主键时,主键定义了表中每一行的唯一标识符。唯一约束并不一定能够标识每一行;它只是指定如果一行在其列中有值,那么这些值必须是唯一的。这对于唯一标识每一行来说是不够的,而这正是主键必须做到的。

在SQL Server中,具有可空列的唯一约束只允许在该列中的值为null时出现一次(假设其他约束列的值相同)。因此,这样的唯一约束实际上表现得就像一个带有可空列的主键。

在Oracle中(我不清楚SQL Server),表可以包含许多行,其中唯一约束中的所有列都为空。然而,如果唯一约束中的某些列不为空,而某些列为空,则强制执行唯一性。

这如何适用于复合UNIQUE约束?

与SQL数据库中的几乎任何其他内容一样,"它取决于实现"。在大多数数据库中,“主键”实际上是一个底层的UNIQUE约束。 "主键"的概念并没有比UNIQUE更特殊或更强大。真正的区别在于,如果表的两个独立方面可以被保证为UNIQUE,那么根据定义,你就没有一个规范化的数据库(你在同一个表中存储了两种类型的数据)。

SQL中的主键是UNIQUE NOT NULL。但是SQL Server中的UNIQUE允许在给定列中最多有一个包含NULL的子行,这与标准的SQL UNIQUE不同,标准的SQL UNIQUE允许任意数量的子行在任何列中包含NULL。这不是日常/数学中的唯一性,将NULL视为另一个值,这是SQL中的IS DISTINCT FROM所有其他子行。

PostgreSQL对于唯一约束的行为是:“... [T]两个NULL值在此比较中永远不被视为相等。这意味着即使在存在唯一约束的情况下,仍然可以存储包含至少一个约束列中的NULL值的重复行。这种行为符合SQL标准,但我们听说其他SQL数据库可能不遵循此规则。因此,在开发旨在可移植的应用程序时要小心。”(来自postgresql.org/docs/12/ddl-constraints.html)

所以,为了解决可空列在复合主键中的问题,我们可以考虑以下方法:

- 避免在复合主键中使用可空列,以确保每一行都具有唯一标识符。

- 如果必须使用可空列,可以考虑使用其他约束,如唯一约束,来确保唯一性。

- 在编写应用程序时,要注意不同数据库对于唯一约束和NULL值的处理方式可能不同,以确保应用程序的可移植性。

通过以上方法,可以解决可空列在复合主键中可能引发的问题。

0