Вы можете реализовать это без подвохов .Просто расширяйте внешний ключ , ссылаясь на выбранную опцию, чтобы включить variable_id
в дополнение к choice_id
.
Вот рабочая демонстрация.Временные столы, так что вы можете легко играть с ним:
CREATE TEMP TABLE systemvariables (
variable_id integer PRIMARY KEY
, variable text
, choice_id integer
);
INSERT INTO systemvariables(variable_id, variable)
VALUES
(1, 'var1')
, (2, 'var2')
, (3, 'var3');
CREATE TEMP TABLE variableoptions (
option_id integer PRIMARY KEY
, option text
, variable_id integer REFERENCES systemvariables(variable_id)
ON UPDATE CASCADE ON DELETE CASCADE
, UNIQUE (option_id, variable_id) -- needed for the foreign key
);
ALTER TABLE systemvariables
ADD CONSTRAINT systemvariables_choice_id_fk
FOREIGN KEY (choice_id, variable_id)
REFERENCES variableoptions(option_id, variable_id);
INSERT INTO variableoptions
VALUES
(1, 'var1_op1', 1)
, (2, 'var1_op2', 1)
, (3, 'var1_op3', 1)
, (4, 'var2_op1', 2)
, (5, 'var2_op2', 2)
, (6, 'var3_op1', 3);
Выбор соответствующего параметра разрешен:
UPDATE systemvariables SET choice_id = 2 WHERE variable_id = 1;
UPDATE systemvariables SET choice_id = 5 WHERE variable_id = 2;
UPDATE systemvariables SET choice_id = 6 WHERE variable_id = 3;
Но нет выхода из линии:
UPDATE systemvariables SET choice_id = 7 WHERE variable_id = 3;
UPDATE systemvariables SET choice_id = 4 WHERE variable_id = 1;
ERROR: insert or update on table "systemvariables" violates foreign key constraint "systemvariables_choice_id_fk"
DETAIL: Key (choice_id,variable_id)=(4,1) is not present in table "variableoptions".
Вуаля .Именно то, что вы хотели.
Все ключевые столбцы NOT NULL
Мне кажется, я нашел лучшее решение в следующем ответе:
Решение вопроса @ ypercube в комментариях , чтобы избежать записей с неизвестной ассоциацией, делающих все ключевые столбцы NOT NULL
, включая внешние ключи.
Круговая зависимость обычно делает это невозможным.Это классическая проблема куриное яйцо : один из них должен быть первым, чтобы породить другого.Но природа нашла способ обойти это, и Postgres: отложенные ограничения внешнего ключа .
CREATE TEMP TABLE systemvariables (
variable_id integer PRIMARY KEY
, variable text
, choice_id integer NOT NULL
);
CREATE TEMP TABLE variableoptions (
option_id integer PRIMARY KEY
, option text
, variable_id integer NOT NULL
REFERENCES systemvariables(variable_id)
ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
, UNIQUE (option_id, variable_id) -- needed for the foreign key
);
ALTER TABLE systemvariables
ADD CONSTRAINT systemvariables_choice_id_fk FOREIGN KEY (choice_id, variable_id)
REFERENCES variableoptions(option_id, variable_id)
DEFERRABLE INITIALLY DEFERRED; -- no CASCADING here!
Новые переменные и связанные с ними опции должны быть вставлены вта же транзакция:
BEGIN;
INSERT INTO systemvariables (variable_id, variable, choice_id)
VALUES
(1, 'var1', 2)
, (2, 'var2', 5)
, (3, 'var3', 6);
INSERT INTO variableoptions (option_id, option, variable_id)
VALUES
(1, 'var1_op1', 1)
, (2, 'var1_op2', 1)
, (3, 'var1_op3', 1)
, (4, 'var2_op1', 2)
, (5, 'var2_op2', 2)
, (6, 'var3_op1', 3);
END;
Ограничение NOT NULL
не может быть отложено, оно вступает в силу немедленно.Но ограничение внешнего ключа может , потому что мы определили его таким образом.Проверяется в конце транзакции, что позволяет избежать проблемы с куриным яйцом.
В этом отредактированном сценарии оба внешних ключа откладываются .Вы можете вводить переменные и параметры в произвольной последовательности.
Возможно, вы заметили, что первое ограничение внешнего ключа не имеет модификатора CASCADE
.(Не имеет смысла разрешать каскадные изменения в variableoptions.variable_id
.
С другой стороны, второй внешний ключ имеет модификатор CASCADE
и, тем не менее, определен как отложенный. Это имеет некоторые ограничения. Руководство :
Ссылочные действия, кроме проверки NO ACTION
, не могут быть отложены, даже если ограничение объявлено отложенным.
NO ACTION
является значением по умолчанию.
Таким образом, проверки ссылочной целостности на INSERT
откладываются, но объявленные каскадные действия на DELETE
и UPDATE
- нет. Следующее не разрешено в PostgreSQL 9.0или 9.1 , поскольку ограничения применяются после каждого оператора:
UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;
Подробности:
Как ни странно, то же самое работает в PostgreSQL 8.4 , хотя документация утверждает, что такое же поведение. Выглядит как ошибка в старой версии - даже если кажется, чтоскорее не вредный, но на первый взгляд.Должно быть исправлено для более новых версий.