Bonsoir Wnejla,
Résumons ainsi la situation :
On a droit à une bijection. La dérivation en MLD proposée par les AGL est la suivante :
Où il apparaît clairement que la bijection peut provoquer une angoisse d’ordre métaphysique : on est désormais confronté au problème de l’œuf et de la poule, on est en présence d’un cycle...
La table INCIDENT est dotée d’une clé primaire {IncidentId} (symbolisée par le mickey <pk>) et d’une clé alternative {TacheId} (symbolisée par le mickey <ak>), avec en plus une contrainte référentielle par rapport à la table TACHE, symbolisée par le mickey <fk>. Ce qui vaut pour la table INCIDENT vaut évidemment pour la table TACHE, mutatis mutandis.
Si on utilise un SGBD conforme à la théorie relationnelle (voyez An Introduction to Relational Database Theory de Hugh Darwen, ouvrage clair, rigoureux et en plus gratuit ), le code qui découle du MLD est le suivant :
Variable relationnelle INCIDENT
1 2 3 4 5 6 7 8 9
| VAR INCIDENT BASE RELATION
{
IncidentId INTEGER
, TacheId INTEGER
, Intitule CHAR
}
KEY {IncidentId}
KEY {TacheId}
FOREIGN KEY {TacheId} REFERENCES TACHE |
Variable relationnelle TACHE
1 2 3 4 5 6 7 8 9
| VAR TACHE BASE RELATION
{
TacheId INTEGER
, IncidentId INTEGER
, Description CHAR
}
KEY {TacheId}
KEY {IncidentId}
FOREIGN KEY {IncidentId} REFERENCES INCIDENT ; |
Pour associer simultanément une tâche et un incident, on procède par affectation multiple :
1 2 3 4 5 6 7 8 9 10 11
| INSERT INCIDENT RELATION {
TUPLE {IncidentId 123,
TacheId 314,
Intitule 'Vol du fétiche arumbaya'}
} ,
INSERT TACHE RELATION {
TUPLE {TacheId 314,
IncidentId 123,
Description 'Envoyer Tintin enquêter'}
} ; |
Les deux inserts font partie de la même instruction, ils y sont simplement séparés par une virgule. La fin de l’instruction est marquée par un point-virgule. Les contraintes d’intégrité ne sont vérifiées qu’à des frontières de points-virgules : en l’occurrence tout se passera donc bien.
Mais comme dit Chris Date, les contraintes référentielles mises en place (cf. les clauses FOREIGN KEY ci-dessus) ne permettent pas de tout contrôler. En effet, elles n’interdisent pas la situation suivante :
INCIDENT TACHE
+------------+---------+ +---------+------------+
| IncidentId | TacheId | | TacheId | IncidentId |
|------------+---------| |---------+------------|
| 123 | 314 | | 314 | 456 |
| 456 | 271 | | 271 | 123 |
+------------+---------+ +---------+------------+
Pour garantir l'intégrité, il faut et il suffit de mettre en œuvre la contrainte suivante :
1 2
| CONSTRAINT INCIDENT_TACHE_CHK01
INCIDENT {IncidentId, TacheId} = TACHE {IncidentId, TacheId} ; |
Ce qui se lit : La projection de la variable INCIDENT sur les attributs IncidentId et TacheId doit être égale à la projection de la variable TACHE sur ces mêmes attributs.
Suite à cela, les clés étrangères deviennent de facto inutiles et on les supprime.
Cas de SQL
Si l’on utilise un SGBD SQL (car je suppose qu’à terme c’est bien le Sorry Query Language qui sera utilisé) :
TABLE INCIDENT
1 2 3 4 5 6 7 8
| CREATE TABLE INCIDENT
(
IncidentId INT NOT NULL
, TacheId INT NOT NULL
, Intitule VARCHAR(64) NOT NULL
, CONSTRAINT INCIDENT_PK PRIMARY KEY (IncidentId)
, CONSTRAINT INCIDENT_AK UNIQUE (TacheId)
) ; |
TABLE TACHE
1 2 3 4 5 6 7 8
| CREATE TABLE TACHE
(
TacheId INT NOT NULL
, IncidentId INT NOT NULL
, Description VARCHAR(64) NOT NULL
, CONSTRAINT TACHE_PK PRIMARY KEY (TacheId)
, CONSTRAINT TACHE_AK UNIQUE (IncidentId)
) ; |
Contraintes référentielles :
1 2 3 4 5 6 7
| ALTER TABLE TACHE
ADD CONSTRAINT TACHE_INCIDENT_FK FOREIGN KEY (IncidentId) REFERENCES INCIDENT
;
ALTER TABLE INCIDENT
ADD CONSTRAINT INCIDENT_TACHE_FK FOREIGN KEY (TacheId) REFERENCES TACHE
; |
Gros problème : avec SQL, on ne dispose pas de l’affectation multiple, ce qui est singulièrement gênant car suite à une tentative d’INSERT dans une des deux tables, le SGBD rejettera l’opération du fait du déclenchement immédiat des contraintes référentielles (la norme SQL et Oracle permettent un déclenchement différé, mais sous le contrôle de l'applicatif, ce qui n'est pas totalement satisfaisant).
Une solution consiste à ne pas mettre en œuvre ces contraintes, mais comme il ne faut pas faire de trapèze volant sans filet, pour garantir à la fois la bijection et l'intégrité on imposera par exemple la mise à jour des tables via une vue de jointure (avec la suppression donc des droits de mise à jour en direct des tables), un trigger se chargeant de ventiler les données dans les tables INCIDENT et TACHE :
La vue :
1 2 3
| CREATE VIEW INCIDENT_TACHE (IncidentId, TacheId, Intitule, Description) AS
SELECT x.IncidentId, x.TacheId, x.Intitule, y.Description
FROM INCIDENT AS x JOIN TACHE AS y ON x.IncidentId = y.IncidentId ; |
Le trigger (MS SQL Server) :
1 2 3 4 5 6 7 8 9
| CREATE TRIGGER INCIDENT_TACHE_INSERT_TR ON INCIDENT_TACHE INSTEAD OF INSERT AS
INSERT INTO INCIDENT (IncidentId, TacheId, Intitule)
SELECT IncidentId, TacheId, Intitule
FROM INSERTED ;
INSERT INTO TACHE (IncidentId, TacheId, Description)
SELECT IncidentId, TacheId, Description
FROM INSERTED ; |
Un bout de jeu d’essai :
1 2 3 4 5
| INSERT INTO INCIDENT_TACHE (IncidentId, TacheId, Intitule, Description)
VALUES (123, 314, 'Vol du fétiche arumbaya', 'Envoyer Tintin enquêter') ;
INSERT INTO INCIDENT_TACHE (IncidentId, TacheId, Intitule, Description)
VALUES (456, 271, 'On a perdu le nord', 'Envoyer Black & White') ; |
Au résultat :
INCIDENT
IncidentId TacheId Intitule
---------- ------- -----------------------
123 314 Vol du fétiche arumbaya
456 271 On a perdu le nord
TACHE
TacheId IncidentId Description
------- ---------- ------------------------
314 123 Envoyer Tintin enquêter
271 456 Envoyer Black & White
Si votre SGBD ne permet pas de mettre à jour les vues de jointure par trigger (cas de MySQL), il faudra garantir la bijection applicativement (avec les risques que cela comporte), ou bien affaiblir la contrainte et se résoudre à une injection :
[INCIDENT]--1----------0..1--[TACHE]
Reste la méthode expéditive qui consiste à fondre les deux tables INCIDENT et TACHE en une seule, mais c’est pour le moins simpliste, inélégant, choquant et brutal...
Partager