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

Rom*_*kov 142 database database-design

ORACLE不允许在包含主键的任何列中使用NULL值.似乎大多数其他"企业级"系统也是如此.

同时,大多数系统还允许可空列的唯一约束.

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

Tom*_*lak 206

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

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

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


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

如果有两个实体A并且可以选择与B哪些实体A相关B,那么干净的解决方案就是创建一个解析表(比方说AB).该表将连接AB:如果一个关系那么它将包含一个记录,如果不是那就不是.

  • 我已经改变了对这个问题的接受答案.从选票来看,这个答案对更多人来说是最清楚的.我仍然觉得Tony Andrews的答案更好地解释了这个设计背后的意图.也检查一下! (5认同)
  • "拥有可空的外键不是干净的关系数据库设计." - 无空数据库设计(第六范式)总是增加了复杂性,所获得的节省空间通常超过了实现这些收益所需的额外程序员工作. (3认同)
  • 问:你什么时候想要一个NULL FK而不是缺少一行?答:仅在针对优化进行非规范化的模式版本中.在非平凡模式中,像这样的非标准化问题可能会在需要新功能时导致问题.otoh,网页设计人群并不在意.我至少会添加一个警告,而不是让它听起来像一个好的设计理念. (2认同)
  • 如果是 ABC 解析表怎么办?与可选的C (2认同)
  • 我试图避免写“因为标准禁止它”,因为这实际上什么也解释不了。 (2认同)

Ton*_*ews 56

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

唯一约束不一定标识每一行; 它只是指定如果某行的列中包含值,它们必须是唯一的.这不足以唯一地标识每一行,这是主键必须做的事情.

  • 在Sql Server中,具有可为空列的唯一约束允许该列中的值"null"仅一次(给定约束的其他列的相同值).因此,这种唯一约束本质上就像具有可空列的pk一样. (10认同)
  • 在Oracle中(我不知道SQL Server),该表可以包含许多行,其中**all**唯一约束中的列为null.但是,如果唯一约束中的某些列不为null且某些列为null,则强制执行唯一性. (2认同)

zxq*_*xq9 44

从根本上说,多列主键中的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));
Run Code Online (Sandbox Code Playgroud)

主键的前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));
Run Code Online (Sandbox Code Playgroud)

在这个版本中,元组的"ext"部分是NOT NULL但是默认为空字符串 - 它在语义上(实际上)与NULL不同.NULL是未知的,而空字符串是"不存在的东西"的故意定义.换句话说,"空"和"空"是不同的东西.它的区别在于"我这里没有价值"和"我不知道这里有什么价值".

当您注册缺少版本扩展的程序包时,您知道它缺少扩展名,因此空字符串实际上是正确的值.只有在您不知道是否有扩展名时,NULL才会正确.在字符串值为常态的系统中,这种情况更容易处理,因为除了插入0或1之外无法表示"空整数",这将在以后进行的任何比较中累积起来(具有它自己的含义).

顺便提一下,这两种方式在Postgres中都是有效的(因为我们讨论的是"企业"RDMBS),但是当你在混合中抛出NULL时,比较结果会有很大差异 - 因为NULL =="不知道"所以所有由于你无法知道未知的东西,所以比较的结果涉及NULL结束为NULL.因此,在排序,比较等时,这可能是微妙错误的根源.Postgres假设您是成年人,可以自己做出这个决定.Oracle和DB2假设您没有意识到您正在做一些愚蠢的事情并抛出错误.这通常是正确的,但并非总是如此 - 在某些情况下,您可能实际上不知道并且有一个NULL,因此留下一个具有未知元素的行,对于该行,有意义的比较是不正确的行为.

在任何情况下,您都应该努力消除整个模式中允许的NULL字段数,并且当涉及到作为主键一部分的字段时,应该加倍.在绝大多数情况下的NULL列的存在的指示未归一化(而不是刻意去规范化)架构设计,应该很难想到之前被接受.

  • 这是一个非常好的答案,并解释了很多关于NULL值的内容,并且它在很多情况下都有其含义.先生,您现在得到我的尊重!即使在大学里我也没有对数据库中的NULL值做出如此好的解释.谢谢! (4认同)

Cog*_*gsy 19

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

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

  • 错误的答案.NULL == NULL - > UNKNOWN.不是假的.问题是,如果测试结果未知,则不会认为违反了约束.这通常使它_SEEM_好像比较产生错误,但实际上并非如此. (8认同)

Adr*_*vel 10

我仍然认为这是技术性带来的基本/功能缺陷。如果您有一个可以识别客户的可选字段,那么您现在必须在其中添加一个虚拟值,只是因为 NULL != NULL,不是特别优雅,但它是“行业标准”


小智 5

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

将任何内容与 NULL 进行比较会导致 UNKNOWN(第三个真值)。因此,正如 nulls 所建议的那样,所有关于平等的传统智慧都被排除在外。乍一看,这就是它的样子。

但我认为这不一定是这样,甚至 SQL 数据库也不认为 N​​ULL 会破坏所有比较的可能性。

在您的数据库中运行查询 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 为空且 Y 为空)

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