Можете да го приложите без мръсни трикове . Просто разширете външния ключ препращане към избраната опция за включване на variable_id
в допълнение към choice_id
.
Ето работеща демонстрация. Временни маси, за да можете лесно да играете с тях:
CREATE TABLE systemvariables (
variable_id int PRIMARY KEY
, choice_id int
, variable text
);
INSERT INTO systemvariables(variable_id, variable) VALUES
(1, 'var1')
, (2, 'var2')
, (3, 'var3')
;
CREATE TABLE variableoptions (
option_id int PRIMARY KEY
, variable_id int REFERENCES systemvariables ON UPDATE CASCADE ON DELETE CASCADE
, option text
, UNIQUE (option_id, variable_id) -- needed for the FK
);
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 TABLE systemvariables (
variable_id int PRIMARY KEY
, variable text
, choice_id int NOT NULL
);
CREATE TABLE variableoptions (
option_id int PRIMARY KEY
, option text
, variable_id int NOT NULL REFERENCES systemvariables
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
ограничението не може да бъде отложено, то се прилага незабавно. Но ограничението на външния ключ може , защото така го дефинирахме. Проверява се в края на транзакцията, което избягва проблема пиле-яйце.
В товаредакт.та сценарий, и двата външни ключа са отложени . Можете да въвеждате променливи и опции в произволна последователност.
Можете дори да го накарате да работи с обикновено неотлагаемо FK ограничение, ако въведете свързани записи в двете таблици в един израз използвайки CTE, както е подробно описано в свързания отговор.
Може да сте забелязали, че първото ограничение за външен ключ няма CASCADE
модификатор. (Не би имало смисъл да се разрешават промени в variableoptions.variable_id
за каскадно връщане назад.
От друга страна, вторият външен ключ има CASCADE
модификатор и е дефиниран DEFERRABLE
въпреки това. Това носи някои ограничения. Ръководството:
Референтни действия, различни от
NO ACTION
проверката не може да бъде отложена, дори ако ограничението е декларирано за отлагане.
NO ACTION
е по подразбиране.
И така, проверките на референтната цялост на INSERT
се отлагат, но декларираните каскадни действия при DELETE
и UPDATE
не са. Следното не е разрешено в PostgreSQL 9.0 или по-нова версия, тъй като ограниченията се прилагат след всеки израз:
UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;
Подробности:
- Дефинираното ограничение ОТЛОЖЕНО ПЪРВОНАЧАЛНО НЕЗАБАВНО все още е ОТЛОЖЕНО?