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

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

同时,大多数系统还允许在可空列上有唯一的限制。

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

主键用于唯一标识行。 这是通过比较一个键的所有部分到input来完成的。

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

另外,允许在外键中使用NULL来标记一个可选的关系。 (*)在PK中也可以打破这个。


(*)谨慎的说法:具有可空的外键是不干净的关系数据库devise。

如果有两个实体AB ,其中A可以select与B相关,那么干净的解决scheme是创build一个分辨表(比方说AB )。 该表将链接AB :如果有关系,那么它将包含一个logging,如果没有,那么它不会。

主键为表中的每一行定义一个唯一的标识符:当一个表有一个主键时,你有一个保证的方式来select它的任何一行。

一个唯一的约束不一定能识别每一行; 它只是指定如果一行中的值在其列中, 那么它们必须是唯一的。 这不足以唯一标识每一行,这是主键必须做的。

从根本上说,多列主键中的NULL没有任何问题。 但有一个有devise师可能不打算的影响,这就是为什么当你尝试这个时,许多系统抛出一个错误。

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

 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”部分不是NULL,而是默认为一个空string – 这在语义上(和实际上)与NULL不同。 NULL是未知的,而空string是“不存在的东西”的有意义的定义。 换句话说,“空”和“空”是不同的东西。 它是“我在这里没有价值”和“我不知道这里的价值是什么”之间的区别。

当你注册一个缺less扩展名的包时,你知道它缺less一个扩展名,所以一个空string实际上是正确的值。 如果你不知道它是否有扩展名,NULL将是正确的。 在string值是标准的系统中,这种情况更容易处理,因为除了插入0或1以外,没有办法表示一个“空整数”,这将在以后进行的任何比较中结束它自己的含义)。

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

在任何情况下,您都应该努力消除在整个模式中允许使用的NULL字段的数量,对于属于主键字段的字段则要加倍。 在大多数情况下,NULL列的存在表示非规范化(而不是刻意去规范化)的模式devise,应该在被接受之前认真考虑。

NULL == NULL – > false(至less在DBMS中)

因此,即使使用具有实际值的附加列,您也无法使用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 OR(X为NULL且Y为NULL)

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

我仍然认为这是技术性带来的根本性/function性缺陷。 如果你有一个可选的字段,你可以通过它来识别一个客户,你现在不得不向其中join一个虚拟值,只是因为NULL!= NULL,不是特别优雅,而是一个“行业标准”