Commit 22ac8ead authored by Jean-Benoist Leger's avatar Jean-Benoist Leger
Browse files

contraintes trigger

parent b2363874
# Implémentation de contraintes ensemblistes
## Introduction
Dans le cours, il y a des contraintes avancées qui sont exprimés sous forme
ensembliste, jusqu'à maintenant vous n'en avez pas implémenté, et il est bon de
savoir que vous pouvez les implémenter.
Au travers de deux exemples ces contraintes sont abordées. Ce ne sont que des
exemples, qui ne sont pas forcément optimaux d'un point de vue implémentation,
ils ont pour objectifs de rester simple.
Ce document est placé sous licence [CC BY]. En plus de toute réutilisation que
vous pouvez faire dans le cadre de cette licence, je serais également heureux
d'intégrer vos propositions, modifications et corrections de toutes natures (y
compris orthographiques) venant de toute part (élèves, enseignants, autres).
## Premier exemple
### Introduction et MLD
Dans le cadre d'un héritage **exclusif** par référence des filles vers la mère,
on aboutit au MLD suivant :
```
Maman(#a)
Fifille1(#a=>Maman.a, b)
Fifille2(#a=>Maman.a, b)
```
Sauf qu'avec ce MLD nous n'avons pas retranscrit l'exclusivité, nous sommes donc
amené à rajouter une contrainte supplémentaire (qui n'est liée à aucune relation
puisque liée à plusieurs relations):
```
Autre contraintes :
- Intersection(Projection(Fifille1,a), Projection(Fifille2,a)) = ∅
```
Ce qui nous amène au MLD suivant:
```
Maman(#a)
Fifille1(#a=>Maman.a, b)
Fifille2(#a=>Maman.a, b)
Autre contraintes :
- Intersection(Projection(Fifille1,a), Projection(Fifille2,a)) = ∅
```
Maintenant se pose la question de comment implémenter ce MLD.
### Implémentation de base
Alors là nous savons faire, et nous obtenons très rapidement l'implémentation de
base:
```sql
CREATE TABLE maman(
a integer,
primary key (a)
);
CREATE TABLE fifille1(
a integer,
b text,
foreign key (a) references maman (a),
primary key (a)
);
CREATE TABLE fifille2(
a integer,
b text,
foreign key (a) references maman (a),
primary key (a)
);
```
### Écriture de la contrainte ensembliste
Nous sommes capable d'écrire rapidement requete qui doit être vide selon la
contrainte ensembliste:
```sql
(SELECT a FROM fifille1)
INTERSECT
(SELECT a FROM fifille2)
```
Il va s'agir par la suite de vérifier que cette requete reste vide à chaque
insersion/update des tables.
### Écriture d'un trigger
En premier, il s'agit d'écrire une fonction qui retourne un trigger excutant la
requête précité. L'idée est que cette fonction lève une exception si il y a un
problème (et donc provoque l'abandon de la transaction en cours tout en
incendiant l'utilisateur).
```sql
CREATE FUNCTION funtrig_insff()
RETURNS trigger AS
$func$
BEGIN
IF EXISTS
(
(SELECT a FROM fifille1)
INTERSECT
(SELECT a FROM fifille2)
)
THEN
RAISE EXCEPTION 'Le lapin est bien cuit';
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
```
Une fois cette fonction crée, l'objectif est de créer des trigger qui vont
s'executer quand on le demande, à chaque insersion/update:
```sql
CREATE CONSTRAINT TRIGGER trig_insff_fifille1
AFTER INSERT OR UPDATE
ON fifille1
FOR EACH ROW EXECUTE PROCEDURE funtrig_insff();
CREATE CONSTRAINT TRIGGER trig_insff_fifille2
AFTER INSERT OR UPDATE
ON fifille2
FOR EACH ROW EXECUTE PROCEDURE funtrig_insff();
```
Voila, c'est terminé.
### Vérification
Insérons des données, juste pour voir, on va commencer par des données valides :
```sql
insert into maman (a) values (1);
insert into maman (a) values (2);
insert into maman (a) values (3);
insert into fifille1 (a) values (1);
insert into fifille1 (a) values (2);
insert into fifille2 (a) values (3);
```
Résultat :
```
dbnf18p177=> \i /tmp/ins_valid.sql
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
```
Parfait, tout va bien. Continons avec une données invalides (car `3` est déjà
dans `Fifille2`) :
```sql
insert into fifille1 (a) values (3);
```
Résultat :
```
dbnf18p177=> insert into fifille1 (a) values (3);
ERREUR: Le lapin est bien cuit
```
C'est bien ce à quoi on s'attendait. La base de données à vérifié pour nous le
respect de la contrainte.
## Second exemple
Là ça se corse, si vous n'avez pas compris ce qu'on fait précédement,
retournez-y jusqu'à comprendre.
Dans le cadre d'un héritage **exclusif** par référence des filles vers la mère,
où la classe mère est **abstraite** on aboutit au MLD suivant, je ne détaille
pas les contraintes ensemblistes, vous devez comprendre d'où elles viennent, si
ce n'est pas le cas, direction le cours :
```
Maman(#a)
Fifille1(#a=>Maman.a, b)
Fifille2(#a=>Maman.a, b)
Autre contraintes :
- Intersection(Projection(Fifille1,a), Projection(Fifille2,a)) = ∅
- Projection(Maman,a) ⊂ Union(Projection(Fifille1,a), Projection(Fifille2,a))
```
Nota: Dans certains documents vous trouvez `Projection(Maman,a) =
Union(Projection(Fifille1,a), Projection(Fifille2,a))`, je considère que pour ma
part, c'est une surspécification de contraintes, la condition
`Union(Projection(Fifille1,a), Projection(Fifille2,a)) ⊂ Projection(Maman,a)`
est une conséquence de la conséquence des contraintes de clefs étrangères.
Marquer une égalité suppose qu'il y a deux inclusions à vérifier, alors qu'il n'y
en a qu'une seule à vérifier une fois qu'on a considéré les contraintes de clefs
étrangères.
Maintenant se pose la question de comment implémenter ce MLD.
### Première partie
On reprend l'exemple précédent, la première est la même contrainte que dans
l'exemple précédent.
```sql
CREATE TABLE maman(
a integer,
primary key (a)
);
CREATE TABLE fifille1(
a integer,
b text,
foreign key (a) references maman (a),
primary key (a)
);
CREATE TABLE fifille2(
a integer,
b text,
foreign key (a) references maman (a),
primary key (a)
);
CREATE FUNCTION funtrig_insff()
RETURNS trigger AS
$func$
BEGIN
IF EXISTS
(
(SELECT a FROM fifille1)
INTERSECT
(SELECT a FROM fifille2)
)
THEN
RAISE EXCEPTION 'Le lapin est bien cuit';
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE CONSTRAINT TRIGGER trig_insff_fifille1
AFTER INSERT OR UPDATE
ON fifille1
FOR EACH ROW EXECUTE PROCEDURE funtrig_insff();
CREATE CONSTRAINT TRIGGER trig_insff_fifille2
AFTER INSERT OR UPDATE
ON fifille2
FOR EACH ROW EXECUTE PROCEDURE funtrig_insff();
```
### Écriture de la contrainte ensembliste
On écrit la contrainte ensembliste. On peut la reforuler en _« on ne doit pas
avoir de `a` de `Maman` qui ne soient pas présent dans les filles »_, on va donc
écrire une requête qui extrait les fautifs.
```sql
SELECT a FROM maman
WHERE a NOT IN
(
(SELECT a FROM fifille1)
UNION
(SELECT a FROM fifille2)
)
```
### Écriture du trigger
Comme précédement, l'écriture de la fonction ne pose pas de problèmes:
```sql
CREATE FUNCTION funtrig_aif()
RETURNS trigger AS
$func$
BEGIN
IF EXISTS
(
SELECT a FROM maman
WHERE a NOT IN
(
(SELECT a FROM fifille1)
UNION
(SELECT a FROM fifille2)
)
)
THEN
RAISE EXCEPTION 'La moutarde est prete';
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
```
Bon, passons à la déclaration de trigger. Ici on a **une très grosse subtilité à
voir**, c'est le moment où s'execute le trigger.
Reformulons le problème, chaque `a` de Maman doit apparaître dans une fille à
cause de la contrainte qu'on veut implémenter, et
chaque `a` d'une fille doit apparaître dans `Maman` à cause de la contrainte de
clef étrangère.
La contrainte de clef étrangère nous interdit d'insérer avant dans les filles,
donc cette insersion est invalide (testez):
```sql
insert into fifille1 (a) values (1);
insert into maman (a) values (1);
```
Donc l'insersion doit forcément être dans l'autre sens :
```sql
insert into maman (a) values (1);
insert into fifille1 (a) values (1);
```
Oui, mais si on vérifie la contrainte juste après la première insersion, elle va
être rompue, il faut la vérifier après la seconde insersion. L'idée est
d'imposer à l'utilisateur de regrouper ces insersion sémantiquement cohérente au
sein d'une même transaction :
```sql
begin transaction;
insert into maman (a) values (1);
insert into fifille1 (a) values (1);
commit;
```
C'est une très bonne pratique qui a de bonnes propriété, on est certain que la
première n'est pas executée (ou du moins est non commité) si la seconde ne l'est
pas, on assure la cohérence de la database. Si vous ne comprenez pas (ou mal) ce
que j'écris, retournez au cours sur les transactions.
Et ainsi, nous nous allons vérifier les contraintes uniquement à la fin de la
transaction. Comprendre, l'utilisateur peut faire n'importe quoi au sein de sa
transaction, mais nous ce qu'on veut s'assurer c'est que quand il cloture sa
transaction celle si respecte les contraintes de notre base de données. Nous
allons donc déclarer le trigger comme retardable et à retarder (_id est_ à
executer en fin de transaction).
```sql
CREATE CONSTRAINT TRIGGER trig_aif_fifille1
AFTER INSERT OR UPDATE OR DELETE
ON fifille1
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW EXECUTE PROCEDURE funtrig_aif();
CREATE CONSTRAINT TRIGGER ins_aif_fifille2
AFTER INSERT OR UPDATE OR DELETE
ON fifille2
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW EXECUTE PROCEDURE funtrig_aif();
CREATE CONSTRAINT TRIGGER ins_aif_maman
AFTER INSERT OR UPDATE OR DELETE
ON maman
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW EXECUTE PROCEDURE funtrig_aif();
```
Et voila.
### Vérification
Il ne nous reste plus qu`à tester, commençons avec une transaction qui insère
des données cohérentes, puisque toutes les `a` de `Maman` sont utilisés :
```sql
begin transaction;
insert into maman (a) values (1);
insert into maman (a) values (2);
insert into maman (a) values (3);
insert into fifille1 (a) values (1);
insert into fifille1 (a) values (2);
insert into fifille2 (a) values (3);
commit;
```
Résultat :
```
dbnf18p177=> \i /tmp/ins_valid2.sql
BEGIN
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
COMMIT
```
Continuons avec une insersion non valide (car `a=4` de `Maman` n'est pas
utilisée par les filles):
```sql
begin transaction;
insert into maman (a) values (4);
insert into maman (a) values (5);
insert into maman (a) values (6);
insert into fifille1 (a) values (5);
insert into fifille1 (a) values (6);
commit;
```
Résultat:
```
dbnf18p177=> \i /tmp/ins_novalid2.sql
BEGIN
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
psql:/tmp/ins_novalid2.sql:8: ERREUR: La moutarde est prete
```
Parfait.
-----
_Jean-Benoist Leger, document distribué sous licence [CC BY]_
[CC BY]: https://creativecommons.org/licenses/by/4.0/
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment