postgresql 复合PRIMARY KEY对所涉及的列强制执行NOT NULL约束

wgx48brx  于 2023-02-15  发布在  PostgreSQL
关注(0)|答案(2)|浏览(276)

这是我在Postgres中遇到的一个奇怪的、不想要的行为:当我创建一个带有复合主键的Postgres表时,它对复合组合的每一列强制执行NOT NULL约束。
例如,

CREATE TABLE distributors (m_id integer, x_id integer, PRIMARY KEY(m_id, x_id));

在列m_idx_id上强制NOT NULL约束,这是我不想要的! MySQL没有这样做。我认为Oracle也没有这样做。
我知道PRIMARY KEY会自动强制UNIQUENOT NULL,但这对于单列主键是有意义的,在多列主键表中,唯一性由组合决定。
有什么简单的方法可以避免Postgres的这种行为吗?当我执行以下命令时:

CREATE TABLE distributors (m_id integer, x_id integer);

当然,我没有得到任何NOT NULL约束,但是我也没有主键。

csga3l58

csga3l581#

如果您 * 需要 * 允许NULL值,请使用**UNIQUE约束**(或索引)而不是PRIMARY KEY(并添加代理PK列-我建议在Postgres 10或更高版本中使用serialIDENTITY列)。

  • 自动增量表列

UNIQUE约束允许列为NULL:

CREATE TABLE distributor (
  distributor_id GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, m_id integer
, x_id integer
, UNIQUE(m_id, x_id)  -- !
-- , CONSTRAINT distributor_my_name_uni UNIQUE (m_id, x_id)  -- verbose form
);

The manual:
出于唯一约束的目的,空值不被视为相等,除非指定了NULLS NOT DISTINCT
在您的例子中,您可以为(m_id, x_id)输入类似(1, NULL)的内容任意次,而不会违反约束,Postgres从不认为两个NULL值 * 相等 *-根据SQL标准中的定义。
如果你需要NULL值视为相等(即"非相异")来禁止这样的"重复",我看到了两个三(Postgres 15起)选项:

0.NULLS NOT DISTINCT

这个选项是随Postgres 15添加的,它允许将NULL值视为"非独特的",因此它们中的两个在唯一约束或索引中冲突。这是最方便的选项。
这意味着即使存在唯一约束,也可能存储至少一个约束列中包含空值的重复行。可以通过添加子句NULLS NOT DISTINCT ...
详细说明:

  • 创建具有空列的唯一约束

1.两个部分索引

  • 除了 * 上述UNIQUE约束之外:
CREATE UNIQUE INDEX dist_m_uni_idx ON distributor (m_id) WHERE x_id IS NULL;
CREATE UNIQUE INDEX dist_x_uni_idx ON distributor (x_id) WHERE m_id IS NULL;

但是,如果有两个以上的列可以为NULL,则会很快失控。请参见:

  • 创建具有空列的唯一约束

2.表达式的多列UNIQUE索引

代替UNIQUE约束。我们需要一个自由的默认值,它永远不会出现在所涉及的列中,如-1。添加CHECK约束以禁用它:
一个二个一个一个

oalqel3c

oalqel3c2#

当您需要多态关系时

表使用的列名指示它们可能是对其它表的引用:

CREATE TABLE distributors (m_id integer, x_id integer);

因此,我认为您 * 可能 * 正在尝试对与其他表的多态关系进行建模-其中表distributors中的记录可以引用一个m记录 xor 一个x记录。
多态关系在SQL中是很困难的。我所看到的关于这个主题的最好的资源是“Modeling Polymorphic Associations in a Relational Database“。那里提供了四个可供选择的选项,大多数情况下的建议称为“Exclusive Belongs To”,在您的情况下,这将导致如下所示的表:

CREATE TABLE distributors (
  id serial PRIMARY KEY,
  m_id integer REFERENCES m,
  x_id integer REFERENCES x,
  CHECK (
    ((m_id IS NOT NULL)::integer + (x_id IS NOT NULL)::integer) = 1
  )
);

CREATE UNIQUE INDEX ON distributors (m_id) WHERE m_id IS NOT NULL;
CREATE UNIQUE INDEX ON distributors (x_id) WHERE x_id IS NOT NULL;

与其他解决方案一样,此解决方案使用代理主键列,因为在SQL标准中强制主键不包含NULL值。
这个解决方案在@Erwin Brandstetter的答案中的三个选项的基础上添加了第四个选项,以避免“您可以为(m_id, x_id)输入类似(1, NULL)的内容任意次而不违反约束”的情况。这里,通过两个度量的组合来排除这种情况:
1.部分唯一索引分别在每列上:两个记录(1, NULL)(1, NULL)不会违反第二列上的约束,因为NULL被认为是不同的,但是它们会违反第一列上的约束(具有值1的两个记录)。
1.**检查约束:**缺少的部分阻止了多个(NULL, NULL)记录,但仍然允许,因为NULL被认为是不同的,而且无论如何,因为我们的部分索引没有覆盖它们以保存空间和写入事件。这是通过CHECK约束实现的,它通过确保恰好一列是NULL来阻止任何(NULL, NULL)记录。
但有一点不同@Erwin Brandstetter的答案中的所有选项都允许至少一个记录(NULL, NULL)和任意数量的记录在任何列中没有NULL值(如(1, 2))。当建模多态关系时,您希望不允许这样的记录。这是通过上面解决方案中的检查约束实现的。

相关问题