Implémentation d'une relation plusieurs-à-plusieurs avec des contraintes de participation totale dans SQL

17

Comment dois-je implémenter en SQL le scénario décrit dans le diagramme Entité-Relation suivant?

Relation plusieurs à plusieurs avec contraintes de participation totale

Comme il est montré, chaque Aoccurrence de type d'entité doit être liée à au moins une B contrepartie (indiquée par les doubles lignes de connexion), et vice versa . Je sais que je devrais créer les trois tableaux suivants:

    CREATE TABLE A
    (
        a INT NOT NULL,
        CONSTRAINT A_PK PRIMARY KEY (a)
    );

    CREATE TABLE B
    (
        b INT NOT NULL,
        CONSTRAINT B_PK PRIMARY KEY (b)
    );

    CREATE TABLE R
    (
        a INT NOT NULL,
        b INT NOT NULL,
        CONSTRAINT R_PK      PRIMARY KEY (a, b),
        CONSTRAINT R_to_A_FK FOREIGN KEY (a)
            REFERENCES A (a),
        CONSTRAINT R_to_B_FK FOREIGN KEY (b)
            REFERENCES B (b)
    );

Mais qu'en est-il de la mise en œuvre des contraintes de participation totale (c.-à-d. Imposer que chaque instance de l'une Aou de l'autre soit Bimpliquée dans au moins une occurrence de relation avec l'autre)?

John
la source

Réponses:

16

Ce n'est pas facile à faire en SQL mais ce n'est pas impossible. Si vous voulez que cela soit appliqué via DDL seul, le SGBD doit avoir implémenté des DEFERRABLEcontraintes. Cela pourrait être fait (et peut être vérifié pour fonctionner dans Postgres, qui les a implémentés):

-- lets create first the 2 tables, A and B:
CREATE TABLE a 
( aid INT NOT NULL,
  bid INT NOT NULL,
  CONSTRAINT a_pk PRIMARY KEY (aid) 
 );

CREATE TABLE b 
( bid INT NOT NULL,
  aid INT NOT NULL,
  CONSTRAINT b_pk PRIMARY KEY (bid) 
 );

-- then table R:
CREATE TABLE r 
( aid INT NOT NULL,
  bid INT NOT NULL,
  CONSTRAINT r_pk PRIMARY KEY (aid, bid),
  CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,  
  CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
 );

Jusqu'ici est la conception "normale", où tout Apeut être lié à zéro, un ou plusieurs Bet chacun Bpeut être lié à zéro, un ou plusieurs A.

La restriction de "participation totale" nécessite des contraintes dans l'ordre inverse (à partir Aet Brespectivement du référencement R). Avoir des FOREIGN KEYcontraintes dans des directions opposées (de X à Y et de Y à X) forme un cercle (un problème de "poule et œuf") et c'est pourquoi nous avons besoin que l'une d'entre elles soit au moins DEFERRABLE. Dans ce cas, nous avons deux cercles ( A -> R -> Aet B -> R -> Bnous avons donc besoin de deux contraintes reportables:

-- then we add the 2 constraints that enforce the "total participation":
ALTER TABLE a
  ADD CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r 
    DEFERRABLE INITIALLY DEFERRED ;

ALTER TABLE b
  ADD CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r 
    DEFERRABLE INITIALLY DEFERRED ;

Ensuite, nous pouvons tester que nous pouvons insérer des données. Notez que le INITIALLY DEFERREDn'est pas nécessaire. Nous aurions pu définir les contraintes comme DEFERRABLE INITIALLY IMMEDIATEmais nous devions alors utiliser l' SET CONSTRAINTSinstruction pour les différer pendant la transaction. Dans tous les cas cependant, nous devons insérer dans les tables en une seule transaction:

-- insert data 
BEGIN TRANSACTION ;
    INSERT INTO a (aid, bid)
    VALUES
      (1, 1),    (2, 5),
      (3, 7),    (4, 1) ;

    INSERT INTO b (aid, bid)
    VALUES
      (1, 1),    (1, 2),
      (2, 3),    (2, 4),
      (2, 5),    (3, 6),
      (3, 7) ;

    INSERT INTO r (aid, bid)
    VALUES
      (1, 1),    (1, 2),
      (2, 3),    (2, 4),
      (2, 5),    (3, 6),
      (3, 7),    (4, 1),
      (4, 2),    (4, 7) ; 
 END ;

Testé à SQLfiddle .


Si le SGBD n'a pas de DEFERRABLEcontraintes, une solution consiste à définir les colonnes A (bid)et B (aid)comme NULL. Les INSERTprocédures / instructions devront ensuite d'abord insérer dans Aet B(mettre des valeurs nulles dans bidet aidrespectivement), puis insérer dans Rpuis mettre à jour les valeurs nulles ci-dessus pour les valeurs non nulles connexes de R.

Avec cette approche, le SGBD n'applique pas les exigences uniquement par DDL mais chaque procédure INSERT(et UPDATEet DELETEet MERGE) doit être considérée et ajustée en conséquence et les utilisateurs doivent être limités à les utiliser uniquement et ne pas avoir un accès direct en écriture aux tables.

La présence de cercles dans les FOREIGN KEYcontraintes n'est pas considérée par beaucoup comme la meilleure pratique et pour de bonnes raisons, la complexité étant l'une d'entre elles. Avec la deuxième approche par exemple (avec des colonnes nullables), la mise à jour et la suppression des lignes devront toujours être effectuées avec du code supplémentaire, selon le SGBD. Dans SQL Server par exemple, vous ne pouvez pas simplement mettre ON DELETE CASCADEcar les mises à jour et les suppressions en cascade ne sont pas autorisées lorsqu'il y a des cercles FK.

Veuillez également lire les réponses à cette question connexe:
Comment avoir une relation un-à-plusieurs avec un enfant privilégié?


Une autre troisième approche (voir ma réponse dans la question ci-dessus) consiste à supprimer complètement les FK circulaires. Ainsi, en gardant la première partie du code (avec des tables A, B, Ret les clés étrangères seulement de R à A et B) presque intact ( en simplifiant réellement), nous ajoutons une autre table pour Astocker le « doit avoir un » élément lié de B. Ainsi, la A (bid)colonne se déplace vers A_one (bid)La même chose se fait pour la relation inverse de B à A:

CREATE TABLE a 
( aid INT NOT NULL,
  CONSTRAINT a_pk PRIMARY KEY (aid) 
 );

CREATE TABLE b 
( bid INT NOT NULL,
  CONSTRAINT b_pk PRIMARY KEY (bid) 
 );

-- then table R:
CREATE TABLE r 
( aid INT NOT NULL,
  bid INT NOT NULL,
  CONSTRAINT r_pk PRIMARY KEY (aid, bid),
  CONSTRAINT a_r_fk FOREIGN KEY (aid) REFERENCES a,  
  CONSTRAINT b_r_fk FOREIGN KEY (bid) REFERENCES b
 );

CREATE TABLE a_one 
( aid INT NOT NULL,
  bid INT NOT NULL,
  CONSTRAINT a_one_pk PRIMARY KEY (aid),
  CONSTRAINT r_a_fk FOREIGN KEY (aid, bid) REFERENCES r
 );

CREATE TABLE b_one
( bid INT NOT NULL,
  aid INT NOT NULL,
  CONSTRAINT b_one_pk PRIMARY KEY (bid),
  CONSTRAINT r_b_fk FOREIGN KEY (aid, bid) REFERENCES r
 );

La différence par rapport aux 1ère et 2ème approches est qu'il n'y a pas de FK circulaires, donc les mises à jour et suppressions en cascade fonctionneront très bien. L'application de la "participation totale" ne se fait pas uniquement par DDL, comme dans la deuxième approche, et doit être effectuée par des procédures appropriées ( INSERT/UPDATE/DELETE/MERGE). Une différence mineure avec la 2ème approche est que toutes les colonnes peuvent être définies comme non nulles.


Une autre, 4ème approche (voir la réponse de @Aaron Bertrand dans la question ci-dessus) consiste à utiliser des index uniques filtrés / partiels , s'ils sont disponibles dans votre SGBD (vous en auriez besoin de deux, dans le Rtableau, dans ce cas). Ceci est très similaire à la 3ème approche, sauf que vous n'aurez pas besoin des 2 tables supplémentaires. La contrainte de "participation totale" doit encore être appliquée par code.

ypercubeᵀᴹ
la source
La 4ème approche (un peu cachée) est en fait parfaite. À titre d'exemple, voir postgresql.org/docs/9.6/static/indexes-partial.html Exemple 11-3 pour les postgres.
Danilo
@Danilo Je vois à quel point il est parfait de s'assurer qu'il y a au maximum 1 participation totale (basée sur un domaine supplémentaire - succès dans l'exemple postgre). Je ne vois pas comment il est utile de s'assurer qu'il y a au moins un succès - la vraie question dans ce fil. Pourriez-vous s'il vous plaît développer?
Alexander Mihailov
3

Vous ne pouvez pas directement. Pour commencer, vous ne seriez pas en mesure d'insérer l'enregistrement pour A sans un B déjà existant, mais vous ne pourriez pas créer l'enregistrement B s'il n'y a pas d'enregistrement A pour lui. Il existe plusieurs façons de l'appliquer à l'aide de choses comme les déclencheurs - vous devez vérifier à chaque insertion et supprimer qu'au moins un enregistrement correspondant reste dans la table de liens AB.

Cylindrique
la source