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

时间:2008-12-22 11:33:55

标签: database database-design

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

与此同时,大多数系统还允许在可空列上使用唯一约束。

为什么唯一约束可以有NULL但主键不能?是否有一个基本的逻辑原因,或者这更多是技术限制?

6 个答案:

答案 0 :(得分:195)

主键用于唯一标识行。这是通过将键的所有部分与输入进行比较来完成的。

根据定义,NULL不能成功进行比较。即使与自身(NULL = NULL)进行比较也会失败。这意味着包含NULL的键不起作用。

另外,外键中允许NULL,以标记可选关系。(*)在PK中允许它也会破坏它。


(*)提醒一句:拥有可空外键不是干净的关系数据库设计。

如果有两个实体AB,其中A可以选择与B相关,那么干净的解决方案就是创建一个解析表(让我们说{{} 1}})。该表会将ABA相关联:如果一个关系,那么它将包含一条记录,如果不是那么它会不

答案 1 :(得分:55)

主键为表中的每个行定义唯一标识符:当表具有主键时,您可以保证从中选择任何行。

唯一约束不一定标识每一行;它只是指定如果一行的列中有值,然后它们必须是唯一的。这不足以唯一地标识每个行,这是主键必须执行的操作。

答案 2 :(得分:43)

从根本上说,多列主键中的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,并且不会因为这样做而造成伤害。

是什么?它应该代表缺乏的信息,一个未知的。也就是说,也许这更有意义:

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类型,以防止随意将“无价值”的语义与“未知值”混淆,这将是非常好的。

答案 3 :(得分:18)

NULL == NULL - > false(至少在DBMS中)

因此即使使用具有实际值的其他列,您也无法使用NULL值检索任何关系。

答案 4 :(得分:4)

托尼·安德鲁斯的回答是不错的。但真正的答案是,这是关系数据库社区使用的惯例,并不是必需的。也许这是一个很好的约定,也许不是。

将任何内容与NULL进行比较会导致UNKNOWN(第3个真值)。 正如有人提出的那样,所有关于平等的传统智慧都会消失。嗯,这看起来乍一看。

但我不认为这必然是这样,甚至SQL数据库都不会认为NULL会破坏所有比较的可能性。

在数据库中运行查询 SELECT * FROM VALUES(NULL) 联盟 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 OR(X IS NULL且Y为NULL)

拥有这种相等运算符会使具有空组件的复合键或具有空值的非复合键无问题。

答案 5 :(得分:0)

我仍然认为这是技术性带来的根本/功能性缺陷。如果你有一个可选的字段,通过它你可以识别一个客户,你现在必须在其中破解一个虚拟值,只是因为NULL!= NULL,不是特别优雅但它是一个行业标准"