Vous pouvez implémenter cela sans sales tours . Il suffit d'étendre la clé étrangère référencer l'option choisie pour inclure variable_id
en plus de choice_id
.
Voici une démo de travail. Tables temporaires, pour que vous puissiez facilement jouer avec :
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)
;
Le choix d'une option associée est autorisé :
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;
Mais il n'y a pas de dépassement :
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".
Exactement ce que vous vouliez.
Toutes les colonnes clés NOT NULL
Je pense avoir trouvé une meilleure solution dans cette dernière réponse :
- Comment gérer les insertions mutuellement dépendantes
Répondant à la question de @ypercube dans les commentaires, pour éviter les entrées avec une association inconnue, faites toutes les colonnes clés NOT NULL
, y compris les clés étrangères.
La dépendance circulaire rendrait normalement cela impossible. C'est le classique œuf de poule problème :l'un des deux doit être là en premier pour faire apparaître l'autre. Mais la nature a trouvé un moyen de contourner cela, tout comme Postgres :contraintes de clé étrangère reportables .
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!
Nouveau les variables et options associées doivent être insérées dans la même transaction :
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;
Le NOT NULL
contrainte ne peut être différée, elle est appliquée immédiatement. Mais la contrainte de clé étrangère peut , parce que nous l'avons défini ainsi. Il est vérifié à la fin de la transaction, ce qui évite le problème de l'œuf de poule.
Dans ce modifié scénario, les deux clés étrangères sont différées . Vous pouvez entrer des variables et des options dans un ordre arbitraire.
Vous pouvez même le faire fonctionner avec une simple contrainte FK non reportable si vous entrez des entrées liées dans les deux tables dans une seule instruction en utilisant les CTE comme détaillé dans la réponse liée.
Vous avez peut-être remarqué que la première contrainte de clé étrangère n'a pas de CASCADE
modificateur. (Cela n'aurait aucun sens d'autoriser les modifications de variableoptions.variable_id
pour revenir en cascade.
Par contre, la deuxième clé étrangère a une CASCADE
modificateur et est défini DEFERRABLE
néanmoins. Cela comporte certaines limites. Le manuel :
Actions référentielles autres que
NO ACTION
le contrôle ne peut pas être différé, même si la contrainte est déclarée différée.
NO ACTION
est la valeur par défaut.
Donc, vérifications d'intégrité référentielle sur INSERT
sont différées, mais les actions en cascade déclarées sur DELETE
et UPDATE
ne sont pas. Ce qui suit n'est pas autorisé dans PostgreSQL 9.0 ou version ultérieure car les contraintes sont appliquées après chaque instruction :
UPDATE option SET var_id = 4 WHERE var_id = 5;
DELETE FROM var WHERE var_id = 5;
Détails :
- La contrainte définie DIFFÉRABLE INITIALEMENT IMMÉDIAT est toujours DIFFÉRÉE ?