У вас есть две проблемы:
- Присутствие : не может быть родительской строки без хотя бы одной дочерней строки.
- Эксклюзивность : не может быть родительской строки с более чем одной дочерней строкой.
В СУБД, поддерживающей отложенные ограничения (включая PostgreSQL и Oracle), обе эти цели могут быть достигнуты декларативно:
Круглый внешний ключ находится между gastropod.snail_id
и snail.snail_id
, а также между gastropod.slug_id
и slug.slug_id
.Существует также CHECK, который гарантирует, что точно один из них соответствует gastropod.gastropod_id
(а другой равен NULL).
Чтобы устранить проблему "курица и яйцо" при вставке новых данных, отложитеодно направление внешних ключей.
Вот как это будет реализовано в PostgreSQL:
CREATE TABLE gastropod (
gastropod_id int PRIMARY KEY,
snail_id int UNIQUE,
slug_id int UNIQUE,
CHECK (
(slug_id IS NULL AND snail_id IS NOT NULL AND snail_id = gastropod_id)
OR (snail_id IS NULL AND slug_id IS NOT NULL AND slug_id = gastropod_id)
)
);
CREATE TABLE snail (
snail_id int PRIMARY KEY,
FOREIGN KEY (snail_id) REFERENCES gastropod (snail_id) ON DELETE CASCADE
);
CREATE TABLE slug (
slug_id int PRIMARY KEY,
FOREIGN KEY (slug_id) REFERENCES gastropod (slug_id) ON DELETE CASCADE
);
ALTER TABLE gastropod ADD FOREIGN KEY (snail_id) REFERENCES snail (snail_id)
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE gastropod ADD FOREIGN KEY (slug_id) REFERENCES slug (slug_id)
DEFERRABLE INITIALLY DEFERRED;
Новые данные вставляются следующим образом:
START TRANSACTION;
INSERT INTO gastropod (gastropod_id, snail_id) VALUES (1, 1);
INSERT INTO snail (snail_id) VALUES (1);
COMMIT;
Однако при попыткене удается вставить только родительский, но не дочерний:
START TRANSACTION;
INSERT INTO gastropod (gastropod_id, snail_id) VALUES (2, 2);
COMMIT; -- FK violation.
Не удается вставить неправильный тип дочернего элемента:
START TRANSACTION;
INSERT INTO gastropod (gastropod_id, snail_id) VALUES (2, 2);
INSERT INTO slug (slug_id) VALUES (2); -- FK violation.
COMMIT;
И вставить слишком малое, слишком много или несоответствующие поля в родительскомтакже происходит сбой:
INSERT INTO gastropod (gastropod_id) VALUES (2); -- CHECK violation.
...
INSERT INTO gastropod (gastropod_id, snail_id, slug_id) VALUES (2, 2, 2); -- CHECK violation.
...
INSERT INTO gastropod (gastropod_id, snail_id) VALUES (1, 2); -- CHECK violation.
В СУБД, которая не поддерживает отложенные ограничения, эксклюзивность (но не наличие) может быть принудительно применена следующим образом:
В СУБД, которая поддерживает вычисляемые поля (например, виртуальные столбцы Oracle 11), дискриминатор типа type
не должен физически храниться на уровне дочерних таблиц (только родительская таблица).
уникальный констraint U1
может понадобиться в СУБД, которые не поддерживают FK-ссылки супер-набор ключа (насколько я знаю, почти все из них), поэтому мы создаем этот супер-набор искусственно.
Должно ли все это быть фактически сделано на практике - это другой вопрос.Это одна из тех ситуаций, когда применение некоторых аспектов целостности данных на уровне приложений может быть оправдано уменьшением накладных расходов и сложности.