Bonsoir,
Je traite ici des cardinalités 1,n de Merise, lesquelles ne sont pas souvent respectées quand on déboule au niveau logique, c’est-à-dire dans l’univers des tables SQL.
Prenons l’exemple suivant dans lequel des professeurs enseignent des matières, avec les contraintes suivantes :
— un professeur enseigne au moins une matière ;
— une matière est enseignée par au moins un professeur.
MCD correspondant, créé avec Looping, gracieusement proposé par le professeur Patrick Bergougnoux (encore une fois, merci Paprick!):
.png)
Code SQL des tables correspondantes :
CREATE TABLE Prof
(
profId SMALLINT NOT NULL
, profMatricule CHAR(5) NOT NULL
, profNom VARCHAR(24) NOT NULL
, CONSTRAINT Prof_PK PRIMARY KEY(profId)
, CONSTRAINT Prof_AK UNIQUE(profMatricule)
);
CREATE TABLE Matiere
(
matiereId SMALLINT NOT NULL
, matiereCode VARCHAR(5) NOT NULL
, matiereNom VARCHAR(24) NOT NULL
, CONSTRAINT Matiere_PK PRIMARY KEY(matiereId)
, CONSTRAINT Matiere_AK1 UNIQUE(matiereCode)
, CONSTRAINT Matiere_AK2 UNIQUE(matiereNom)
);
CREATE TABLE Enseigner
(
profId SMALLINT NOT NULL
, matiereId SMALLINT NOT NULL
, CONSTRAINT Enseigner_PK PRIMARY KEY(profId, matiereId)
, CONSTRAINT Enseigner_Prof_FK FOREIGN KEY(profId) REFERENCES Prof(profId)
, CONSTRAINT Enseigner_Matiere_FK FOREIGN KEY(matiereId) REFERENCES Matiere(matiereId)
);
Pour que les contraintes soient systématiquement respectées, on ne peut pas se contenter de la séquence suivante :
— (a) Créer des profs (table Prof) ;
— (b) Créer des matières (table Matiere) ;
— (c) Créer les liens profs/matières (table Enseigner).
Pour ma part, je préfère en passer par des triggers.
Par exemple, un 1er trigger aura pour objet de ventiler dans les tables les données présentées à une vue créée à cet effet.
Soit ProfMatiere_V le nom de cette vue. Sa structure est définie selon le code suivant:
create view ProfMatiere_V
(profId, profMatricule, profNom, matiereId, matiereCode, matiereNom)
as
select e.profId, profMatricule, profNom, e.matiereId, matiereCode, matiereNom
from Prof as p
join Enseigner as e on p.profId = e.profId
join Matiere as m on e.matiereId = m.matiereId ;
Quelques insertions dans la vue :
insert into ProfMatiere_V
values
(1, 'fenau', 'Fernand', 1, 'phy', 'physique')
, (1, 'fenau', 'Fernand', 2, 'chm', 'chimie')
, (2, 'ravol', 'Raoul', 2, 'chm', 'chimie')
, (2, 'ravol', 'Raoul', 3, 'maths', 'mathématiques')
, (3, 'pat', 'Patricia', 4, 'mus', 'musique')
;
Ainsi, Fernand enseigne la physique et la chimie, Raoul enseigne pour sa part les maths et la chimie, tandis que Patricia enseigne la musique. On constate à cette occasion certaines redondances, mais ceci serait l’objet d’un autre débat.
Comme je n’ai plus accès à DB2 depuis au moins 20 ans, j’utilise ici SQL Server. Le trigger qui suit a pour mission de ventiler dans les tables concernées les lignes proposées à la vue.
CREATE TRIGGER ProfMatiere_instead_Tr ON ProfMatiere_V INSTEAD OF INSERT AS
begin
declare @nProf as smallint ;
declare @nMatiere as smallint ;
declare @tableProf table
(
profId smallint
, profMatricule varchar(24)
, profNom varchar(24)
) ;
declare @tableMatiere table
(
matiereId smallint not null
, matiereCode varchar(5) not null
, matiereNom varchar(24) not null
) ;
declare @tableEnseigner table
(
profId smallint not null
, matiereId smallint not null
) ;
insert into @tableProf
select distinct i.profId, i.profMatricule, i.profNom
from inserted as i join Prof as p on i.profId = p.profId
set @nProf = (select count(a.profId)
from Prof as a join @tableProf as b on a.profId = b.profId
) ;
-- pour debug :
print '@nProf = ' + cast(@nProf as varchar) ;
if @nProf = 0
begin ;
disable trigger Prof_after_insert on Prof ;
-- print 'insert into Prof' ;
insert into Prof
select distinct profId, profMatricule, profNom
from inserted ;
enable trigger Prof_after_insert on Prof ;
end
insert into @tableMatiere
select distinct i.matiereId, i.matiereCode, i.matiereNom
from inserted as i join Matiere as m on i.matiereId = m.matiereId
set @nMatiere = (select count(a.matiereId)
from Matiere as a
join @tableMatiere as b on a.matiereId = b.matiereId
) ;
-- pour debug :
print '@nMatiere = ' + cast(@nMatiere as varchar) ;
if @nMatiere = 0
begin ;
disable trigger Matiere_after_insert on Matiere ;
-- print 'insert into Matiere' ;
insert into Matiere
select distinct matiereId, matiereCode, matiereNom
from inserted ;
enable trigger Matiere_after_insert on Matiere ;
end
insert into Enseigner
select profId, matiereId
from inserted ;
print 'the end!' ;
end
Quelques commentaires :
Le code est très vraisemblablement simplifiable, mais peu importe ici. L’idée est la suivante :
Fernand fait l’objet de deux lignes et l’on ne peut pas le créer deux fois dans la table Prof, on se ferait jeter par le SGBD au motif de viol de la contrainte de clé primaire. On n’insère donc que la 1re ligne si Fernand n’existe pas encore dans la table, sinon on s’abstient.
Concernant les matières (table Matiere), le principe est le même.
Quant à la table Enseigner, on ne contrôle rien, le SGBD se chargera de faire respecter la contrainte de clé primaire (voire les contraintes d’intégrité référentielle).
Le trigger qu’on vient de définir n’est pas le seul à mettre en oeuvre. En effet, Hilarion Lefuneste pourrait violer les règles, d’où la nécessité de définir un trigger de contrôle des inserts dans la table Prof et un 2e trigger pour contrôle les inserts dans la table Matiere.
Exemple de trigger pour contrôler les inserts dans la table Prof :
CREATE TRIGGER Prof_after_insert on Prof after insert as
begin
declare @n varchar(24) -- int ;
declare @tableProfInserted table (profNom varchar(24)) ;
declare @errId as int = 314115 -- numéro de l'erreur à afficher
declare @errTexte as varchar (255) -- message d'erreur
declare @profNom as varchar(24)
insert into @tableProfInserted (profNom)
select distinct profNom
from inserted
where profId not in (select profId from Enseigner) ;
set @n = (select count(profNom) from @tableProfInserted) ;
if @n > 0
begin
set @profNom = (select top (1) profNom from @tableProfInserted)
set @errTexte =
char(13)
+ 'Un prof doit enseigner au moins une matière.'
+ char(13)
+ 'Le prof '''
+ @profNom
+ ''' ne répond pas à cette obligation.'
+ char(13)
; throw @errId, @errTexte, 16
end ;
end
Exemple de trigger pour contrôler les inserts dans la table Matiere :
CREATE TRIGGER Matiere_after_insert on Matiere after insert as
begin
declare @n varchar(24) -- int ;
declare @tableMatiereInserted table (matiereNom varchar(24)) ;
declare @errId as int = 314116 -- numéro de l'erreur à afficher
declare @errTexte as varchar (255) -- message d'erreur
declare @matiereNom as varchar(24)
insert into @tableMatiereInserted (matiereNom)
select distinct matiereNom
from inserted
where matiereId not in (select matiereId from Enseigner) ;
set @n = (select count(matiereNom) from @tableMatiereInserted) ;
if @n > 0
begin
set @matiereNom = (select top(1) matiereNom from @tableMatiereInserted)
set @errTexte =
char(13)
+ 'Une matière doit être enseignée par au moins un prof.'
+ char(13)
+ 'La matière '''
+ @matiereNom
+ ''' ne répond pas à cette obligation.'
+ char(13)
; throw @errId, @errTexte, 16
end ;
end
Mais ces deux triggers prennent le contrôle dès qu’on effectue un insert, aussi faut-il les désactiver (le plus brièvement possible, à cause d’Hilarion toujours à l’affût!) quand cet insert est effectué dans le trigger ProfMatiere_instead_Tr. A cet effet, on utilise l’instruction disable trigger juste avant insert et l’instruction enable trigger juste après l’insert. Ainsi, dans le cas de la table Prof:
disable trigger Prof_after_insert on Prof ;
insert into Prof
select distinct profId, profMatricule, profNom
from inserted ;
enable trigger Prof_after_insert on Prof ;
Après exécution de l’insert dans la vue ProfMatiere_V, au résultat :
Table Prof :
Prof profId profMatricule profNom
1 fenau Fernand
2 ravol Raoul
Table Matiere :
Matiere matiereId matiereCode matiereNom
1 phy physique
2 chm chimie
3 maths mathématiques
Table Enseigner :
Enseigner profId matiereId
1 1
1 2
2 2
2 3
Hilarion tentera de violer les règles, avec des instructions du genre :
insert into Prof values
(3, 'paulo', 'Paul') ;
Réaction du système :
Msg 314115, Niveau 16, État 16, Procédure Prof_after_insert, Ligne 30 [Ligne de départ du lot 237]
Un prof doit enseigner au moins une matière.
Le prof 'Paul' ne répond pas à cette obligation.
Dans le même genre :
insert into Matiere values
(99, 'info', 'informatique') ;
=>
Msg 314116, Niveau 16, État 16, Procédure Matiere_after_insert, Ligne 27 [Ligne de départ du lot 237]
Une matière doit être enseignée par au moins un prof.
La matière 'informatique' ne répond pas à cette obligation.
Je n’ai fait ici qu’une 1re tentative à propos du respect des cardinalités 1,n/1,n, nul doute qu’Hilarion reviendra à la charge.
J’ai essayé de traiter des insert, mais concernant les update et delete, le chantier reste bien entendu à ouvrir.
Je n’ai pas traité du cas plus simple des cardinalités 1,n/1,1, mais ça viendra.
Par ailleurs les triggers Prof_after_insert et Matiere_after_insert sont du type AFTERINSERT, l’alternative INSTEADOF est à étudier.
En tout cas, merci de votre indulgence...
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
71.png)
70.png)
.jpg)

!
...

60.png)
...