信息模式中referential_constraints.unique_constraint_* 列的NULL 值

bla*_*een 4 sql postgresql foreign-keys information-schema database-metadata

在 Postgres 10 中我声明了以下内容:

create table test_abc (
    pk integer not null,
    id integer not NULL,
    id2 integer not null,
    PRIMARY KEY (pk)
);
CREATE UNIQUE INDEX test_abc_ids ON test_abc(id,id2);
Run Code Online (Sandbox Code Playgroud)

然后是第二个表,其中 FK 引用第一个表:

create table test_def (
    id integer not null,
    abc_id integer,
    abc_id2 integer,
    PRIMARY KEY (id),
    FOREIGN KEY (abc_id,abc_id2) references test_abc(id,id2)
);
Run Code Online (Sandbox Code Playgroud)

现在考虑此查询的输出:

SELECT unique_constraint_catalog, unique_constraint_schema, unique_constraint_name
FROM   information_schema.referential_constraints r
WHERE  r.constraint_name = 'test_def_abc_id_fkey'
----------------------
NULL NULL NULL
Run Code Online (Sandbox Code Playgroud)

所有unique_constraint_*列都有空值。

Postgres 文档看来这些元列应该包含

包含外键约束引用的唯一或主键约束的 [object] 的名称(始终是当前数据库)

问题: 我肯定在同一个数据库中,并且表上声明的唯一索引test_abc是唯一约束(否则我无法首先声明FK),那么为什么这些列是空的?

我使用referential_constraints一些联接来获取有关外键引用的列的信息,但这样我就错过了使用索引设置唯一约束的所有信息。

Erw*_*ter 6

测试设置

您假定约束 name test_def_abc_id_fkey,这是您在 Postgres 11 或更早版本中的设置产生的默认名称。不过,值得注意的是,Postgres 12 的默认名称已得到改进,相同的设置会生成test_def_abc_id_abc_id2_fkey. Postgres 12 的发行说明:

  • 为外键选择默认约束名称时使用所有键列的名称 (Peter Eisentraut)

以前,约束名称中仅包含第一个列名称,导致多列外键不明确。

看:

db<>在这里摆弄

test_def_abc_fkey因此,让我们使用FK 约束的显式名称以避免混淆:

CREATE TABLE test_abc (
  pk  int PRIMARY KEY
, id  int NOT NULL
, id2 int NOT NULL
);

CREATE UNIQUE INDEX test_abc_ids ON test_abc(id,id2);

CREATE TABLE test_def (
  id      int PRIMARY KEY
, abc_id  int
, abc_id2 int
, CONSTRAINT test_def_abc_fkey  -- !
     FOREIGN KEY (abc_id,abc_id2) REFERENCES test_abc(id,id2)
);
Run Code Online (Sandbox Code Playgroud)

在 Postgres 9.5 - Postgres 12 中有效甚至在 Postgres 9.3 中也有效。
(我一直有错误的印象,需要实际的约束。)

回答

您通过查询信息模式观察到的结果是:

SELECT *
FROM   information_schema.referential_constraints
WHERE  constraint_name = 'test_def_abc_fkey';  -- unequivocal name
Run Code Online (Sandbox Code Playgroud)

我们得到一行,但是三个字段unique_constraint_catalogunique_constraint_schemaunique_constraint_nameNULL

解释似乎很简单。正如手册所述,这些列描述了:

...外键约束引用的唯一或主键约束

但没有UNIQUE 约束,只是一个UNIQUE 索引。约束UNIQUE是使用UNIQUEPostgres 中的索引来实现的。约束由 SQL 标准定义,索引是实现细节。存在一些差异,就像您发现的那样。有关的:

UNIQUE 具有实际约束的相同测试显示了预期的数据:

db<>在这里摆弄

所以这似乎是有道理的。特别是因为信息模式也是由 SQL 标准委员会定义的,并且索引没有标准化,只有约束。(信息模式视图中没有索引信息。)

全清?不完全的。

然而

还有另一个信息模式视图key_column_usage。其最后一栏描述为:

position_in_unique_constraint...对于外键约束,引用列在其唯一约束内的顺序位置(计数从 1 开始);否则为空

大胆强调我的。这里无论如何都会列出列在索引中的顺序位置:

SELECT *
FROM   information_schema.key_column_usage
WHERE  constraint_name = 'test_def_abc_fkey';
Run Code Online (Sandbox Code Playgroud)

看:

db<>在这里摆弄

看起来不一致。

更糟糕的是,该手册声称创建约束需要实际PRIMARY KEY或约束:UNIQUEFOREIGN KEY

外键必须引用作为主键或形成唯一约束的列。这意味着引用的列总是有一个索引(主键或唯一约束的基础索引);因此检查引用行是否有匹配将是有效的。

似乎是文档错误?如果没有人能指出我哪里出错了,我将提交错误报告。

有关的:

解决方案

我使用referential_constraints一些联接来获取有关外键引用的列的信息,但这样我就错过了使用索引设置唯一约束的所有信息。

在 Postgres 中,系统目录是实际的事实来源。看:

所以你可以使用这样的东西(就像我在上面的小提琴中添加的那样):

SELECT c.conname
     , c.conrelid::regclass  AS fk_table, k1.fk_columns
     , c.confrelid::regclass AS ref_table, k2.ref_key_columns
FROM   pg_catalog.pg_constraint c
LEFT   JOIN LATERAL (
   SELECT ARRAY (
      SELECT a.attname
      FROM   pg_catalog.pg_attribute a
           , unnest(c.conkey) WITH ORDINALITY AS k(attnum, ord)
      WHERE  a.attrelid = c.conrelid
      AND    a.attnum = k.attnum
      ORDER  BY k.ord
      ) AS fk_columns
   ) k1 ON true
LEFT   JOIN LATERAL (
   SELECT ARRAY (
      SELECT a.attname
      FROM   pg_catalog.pg_attribute a
           , unnest(c.confkey) WITH ORDINALITY AS k(attnum, ord)
      WHERE  a.attrelid = c.confrelid
      AND    a.attnum = k.attnum
      ORDER  BY k.ord
      ) AS ref_key_columns
   ) k2 ON true
WHERE  conname = 'test_def_abc_fkey';
Run Code Online (Sandbox Code Playgroud)

返回:

同名| fk_表 | fk_列 | 参考表 | 参考键列
:---------------- | :----- | :-------------- | :-------- | :--------------
测试_def_abc_fkey | 测试定义| {abc_id,abc_id2} | 测试_abc | {id,id2}       

有关的: