Значения NULL для столбцов referential_constraints.unique_constraint_ * в информационной схеме - PullRequest
2 голосов
/ 16 апреля 2020

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

А затем вторая таблица с 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)
);

Теперь рассмотрим вывод этого query:

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 документации кажется, что эти мета-столбцы должны содержать

имя [объекта], содержащего ограничение уникального или первичного ключа, на которое ссылается ограничение внешнего ключа (всегда текущая база данных)

Вопрос: I Я точно нахожусь в той же базе данных, и уникальный индекс, объявленный в таблице test_abc, является уникальным ограничением (иначе я не смог бы объявить FK для начала), так почему же эти столбцы пусты?

Я использую referential_constraints с некоторыми объединениями, чтобы получить информацию о столбцах, на которые ссылаются мои внешние ключи, но в этом случае я пропускаю все те, где ограничение уникальности установлено с индексом.

1 Ответ

3 голосов
/ 16 апреля 2020

Тестовая настройка

Вы принимаете имя ограничения test_def_abc_id_fkey, имя по умолчанию, полученное в результате вашей настройки в Postgres 11 или более ранней версии. Стоит отметить, однако, что имена по умолчанию были улучшены для Postgres 12, где такая же настройка приводит к test_def_abc_id_abc_id2_fkey. Примечания к выпуску для Postgres 12:

  • Использовать имена всех ключевых столбцов при выборе имен ограничений по умолчанию для внешних ключей (Питер Эйзентраут)

    Ранее в имя ограничения было включено только имя первого столбца, что приводило к неоднозначности для внешних ключей из нескольких столбцов.

См .:

db <> fiddle здесь

Итак, давайте используем явное имя 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)
);

И что работает в Postgres 9,5 - Postgres 12.
Даже в Postgres 9,3.
(у меня было неверное впечатление, фактическое потребуется ограничение .)

Ответ

Ваше наблюдение по запросу информационной схемы имеет вид:

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

Мы получаем строку, но три поля unique_constraint_catalog, unique_constraint_schema и unique_constraint_name равны NULL.

Объяснение кажется простым. Эти столбцы описывают, как сказано в руководстве:

... ограничение уникального или первичного ключа, на которое ссылается ограничение внешнего ключа

Но нет UNIQUE ограничение , просто UNIQUE индекс . Ограничение UNIQUE реализовано с использованием индекса UNIQUE в Postgres. Ограничения определены стандартом SQL, индексы - это детали реализации. Есть различия, подобные той, которую вы обнаружили. Связанный:

Тот же тест с фактическим UNIQUE ограничение показывает данные, как и ожидалось:

дБ <> fiddle здесь

Так что это, кажется, имеет смысл , Тем более что информационная схема также определена комитетом по стандартам SQL, а индексы не стандартизированы, только ограничения. (Нет информации об индексе в представлениях информационной схемы.)

Все ясно? Не совсем.

Однако

Существует еще одно представление информационной схемы key_column_usage. Его последний столбец описывается следующим образом:

position_in_unique_constraint ... Для ограничения по внешнему ключу - порядковый номер ссылочного столбца в его уникальном ограничении (отсчет начинается с 1 ); в противном случае null

Жирный выделение мое. Здесь в любом случае указывается порядковый номер столбца в index :

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

См .:

db <> fiddle here

Кажется несовместимым.

Что еще хуже, руководство утверждает, что для создания фактического ограничения PRIMARY KEY или UNIQUE потребуется FOREIGN 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';

Возвращает:

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

Связано:

...