Contraintes de chemin
Envoyé par
julieen03
Le prix n’est pas le même pour tous les fournisseurs ! Je devrais donc placer Prix_achat_htva, Prix_achat_tvac, prix_vente_htva, prix_vente_tvac dans « Catalogue » ?
D’accord en ce qui concerne le prix d’achat (je ne vois pas ce que vient faire le prix de vente...)
Voyons voir maintenant votre association ternaire CATALOGUE :
Une commande peut participer N fois à l’association, ce qui a pour conséquence qu’une commande peut être associée à plusieurs produits, ce qui est normal, mais aussi à plusieurs fournisseurs, ce qui cette fois-est problématique...
Si la règle est qu’une commande est passée auprès d’un seul fournisseur, la représentation doit devenir la suivante :
En complétant avec les lignes de commande :
Mais attention, on voit apparaître une boucle : il y a un 1er chemin, appelons-le Chemin_1, permettant d’aller de l’entité-type LIGNE_COMMANDE à l’entité-type PRODUIT, via l’association REFERENCER, mais il existe aussi un 2e chemin, appelons-le Chemin_2, permettant d’aller de l’entité-type LIGNE_COMMANDE à l’entité-type PRODUIT, via les associations COMPOSER > PASSER_A > CATALOGUE, et le risque est que, via le chemin Chemin_2, le fournisseur impliqué dans cette affaire ne fournisse pas le produit déterminé par la ligne de commande (chemin_1).
La résolution du problème est ici à effectuer au niveau relationnel. Avec un langage conforme à la théorie relationnelle, ceci passe par une contrainte :
CONSTRAINT CHK01
JOIN (LIGNE_COMMANDE, COMMANDE, FOURNISSEUR) {id_fournisseur, id_produit} ⊆ CATALOGUE {id_fournisseur, id_produit} ;
Ce qui se lit : l’ensemble défini par la projection {id_fournisseur, id_produit} appliquée à la jointure naturelle des variables LIGNE_COMMANDE, COMMANDE, FOURNISSEUR, doit être inclus dans l’ensemble défini par la projection {id_fournisseur, id_produit} appliquée à la variable CATALOGUE.
En SQL, la contrainte fera l’objet d’une assertion (instruction CREATE ASSERTION), mais aujourd’hui les SGBD du marché ne proposent pas cette instruction. Donc, soit on met en œuvre des triggers, soit on agit directement au niveau des instructions CREATE TABLE.
Agissons au niveau des CREATE TABLE. Ceci nécessite déjà la transformation de l’association CATALOGUE en entité-type et sa mise en relation avec l’entité-type LIGNE_COMMANDE :
A cette occasion, vous observerez que, à l’instar de CARTE_FIDELITE, la nouvelle entité-type CATALOGUE n’a pas d’identifiant en propre, mais elle a un identifiant relatif composé, hérité de PRODUIT d’une part (par le truchement de l’association CAT_PRO) et de FOURNISSEUR d’autre part (par le truchement de l’association CAT_FOU).
Je crois qu’il est temps de passer au diagramme relationnel puis au script SQL de définition des tables pour mieux voir comment on procède. J’abandonne JMerise qui est déficient à ce stade, et passe à MySQL Workbench :
La notation utilisée pour les cardinalités est dite patte d’oie ou de corbeau (Crow’s foot) : « » pour 0,N, et « » pour 1,N.
Les traits continus symbolisent les associations dans lesquelles l'identification relative est partie prenante.
Les losanges rougeâtres symbolisent des clés étrangères. Les clés primaires sont repérées par les mickeys et (rouge : la clé est en même temps clé étrangère, conséquence de l’identification relative).
Script SQL correspondant :
CREATE TABLE FOURNISSEUR
(
id_fournisseur INT NOT NULL
, nom_fournisseur VARCHAR(25) NOT NULL
, CONSTRAINT FOURNISSEUR_PK PRIMARY KEY (id_fournisseur)
) ;
CREATE TABLE PRODUIT
(
id_produit INT NOT NULL
, cnk_produit VARCHAR(15) NOT NULL
, nom_produit VARCHAR(48) NOT NULL
, code_barre VARCHAR(13) NOT NULL
, prix_vente_htva DECIMAL(7,2) NOT NULL
, prix_vente_htva_depuis DATE NOT NULL
, taux_tva DECIMAL(5,2) NOT NULL
, taux_tva_depuis DATE NOT NULL
, CONSTRAINT PRODUIT_PK PRIMARY KEY (id_produit)
, CONSTRAINT PRODUIT_CNK_AK UNIQUE (cnk_produit)
, CONSTRAINT PRODUIT_EAN_AK UNIQUE (code_barre)
) ;
CREATE TABLE CATALOGUE
(
id_fournisseur INT NOT NULL
, id_produit INT NOT NULL
, prix_achat_ht DECIMAL(7,2) NOT NULL
, taux_tva_achat DECIMAL(5,2) NOT NULL
, CONSTRAINT CATALOGUE_PK PRIMARY KEY (id_fournisseur, id_produit)
, CONSTRAINT CATALOGUE_PRODUIT_FK FOREIGN KEY (id_produit)
REFERENCES PRODUIT (id_produit)
, CONSTRAINT CATALOGUE_FOURNISSEUR_FK FOREIGN KEY (id_fournisseur)
REFERENCES FOURNISSEUR (id_fournisseur)
) ;
CREATE TABLE COMMANDE
(
id_fournisseur INT NOT NULL
, id_commande INT NOT NULL
, numero_commande CHAR(12) NOT NULL
, date_commande DATE NOT NULL
, CONSTRAINT COMMANDE_PK PRIMARY KEY (id_commande)
, CONSTRAINT COMMANDE_AK UNIQUE (numero_commande)
, CONSTRAINT COMMANDE_FOURNISSEUR_FK FOREIGN KEY (id_fournisseur)
REFERENCES FOURNISSEUR (id_fournisseur)
) ;
CREATE TABLE LIGNE_COMMANDE
(
id_commande INT NOT NULL
, id_ligne_commande INT NOT NULL
, id_produit INT NOT NULL
, id_fournisseur INT NOT NULL
, quantite_ligne_commande INT NOT NULL
, CONSTRAINT LIGNE_COMMANDE_PK PRIMARY KEY (id_commande, id_ligne_commande)
, CONSTRAINT LIGNE_COMMANDE_COMMANDE_FK FOREIGN KEY (id_commande)
REFERENCES COMMANDE (id_commande) ON DELETE CASCADE
, CONSTRAINT LIGNE_COMMANDE_CATALOGUE_FK FOREIGN KEY (id_fournisseur, id_produit)
REFERENCES CATALOGUE (id_fournisseur, id_produit)
) ;
Dans ce script, vous observerez que {cnk_produit}, {code_barre} et {numero_commande} sont des clés alternatives (contrainte UNIQUE).
Mais, dans le contexte des SGBD du jour, le problème de la contrainte de chemin n’est pas encore résolu. Pour y parvenir, il faut propager l’attribut id_fournisseur depuis la table COMMANDE jusqu’à la table LIGNE_COMMANDE, et pour cela :
1) On dote la table COMMANDE d’une surclé {id_commande, id_fournisseur} :
CREATE TABLE COMMANDE
(
id_fournisseur INT NOT NULL
, id_commande INT NOT NULL
, numero_commande CHAR(12) NOT NULL
, date_commande DATE NOT NULL
, CONSTRAINT COMMANDE_PK PRIMARY KEY (id_commande)
, CONSTRAINT COMMANDE_SK UNIQUE (id_commande, id_fournisseur)
, CONSTRAINT COMMANDE_AK UNIQUE (numero_commande)
, CONSTRAINT COMMANDE_FOURNISSEUR_FK FOREIGN KEY (id_fournisseur)
REFERENCES FOURNISSEUR (id_fournisseur)
) ;
2) On modifie la contrainte référentielle LIGNE_COMMANDE_COMMANDE_FK de la table LIGNE_COMMANDE pour qu’elle fasse référence à la surclé de la table COMMANDE :
CREATE TABLE LIGNE_COMMANDE
(
id_commande INT NOT NULL
, id_ligne_commande INT NOT NULL
, id_produit INT NOT NULL
, id_fournisseur INT NOT NULL
, quantite_ligne_commande INT NOT NULL
, CONSTRAINT LIGNE_COMMANDE_PK PRIMARY KEY (id_commande, id_ligne_commande)
, CONSTRAINT LIGNE_COMMANDE_COMMANDE_FK FOREIGN KEY (id_commande, id_fournisseur)
REFERENCES COMMANDE (id_commande, id_fournisseur) ON DELETE CASCADE
, CONSTRAINT LIGNE_COMMANDE_CATALOGUE_FK FOREIGN KEY (id_fournisseur, id_produit)
REFERENCES CATALOGUE (id_fournisseur, id_produit)
) ;
Dans cette table, l’attribut id_fournisseur n’est présent qu’une seule fois et il est utilisé aussi bien avec la contrainte référentielle LIGNE_COMMANDE_COMMANDE_FK ciblant la table COMMANDE qu’avec la contrainte référentielle LIGNE_COMMANDE_CATALOGUE_FK ciblant la table CATALOGUE : désormais il n’est plus possible, selon le chemin emprunté, qu’une ligne de commande fasse référence à des fournisseurs et produits distincts.
Je conviens que l'exercice n’est pas basique, mais l’intégrité des données a un prix...
Une autre solution, plus simple (absence de surclé), aurait été, dans le dernier diagramme, d’identifier l’entité-type COMMANDE relativement à l’entité-type FOURNISSEUR : vous pouvez vous y exercer.
Partager