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
, чтобы быть в значительной степени законным.Связанные случаи с примерами кода и обоснованием: