PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Contrainte de clé étrangère complexe dans SQLAlchemy

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 ?