Как принудительно применить ограничение, что указанная строка также имеет заданный тип - PullRequest
1 голос
/ 02 апреля 2019

У меня есть несколько таблиц B, C, D ... ссылающихся друг на друга таблицы A.

Давайте просто посмотрим на A и B:

CREATE TABLE A (
  ID int PRIMARY KEY
, TYPE enum_type
);

CREATE TABLE B (
  A_ID int REFERENCES A(ID)
);

Можно ли проверить, чтоA всегда имеет один конкретный TYPE, если на него ссылается таблица B?

  • каждая строка в A, на которую ссылается таблица B, должна иметь TYPE 'X'
  • на каждую строку в A, на которую ссылается таблица Cдолжен иметь ТИП 'Y'
  • каждая строка в A, на которую ссылается таблица D, должна иметь ТИП 'Z'
  • и т. д.

Возможно ли это как-то сограничения?Или я должен использовать функции?Или какой подход лучше для этого?

Ответы [ 3 ]

2 голосов
/ 02 апреля 2019

1.Ограничение FK

Если вы хотите применить ссылочную целостность с ограничениями FK, вам нужно будет избыточно включить type в таблицу b, добавить (избыточное) ограничение UNIQUE для a(id, type) исделать это многоколоночным ограничением FK на (a_id, type) REFERENCES a(id, type).

Связано (с кодом):

2.Разделение

Менее строгий дизайн может быть создан с разбиением таблицы .Иметь основную таблицу a с разделением списка на ключе раздела type.Таким образом, у вас будет раздел a_x со всем типом 'X' и раздел a_y со всем типом 'Y'.Затем создайте ограничения FK для соответствующего раздела, и вы можете быть уверены в его типе.

Я предлагаю список разделов в Postgres 11 или новее.Связано (с примером кода для секционирования списка):

Примечаниечто у вас не может быть ограничения FK для таблицы master . Руководство :

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

Решение FK занимает больше дискового пространства из-за избыточного столбца иизбыточный индекс.С разделением OTOH, в зависимости от вашей версии Postgres и выбранного метода разделения, вам приходится иметь дело с ограничениями / предостережениями этой конкретной установки, хотя

3.Триггер

Вы опубликовали другое решение на основе триггера, которое не занимает дополнительного места и не усложняет реляционный дизайн.Но это легче сломать / обойти.Не такой пуленепробиваемый, как решение FK.В любом случае, вот улучшенная версия вашей идеи:

CREATE TABLE a (
    id int PRIMARY KEY,   
    type enum_type NOT NULL  -- ! NOT NULL or adapt the check in the trigger
);

CREATE UNIQUE INDEX a_type_id_idx ON a(type, id);  -- ! see below

CREATE TABLE b (
    a_id int REFERENCES a(id)
);

CREATE FUNCTION type_check()  -- ! rewritten
  RETURNS trigger AS
$type_check$
BEGIN
   IF EXISTS (
      SELECT 
      FROM   a
      WHERE  a.id = NEW.a_id
      AND    a.type = TG_ARGV[0]) THEN
      -- do nothing, all good
   ELSE
      RAISE EXCEPTION 'Type does not match table type!';
   END IF;

   RETURN NEW;
END
$type_check$  LANGUAGE plpgsql;

CREATE TRIGGER tg_check_type_b
BEFORE INSERT OR UPDATE ON b
FOR EACH ROW EXECUTE PROCEDURE type_check('X');
  • Индекс на a(type, id) позволяет сканировать только по индексу для частых поисков.Индексные выражения в этом порядке.Вот почему:

  • Вы проверяли с помощью IF var_type <> var_allowed_type, но a.type не определено NOT NULL.Это позволило бы несовпадения с NULL.

  • Назначения сравнительно дорогостоящие в plpgsql.Я упростил функцию, чтобы сделать ее быстрой .IF EXISTS ... заменяя переменные, дополнительный запрос с присваиваниями должен быть значительно быстрее, а также обнаруживать потенциальное несоответствие с NULL путем инвертирования логики.

4.Злоупотребление ограничением CHECK

Еще быстрее и еще более хакерским.Сделайте это NOT VALID, чтобы быть в значительной степени законным.Связанные случаи с примерами кода и обоснованием:

1 голос
/ 03 апреля 2019

Опция Erwins на самом деле работала для меня, но, поскольку у меня много таблиц и нескольких полей, я выбрал другую опцию, как причину накладных расходов в таблицах:

CREATE TABLE A (
    ID int PRIMARY KEY,   
    TYPE enum_type
);

CREATE TABLE B (
    A_ID int REFERENCES A(ID)
);

CREATE FUNCTION type_check() RETURNS trigger AS $type_check$
    DECLARE
    var_type enum_type;
    var_allowed_type enum_type;
BEGIN
    SELECT TYPE FROM A INTO var_type WHERE ID = NEW.A_ID;
    var_allowed_type :=  TG_ARGV[0];

    --Check that TYPE is the same as table
    IF var_type <> var_allowed_type THEN
       RAISE EXCEPTION 'Type does not match table type';
    END IF;


    RETURN NEW;
END;  
$type_check$ LANGUAGE plpgsql;

CREATE TRIGGER tg_check_type_B
     BEFORE INSERT OR UPDATE ON B
     FOR EACH ROW EXECUTE PROCEDURE type_check('X');
0 голосов
/ 02 апреля 2019

Вы можете добавить проверочное ограничение для условия xy

...