Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
Une fois cette représentation faite il est nécessaire d’associer des fonctionnalités (programmes et des
requêtes) à cette base de données afin de pouvoir l’exploiter le plus facilement possible.
Toutes les personnes exploitant la même base de données n’ont pas la même fonction et n’ont donc
pas forcément besoin de voir les mêmes informations ou d’appliquer les mêmes actions à la base de
données. Les systèmes des privilèges, des vues et des programmes stockés permettent de délimiter
rigoureusement ces différentes visions d’une même base de données (chaque vision est nommée schéma
externe).
Enfin, plusieurs utilisateurs peuvent appliquer simultanément des modifications à la même base de
données, il est alors nécessaire d’utiliser des techniques d’isolation et de synchronisation afin de garantir
la cohérence de ces modifications.
1
2 CHAPITRE 1. BASE DE DONNÉES ET SYSTÈME DE GESTION DE BASE DE DONNÉES
– Le cœur d’un SGBD est le modèle de données qu’il supporte, c’est à dire la manière d’organiser
les données qu’il offre. Le modèle actuellement le plus utilisé est le relationnel inventé dans les années
1970 dont une belle qualité est probablement la symétrie naturelle qu’il offre dans les associations
inter-données. Il existe d’autres modèle de données : hiérarchique, réseau et objet, qui eux ne sont
pas franchement symétriques.
– Fournir un langage de haut niveau adapté au modèle : SQL pour le modèle relationnel, CODASYL
pour le modèle réseau, OQL pour le modèle objet.
– Exemples de SGBD relationnels : Oracle, PostgreSQL, MySQL, Access et plein d’autres !
Une instance d’un schéma correspond aux données stockées dans la base à un moment donné. Les
données d’une instance respectent évidemment l’organisation imposée par le schéma. Le contenu d’une
BD est éminemment variable : chaque modification de la BD produit une nouvelle instance du schéma
correspondant.
Exemple :
1. soit le schéma relationnel : Personne (Nom, Prénom), et deux instances possibles de ce schéma :
DURAND Gaston
LAGAFFE Gaston
DUPOND Jules et
PERSONNE Paul
LAGAFFE Gaston
2. le même schéma avec un modèle objet (ici ODL de l’ODMG) :
class Personne (extent lesPersonnes key Nom) {
attribute string Nom ;
attribute string Prénom ;
}
Le mot clef extent introduit le nom de la collection qui contiendra les objets Personne.
3. le même schéma en SQL :
create table Personne (
Nom Varchar2 (20) primary key,
Prenom Varchar2 (20)
) ;
Enrichissement du schéma avec la vue Effectifs donnant le nombre d’étudiants par diplôme :
4 CHAPITRE 1. BASE DE DONNÉES ET SYSTÈME DE GESTION DE BASE DE DONNÉES
Bien que recalculées à chaque sollicitations, certaines vues sont comme des tables (on peut y ajouter,
modifier et supprimer des lignes, ces modifications étant en fait reportées par le SGBD sur les tables
sous-jacentes, chapitre 10).
– par exemple, pour le modèle objet, la norme ODMG propose ODL (Object Definition Language).
Il faut donc distinguer clairement entre ce qui doit tourner sur le serveur et ce qui doit tourner sur le
client.
Les SGBD proposent souvent leur propre langage de programmation : PL/SQL pour Oracle, PL/pgSQL
pour PostgreSQL et le langage de MySQL.
Relationnel et SQL
6
Chapitre 2
Ce modèle est lié à la théorie des ensembles (unicité des éléments, sous-ensemble, produit cartésien, . . .)
Historique
– 1970, Codd invente l’algèbre relationnelle,
– 1972 à 1975 IBM invente SEQUEL puis SEQUEL/2 en 1977 pour le prototype SYSTEM-R de
SGBD relationnel
– SEQUEL donne naissance à SQL
– Parallèlement, Ingres développe le langage QUEL en 1976
– Dès 1979, Oracle utilise SQL
– 1981, IBM sort SQL/DS
– 1983, IBM sort DB2 (héritier de SYSTEM-R) qui fournit SQL.
– 1982, l’ANSI (organisme de normalisation américain) commence la normalisation de SQL qui aboutit
en 1986 et donne la norme ISO en 1987
– Une nouvelle norme SQL-89
– Puis la norme SQL-92 (ou SQL2) qui est la plus utilisée,
– Puis la normalisation SQL-99 (ou SQL3) avec, entre-autres, les extensions relationnel-objet, qui
n’est pas encore terminée !
7
8 CHAPITRE 2. LE MODÈLE RELATIONNEL ET SQL
SQL
En Oracle 10 :
create table Ville (
id Number (5),
nom Varchar2 (50),
departement Number (3),
population Number (10),
constraint Ville_PK primary key (id),
constraint Ville_Dpt_Intervalle check (departement between 1 and 100),
constraint Ville_Pop_Val check (0 <= population)
) ;
Cet ordre create crée la table Ville dont le schéma, décrit entre les parenthèses, est composé de
quatre attributs et comporte aussi des contraintes permettant de garantir les propriétés :
– constraint Ville_PK primary key (id) garantit que deux lignes de Ville auront toujours une
valeur définie et différente pour la colonne id. De façon plus consise on dit que id est la clef primaire
de Ville. La tentative d’ajouter dans la table Ville une ville dont id existe déjà dans une ligne de
Ville échouera et la valeur de Ville sera inchangée.
1
Ici on a affaire à des 3-uplet.
2.2. NOTION CENTRALE : SCHÉMA ET VALEUR D’UNE RELATION 9
– constraint Ville_Dpt_Intervalle check (departement between 1 and 100) garantit que que
la colonne departement aura une valeur comprise entre 1 et 100 si elle est définie. La tentative
d’ajouter dans la table Ville une ville dont departement vaut 105 échouera et la valeur de Ville
sera inchangée.
– constraint Ville_Pop_Val check (0 <= population) garantit que que la colonne population
aura une valeur positive ou nulle quand elle est définie : la tentative d’ajouter dans la table Ville
une ville à population négative échouera et la valeur de Ville sera inchangée.
Une table SQL ressemble à une variable relationnelle mais avec quelques différences dont la première
est importante :
– la valeur d’une variable relationnelle ne peut pas comporter plusieurs fois le même n-uplet alors
qu’une table — sauf si on pose explicitement une contrainte de clef primaire — peut comporter
plusieurs lignes identiques,
– un élément d’une relation s’appelle un n-uplet, alors qu’un élement d’une table s’appelle une ligne
(ou row en anglais).
– il est possible en SQL qu’une colonne n’ait pas de valeur, on dit qu’elle est indéfinie et cela se teste
avec l’opérateur booléen is null. En revanche cela n’aurait pas de sens pour une relation car cela
correspondrait à un n-uplet auquel il manque un attribut, ce qui n’aurait pas de sens en théorie.
SQL
Dans cette forme on doit donner une valeur à chaque colonne dans l’ordre dans lequel sont déclarées
les colonnes.
– insert into Ville (id, Departement, Nom , Population)
values ( 2, 75, ’Paris’, 2200000) ;
Ici on voit qu’en explicitant les noms des colonnes on peut utiliser un autre ordre.
– insert into Ville (Nom, id) values (’Paris-Texas’, 5) ;
Enfin, en explicitant les colonnes à initialiser on peut n’en donner qu’un sous-ensemble, les colonnes
non mentionnées seront indéfinies (is null).
Par exemple le numéro de carte d’étudiant détermine le nom de l’étudiant et certainement d’autres
informations.
Autrement, dit une valeur de C apparaı̂t au plus une fois dans toute extension de R.
Une relation peut posséder plusieurs clefs candidates, on en choisira une qu’on appellera clef primaire.
Par exemple : Etudiant (num_carte, num_insee, nom, prénom, datenaiss) pourrait posséder
deux clefs candidates : (num_carte) qui doit être différent pour chaque étudiant et (num_insee) qui
identifie la naissance d’une personne et est censée être unique pour chaque personne née en France.
On peut choisir (num_carte) comme clef primaire.
Q. 2 Quel problème se poserait si on choisissait (num carte, nom) comme clef primaire d’un étudiant ?
En SQL, la clef primaire fait l’objet d’une contrainte primary key, les autres clefs candidates peuvent
faire l’objet d’une contrainte d’unicité (unique).
En Oracle ainsi qu’en PostgreSQL, aucune des colonnes d’une clef primaire ne peut être indéfinie (is
null).
Par exemple une fête référence la ville dans laquelle elle se passe en mentionnant en tant que clef
étrangère le numéro de département et le nom de la ville dans ce département (deux villes de deux
départements différents pouvant porter le même nom) :
L’ordre des colonnes est bien entendu important dans la déclaration de la contrainte foreign key.
2.5. L’ALGÈBRE RELATIONNELLE ET LE LANGAGE DE REQUÊTE SQL 11
Une clef étrangère comportant une colonne indéfinie ne désigne aucune ligne, sinon le SGBD (Oracle,
PostgreSQL et MySQL avec InnoDB) garantit que la ligne désignée existe, sinon l’ordre échoue.
Par défaut, une ligne référencée par une clef étrangère ne peut pas être détruite, d’autres comporte-
ments peuvent être spécifiés grâce à des options de déclaration de clef étrangère, par exemple si une
ligne référencée est détruite on peut demander que les lignes référençantes le soient aussi.
En SQL il faut par contre écrire la requête suivante pour exprimer le contenu d’une table :
select * from Ville ;
Q. 3 Si Ville possède une clef primaire, le distinct est-il utile dans la requête précédente ?
ΠAp1 ,...,Apk (R) = {(xp1 , . . . , xpk ) | ∃(y1 , . . . , yn ) ∈ R, xpi = ypi ∀i ∈ [1, k]}
Par exemple :
Id Nom Dpt Population
Dpt Population
1 Lille 59 222.400
59 222.400
21 Gruson 59 5.000
59 5.000
ΠDpt,P opulation 7 Dunkerque 59 222.400 =
75 2.200.000
2 Paris 75 2.200.000
5 Paris-Texas
69 420.000
12 Lyon 69 420.000
La restriction : WHERE
Pour ne conserver que les nuplets vérifiant le prédicat P .
Le symbole * indique qu’il n’y a pas de projection (on retient toutes les colonnes).
L’union : UNION
R et S sont deux relations de même schéma.
Une requête select peut être utilisée comme une table, on peut donc avoir des emboı̂tements de
requêtes.
1. La requête ensembliste (sans doublons) :
select nom, ’Etudiant’ as categorie from Etudiant
Union
select nom, ’Enseignant’ as categorie from Enseignant ;
Lors d’une instruction insert il est possible d’ajouter 0, 1 ou plusieurs lignes d’un coup à condition
de remplacer la clause values par une requête, par exemple :
create table Ville_Du_Nord (
id Number (5),
nom Varchar2 (50),
constraint Ville_Du_Nord_PK primary key (id)
) ;
Q. 4 Utiliser cette technique pour éviter d’utiliser l’opérateur d’union dans les requêtes 1 et 2.
2.5. L’ALGÈBRE RELATIONNELLE ET LE LANGAGE DE REQUÊTE SQL 13
La différence : MINUS
R et S sont deux relations de même schéma.
Fig. 2.1 – Un exemple de valeur de table avec deux clefs étrangères etu et mat dans la table Note.
Si on ne veut afficher que la partie Etudiant de chaque élément du produit cartésien, on peut préfixer
* avec le nom de la table ou son alias :
select etu.*
from Etudiant etu
cross Join Matiere mat ;
Q. 6 Sous quelle condition les deux requêtes suivantes ont-elle la même valeur, sous quelle condition
ont-elle des valeurs différentes ?
14 CHAPITRE 2. LE MODÈLE RELATIONNEL ET SQL
R ⊲⊳P S = σP (R × S)
SQL2, PostgreSQL et Oracle 10 (et d’autres bien entendu) disposent d’un opérateur de jointure
spécifique <table> inner join <table> on <condition>. La requête précédente peut alors être
réécrite plus clairement en :
select e.nom as etudiant, m.nom as matiere
from Etudiant e
inner join Note n on e.id = n.etu
inner join Matiere m on n.mat = m.id ;
Alfred BD 12
Alfred CL 14
Julie CL 15
Équi-jointure Égalité entre colonnes : c’est probablement la plus courante, très souvent on teste
l’égalité entre la clef étrangère d’une table et la clef primaire d’une autre table. L’exemple précédent
est une équi-jointure.
Jointure naturelle : attention danger Équi-jointure de R et S sur les colonnes de mêmes noms.
En SQL92 et PostgreSQL on ajoute le mot clef natural.
La jointure naturelle est particulièrement dangereuse : supposons une application qui utilise la jointure
naturelle entre deux tables T1 et T2 . Si, plus tard, on ajoute à T1 et à T2 une colonne homonyme et de
même type alors ces deux colonnes participeront automatiquement à cette jointure naturelle, ce qui
n’est pas forcément ce que souhaite celui qui ajoute ces colonnes.
Auto-jointure Jointure d’une relation avec elle-même. Par exemple, les employés qui sont chef d’au
moins un autre employé :
select distinct chef.*
from Employe emp
inner join Employe chef on chef.id = emp.mon_chef ; -- équi-jointure
Non équi-jointure Le prédicat de la clause on d’une jointure n’est pas forcément une égalité :
toute condition peut convenir.
update Etudiant
set sexe = ’F’
where id = 3 ;
Q. 9 Écrire la requête qui donne tous les binômes mixtes d’étudiant et sans redondance : si on obtient
le binôme (Alfred, Julie) on ne doit pas obtenir aussi le binôme (Julie, Alfred).
Un autre exemple : on a une table F contenant des couples (x, y) d’une fonction y = f (x) définie sur
les entiers. On veut une requête contenant 0 lignes si la fonction stockée dans F est croissante (pour
tout couple de lignes (x1 , y1 ), (x2 , y2 ) vérifiant x1 < x2 on a f (x1 ) > f (x2 )) et contenant au mois une
ligne si elle est décroissante.
Q. 10 Pourquoi est-il logique que x soit la clef primaire de F ?
Q. 12 En utilisant la fonction count (voir section 2.8 page 21) modifier la requête précédente pour
qu’elle valle une seule ligne d’une colonne contenant le nombre de couple de lignes décroissant.
L’intersection
La division
Le schéma de R englobe strictement celui de S, c’est à dire que R comporte toutes les colonnes de S
(mêmes noms et domaines) et a au moins une colonne en plus.
Soit CR l’ensemble des colonnes de R n’apparaissant pas dans S. La division est la projection sur CR
des groupes de lignes de R ayant la même valeur en CR et comportant toutes les lignes de S dans les
colonnes S.
Autrement dit ΠCR (x) appartient à la division si les lignes de R ayant ces valeurs couvrent toutes les
lignes de S.
A B C D
a b c d
a b e f C D A B
Par exemple : b c e f ÷ c d = a b
e d c d e f e d
e d e f
a b d e
La division peut s’exprimer grâce aux autres opérateurs :
En effet, ΠA,B (ΠA,B R × S − R) sont les nuplets qui n’appartiennent pas à la division.
Par exemple l’ensemble des étudiants qui sont inscrits à toutes les UE peut être calculé en divisant
la jointure des étudiants avec leurs inscriptions par la table UE projetée sur sa colonne id. Mais
comme SQL ne dispose pas d’opérateur de division, on est obligé de s’y prendre autrement en utilisant
l’égalité (2.1).
select e.id, e.nom
from Etudiant e
Minus
select id_etu, nom_etu
from (-- La relation totale : Tous les couples étudiant, UE
select e.id as id_etu, e.nom as nom_etu, u.id as id_UE
from Etudiant e cross join UE u
Minus
-- La relation à diviser est obtenue par une jointure.
-- Les couples étudiant, UE si l’étudiant y a une note
select e.id as id_etu, e.nom as nom_etu, i.UE as id_UE
from Etudiant e
inner join Inscrit i on e.id = i.etu) ;
Il y a d’autres manières plus simples d’obtenir le même résultat, mais elles utilisent des fonctions
d’agrégation (ici la fonction count(), voir 2.8 page 21) et éventuellement la partition des nuplets en
groupes (group by, voir 2.11 page 27) qui ne font pas partie de l’algèbre relationnelle :
Utiliser count() pour compter le nombre d’UE et le nombre d’inscriptions d’un étudiant :
select e.id, e.nom
from Etudiant e
cross join (select count (*) as nb_UE from UE) m
where m.nb_UE = (select count (*) from Inscrit i where i.etu = e.id) ;
2.6. LE CAS DES VALEURS INDÉFINIES 17
On peut espérer que le calcul du nombre total d’UE nb_UE ne sera fait qu’une seule fois car la
sous-requête qui fait ce calcul ne dépend pas de la requête englobante, on dit que cette sous-
requête est close ou autonome.
Attention : cette technique ne marche que si les tables disposent des contraintes nécessaires :
create table UE (
create table Etudiant (
id Number (5) primary key,
id Number (5) primary key,
nom varchar2 (20),
nom varchar2 (20)
coeff Number (5)
) ;
) ;
create table Inscrit (
etu Number (5) references Etudiant (id),
UE Number (5) references UE (id),
primary key (etu, UE)
) ;
En particulier la contrainte primary key garantit que ses colonnes sont définies et donc les co-
lonnes clefs étrangères de Inscrit sont forcément définies.
Créer un groupe par étudiant et toujours compter le nombre total d’UE :
select e.id, e.nom
from Etudiant e
inner join Inscrit i on e.id = i.etu
cross join (select count (*) as nb_UE from UE) m
group by e.id, e.nom, m.nb_UE
having count (*) = m.nb_UE ;
La clause having représente une condition de conservation d’un groupe. Ici un groupe correspond
aux concaténations d’une ligne étudiant avec chaque ligne d’inscription le concernant ainsi que
le nombre total d’UE. Cette condition porte sur chaque groupe (ou étudiant) séparément, ainsi
l’expression count (*) représente le nombre d’inscriptions d’un même étudiant.
Pour résumer : la condition du where porte sur chaque ligne produite par la clause from et la
condition du having porte sur chaque groupe construit par le group by.
Par exemple je veux quand même pouvoir enregistrer un nouveau client même si je ne connais pas son
numéro de téléphone. Par exemple voici deux ordres équivalents qui ne renseignent pas le téléphone
d’un nouveau client :
Insert into Client (id, nom, tel) values (13, ’Tartampion’, null) ;
Insert into Client (id, nom) values (13, ’Tartampion’) ;
Et une manière d’enregistrer le fait qu’on ne connaı̂t plus le nouveau numéro du client 15 :
update Client set tel = null where id = 15 ;
La colonne téléphone sera alors dite indéfinie : elle n’a pas de valeur. On pourra tester si une colonne
(etplus généralement une expression) est définie ou non avec le prédicat booléen is [not] null :
– <expr> is null vrai ssi <expr> est indéfinie, faux ssi <expr> est définie.
– <expr> is not null est équivalent à not (<expr> is null)
Par exemple, les villes dont on ne connaı̂t ni la population ni le département :
select v.nom
from Ville v
18 CHAPITRE 2. LE MODÈLE RELATIONNEL ET SQL
Paris-Texas
Q. 13 Quelle ambiguı̈té y a-t-il dans la question : les villes du nord du tableau page 9.
Q. 14 Lister les villes qui ne sont pas dans le département du Nord ou dont le département n’est pas
renseigné.
SQL permet qu’une colonne soit is null à condition qu’elle ne soit assujettie ni à la contrainte not
null ni à primary key.
Attention : ne pas interpréter ce null comme le pointeur null des langages de programmation ni
comme le zéro des entiers !
a b a+b
1 2 3
is null 2 is null
0 0 0
0 is null is null
is null is null is null
exemple :
Par
(1 + n.note) is null ⇔ n.note is null
a b a = b, a != b, a <= b, ...
is not null is not null vrai ou faux
Au moins un des deux is null unknown
Par exemple, quel que soit l’état de la colonne nom, les expressions null=null et nom!=null valent
nécessairement unknown.
Quand aucun des opérandes n’est unknown on a affaire à la logique binaire habituelle. Précisons ce
qui se passe quand un des opérandes vaut unknown :
not vaut évidemment unknown.
and vaut faux si l’autre opérande vaut faux, sinon unknown.
2.7. QUELQUES OPÉRATEURS ET FONCTIONS SCALAIRES DE SQL/ORACLE 19
a b not b a and b a or b
unknown unknown unknown unknown unknown
unknown faux vrai faux unknown
unknown vrai faux unknown vrai
Q. 16 Donner une définition du prédicat x between a and b en utilisant uniquement les opérateurs
<= et and.
Q. 17 Que donnerait l’opérateur a between b and c si un de ses opérandes est indéfini ?
Q. 18 Définir le comportement que devrait avoir l’opérateur ou exclusif (qui n’existe ni en Oracle ni
en PostgreSQL !).
end as categorie
from Ville v ;
Q. 20 Donner une autre formulation équivalente au case précédent qui utilise le prédicat is null.
Attention : Oracle confond les notions de chaı̂ne vide (de longueur nulle) et de chaı̂ne indéfinie (une
chaı̂ne indéfinie se comporte à peu près comme une chaı̂ne vide) ! Ce défaut devrait disparaı̂tre dans
les versions futures. PostgreSQL n’a pas ce défaut !
Je ne dis bonjour qu’aux étudiants dont le nom contient un r qui n’est pas la dernière lettre :
select ’Bonjour ’ || e.nom from Etudiant e
where e.nom like ’%r_%’ ;
Bonjour Alfred
Bonjour Marc
Bonjour rené
Q. 24 Écrire le modèle qui reconnaı̂t toute chaı̂ne contenant deux caractères x séparés par au moins
deux caractères.
Q. 25 Comment reconnaı̂tre les chaı̂nes qui ont un caractère x en première et/ou en dernière position ?
Mais comment distinguer entre la chaı̂ne vide et le fait qu’une expression de type chaı̂ne est indéfinie
puisqu’Oracle lui-même confond les deux ? ? ? ? ?
Toujours à propos des chaı̂nes vides (et non pas indéfinies) :
Although Oracle treats zero-length character strings as nulls, concatenating a zero-length
character string with another operand always results in the other operand, so null can result
only from the concatenation of two null strings. However, this may not continue to be true
in future versions of Oracle Database. To concatenate an expression that might be
null, use the NVL function to explicitly convert the expression to a zero-length
string.
Autrement, bien qu’actuellement (version Oracle 10) on ait les égalités suivantes :
mon commentaire
’’ is null = vrai n’importe quoi ! on devrait avoir faux
’’ || ’toto’ = ’toto’ c’est cohérent
null || ’toto’ = ’toto’ n’importe quoi ! on devrait avoir indéfini
null = ’’ = unknown c’est cohérent
’’ = ’’ = unknown n’importe quoi ! on devrait avoir vrai
Oracle annonce que bientôt il appliquera la norme, c’est à dire que la chaı̂ne vide sera considérée
comme définie. Pour garantir la portabilité du code il recommande d’utiliser systématiquement la
fonction nvl() lors des concaténations :
’Nom du client : ’ || nvl (client.nom, ’’).
En revanche PostgreSQL est parfaitement cohérent sur la notion de chaı̂ne vide qui est bien entendu
parfaitement définie.
Par exemple sum calcule la somme des valeurs définies que prend son expression pour chacun des
nuplets et min en calcule la plus petite.
Une requête dont la clause select comporte de telles fonctions dans ses expressions de projection
fournit exactement une ligne (sauf si la requête est munie d’une clause group by, voir la section 2.11).
sum, avg, min et max donnent un résultat indéfini si l’expression argument n’est jamais définie,
c’est en particulier le cas quand aucun nuplet n’est sélectionné.
En revanche count, qui compte le nombre de fois que son expression a une valeur définie, a toujours
une valeur définie (éventuellement la valeur zéro).
Par exemple count (e.id) donne le nombre de fois que l’attribut e.id est défini. Formes spéciales :
– count (*) renvoie le nombre total de nuplets fournis.
– count (distinct <expression>) nombre de valeurs différentes et définies que prend l’expression.
Q. 26 Donner d’autres formes de count (*) qui soient équivalentes.
Enfin, on ne peut pas demander à la clause select de fournir à la fois une information synthétique
(exactement un nuplet) et une information individuelle (0, 1 ou plusieurs nuplets). Donc, dès qu’une
fonction d’agrégation apparaı̂t dans la clause select, un nom de colonne ne peut apparaı̂tre que dans
une expression argument d’une fonction d’agrégation.
La requête suivante fournira toujours exactement une ligne.
select count (distinct n.mat) as nb_matieres,
avg (n.note) as moyenne,
sum (n.note) / count (n.note) as autre_moyenne,
22 CHAPITRE 2. LE MODÈLE RELATIONNEL ET SQL
n c n*c
n c n*c
Différentes n c n*c ? 1 ?
1 ? ?
valeurs de n c n*c ? ? ? 3 2 6
? 2 ?
la table T ? ? ? 4 3 12
4 ? ?
3 ? ?
<expr>
Sum (n) ? ? 5 10
Sum (2) ? 4 6 8
Sum (n*c) ? ? ? 18
Max (n) ? ? 4 4
Max (15) ? 15 15 15
Count (n) 0 0 2 3
Count (distinct n) 0 0 2 2
Count (*) 0 2 3 4
Fig. 2.2 – Un exemple où on évalue la requête select <expr> from T pour différentes
valeurs de la table T. Un ? signifie que la valeur est indéfinie (is null). La colonne n*c
montre que le produit d’un entier par un indéfini est indéfini. La première collection met
en évidence la spécificité de count par rapport aux autres fonctions d’agrégation.
2 13.66 13.66 15
Et voici un exemple incorrect car il mélange information synthétique et information individuelle :
select e.nom as nom
count (*) as nb_etudiants,
from Etudiant e ;
-- erreur Oracle
Le tableau suivant résume les différentes fonctions d’agrégation count, sum, avg, min, max
Q. 27 Quel est le résultat de select count (distinct 1+5) from T pour chaque valeur de T de la
figure 2.2 page 22 ?
Q. 28 Évaluer l’expression Sum (n*c)/Sum (c) pour les valeurs de la figure 2.2 page 22. Si on
interprète n comme une note et c comme un coefficient, en quoi et pour quelle(s) collection(s) le
résultat est-il incorrect, corriger l’expression en conséquence.
2.8. LES FONCTIONS D’AGRÉGATION COUNT, SUM, AVG, MIN, MAX 23
Q. 29 Parmi les expressions de la figure 2.3 page 23, regrouper celles qui ont exactement le même
comportement (vous devriez obtenir 7 groupes).
Les expressions arguments des fonctions d’agrégation sont donc évaluées séparément pour chaque nu-
plet et les expressions externes aux fonctions d’agrégation sont calculées en dernier.
24 CHAPITRE 2. LE MODÈLE RELATIONNEL ET SQL
Pour avoir cette même moyenne pour chaque étudiant, il faudra utiliser la clause group by, voir la
section 2.11.
Remarquer que la sous-requête calculant la moyenne de population par département est close (auto-
nome) : elle ne dépend en rien de la requête englobante.
Une clause on ne peut mentionner que des alias de tables déjà déclarés.
Une sous-requête dans la clause from ne peut pas mentionner des colonnes appartenant aux tables
cette clause from : elle doit être close ou autonome (idem en PostgreSQL). Autrement dit : une sous-
requête dans une clause from ne peut pas être corrélée (ou dépendante) avec une table ou une autre
sous-requête de la même clause from.
L’exemple suivant est refusé par Oracle car la sous-requête n’est pas close :
select v.nom
from Ville v
inner join (select AVG (vl.population) as moyenne,
max (vl.departement) as departement
from Ville vl
where vl.departement = v.departement) pop_par_dpt
on v.departement = pop_par_dpt.departement
where v.population >= pop_par_dpt.moyenne ;
C’est parti !
ORA-00904: "V"."DEPARTEMENT" : identificateur non valide
– un nombre quelconque de nuplets, elle devra être utilisée avec un opérateur ensembliste approprié
(any, all, in, exists)
Dans where et select une sous-requête peut être corrélée si elle mentionne des colonnes appartenant
à des tables de la clause from de la requête englobante.
sous-requête corrélée
Le résultat d’une sous-requête corrélée dépend du nuplet courant de la requête principale car elle
mentionne des colonnes de ce nuplet.
Par exemple les villes dont la population est supérieure ou égale à la moyenne de leur département :
select v.nom from Ville v
where v.population >= (select AVG (vl.population) from Ville vl
where vl.departement = v.departement) ;
Q. 30 Lister les couples matière, nom d’un étudiant ayant la meilleure note dans cette matière avec
les deux techniques : sous-requête dans la clause from et sous-requête dans la condition. On a trois
tables : Etudiant, Note et Matiere.
En voici la syntaxe :
with <query-name> as ( <subquery> ) { , <query-name> as ( <subquery> ) }
select ... ;
Une sous-requête factorisée peut mentionner les noms des sous-requêtes factorisées qui la précèdent.
La requête principale peut évidemment utiliser tous les noms des sous-requêtes factorisées.
Intérêt : simplifier des requêtes complexes contenant des sous-requêtes non corrélées.
Un seul with par instruction SQL.
Exemple :
with R1 as (select * from X where ...)
R2 as (select ... from R1 ...)
select ... R1 ... R2 ... ;
Exemple Oracle :
with
Dept_Costs as (
select d.department_name, sum (e.salary) dept_total
from Employees e
inner join Departments d on e.department_id = d.department_id
26 CHAPITRE 2. LE MODÈLE RELATIONNEL ET SQL
group by department_name),
Avg_Cost as (
select sum (dept_total)/count (*) avg
from Dept_Costs)
select * from Dept_Costs
where dept_total > (select avg from Avg_Cost)
order by department_name ;
Q. 35 Pourquoi un étudiant ne peut-il avoir plus d’une note pour une matière ? (voir la figure 2.1
page 13)
Q. 36 Réécrire cette requête en évitant que la sous-requête soit corrélée. Suggestion : déplacer les
sous-requêtes dans la clause from principale et utiliser un comptage.
Q. 37 En utilisant une clause with pour factoriser la sous-requête non corrélée, donner deux requêtes
différentes qui calculent la même chose.
BD Alfred
CL Julie
Q. 38 Récrire la requête précédente en utilisant not exists plutôt que >= All.
Q. 39 any vaut faux si la sous-requête renvoie un ensemble vide, que vaut all dans ce même cas ?
Q. 40 Pour chaque matière, lister les étudiants qui n’ont pas la plus mauvaise note.
Q. 41 Donner un opérateur ensembliste équivalent à expr IN (select ...)
En Oracle 10 cette règle n’est plus vraie : il est possible d’utiliser la clause order by dans une
sous-requête.
Pour trier les villes par départements croissants, puis populations décroissantes, puis noms croissants :
select * from Ville v
order by v.Departement asc, v.Population desc, v.Nom ;
Par défaut l’ordre est asc (i.e. croissant), desc demande un ordre décroissant.
On n’est évidemment pas obligé d’ordonner sur toutes les colonnes et on peut trier sur le résultat
d’une expression :
select * from Ville v
order by upper (v.Nom) ;
qu’en paramètre d’une fonction d’agrégation : cette fonction s’appliquera donc aux nuplets de chaque
groupes pris séparément. Par exemple pour calculer la moyenne de chaque étudiant on utilise la clef
de groupe e.id, e.nom :
select e.id, e.nom, avg (n.note) as moyenne
from Etudiant e
inner join Note n on e.id = n.etu
group by e.id, e.nom ;
1 Alfred 13
3 Julie 15
Une telle requête peut constituer un nombre quelconque de groupes (éventuellement aucun groupe si
aucun nuplet n’est retenu par le where) et elle produira autant de nuplets qu’il y a de groupes.
Une manière de visualiser ce regroupement est de remplacer la clause group by par une clause order
by dont la clef de tri est la clef de groupe :
select e.id as id, e.nom as nom, n.note as note
from Etudiant e
inner join Note n on e.id = n.etu
order by e.id, e.nom ;
Qui donne :
clef de groupe
id nom note
premier groupe 1 Alfred 12
1 Alfred 14
second groupe 3 Julie 15
Remarquer que dans ce cas on ne peut pas appliquer la fonction avg() sur les notes.
Le regroupement devient intéressant dès qu’on veut obtenir une information synthétique sur chaque
groupe grâce aux fonctions d’agrégation (sinon on peut se contenter du qualificatif distinct de la
clause select). Par exemple on souhaite connaı̂tre la moyenne de chaque étudiant :
select e.id, e.nom, AVG (n.note) as moyenne, count (*) as nb_notes
from Etudiant e
inner join Note n on e.id = n.etu
-- -----------------------
-- Julie 3 | 3 15 2
Q. 42 En supposant que chaque matière soit dotée d’un coefficient coeff, calculer la moyenne
pondérée de chaque étudiant. On supposera que toutes les notes et coefficients sont renseignés (is
not null).
Q. 44 Comment calculer une moyenne correcte pour l’étudiant si certaines notes ne sont pas ren-
seignées ? (si une note n’est pas renseignée, il faut ne pas la prendre en compte)
-- Résultat du having :
-- -----------------------
-- 1 groupe | individus du groupe
-- nom id | etu note mat
-- -----------------------
-- Alfred 1 | 1 12 1
-- | 1 14 2
;
1 Alfred 13 2
30 CHAPITRE 2. LE MODÈLE RELATIONNEL ET SQL
Q. 45 Moyenne pondérée des étudiants ayant une note renseignée dans chaque matière.
Q. 46 Pour chaque étudiant, nombre de matières pour lesquelles il a une note définie.
Q. 47 Quelle sera la valeur systématique d’une requête ayant un group by muni de la clause having
count (*) = 0 ?
Q. 48 Donnez une nouvelle version de la requête listant les étudiants inscrits à toutes les UE (voir
section 2.5.3).
1 Alfred 12
1 Alfred 14
2 Marc <-- nuplet supplémentaire gr^
ace à la jointure externe
3 Julie 15
Si un nuplet Etudiant n’a pas de note, le left outer join le concatène quand même avec un nuplet
Note dont toutes les colonnes sont indéfinies.
Le left désigne la table dont on veut conserver tous les nuplets : celle de gauche. Cette jointure externe
est signalée par left outer join.
Q. 50 Dans la requête précédente, qu’obtiendrait-on avec une jointure externe conservant les lignes
de la table de droite : right outer join ?
La jointure externe n’est pas une primitive car on peut l’exprimer grâce aux opérateurs précédents,
voici l’équivalent de la requête précédente :
select e.id, e.nom, n.note
from Etudiant e inner join Note n on e.id = n.etu
union
select e.id, e.nom, null
from Etudiant e
where e.id not in (select distinct n.etu from Note n) ;
2.13. CONTRAINTES SUR L’USAGE DES FONCTIONS D’AGRÉGATION 31
Il suffit de rajouter le group by pour obtenir des informations synthétiques par étudiant (Oracle10,
PostgreSQL, SQL92) :
select e.id, e.nom, count (n.etu) as nb_notes
from Etudiant e
left outer join Note n on e.id = n.etu
group by e.id, e.nom ;
1 Alfred 2
2 Marc 0 <-- car n.etu est indéfini pour Marc
3 Julie 1
La fonction count (expression) compte le nombre de fois que expression est définie. n.etu étant
indéfini pour Marc, son nombre de matières vaut zéro.
Une clause where ne peut mentionner aucune fonction d’agrégation car elle s’applique à exactement
une ligne de la clause from. Cependant elle peut contenir une sous-requête utilisant des fonctions
d’agrégation car une sous-requête est un nouveau monde et n’a donc pas d’impact sur la clause where,
par exemple pour avoir les notes des étudiants supérieures à leurs moyennes :
select e.id as id, e.nom as nom, n.note as note, n.ue as ue
from Etudiant e
inner join Note n on n.etudiant = e.id
where n.note > (select Avg (n.note) as moyenne
from Note n
where n.etudiant = e.id) ;
Une clause group by ne peut mentionner aucune fonction d’agrégation.
Une clause having peut mentionner des fonctions d’agrégation mais avec une profondeur d’au plus 1.
Les colonnes clef du group by peuvent apparaı̂tre en dehors ou à l’intérieur de fonctions d’agrégation,
les autres colonnes doivent absolument apparaı̂tre à l’intérieur de fonctions d’agrégation.
– si R est une requête synthétique, toute colonne provenant de sa clause from doit apparaı̂tre
dans une fonction d’agrégation dont la profondeur est exactement de 1.
En revanche des constantes ou des colonnes provenant d’une requête englobante peuvent ap-
paraı̂tre en dehors des fonctions d’agrégation, ou à l’intérieur, car elles ont une valeur constante
pour l’évaluation de R.
2. si R a une clause group by :
– si R n’est pas une requête synthétique alors toute colonne ne faisant pas partie de la clef de
groupe doit apparaı̂tre dans une fonction d’agrégation avec une profondeur de 1. Les colonnes
clef de groupe peuvent apparaı̂tre à l’extérieur ou à l’intérieur des fonctions d’agrégation.
– si R est une requête synthétique alors toute colonne ne faisant pas partie de la clef de groupe
doit apparaı̂tre dans un double emboı̂tement de fonctions d’agrégation (profondeur de 2).
Les colonnes clef de groupe doivent apparaı̂tre à une profondeur 1 ou 2 dans les fonctions
d’agrégation.
En revanche des constantes ou des colonnes provenant d’une requête englobante peuvent ap-
paraı̂tre en dehors des fonctions d’agrégation, ou à l’intérieur, car elles ont une valeur constante
pour l’évaluation de R. Par exemple :
select Avg (Sum (n.note*n.coeff) / Sum (n.coeff)) as moyenne_promo
from Note n where n.promotion = ’L3GMI’ and n.note is not null
group by n.etudiant ; -- Sum porte sur toutes les notes d’un m^
eme étudiant
Un autre exemple où on suppose qu’un étudiant est inscrit à exactement un groupe : on veut connaı̂tre
le nombre de groupes, l’effectif moyen des groupes et l’effectif maximum d’un (ou plusieurs) groupe :
select
Count (g.id_groupe) as nb_groupes,
Avg (Count (*)) as effectif_moyen_par_groupe,
Max (Count (*)) as effectif_maximum
from Etudiant e
inner join Groupe g on g.id_etu = e.id_etu
group by g.id_groupe ;
Les deux count (*) calculent le nombre de lignes de chaque groupe (autrement dit le nombre
d’étudiants inscrits par groupe).
Il est possible d’emboı̂ter des fonctions d’agrégation dans le select d’une requête munie d’une clause
group by, mais sans dépasser une profondeur d’emboı̂tement de deux. Dans ce cas la requête donne
une information synthétique des informations obtenues pour chaque groupe, par exemple la moyenne
des moyennes des étudiants :
select Avg (Avg (n.note)) as moyenne_promo
from Etudiant e
inner join Note n on e.id = n.etu
group by e.id, e.nom ;
Cette requête calcule la moyenne de chaque étudiant, puis la moyenne de ces moyennes.
Il est aussi possible d’utiliser des fonctions d’agrégation dans l’expression du having mais avec une
profondeur d’emboı̂tement de un : donc on ne peut y emboı̂ter deux fonctions d’agrégation. Par
exemple si on veut la moyenne des moyennes supérieures ou égales à 10 :
select Avg (Avg (n.note)) as moyenne_promo
from Etudiant e
2.15. POUR CONCLURE 33
Dépendances fonctionnelles et
normalisation
Une relation universelle est l’unique relation formée de tous les attributs pertinents d’un problème.
A, B, C, D désignent des attributs.
R, T, X, Y, Z désignent des ensembles d’attributs (éventuellement vides).
F un ensemble de dépendances fonctionnelles (DF)
On notera indifféremment X ∪ Y ou XY .
Q. 56 Quelles vérifications un programme doit-il faire préalablement à l’ajout d’un tuple LDF.
34
3.3. AXIOMES DE ARMSTRONG 35
On a donc souvent besoin de décomposer (normaliser) une relation en plusieurs sous-relations afin
d’éviter ces anomalies.
Q. 58 Proposer une telle décomposition de la relation Ligne-de-Facture et indiquer les dépendances
fonctionelles qui sont conservées par les sous-relations.
Q. 62 Prouver ces corollaires à l’aide des axiomes et des corollaires déjà prouvés.
Soit R = {A, B, C, D, E, F } munie de : F = {{A, B} → {C}, {C, D} → {E, F }, {E} → {F, D}}
Q. 67 Marquer les nœuds de ce graphe déterminés directement ou indirectement par (date, client,
produit) puis montrer qu’on obtient le même résultat en utilisant les DF et les axiomes et corollaires
de Armstrong.
Q. 68 Donner les clés candidates de Ligne-de-Facture.
la relation munie des dépendances fonctionnelles
R = {A, B, C, D, E, F, G, H, I} {A → BC, C → D, BDE → A, F → AG, G → H}
Q. 69 Donner
R = {A, B, C, D, E, F, G} {AC → B, B → C, C → DE, D → F, E → F, F → G}
les clés de :
R = {A, B, C, D, E} {A → DE, BC → A, E → B, D → C}
R = {A, B, C, D, E} {A → DE, B → AC → A, E → B, D → C}
Q. 71 Par exemple R = {cru, pays,région, qualité} munie de {{cru, pays} → {région, qualité},
{région} → {pays}} n’est pas BCNF car {région} n’est pas une clé. Est-elle 2NF ? 3NF ?
Q. 72 Normalité de LDF (voir Q.52) ?
Démonstration : Soit r une valeur quelconque de R et r1 = ΠR1 (r), r2 = ΠR2 (r). On montre d’abord
que r1 ⊲⊳ r2 ⊆ r, pour cela on peut montrer que r1 ⊲⊳ r2 6⊆ r est une absurdité : supposons que
(xi , yi ) ∈ r1 et (xi , zi ) ∈ r2 et que (xi , yi , zi ) 6∈ r, puisque (xi , yi ) ∈ r1 et (xi , zi ) ∈ r2 ont été obtenus
par projection de r, c’est qu’il existe deux nuplets (xi , yi , zi′ ), (xi , yi′ , zi ) appartenant à r, or X → Y on
a donc yi = yi′ et donc (xi , yi , zi ) ∈ r. De la même manière on montre que r ⊆ r1 ⊲⊳ r2 .
Q. 77 Montrer que la condition du théorème est aussi nécessaire, c’est à dire que si une décomposition
est sans perte alors elle vérifie nécessairement la condition du théorème. Suggestion : montrer que si
on n’a ni R1 ∩ R2 → R1 ni R1 ∩ R2 → R2 alors la décomposition est avec perte, un exemple suffit.
Q. 80 Donner une autre décomposition de R qui préserve à la fois l’information et les DF.
Q. 82 Décomposer LDF (voir Q.52) en sous-relations qui sont toutes BCNF, cette décomposition
conserve-t-elle toutes les DF ?
Remarque : pour un même problème R muni de F il peut y avoir plusieurs décompositions différentes
permettant d’obtenir des sous-relations vérifiant une forme normale.
Attention : une décomposition BCNF sans perte d’information peut perdre des dépendances fonc-
tionnelles (ce n’est pas le cas de 3NF).
1
Autremrent dit : R1 , R2 est sans perte d’information ssi R = R1 ∪ R2 et (R1 ∩ R2 → R1 ou R1 ∩ R2 → R2 ).
38 CHAPITRE 3. DÉPENDANCES FONCTIONNELLES ET NORMALISATION
Q. 84 Décomposer D par étapes successives en sous-relations qui sont BCNF et qui conservent,
globalement, toutes les DF de F (section 3.5).
Q. 85 Dessiner le MCD de la décomposition obtenue.
Q. 86 Écrire les ordres SQL de création des tables BCNF et leurs garnissages à partir d’une table D
déjà peuplée.
Chapitre 4
On peut explicitement indiquer qu’une colonne n’est pas définie (is null) en mettant null pour
signifier l’absence de valeur.
– Insertion d’une ligne en explicitant les valeurs d’un sous-ensemble des colonnes de la table :
insert into Client (num_client, nom, prenom) values (5, ’Durif’, ’Pablo’) ;
Les colonnes non mentionnées seront indéfinies ou bien auront leur valeur par défaut éventuellement
indiquée lors de la création de la table (default).
– Insertion de toutes les lignes produites par une requête :
insert into Client (num_client, nom, prenom)
select ref, nom, prenom
from Employe
where salaire > 1000 ;
Le mot clef default peut être utilisé en tant que valeur d’une colonne et indique que la colonne doit
prendre sa valeur par défaut si elle en a une (voir create table section 5.1 page 41) ou être indéfinie
si elle n’en a pas.
39
40 CHAPITRE 4. SQL/DML LES ORDRES DE MODIFICATION DES TABLES
On veut déplacer sur Paris les employés des départements de Lille et Lyon en doublant leurs salaires
et en leur accordant une commission de 500 :
update Employe
set (salaire, commission, deptno) =
(select 2 * Employe.salaire, 500.0, d.deptno
from Departement d
where d.prefecture = ’Paris’)
where deptno in (select deptno from Departement
where prefecture in (’Lille’, ’Lyon’)) ;
La clause default n’est pas une contrainte, elle provoque simplement l’introduction de la valeur par
défaut lors d’un insert ne précisant pas de valeur explicite.
Le mot clé BOOLEAN n’apparaı̂t même pas dans l’index de l’ouvrage Oracle i SQL Reference Release
3 (8.1.7) qui compte quand même plus de mille pages !
41
42 CHAPITRE 5. CONTRAINTES D’INTÉGRITÉ EN SQL
En revanche, PostgreSQL dispose du type boolean, mais du coup PostgreSQL n’a pas la valeur unk-
nown d’Oracle ; en PostgreSQL c’est l’absence de valeur (is null) qui joue le rôle de unknown.
Les types définis par la norme ne sont malheureusement pas toujours respectés.
affiche 0.66.
– La différence entre deux dates est exprimée en nombre de jours (nombre réel éventuellement négatif)
et on peut ajouter un nombre de jours à une date.
– Months_Between (Date1, Date2) en gros : Date1 - Date2 en nombre de mois, donc positif si
Date1 est postérieure à Date2. Le résultat est un réel, il n’est entier que si Date1 et Date2 sont le
même jour du mois (par exemple le 12/3/05 et le 12/11/03) ou le dernier jour du mois (par exemple
le 28/2/06 et le 31/12/01).
– Pour avoir des dates sans prendre en compte l’heure de la journée, Oracle propose la fonction
Trunc (D in Date) qui renvoit la date D dont la partie heure est à zéro. PostgreSQL propose la
fonction date_trunc.
44 CHAPITRE 5. CONTRAINTES D’INTÉGRITÉ EN SQL
Si une contrainte n’est pas vérifiée en fin d’instruction DML, il y a annulation de la mise à jour avec
message d’erreur. Plus précisément, la table est remise dans l’état dans lequel elle était avant le début
de l’instruction DML (fonctionnement en tout ou rien).
La seule contrainte qui ne peut être décrite qu’en tant que contrainte de colonne est not null car elle
qualifie toujours une seule colonne.
Une autre contrainte exprimable dans les deux syntaxe est unique pouvant s’applique à plusieurs
colonnes.
Les autres contraintes peuvent être décrites indifféremment en tant que contrainte de colonne ou
contrainte de table ce sont unique, primary key, foreign key et check.
Deux contraintes portent sur la colonne stock. default n’est pas une contrainte.
unique sur un attribut ou un groupe d’attributs dont la valeur, quand elle est définie, doit être
unique dans la table (Oracle crée un index unique pour cette contrainte).
Restriction Oracle 10 : contrairement à la norme SQL, Oracle considère que, dans une contrainte
d’unicité définie, les valeurs indéfinies pour une même colonne sont égales si d’autres colonnes
sont définies. Par exemple si on pose la contrainte unique (formation, rang) les deux couples
(1, 23) et (1, 24) sont bien distincts, en revanche (1, null) et (1, null) seront considérés
par Oracle comme égaux et ne pourront donc pas coexister.
En revanche si deux lignes sont indéfinies sur toutes les colonnes d’unicité alors Oracle les
considère comme satisfaisant l’unicité, par exemple (null, null) et (null, null) sont considérés
comme différents.
PostgreSQL respecte la norme SQL, c’est à dire qu’il considère (1, null) et (1, null) comme
distincts.
check prédicat portant sur les colonnes d’un même nuplet
check (qte >= 0)
En SQL2 la condition de check est presque équivalente à celle de where (y compris des sous-
requêtes)
Restrictions Oracle 10 et PostgreSQL 8.2 : le prédicat doit porter uniquement sur la valeur de la
ligne courante, pas de sous-requête, de séquence, on ne peut pas utiliser les fonctions SYSDATE,
UID, USER ou USERENV ni les pseudo-colonnes LEVEL ou ROWNUM.
Si la condition de check est vraie ou unknown (présomption d’innocence) la propriété est
considérée comme respectée et la mise à jour est acceptée.
Q. 87 À votre avis, le delete provoque-t-il la vérification des contraintes not null et check ?
Q. 88 Ce même delete a-t-il des vérifications à faire quand il y a des contraintes primary key et
unique, lesquelles ?
46 CHAPITRE 5. CONTRAINTES D’INTÉGRITÉ EN SQL
Par exemple :
check (salaire > 0 or (salaire = 0 and commission > 0))
Q. 89 Montrer que si salaire is null la mise à jour est acceptée quel que soit l’état de commission.
L’idée est qu’on ne peut pas empêcher la création d’un nuplet en l’absence d’information (présomption
d’innocence).
Q. 90 Si commission n’est pas définie, le salaire peut-il être négatif ?
Q. 91 Corriger la contrainte pour garantir que le salaire et la commission ne sont jamais négatifs
(une idée consiste à utiliser l’opérateur is null, une autre idée à mettre plusieurs check).
Les colonnes de la clef primaire doivent être définies et les clefs primaires forment un ensemble (unicité).
Sous Oracle (et d’autres), un index unique est automatiquement créé sur la clef primaire, il prend le
nom de la contrainte (Produit_PK dans l’exemple).
En Oracle comme en PostgreSQL il est possible de définir une table sans clef :
5.3. LES CONTRAINTES 47
Le fait que la colonne Note.etudiant est une clef étrangère implique que la table Note dépend de la
table Etudiant. Autrement dit Note ne peut être créée que quand Etudiant existe.
Considérons une ligne de la table Note :
– si sa colonne etudiant est définie, il doit exister exactement une ligne de Etudiant dont le id est
égal à etudiant.
L’unicité de Etudiant.id est garantie puisque c’est justement la clef primaire.
– si sa colonne etudiant est indéfinie (is null), c’est qu’elle ne référence aucune ligne de Etudiant.
La colonne Note.etudiant est alors appelée une clef étrangère, on peut aussi la comprendre comme
un pointeur associatif qui n’est pas une adresse mémoire mais une valeur permettant de retrouver la
ligne désignée de la table Etudiant.
Une conséquence du exactement une ligne de la table Etudiant est que la colonne id doit garantir
l’unicité des lignes de Etudiant : id doit soit être une clef primaire soit supporter une contrainte
d’unicité (unique).
Une clef étrangère peut-être constituée de plusieurs colonnes : ces colonnes ne référencent une ligne
que si elles toutes définies.
Très souvent une clef étrangère référence directement une clef primaire.
Il peut être souhaitable et même agréable de ne pas expliciter le type de la clef étrangère qui sera celui
de la colonne id de Etudiant. Cela est possible en Oracle 10 :
– en contrainte de colonne :
create table Note (
note Number (2),
etudiant constraint Note_Etudiant_FK
foreign key (etudiant) references Etudiant (id)
48 CHAPITRE 5. CONTRAINTES D’INTÉGRITÉ EN SQL
) ;
– en contrainte de table :
create table Note (
note Number (2),
etudiant,
constraint Note_Etudiant_FK
foreign key (etudiant) references Etudiant (id)
) ;
Depuis sûrement assez longtemps MySQL accepte la syntaxe de déclaration de clef étrangère, il n’en
assure la sémantique que depuis sa version 6 et uniquement dans InnoDB.
Pour cela il propose un certain nombre de comportements, qui ne sont pas tous implémentés par
Oracle :
Oracle PostgreSQL
SQL Commentaire (10.2) (8.1.3)
on delete|update no action (par Modification interdite (échec de par défaut par défaut
défaut) l’instruction).
on delete cascade Suppression propagée : les nuplets oui oui
référençant sont supprimés
on update cascade Modification propagée. non oui
on delete|update set null La référence devient indéfinie. oui oui
on delete|update set default La référence est remise à sa valeur non oui
par défaut.
Un tel comportement est indiqué lors de la déclaration d’une clef étrangère, ainsi on peut avoir des
clefs étrangères ayant la même cible et n’ayant pas le même comportement. Ces comportements sont
des compléments optionnels à ajouter à la définition d’une clef étrangère.
Un problème apparaı̂t cependant quand la cohérence à maintenir couvre plusieurs tables : il est alors
possible de différer en fin de transaction (lors du commit) les vérifications de manière à pouvoir
modifier les différentes tables.
Remarquer qu’on a pris soin de dire, lors du alter table, que la contrainte Conducteur_Voiture_FK
est deferrable, car, par défaut, les contraintes ne sont pas différables. On peut alors demander à
différer la vérification de cette contrainte en fin de transaction :
set constraint Conducteur_Voiture_FK deferred ;
Enfin pour détruire ces deux tables interdépendantes on peut commencer par supprimer les contraintes
ou bien faire tout simplement :
drop table Conducteur cascade constraint ;
-- Détruit la contrainte de clef étrangère Voiture_Conducteur_FK
-- puis détruit la table Conducteur.
La réactivation d’une contrainte échoue tant qu’elle n’est pas vérifiée, on est donc obligé de corriger
les données.
Par défaut les contraintes sont actives.
On peut les désactiver dès leur définition, ou bien plus tard :
create table Emp (
empno Number (5) primary key disable,
...
)
Remarque : les contraintes primary key et unique créent des index sur la table qui sont reconstruits
à chaque réactivation de la contrainte.
Effets :
– enlève la définition de la table du dictionnaire,
– tous les index et triggers associés sont détruits,
– les sous-programmes PL/SQL qui dépendent de cette table deviennent inutilisables (ils sont toujours
là !)
– les vues et les synonymes qui dépendent de cette table sont toujours là mais renvoient une erreur
quand on les utilise !
– la place occupée par la table est restituée.
5.6. GÉNÉRATEUR D’ENTIERS : LES SEQUENCE 53
La suppression
drop table Maitre ;
-- erreur oracle
ne marche pas : il faut d’abord supprimer les tables référençantes ou désactiver/supprimer certaines
contraintes ou encore, plus simplement :
drop table Maitre cascade constraints; -- ok (supprime les contraintes référençantes)
qui supprime la contrainte Esclave_Vers_Maitre_FK qui fait référence à la table Maitre.
Renvoie le premier <result> tel que <expr> = <search>, sinon renvoie <default>, et s’il n’y a pas
de <default> alors null. Attention : decode considère que deux valeurs indéfinies sont égales (ce qui
est en contradiction avec le reste de SQL !).
Q. 92 Que vaut decode (n, 1, ’Intro’, 4, ’Techno’, ’Conclusion’) si n = 4 et si n = 3 ?
55
56 CHAPITRE 6. COMPLÉMENTS ORACLE SQL
MATIERE MOYENNE
---------------
BD 13
CL 13
SSM 14
En fait Cube et RollUp peuvent ne porter que sur une partie des clefs de groupre, par exemple les
deux exemples suivants sont corrects :
group by Cube (a,b,c) --
group by a, Cube (b,c) -- a apparaitra dans toutes les clefs de groupe
le premier va constuire les huit regroupements possibles : (), (a), (b), (c), (a, b), (a, c), (b, c) et (a, b, c),
le second les quatre contenant tous a : (a), (a, b), (a, c) et (a, b, c).
Cube explore tous les groupes pour tous les sous-ensembles des expressions mises entre parenthèses
après Cube, y compris l’ensemble vide : si la clef de groupe contient n expressions alors Cube
génèrera les 2n ensembles de groupes correspondant chacun à une des 2n sous-clef de groupe.
Si on se place dans le cas où la clef de groupe comporte trois expressions x, y et z, cube introduit
effectivement les huit sommets d’un cube en trois dimensions.
les 8 sous-clefs de groupe qu’on peut voir comme les huit som-
Pour la requête
sont : mets d’un cube :
{bc} {abc}
{} {a}
Q. 94 Si la clef de groupe comportait deux expressions entre parenthèses, à quoi correspondrait
cube ? et si elle en comportait quatre ?.
Q. 95 Combien de lignes produisent les requêtes suivantes (dual contient une seule ligne) :
6.3. GROUP BY CUBE ET GROUP BY ROLLUP 57
Q. 96 Sur le modèle de la requête précédente, écrire une requête qui imprime toutes les combi-
naisons des trois lettres a, b et c (voir la fonction grouping section 6.3.3).
RollUp explore tous les préfixes de la clef de groupe entre parenthèses : si la clef de groupe contient
n expressions alors rollUp génèrera n + 1 sous-clefs de groupe.
Q. 99 Dessiner comment group by rollup (CDM, CDP) groupe les lignes suivantes :
ENVOI
CDC CDP CDM QTE
1 A1 B1 C1 2
2 A1 B1 C4 7
3 A2 B3 C1 4
4 A2 B3 C4 5
5 A2 B3 C5 6
6 A3 B3 C1 2
7 A3 B4 C2 5
8 A5 B1 C4 3
9 A5 B2 C2 2
10 A5 B2 C4 1
11 A5 B3 C4 7
12 A5 B6 C4 5
Q. 100 quelles sont les clefs de groupe construites par group by a, rollup (b, c) ?
Q. 101 Combien de lignes produisent les requêtes suivantes (dual contient une seule ligne) :
grouping ().
Dans les clauses select et having la fonction de groupe grouping ( <expr> ) renvoie 0 si l’ex-
fait partie de la sous-clef du groupe actuellement traité et renvoie 1 sinon.
pression en paramètre
L’expression <expr> doit évidemment faire partie de la clef complète de groupe.
MATIERE MOYENNE
------------------------
BD 13
CL 13
SSM 14
Moyenne générale 13.2
Ici, on utilise la fonction grouping() pour choisir le bon libellé de la première colonne. En revanche,
la formule de calcul de la deuxième colonne est la même pour tous les super-groupes : c’est la moyenne
de toutes les notes des étudiants (ceci explique que la moyenne générale ne soit pas égale à la moyenne
des moyennes de matière).
LIBELLÉ NOTE_OU_MOYENNE
----------------------------
BD et Prévert 13
BD 13
CL et Prévert 12
CL et Sartre 15
CL et Vian 12
6.3. GROUP BY CUBE ET GROUP BY ROLLUP 59
CL 13
SSM et Prévert 14
SSM 14
Moyenne générale 13.2
NON car ORA-30480 : L’option distinct n’est pas autorisée avec group by cube
ou rollup.
LIBELLÉ NOTE_OU_MOYENNE
----------------------------
BD et Prévert 13
BD 13
CL et Prévert 12
CL et Sartre 15
CL et Vian 12
CL 13
SSM et Prévert 14
SSM 14
Prévert 13
60 CHAPITRE 6. COMPLÉMENTS ORACLE SQL
Sartre 15
Vian 12
Moyenne générale 13.2
Les premières expressions du group by peuvent être en dehors du cube ou du rollUp, elles font
alors partie de toutes les sous-clefs de groupe. La requête suivante est plus simple et donne le même
résultat que la précédente :
– par matière et étudiant
– par matière et promotion
Les nuplets de la hiérarchie sont parcourus en ordre préfixé à partir du nuplet racine (la racine est
prise avant ses enfants).
La clause where ne fait que retenir ou non les lignes produites par la requête hiérarchique, mais
ne modifie pas l’ensemble des nuplets sélectionnés par la requête hiérarchique (where agit après la
production hiérarchique des nuplets).
Une requête hiérarchique ne peut pas fonctionner sur une jointure : il ne peut y avoir qu’une seule
table dans la clause from.
Pour cela la valeur de la pseudo-colonne level est la distance à la racine plus 1 (pour la racine,
level vaut 1). La requête précédente pourrait alor s’écrire :
select lpad (’ ’, level-1) || e.nom, e.id, e.superieur
from Employe e
start with e.id = 1
connect by prior e.id = e.superieur ;
Remonter une hiérarchie
Autrement dit un employé est le père ou l’ancêtre de tous les employés qui ont un id strictement
supérieur au sien.
Deuxième partie
Développement serveur
63
Chapitre 7
Introduction à PL/SQL
La seule exception concerne select qui, étant une expression, renvoie une valeur qu’il faudra affecter
à une variable PL/SQL avec la nouvelle clause obligatoire : into.
Le N des types PositiveN et NaturalN indique que les valeurs ne peuvent pas être indéfinies (is null).
64
7.3. FONCTION STOCKÉE 65
Tous les types SQL sont utilisables en PL/SQL, y compris ceux définis par le programmeur
dans le contexte du relationnel-objet.
Attention : le type Boolean n’existe pas dans le SQL d’Oracle, la conséquence est qu’une fonction
booléenne ne pourra être utilisée nulle part dans un ordre DML, même pas dans la clause where. Elle
pourra seulement être utilisée par un autre programme PL/SQL.
Le / indique à SQL/PLUS la fin du texte du sous-programme (ou du paquetage) qui est compilé et
stocké immédiatement.
Un appel à une fonction est une expression ou un bout d’expression, il est donc possible, pour tester
la fonction, d’en faire figurer un appel dans la clause select d’une requête :
SQL> select pgcd (7, 21) from Dual ; -- Dual : table prédéfinie d’une ligne
66 CHAPITRE 7. INTRODUCTION À PL/SQL
On voit aussi que les paramètres de la procédure s’utilisent tout naturellement dans le insert.
Les blocs sont bien pratiques pour tester vite fait des sous-programmes, et ils n’ont probablement pas
d’autre utilité ! Par exemple, si on veut tester les deux sous-programmes précédents :
SQL> declare
P constant Positive Not Null := pgcd (33, 56) ;
begin
if P = 2 then ajouterClient (17, ’Tartempion’) ;
else ajouterClient (P, ’Bof’) ;
end if ;
end ;
7.6 Autres
PL/SQL autorise aussi la programmation récursive (éventuellement croisée) et l’emboı̂tement de sous-
programmes.
Ou plutôt :
procedure Solde_De (id in Client.id%type, Solde out Natural) is
Le type d’un paramètre pas être contraint, par exemple on ne peut pas définir un
formel ne peut
paramètre par Nom in VARCHAR (20) .
Les paramètres peuvent être de mode in, in out ou out et sont de mode in par défaut.
Ceci n’est qu’une indication (hint) : le compilateur peut quand même choisir le passage par copie.
Suivant que les paramètres sont passés par copie ou par adresse, l’effet peut-être très différent quand le
sous-programme se termine par une exception non traitée. Lors d’un passage par copie, si la procédure
modificatrice (ici Incr_Copie) est abandonnée par une exception, les modifications des paramètres
formels ne sont pas reportées sur les paramètres effectifs, comme le montre l’exemple suivant :
create or replace package Global is
Mon_Exception exception ;
end Global ;
En revanche si le passage se fait par adresse (nocopy), alors les modifications des paramètres effectifs
seront effectives :
create or replace procedure Incr_Adresse (i in out nocopy Natural) is
begin
i := i + 1 ;
raise Global.Mon_Exception ;
end Incr_Adresse ;
TEST_ADRESSE(3)
----------------------------
4
Si on déclare la variable R Reunion ; on pourra accéder à ses champs par une notation pointée, par
exemple R.d.h pour manipuler le nombre d’heures de la durée.
case <expr-ctr>
when <expr-choix> then <sequence-d-instructions>
{when <expr-choix> then <sequence-d-instructions>}
[else <sequence-d-instructions>]
end case ;
-- Le premier ’when’ dont <expr-choix> est égal à <expr-ctr> est pris,
-- si aucun ’when’ on prend le ’else’
Attention : l’ordre exit permet de continuer l’exécution après la boucle qui le contient (équivalent
du break de C et Java) : exit ne termine pas le sous-programme !
Quand un prédicat est indéfini (is null), l’aiguillage se fait comme si le prédicat était faux. Par
exemple, si la condition d’un exit when est indéfinie, on reste dans la boucle ( !).
Pour voir les noms, types et états de validité des objets (tables, synonymes, contraintes, index, vues,
sous-programmes, paquetages, triggers, . . .) de l’utilisateur :
select Object_Type, Object_Name, Status
from user_objects
order by Object_Type, Object_Name ;
exception signification
No_Data_Found si la requête n’a aucune ligne.
Too_Many_Rows si la requête a plus d’une ligne.
Si la fonction échoue avec l’exception No_Data_Found, celle-ci est récupérée par le select qui donne
alors un nuplet dont l’unique colonne est indéfinie ! En revanche No_Data_Found est bien visible quand
on teste avec un bloc anonyme.
L’exemple précédent (Nb_Employe) ne pose pas ce problème car un select count (*) sans group
by fournit toujours exactement un nuplet. On pourrait en revanche avoir une exception avec :
select * into Le_Client
from Client
where nom = ’toto’ ;
-- Exceptions :
-- No_Data_Found si aucun nuplet n’est sélectionné,
-- Too_Many_Rows si plus d’un nuplet est sélectionné.
...
7.14.3 Les noms des colonnes des tables peuvent cacher les variables/paramètres
Réécrivons la fonction Nb_Employe en donnant au paramètre formel le même nom de la colonne :
create function Nb_Emp (Categorie in Employe.categorie%type) return Natural is
nb Natural ;
begin
select count (*) into nb
from Employe e
where e.categorie = Categorie ; -- Aı̈e !!!
return nb ;
end Nb_Emp ;
Le problème est alors dans la clause where de la requête la mention du paramètre Categorie est en
fait comprise comme la colonne Categorie de la table Employe1 ! Le test d’égalité vaudra toujours
vrai (sauf pour les employés dont la catégorie est indéfinie), et la fonction ne fait plus ce qu’elle est
censée faire.
Une solution consiste à donner aux variables et paramètres PL/SQL des noms différents des noms des
colonnes des tables manipulées comme cela est fait dans la première version de la fonction Nb_Employe.
Une autre solution, probablement plus fiable, consiste à préfixer le nom de variable ou de paramètre
par le nom de la structure qui le déclare, dans notre exemple il s’agit du nom de la fonction :
create function Nb_Emp (Categorie in Employe.categorie%type)
return Natural is
nb NaturalN ;
begin
select count (*) into nb
from Employe e where e.categorie = Nb_Emp.Categorie ; -- Ouf !!!
return nb ;
end Nb_Emp ;
end F ;
Un select ne peut se servir de cette fonction car elle tente de modifier la base de données
select f (5) from dual ;
ORA-14551: impossible d’effectuer une opération DML dans une interrogation
ORA-06512: à "DURIF.F", ligne 3
ORA-06512: à ligne 1
pour autant qu’il soit raisonnable d’avoir des fonctions à effet de bord.
parameter ::= <parameter_name> [in] datatype [{:= | default} <expression>]
Le return_type doit être un record ou un %rowtype (un %rowtype est un record).
C’est lors de l’ouverture du curseur (instruction open, voir 7.15.4, page 75) qu’on fixera les paramètres
effectifs.
Une manière de nommer un curseur pourrait alors être Les_<nature des objets>, c’est exactement
ce qu’on a fait avec le curseur Les_Nom_Prenom.
On peut utiliser l’attribut %ROWTYPE pour typer une variable à partir d’un curseur :
nom_prenom Les_Nom_Prenom%ROWTYPE ; -- nom_prenom.nom
On peut récupérer le nuplet courant soit dans un record du même type que le curseur soit, dans
autant de variables scalaires que le curseur a de colonnes :
declare
np Nom_Prenom ; nom Client.nom%type ; prenom Client.prenom%type ;
begin
fetch Les_Nom_Prenom into np ;
fetch Les_Nom_Prenom into nom, prenom ;
close <curseur> ;
close Les_Nom_Prenom ;
Voici un code naı̈f qui exploite cette exception pour trouver une clef satisfaisante (cette méthode
marche mais il est clair qu’il serait déraisonnable de la mettre en exploitation !) :
SQL> create table T (id Number (5) primary key, nom Varchar2 (20)) ;
end Ajouter ;
Si on n’utilise pas la procédure raise_application_error, c’est l’erreur Oracle -06510 qui sera
transmise au moteur SQL :
begin Bof ; end ;
ORA-06510: PL/SQL : exception définie par l’utilisateur non traitée
7.15.6 Récupérer les erreurs Oracle sous forme d’exception : pragma Excep-
tion Init
Il est donc possible, dans PL/SQL, de traiter les erreurs Oracle avec le mécanisme des exceptions.
De plus, avec le pragma Exception_Init, on peut associer explicitement une exception à un code
d’erreur Oracle, ce qui permet ensuite d’utiliser cette exception pour traiter l’erreur correspondante.
Par exemple, ici on s’arrange pour que l’exception Trop_De_Nuplets soit synonyme de Too_Many_Rows :
78 CHAPITRE 7. INTRODUCTION À PL/SQL
declare
Trop_De_Nuplets exception ;
pragma Exception_Init (Trop_De_Nuplets, -1422) ;-- erreur SQL de Too_Many_Rows
Le_Client Client%rowtype ;
begin
select * into Le_Client from Client where age=25 ;
exception
when Trop_De_Nuplets then -- idem : Too_Many_Rows
...
end ;
7.15.7 Les exceptions sont propagées vers SQL sous forme d’erreurs Oracle
Quand une exception n’est pas traitée par le code PL/SQL, elle est propagée vers oracle sous la forme
d’un code d’erreur accompagné d’un message :
declare
Mon_Exception exception ;
begin
raise Mon_Exception ;
end ;
[−20999, −20000] est l’intervalle des numéros d’erreurs utilisables par le programmeur.
Comme en Ada, on distingue la déclaration de paquetage qui définit des entités utilisables de l’extérieur
et le corps de paquetage qui implémente les sous-programmes annoncés dans la déclaration de paque-
tage et peut définir ses propres entités privées (non visibles de l’extérieur du paquetage).
Q. 107 Aucune des exceptions No Data Found ou Too Many Rows ne peut être déclenchée par le select
de l’initialisation du paquetage, pourquoi ?
Remarques importantes : les variables globales ont une persistance limitée à la durée de la session :
à chaque début de session elles sont réinitialisées.
De même, la partie initialisation du corps de paquetage est exécutée une et une seule fois au début de
chaque session.
C’est probablement pourquoi il est tellement pénible de faire, en PL/SQL, de l’interaction homme-
machine, même de façon très primitive.
Le paquetage prédéfini DBMS_OUTPUT permet, sous SQL*Plus, d’écrire des messages dans une table
gérée par ce paquetage :
SQL> create procedure Imp_Pgcd (A in PositiveN, B in PositiveN) is
begin
Dbms_Output.Put_Line (’Pgcd = ’ || To_Char (pgcd (A, B))) ;
end Imp_Pgcd ;
Pour qu’en fin d’exécution les messages de la table soient affichés à l’écran il faut le demander à
SQL*Plus :
SQL> set serveroutput on
SQL> Execute Imp_Pgcd (45, 129) ;
Pgcd = 3
Tous les messages du programme sont donc affichés d’un seul coup lorsque ce dernier se termine.
Si on tente de le faire alors Oracle, logiquement, fait avorter l’ordre principal avec l’erreur ORA-04091.
Q. 108 Réécrire correctement cette fonctionalité sous forme d’une procédure PL/SQL.
Lors de l’insertion d’une ligne dans Fils, la pratique courante consiste souvent à vérifier d’abord,
grâce à une requête la présence de la clef dans Mere.
Or ceci est inutile, car lors de l’insertion, si la clef étrangère n’apparaı̂t pas dans Mere alors Oracle
déclenche l’erreur -02291 (touche parent introuvable) : il suffit de récupérer cette erreur sous forme
d’une exception.
Voici deux versions de la procédure d’ajout d’un fils qui ont, grosso modo, le même comportement
(sauf si on se place dans un contexte multi-transactionnel) :
7.20. RÉCUPÉRATION DES VALEURS PRODUITES PAS LE SGBD (DML RETURNING) 81
version 1 version 2
create procedure Ajouter_Fils ( create procedure Ajouter_Fils (
f in Fils.id_fils%type, f in Fils.id_fils%type,
m in Mere.id_mere%type) m in Mere.id_mere%type)
is is
begin Mere_inexistante exception ;
if m is not null then pragma Exception_init
-- Tester si la mère existe (Mere_inexistante, -02291) ;
declare begin
nb Natural ; insert into Fils values (f, m) ;
begin exception
select count (*) into nb when Mere_inexistante then
from Mere raise_application_error
where id_mere = m ; (-20100, ’Mère inexistante’);
if nb = 0 then end Ajouter_Fils ;
raise_application_error
(-20100, ’Mère inexistante’);
end if ;
end ;
end if ;
-- La mère existe ou est indéfinie
insert into Fils values (f, m) ;
end Ajouter_Fils ;
Lors d’une instruction DML les nouvelles valeurs d’une ligne insérée ou modifiée peuvent être produites
par le SGBD lui-même et donc inconnues de la procédure :
1. lors d’une insertion, la clef est obtenue grâce à une sequence Oracle :
insert into Employe (id, nom, salaire)
values (Generateur_De_Clef.nextval, ’Dupont’, 2000.0) ;
On ne peut pas retrouver l’id de ce nouvel employé, si un ensemble d’autres colonnes n’est pas
aussi une clef.
2. c’est le update qui augmente le salaire :
update Employe
82 CHAPITRE 7. INTRODUCTION À PL/SQL
On peut aussi mettre une table par colonne fabriquée par la requête ou le curseur ou la clause retur-
ning.
smax in Employe.salaire%type,
augm in Employe.salaire%type) is
type Nouvel_Etat_Employe is record (
id Employe.id%type,
nom Employe.nom%type,
nouveau_salaire Employe.salaire%type
) ;
type Des_Employes is table of Nouvel_Etat_Employe ;
Les_Employes Des_Employes ;
begin
update Employe
set salaire = salaire + augm
where salaire between smin and smax
returning id, nom, salaire BULK COLLECT INTO Les_Employes ;
if Les_Employes.count != 0 then
for I in Les_Employes.First..Les_Employes.Last loop
Dbms_Output.Put_Line (Les_Employes (I).id || ’: ’ ||
Les_Employes (I).nom || ’ ’ ||
Les_Employes (I).nouveau_salaire) ;
end loop ;
end if ;
end Augmenter ;
Pour un insert, update ou delete on est obligé de préciser les colonnes (* ne convient pas).
7.21.2 Limiter le nombre de lignes récupérées par fetch ... bulk collect
Seulement avec un curseur (fetch) on peut spécifier un nombre maximum de lignes à récupérer à
chaque fois, l’utilisation du curseur doit alors se faire à nouveau dans une boucle. La limite est donnée
après le mot clef limit. Voici une reprise de l’exemple précédent avec limit :
create or replace procedure Tranche (smin in Employe.salaire%type,
smax in Employe.salaire%type) is
cursor Employes return Employe%rowtype is
select *
from Employe
where salaire between smin and smax ;
type Des_Employes is table of Employe%rowtype ;
Les_Employes Des_Employes ;
Max_Lignes Natural := 2 ;
begin
open Employes ;
loop
fetch Employes BULK COLLECT INTO Les_Employes LIMIT Max_Lignes ;
exit when Les_Employes.count = 0 ;
for I in Les_Employes.First..Les_Employes.Last loop
Dbms_Output.Put_Line (Les_Employes (I).id || ’: ’ ||
Les_Employes (I).nom || ’ ’ ||
Les_Employes (I).salaire) ;
end loop ;
end loop ;
close Employes ;
end Tranche ;
7.21. AMÉLIORATION DES PERFORMANCES DU CODE PL/SQL 85
Q. 113 Ici il ne faut surtout pas utiliser Employes%notfound pour sortir de la boucle. Pourquoi à
votre avis ?
Chapitre 8
Les triggers
DDL
Un trigger est un bout de code qui sera exécuté à chaque fois qu’un événement particulier se produira
sur une table particulière. Un événement correspond à la modification d’une table (insert, update
ou delete).
La programmation par trigger est donc une forme de programmation événementielle.
Un trigger est une procédure compilée (en pcode) et stockée dans le dictionnaire, qui s’exécute auto-
matiquement chaque fois que l’événement déclenchant se produit.
Les triggers existent dans la plupart des SGBD (par exemple Oracle, PostgreSQL, MySQL 5.1 qui ne
permet que les triggers ligne et pas plus d’un trigger before et d’un trigger after par table)
Sous Oracle, le corps du trigger s’écrit en PL/SQL (on peut aussi utiliser C ou Java depuis Oracle 8).
Les triggers peuvent être utilisés pour garantir des propriétés que les contraintes déclaratives (check)
ne peuvent garantir. Un trigger qui échoue par une exception fait échouer l’ordre DML qui a provoqué
sont exécution, la table est alors remise dans son état d’origine.
Ils peuvent aussi servir à rendre la base plus dynamique ; par exemple, on peut grâce au trigger, es-
pionner les opérations faites sur la table des salaires en enregistrant dans une autre table l’heure et
l’identité de celui qui a tenté la modification.
La programmation de triggers est une tâche délicate puisqu’elle insère du code dans le fonctionnement
normal du moteur SQL.
Exemples :
– garantir que le nombre d’étudiants inscrits à une unité d’enseignement est toujours inférieur à sa
capacité d’accueil.
– garantir que le salaire d’un employé est inférieur à celui de son supérieur.
Attention : quand c’est possible, une contrainte déclarative est toujours préférable à l’introduction
d’un trigger.
– Pour automatiser des traitements lors de certains événements, ce type de trigger permet
de mettre en œuvre la notion de BD active.
Exemples :
– on veut conserver la trace de toutes les modifications appliquées à une table en enregistrant dans
une autre table le nom de l’auteur de la modification et la date de modification.
86
8.2. STRUCTURE D’UN TRIGGER 87
– créer une commande de produit à chaque fois que sa quantité en stock passe en dessous d’un
certain seuil.
PostgreSQL permet, de façon cohérente, à un trigger ligne after de consulter la table en cours de
modification en fait cela est cohérent car les triggers ligne after ne sont déclenchés qu’après que la
table ait été complètement modifiée (voir la section 8.10).
PostgreSQL a le mérite de permettre de dire explicitement qu’il s’agit d’un trigger instruction avec le
qualificatif for each statement. Cependant, comme en Oracle, si aucun des deux qualificatifs n’est
donné, il s’agit d’un trigger instruction.
:old.col :new.col
insert is null valeur insérée
delete valeur originale is null
update valeur originale nouvelle valeur ou valeur originale si pas de nouvelle valeur
:new et :old ont les mêmes valeurs, que le trigger soit before ou after mais une modification de :new
n’aura d’effet que dans un trigger before.
:old et :new ne peuvent être utilisés que dans le bloc anonyme du trigger.
Pour insert et update, on peut réaffecter :new dans le trigger, mais seulement pour un trigger before.
Un autre trigger ligne after verra les modifications apportées à :new par un trigger ligne before.
Pour les triggers ligne, utilisation obligatoire des préfixes :old. et :new. pour désigner les colonnes
en cours de modification.
En revanche, les triggers ligne sont exécutés pendant l’exécution de l’instruction de mise à jour, c’est
à dire à un moment où la table n’est pas dans un état stable (elle est dite mutating).
On voit que les triggers ligne remettent en cause l’apparente atomicité des ordres DML en permet-
tant d’injecter du code (celui des triggers ligne) qui sera exécuté pendant l’exécution de l’ordre DML.
Si un trigger échoue en déclenchant une erreur, quelle qu’elle soit, alors Oracle garantit que la base
est remise dans l’état dans lequel elle était avant l’exécution de l’instruction ayant déclenché ce ou ces
triggers (l’effet des ces triggers est lui aussi gommé).
end if ;
end ;
Q. 118 Enrichir la condition de when pour n’exécuter le bloc anonyme qu’en cas d’erreur de salaire.
Q. 119 Écrire un trigger qui garantit qu’une fois défini le salaire est constant.
On souhaite que lors de la modification d’une note celle-ci soit éventuellement recadrée entre 0 et 20 :
create or replace trigger Cadrer_Note
before insert or update of note on Les_Notes
for each row when (new.note < 0 or 20 < new.note)
begin
:new.note := case when :new.note < 0 then 0 else 20 end ;
end Cadrer_Note ;
8.5. EXEMPLES DE TRIGGERS RENDANT ACTIVE LA BASE 93
Q. 123 Peut-on remplacer impunément before par after ? voir la section 8.3 page 88
Voici alors ce qui se passe lors d’une augmentation de 1 point des notes de la matière 2 par la
commande :
update Les_Notes set note = note + 1 where mat = 2 ;
2 20 20 21 20 20 2 20
1 14
1 9
Chronologiquement, voici ce qui se passe :
1 Début de la commande update
2 Sélection et lecture dans old du premier tuple
La clause set calcule new.note : 8
Le trigger Cadrer_Note s’arrête sur when
Écriture du tuple avec new.
3 Sélection et lecture dans old du deuxième tuple
La clause set calcule new.note : 21
Exécution du trigger Cadrer_Note
Écriture du tuple avec new.
4 Sélection et lecture dans old du troisième et dernier tuple
La clause set calcule new.note : 9
Le trigger Cadrer_Note s’arrête sur when
Écriture du tuple avec new.
5 Fin de la commande update
On voit que lorsque le trigger s’exécute la table Les_Notes est en cours de modification, on dit qu’elle
est mutante ou mutating.
Pour cette raison, un trigger ligne ne peut ni consulter ni modifier la table à laquelle il est attaché
sous peine d’un déclenchement d’erreur de table mutante.
La notion de table mutante est strictement interne à une seule transaction : une table est mutante
pendant l’exécution d’une instruction insert, update ou delete.
Pendant qu’une table est mutante elle ne peut ni être consultée ni être modifiée de façon emboı̂tée.
Si on tente de le faire alors Oracle, logiquement, fait avorter l’ordre principal avec l’erreur ORA-04091.
Ce problème peut apparaı̂tre notamment avec l’utilisation des triggers ligne puisque ceux-ci sont
exécutés pendant l’exécution de l’instruction qui les déclenchent. Il peut aussi apparaı̂tre avec des
fonctions stockées, par exemples si elles sont appelées dans la clause where d’un update et qu’elle
tente de consulter la table modifiée par le update.
La raison de cette erreur est qu’une table mutante est dans un état intermédiaire probablement
incohérent et que cela n’aurait alors aucun sens de la consulter.
Voici un trigger très simple qui est erroné car il tente de consulter la table en cours de modification.
Soit la table :
__________________________
v |
Employe (id, salaire), Adresse (id_employe, ville, dpt)
-- ----------
On veut garantir la propriété Psalaires égaux :
Tous les salaires sont égaux et un salaire indéfini est considéré comme
Psalaires égaux ≡
égal à n’importe quelle autre valeur.
8.6. TABLE MUTANTE (MUTATING TABLE) 95
Tout d’abord on remarque que seule la table Employe est impliquée dans le maintien de Psalaires égaux .
Q. 127 L’ordre delete peut-il casser Psalaires égaux ?
Analyse des cas :
– delete : ne peut évidemment pas casser Psalaires égaux
– insert :
– new.id ne peut casser Psalaires égaux
– new.salaire s’il est indéfini ne casse pas Psalaires égaux
– new.salaire s’il est défini peut casser Psalaires égaux
– update
– new.id ne peut casser Psalaires égaux
– new.salaire s’il est indéfini ne casse pas Psalaires égaux
– new.salaire s’il est défini peut casser Psalaires égaux
On décide donc d’écrire un trigger ligne erroné qui fera la vérification pour chaque employé modifié :
create or replace trigger Salaire_Egaux
before insert or update of salaire on Employe
for each row when (new.salaire is not null)
declare
Cpt_Sal_Diff Natural ;
begin
select Count (*) into Cpt_Sal_Diff
from Employe e
where e.salaire is not null and e.salaire != :new.salaire ;
if Cpt_Sal_Diff != 0 then
raise_application_error (-20111, ’salaires non égaux !’) ;
end if ;
end ;
On remarque que la requête du trigger utilise la table Employe qui est cours de modification par
l’ordre insert ou update qui a déclenché le trigger. Par exemple, l’ordre suivant qui tente d’augmenter
les salaires de 10 unités conserve évidemment Psalaires égaux et pourtant il échouera à cause de la
consultation d’une table mutante :
update Employe set salaire=salaire+10; -- échec : table mutante dans le trigger
Si Oracle ne déclenchait pas cette erreur de table mutante, le comportement serait bien pire : avant
de modifier le salaire du premier employé, le trigger détecterait que le nouveau salaire est différent de
ceux présents dans la table et déclencherait à tort l’erreur de salaires inégaux.
En revanche PostgreSQL (version 7.3.4) ne connaı̂t pas la notion de table mutante, du coup, pour le
même exemple :
– avec un trigger ligne before il déclencherait incorrectement une erreur de salaires inégaux !
– en revanche cela marche bien pour les triggers ligne after car ces triggers sont exécutés quand
la modification de la table est complètement terminée. Les valeurs de :new sont celles présente
dans la table et les valeurs :old sont (très probablement) celles mémorisées par le multiversion
(ou l’historique) des valeurs de chaque ligne (voir la partie sur les transactions, section 13.9.1 et 14
pages 157 et 165).
En fin de compte, une erreur de table mutante signifie une erreur de programmation.
Pourquoi Oracle ne signale-t-il pas cette erreur dès la compilation ? La raison est que dans certains cas
un trigger peut légitimement consulter ou modifier la table sur laquelle l’événement déclenchant a eu
lieu. Le cas principal est celui où le trigger est du type instruction, en effet un trigger instruction
s’exécute avant ou après l’instruction déclenchante, il travaillera donc sur une table non mutante , voir
la section 8.3 page 88.
96 CHAPITRE 8. LES TRIGGERS
Une solution, pour garantir Psalaires égaux , consiste donc à confier la vérification de la propriété à
un trigger instruction after.
Q. 128 Pour résoudre le problème de table mutante, remplacer le trigger ligne Salaire Egaux par un
trigger instruction after qui lui peut consulter la table Employe après modification.
Attention : un problème de table mutante peut aussi se produire pour un trigger instruction dans le
cas d’une cascade de déclenchements.
Q. 129 Donner un exemple où un trigger instruction échoue pour cause de table mutante.
C’est pourquoi, si la technique des triggers semble incontournable, il est important de faire une analyse
structurée avant de les implanter.
Le problème est : en quoi une modification de la BD peut-elle casser la propriété.
1. faire l’inventaire des tables pour lesquelles une modification pourrait casser la propriété,
2. construire un tableau à deux entrées : en lignes les tables, en colonnes les événements (insert,
update, delete) et, pour chaque case, en quoi l’événement se produisant sur la table est sus-
ceptible ou non de casser la propriété. Il est aussi intéressant d’y faire figurer les colonnes de la
table intervenant dans le maintien de la propriété.
3. utiliser les informations précédentes pour savoir si fonctionnellement un ou des triggers ligne ou
instruction peuvent ou doivent être mis en place.
Le choix entre trigger ligne ou instruction n’est pas forcément évident :
– le trigger ligne vérifie que la modification de chaque ligne conserve la propriété, il peut être intéressant
si très peu de lignes sont modifiées à chaque mise à jour de la BD.
– l’avantage du trigger instruction est qu’il travaille toujours sur une BD stable (non mutante), ce-
pendant il peut être coûteux si à chaque modification d’une table il vérifie que ses 10 millions de
lignes vérifient toujours la propriété alors qu’une seule ligne a été modifiée !
La propriété Psolde suffisant à garantir est :
le solde d’un client est soit indéfini soit supérieur ou égal au total de ses achats .
D’abord on ne peut garantir cette propriété Psolde suffisant de façon déclarative : vérifier Psolde suffisant
nécessite d’observer l’état global des trois tables Produit, Achat et Client grâce à une requête qui
calcule la somme des achats de chaque client. Or Oracle ne permet pas d’évaluer une requête dans une
contrainte check et ne dispose pas des assertions définies par la norme SQL.
Q. 130 Réécrire plus simplement la clause when en utilisant la fonction nvl.
– table Produit : un ordre update peut casser la propriété. Pour cet événement, un trigger ligne
n’est pas approprié car il a be