信息架构中 referential_constraints.unique_constraint_* 列的 NULL 值

NULL values for referential_constraints.unique_constraint_* columns in information schema

在 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);

然后是第二个 table,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)
);

现在考虑这个查询的输出:

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

所有 unique_constraint_* 列都有空值。

Postgres documentation 看来,这些元列应该包含

name of the [object] that contains the unique or primary key constraint that the foreign key constraint references (always the current database)

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

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

测试设置

您假定约束名称 test_def_abc_id_fkey,这是您在 Postgres 11 或更早版本中设置的默认名称。不过,值得注意的是,默认名称已针对 Postgres 12 进行了改进,其中相同的设置会导致 test_def_abc_id_abc_id2_fkeyThe release notes for Postgres 12:

  • Use all key columns' names when selecting default constraint names for foreign keys (Peter Eisentraut)

    Previously, only the first column name was included in the constraint name, resulting in ambiguity for multi-column foreign keys.

参见:

db<>fiddle here

所以让我们为 FK 约束使用显式名称 test_def_abc_fkey 以避免混淆:

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)
);

还有那个works in Postgres 9.5 - Postgres 12.
Even in Postgres 9.3.
(我的印象是需要实际的 约束。)

回答

您查询信息模式的观察结果成立:

SELECT *
FROM   information_schema.referential_constraints
WHERE  constraint_name = 'test_def_abc_fkey';  -- unequivocal name

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

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

... the unique or primary key constraint that the foreign key constraint references

但是没有UNIQUE constraint, just a UNIQUE indexUNIQUE 约束是使用 Postgres 中的 UNIQUE 索引实现的。约束由 SQL 标准定义,索引是实现细节。有像你发现的那样的差异。相关:

  • How does PostgreSQL enforce the UNIQUE constraint / what type of index does it use?

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

db<>fiddle here

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

都清楚了吗?不完全是。

然而

还有另一个信息模式视图key_column_usage。它的最后一列被描述为:

position_in_unique_constraint ... For a foreign-key constraint, ordinal position of the referenced column within its unique constraint (count starts at 1); otherwise null

粗体 强调我的。在这里,列在索引中的顺序位置无论如何都列出了:

SELECT *
FROM   information_schema.key_column_usage
WHERE  constraint_name = 'test_def_abc_fkey';

参见:

db<>fiddle here

似乎不​​一致。

更糟糕的是,the manual 声称创建 FOREIGN KEY 约束需要实际的 PRIMARY KEYUNIQUE 约束:

A foreign key must reference columns that either are a primary key or form a unique constraint. This means that the referenced columns always have an index (the one underlying the primary key or unique constraint); so checks on whether a referencing row has a match will be efficient.

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

相关:

  • Postgres unique constraint vs index

解决方案

I'm using the referential_constraints with some joins to get information about the columns referenced by my foreign keys, but this way I'm missing all those where the unique constraint is set with an index.

在 Postgres 中,系统目录是真实的真实来源。参见:

所以你可以使用这样的东西(就像我在上面的 fiddle 中添加的一样):

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';

Returns:

conname           | fk_table | fk_columns       | ref_table | ref_key_columns
:---------------- | :------- | :--------------- | :-------- | :--------------
test_def_abc_fkey | test_def | {abc_id,abc_id2} | test_abc  | {id,id2}       

相关:

  • Find the referenced table name using table, field and schema name
  • Find referenced field(s) of foreign key constraint
  • How can I find tables which reference a particular row via a foreign key?