coursbdd_théorie
TRANSCRIPT
Chapitre 1
Base de donnees et Systeme de gestionde base de donnees
1.1 Qu’est-ce qu’une base de donnees (BD)
Une base de donnees peut etre vue comme le besoin de memoriser de facon durable des donnees et depouvoir exprimer le plus precisement possible les relations qu’entretiennent ces donnees.
Une fois cette representation faite il est necessaire d’associer des fonctionnalites (programmes et desrequetes) a cette base de donnees afin de pouvoir l’exploiter le plus facilement possible.
Toutes les personnes exploitant la meme base de donnees n’ont pas la meme fonction et n’ont doncpas forcement besoin de voir les memes informations ou d’appliquer les memes actions a la base dedonnees. Les systemes des privileges, des vues et des programmes stockes permettent de delimiterrigoureusement ces differentes visions d’une meme base de donnees (chaque vision est nommee schemaexterne).
Enfin, plusieurs utilisateurs peuvent appliquer simultanement des modifications a la meme base dedonnees, il est alors necessaire d’utiliser des techniques d’isolation et de synchronisation afin de garantirla coherence de ces modifications.
1.2 Qu’est-ce qu’un systeme de gestion de base de donnees (SGBD)
Un SGBD est la structure d’accueil d’une ou plusieurs bases de donnees : il offre les outils necessairesa la mise en place d’une base de donnees. On pourrait comparer le SGBD au systeme d’exploitationet la base de donnees a un programme d’application utilisant les services du systeme.
Voici quelques-unes des caracteristiques d’un SGBD :
– Capacite de gerer des donnees persistantes et structurees.– Capacite a gerer, autant que possible, la semantique des donnees et a garantir des proprietes (les
contraintes, assertions, domaines des attributs, triggers et procedures stockees)– Pouvoir manipuler facilement et efficacement de tres grand volumes de donnees.– Permettre l’execution de transactions concurrentes par un ou plusieurs utilisateurs tout en conser-
vant les proprietes de la BD.– Assurer la securite des donnees :
– controler les acces en fonction de droits accordes aux differents utilisateurs.– tolerer les pannes logicielles ou materielles grace a des procedures de reprise.
– Procurer l’independance physique : le SGBD permet de manipuler les donnees independemmentde leurs implantations materielles.
– Procurer l’independance logique : chaque utilisateur ne voit de la base que les donnees qui luisont necessaires (schema externe).
1
2 CHAPITRE 1. BASE DE DONNEES ET SYSTEME DE GESTION DE BASE DE DONNEES
– Le cœur d’un SGBD est le modele de donnees qu’il supporte, c’est a dire la maniere d’organiserles donnees qu’il offre. Le modele actuellement le plus utilise est le relationnel invente dans les annees1970 dont une belle qualite est probablement la symetrie naturelle qu’il offre dans les associationsinter-donnees. Il existe d’autres modele de donnees : hierarchique, reseau et objet, qui eux ne sontpas franchement symetriques.
– Fournir un langage de haut niveau adapte au modele : SQL pour le modele relationnel, CODASYLpour le modele reseau, OQL pour le modele objet.
– Exemples de SGBD relationnels : Oracle, PostgreSQL, MySQL, Access et plein d’autres !
1.3 Les modeles de donnees
Un modele de donnees est un formalisme permettant de :– decrire les donnees (organisation, typage, ...)– manipuler ces donnees.Les deux principaux modeles :
Modeles a acces purement associatif Ce sont :
Relationnel annees 1970, SQL1 1987, SQL2 1992
Deductif annee 1980-1990, calcul des predicats logiques du premier ordre, par exemple DATA-LOG (a la Prolog)
La manipulation des donnees est declarative : le programmeur n’a pas a se soucier du commentmais seulement du quoi, par exemple : je veux la liste des clients dont les soldes sont positifs, jen’ai pas a dire comment faire pour obtenir cette liste, c’est le SGBD qui s’en charge.
Modeles a acces Navigationnel Ce sont :
Fichiers avec chaınage programme APOLLO 1965,
Hierarchique fin des annees 1960, utilistation de pointeurs permettant la navigation
Reseaux fin des annees 1960, COSET
Oriente Objet annees 1980-1990 (O2)
La manipulation des donnees est procedurale : en plus du quoi, le programmeur doit se preoccuperdu comment, par exemple : tant qu’il reste au moins un client, mettre le prochain client dans laliste si son solde est positif.
Modeles hybrides On trouve des modeles hybrides qui disposent d’acces associatif et navigationnel :le relationnel-objet (SQL3 1999, Oracle, PostgreSQL).
1.4 Les niveaux d’abstraction
Pour assurer l’independance logique et l’independance physique, le groupe ANSI/X3/SPARC a definien 1975 trois niveaux de description d’une base de donnees :– Des schemas externes donnent differentes vues d’un meme schema conceptuel, chacun etant ap-
proprie a un type d’utilisateur (SQL introduit la notion de vue et de privilege).– le schema conceptuel, a ce niveau on definit la structuration et le typage des donnees. C’est le
domaine du concepteur de la base.– le schema interne qui definit les parametres de stockage, les index favorisant certains acces, . . .C’est
le domaine de l’administrateur/optimiseur.Ce niveau est le dernier avant la representation physique des donnees sur disque et en memoirecentrale et qui est a la charge du SGBD.
1.5 Schema et instances
Dans une BD, il y a un schema et des donnees.Le schema d’une BD est le resultat de la conception (par exemple le MCD de Merise) qui decritl’organisation des donnees. Un schema n’est pas destine a etre modifie (ou bien rarement).
1.6. LES DIFFERENTS LANGAGES COTE SERVEUR 3
Une instance d’un schema correspond aux donnees stockees dans la base a un moment donne. Lesdonnees d’une instance respectent evidemment l’organisation imposee par le schema. Le contenu d’uneBD est eminemment variable : chaque modification de la BD produit une nouvelle instance du schemacorrespondant.
Exemple :
1. soit le schema relationnel : Personne (Nom, Prenom), et deux instances possibles de ce schema :
DURAND GastonDUPOND JulesLAGAFFE Gaston
etLAGAFFE GastonPERSONNE Paul
2. le meme schema avec un modele objet (ici ODL de l’ODMG) :
class Personne (extent lesPersonnes key Nom) {
attribute string Nom ;
attribute string Prenom ;
}
Le mot clef extent introduit le nom de la collection qui contiendra les objets Personne.
3. le meme schema en SQL :
create table Personne (
Nom Varchar2 (20) primary key,
Prenom Varchar2 (20)
) ;
Ici Personne represente a la fois le schema de relation et la variable contenant l’instance.
1.6 Les differents langages cote serveur
1.6.1 DDL : Data Definition Language
Pour definir/modifier les schemas externes et le schema conceptuel
– par exemple, pour le modele relationnel, SQL propose
create table Diplome (
id Number (5),
mention Varchar (20),
constraint Diplome_PK primary key (id)
) ;
create table Etudiant (
id Number (5),
nom Varchar (20),
prenom Varchar (20),
constraint Etudiant_PK primary key (id)
) ;
Modification du schema qui ajoute une colonne aux etudiants :
alter table Etudiant
add (mon_diplome Number (5))
add (constraint Etudiant_Diplome_FK
foreign key (mon_diplome) references Diplome (id)) ;
Enrichissement du schema avec la vue Effectifs donnant le nombre d’etudiants par diplome :
4 CHAPITRE 1. BASE DE DONNEES ET SYSTEME DE GESTION DE BASE DE DONNEES
create view Nb_Homonymes (Nom, Nombre_D_Etudiants_Portant_Ce_Nom) as
select e.nom, count (*)
from Etudiant e
group by e.nom ;
create view Effectifs (id, mention, nb_etudiants) as
select d.id, d.mention, count (e.id)
from Diplome d
left outer join Etudiant e on e.mon_diplome = d.id
group by d.id, d.mention ;
Bien que recalculees a chaque sollicitations, certaines vues sont comme des tables (on peut y ajouter,modifier et supprimer des lignes, ces modifications etant en fait reportees par le SGBD sur les tablessous-jacentes, chapitre 10).
– par exemple, pour le modele objet, la norme ODMG propose ODL (Object Definition Language).
1.6.2 DML : Data Manipulation Language
Permet de modifier le contenu de la base (insertion, mises a jour, suppression de donnees) et d’inter-roger la base (langage de requete).– par exemple, pour le modele relationnel, SQL propose les instructions insert, update, delete et
la requete select.– par exemple, pour le modele objet, la norme ODMG propose OQL (Object Query Language) et
OML (Object Manipulation Language).
1.6.3 DCL : Data Control Language
Pour gerer les utilisateurs et leurs privileges.Par exemple en SQL Oracle :
CREATE USER ...
DROP USER ...
GRANT ...
1.7 L’Architecture Client/Serveur
Tres souvent le SGBD tourne sur une machine serveur plus ou moins dediee, par contre les applicatifsclient tournent sur d’autres machines et doivent se connecter au SGBD via le reseau.
Il faut donc distinguer clairement entre ce qui doit tourner sur le serveur et ce qui doit tourner sur leclient.
1.7.1 Le code execute par le SGBD (le serveur)
Les ordres SQL
Les triggers reflexes declenches lors d’une modification des donnees, pour verifier des contraintescomplexes, ou pour rendre la base de donnees plus autonome (langage : PL/SQL d’Oracle, ouPLPGSQL de Postgres qui ressemblent tous deux fortement a Ada).
Les procedures stockees pour ecrire des traitements complexes n’ayant de sens que s’ils sont menesjusqu’a leur terme, par exemple une operation de virement d’un compte a un autre qui necessitedeux operations de mise a jour successives (2 update) (langage : PL/SQL d’Oracle, ou PLPGSQLde Postgres qui ressemblent tous deux fortement a Ada).
Les methodes des objets pour un SGBD oriente objet ou relationnel-objet.
1.8. LE CODE APPLICATIF EXECUTE COTE SERVEUR ET/OU CLIENT 5
Les SGBD proposent souvent leur propre langage de programmation : PL/SQL pour Oracle, PL/pgSQLpour PostgreSQL et le langage de MySQL.
1.8 Le code applicatif execute cote serveur et/ou client
Ce code est en general ecrit dans un langage hote : ce sont des langages classiques (Cobol, C, Ada,Java, . . .) qui permettront d’ecrire une application cliente complete, ou du code destine a etre executepar le serveur.
Il y a deux possibilites pour utiliser le SGBD a partir d’un langage hote :
API La premiere possibilite est de fournir une API plus ou moins specifique au SGBD (ODBC, JDBC,libpq pour C de Postgres, OCI pour Oracle, . . .), il suffit d’utiliser les primitives de l’API dansun programme traditionnel.
SQL embarque La seconde, de loin la plus agreable, repose sur une extension du langage hotepermettant d’ecrire et d’exploiter tres naturellement des ordres du SGBD (des ordres SQL parexemple, et on parle alors de SQL embarque ou embedded SQL). Le programme obtenu doit etretraite par un preprocesseur, en general fourni par l’editeur du SGBD, qui, entre autres choses,remplace les ordres embarques par des appels a une API specifique. Le nouveau programmeobtenu est ecrit dans le langage hote d’origine et contient des appels a une API, on est alorsramene a la premiere possibilite.
Exemples de preprocesseurs :– Oracle : Pro*C/C++, Pro*COBOL, SQLJ,– Postgres : ecpg,– le projet GNADE : SQL embarque dans du Ada 95, avec des API ODBC, PostgreSQL et
MySQL
Avec le developpement de l’acces a des bases de donnees via le reseau Internet, de nombreux envi-ronnements normalises ou non existent. Par exemple l’environnement Hibernate qui tend a rendretransparent au programmeur la persistance des objets stockes dans une base de donnees.
Premiere partie
Relationnel et SQL
6
Chapitre 2
Le modele relationnel et SQL
Invente par E.F. Codd en 1970, chez IBM.
Ce modele est lie a la theorie des ensembles (unicite des elements, sous-ensemble, produit cartesien, . . .)
Une de ses realisations pratiques : SQL (Structured Query Language).
Historique
– 1970, Codd invente l’algebre relationnelle,– 1972 a 1975 IBM invente SEQUEL puis SEQUEL/2 en 1977 pour le prototype SYSTEM-R de
SGBD relationnel– SEQUEL donne naissance a SQL– Parallelement, Ingres developpe le langage QUEL en 1976– Des 1979, Oracle utilise SQL– 1981, IBM sort SQL/DS– 1983, IBM sort DB2 (heritier de SYSTEM-R) qui fournit SQL.– 1982, l’ANSI (organisme de normalisation americain) 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 utilisee,– Puis la normalisation SQL-99 (ou SQL3) avec, entre-autres, les extensions relationnel-objet, qui
n’est pas encore terminee !
2.1 Qu’est-ce qu’un ensemble
Un ensemble est une collection d’elements de meme nature. Par exemple l’ensemble des entiers negatifs,ensemble des caracteres, des voyelles, des mots de la langue francaises.
Definition d’un ensemble :
– par extension (ou enumeration) : on explicite chaque element, par exemple l’ensemble des voyelles :{a, e, i, o, u, y}.L’ordre des elements n’a aucune importance : {a, e, i} = {i, a, e}.Unicite de chaque element apparaissant dans un ensemble, contre-exemple : {a, e, i, a} n’est pas unensemble.L’ensemble vide : {} = ∅
– par intention (ou caracterisation) : on definit la ou les proprietes verifiees par chaque element del’ensemble et seulement les elements de l’ensemble. Par exemple l’ensemble des entiers naturelspairs :{x|x = 2p, p ∈ N}
En SQL on parle plutot de domaine que d’ensemble, par exemple Varchar (20) est l’ensemble detoutes les chaınes de caracteres de longueurs inferieures ou egales a 20 et, en Oracle, Number (5, 2)
est l’ensemble des nombres positifs ou negatifs pouvant s’exprimer avec 5 chiffres decimaux dont 2apres la virgule.
7
8 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
2.2 Notion centrale : schema et valeur d’une relation
Le schema d’une relation exprime comment est constituee une relation : le nombre d’attributs parn-uplet, un nom different pour chaque attribut et, pour chaque attribut, le domaine dans lequel ilprend ses valeurs. Par exemple :
schema : Etudiant (NumCarte : Entier ; Nom : Chaine ; Note : Entier)
Le nombre d’attributs du schema s’appelle son arite, le schema Etudiant a une arite de 3.
La valeur d’une relation est un sous-ensemble du produit cartesien des domaines de son schema (undomaine est un ensemble de valeurs, par exemple l’ensemble des chaınes de caracteres, l’ensemble descouleurs primaires, l’ensemble des notes de 0 a 20, l’ensemble des mentions de diplomes delivres parl’USTL, . . .).Voici un exemple de valeur d’une relation :
NumCarte Nom Note
(122.678.555, Toto, 12)
(123.678.555, Truc, 10)
(213.678.555, Bidule, 15)
qui est bien un sous-ensemble du produit cartesien : Entier × Chaıne × Entier.Chaque ligne de la relation est un n-uplet1 dont l’ordre des attributs est fixe par le schema. Dansl’exemple, la premiere valeur de chaque n-uplet est le numero de carte d’un etudiant, la deuxieme sonnom, la troisieme sa note. Chaque n-uplet represente un etudiant.Une variable relationnelle contient une valeur de relation, la variable et la valeur ont evidemment avoirle meme schema de relation. L’exemple precedent pourrait etre designe par la variable relationnellep2006.On pourrait faire une analogie avec les langages de programmation : un schema relationnel ressemblea un type de donnee (Natural en Ada, boolean en Java) une variable relationnelle ressemble a unevariable qui est d’un type fixe lors de sa declaration (N : Natural en Ada, boolean found en Java).
2.2.1 Schema ou intention d’une relation
Par exemple voici la relation Ville :
schema : Ville (Id : Entier, Nom : Chaine, Departement : 1..100, Population : Naturel)
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 cree la table Ville dont le schema, decrit entre les parentheses, est compose dequatre attributs et comporte aussi des contraintes permettant de garantir les proprietes :– constraint Ville_PK primary key (id) garantit que deux lignes de Ville auront toujours une
valeur definie et differente pour la colonne id. De facon plus consise on dit que id est la clef primairede Ville. La tentative d’ajouter dans la table Ville une ville dont id existe deja dans une ligne deVille echouera et la valeur de Ville sera inchangee.
1Ici on a affaire a des 3-uplet.
2.2. NOTION CENTRALE : SCHEMA ET VALEUR D’UNE RELATION 9
– constraint Ville_Dpt_Intervalle check (departement between 1 and 100) garantit que quela colonne departement aura une valeur comprise entre 1 et 100 si elle est definie. La tentatived’ajouter dans la table Ville une ville dont departement vaut 105 echouera et la valeur de Ville
sera inchangee.– constraint Ville_Pop_Val check (0 <= population) garantit que que la colonne population
aura une valeur positive ou nulle quand elle est definie : la tentative d’ajouter dans la table Ville
une ville a population negative echouera et la valeur de Ville sera inchangee.
Une table SQL ressemble a une variable relationnelle mais avec quelques differences dont la premiereest importante :
– la valeur d’une variable relationnelle ne peut pas comporter plusieurs fois le meme n-uplet alorsqu’une table — sauf si on pose explicitement une contrainte de clef primaire — peut comporterplusieurs lignes identiques,
– un element d’une relation s’appelle un n-uplet, alors qu’un element 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 indefinie et cela se testeavec l’operateur booleen is null. En revanche cela n’aurait pas de sens pour une relation car celacorrespondrait a un n-uplet auquel il manque un attribut, ce qui n’aurait pas de sens en theorie.
2.2.2 Contenu ou instance ou extension d’une relation
L’extension d’une relation est un sous-ensemble du produit cartesien D1 ×D2 × . . .×Dk.
Les membres (ou elements) d’une relation sont appeles nuplets (k-uplets).
SQL
Plusieurs facons d’ajouter une ville dans la table Ville en Oracle 10 :
– insert into Ville values (1, ’Lille’, 59, 222400) ;
Dans cette forme on doit donner une valeur a chaque colonne dans l’ordre dans lequel sont declareesles 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 a initialiser on peut n’en donner qu’un sous-ensemble, les colonnesnon mentionnees seront indefinies (is null).
2.2.3 Schema et extension
Souvent on represente par un seul tableau a la fois le schema et une instance possible de la relation :
Id Nom Departement Population
1 Lille 59 222.4007 Dunkerque 59 222.4002 Paris 75 2.200.0005 Paris-Texas
12 Lyon 69 420.000
Les colonnes blanches ou vides de Paris-Texas correspondent a des colonnes indefinies.
Q. 1 Combien d’elements ou lignes contient le produit cartesien du tableau precedent avec lui-meme ?
10 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
2.3 Clef d’une relation
Une clef candidate C d’une relation R est un sous-ensemble minimal d’attributs de R qui determinentles autres attributs de R, c’est a dire que pour une valeur donnee de C, les autres attributs ont exac-tement une valeur.
Par exemple le numero de carte d’etudiant determine le nom de l’etudiant et certainement d’autresinformations.
Autrement, dit une valeur de C apparaıt au plus une fois dans toute extension de R.
Une relation peut posseder plusieurs clefs candidates, on en choisira une qu’on appellera clef primaire.
Par exemple : Etudiant (num_carte, num_insee, nom, prenom, datenaiss) pourrait possederdeux clefs candidates : (num_carte) qui doit etre different pour chaque etudiant et (num_insee) quiidentifie la naissance d’une personne et est censee etre unique pour chaque personne nee en France.
On peut choisir (num_carte) comme clef primaire.
Q. 2 Quel probleme se poserait si on choisissait (num carte, nom) comme clef primaire d’un etudiant ?
En SQL, la clef primaire fait l’objet d’une contrainte primary key, les autres clefs candidates peuventfaire l’objet d’une contrainte d’unicite (unique).
En Oracle ainsi qu’en PostgreSQL, aucune des colonnes d’une clef primaire ne peut etre indefinie (isnull).
2.4 Clef etrangere
Une clef etrangere est constituee d’une ou plusieurs colonnes et permet de designer au plus une ligned’une autre table ou de la meme table.
Une clef etrangere peut etre interpretee comme un pointeur associatif vers une ligne d’une autre tableou de la meme table. Les colonnes de l’autre table correspondant a celles de la clef etrangere doiventetre la clef primaire complete de cette table ou constituer completement les colonnes d’une contrainted’unicite.
Associatif signifie que pour retrouver la ligne referencee on recherche dans l’autre table la ligne dont lescolonnes de la clef primaire ou de la contrainte d’unicite sont egales a celles de la ligne referencante (celapeut heureusement se faire efficacement grace aux index associees aux clefs primaires et contraintesd’unicite).
Par exemple une fete reference la ville dans laquelle elle se passe en mentionnant en tant que clefetrangere le numero de departement et le nom de la ville dans ce departement (deux villes de deuxdepartements differents pouvant porter le meme nom) :
create table Ville ( create table Fete (
departement Number (3), <--- departement Number (3),
nom Varchar (20), <--- nom Varchar (20),
primary key (departement, nom) id Number (10),
) jour Date,
primary key (id),
foreign key (departement, nom)
-- | |
-- V V
references Ville (departement, nom)
)
L’ordre des colonnes est bien entendu important dans la declaration de la contrainte foreign key.
2.5. L’ALGEBRE RELATIONNELLE ET LE LANGAGE DE REQUETE SQL 11
Une clef etrangere comportant une colonne indefinie ne designe aucune ligne, sinon le SGBD (Oracle,PostgreSQL et MySQL avec InnoDB) garantit que la ligne designee existe, sinon l’ordre echoue.
Par defaut, une ligne referencee par une clef etrangere ne peut pas etre detruite, d’autres comporte-ments peuvent etre specifies grace a des options de declaration de clef etrangere, par exemple si uneligne referencee est detruite on peut demander que les lignes referencantes le soient aussi.
2.5 L’algebre relationnelle et le langage de requete SQL
2.5.1 Preliminaire : l’identite
En notation relationnelle, il suffit de mentionner le nom de la relation, par exemple R, et on a alorsacces implicitement a sa valeur (son extension), exactement comme lorsqu’on mentionne la variable xdans une expression arithmetique.
En SQL il faut par contre ecrire la requete suivante pour exprimer le contenu d’une table :
select * from Ville ;
Tous les nuplets de la table Ville sont alors affiches.
Ou, si on veut garantir l’unicite de chaque nuplet affiche :
select distinct * from Ville ;
Q. 3 Si Ville possede une clef primaire, le distinct est-il utile dans la requete precedente ?
2.5.2 Les operateurs de base
La projection : SELECT
Pour ne conserver que certaines colonnes.
ΠAp1,...,Apk
(R) = {(xp1, . . . , xpk
) | ∃(y1, . . . , yn) ∈ R,xpi= ypi
∀i ∈ [1, k]}
Par exemple :
ΠDpt,Population
Id Nom Dpt Population
1 Lille 59 222.40021 Gruson 59 5.0007 Dunkerque 59 222.4002 Paris 75 2.200.0005 Paris-Texas
12 Lyon 69 420.000
=
Dpt Population
59 222.40059 5.00075 2.200.000
69 420.000
Remarquer l’unicite des n-uplets du resultat.
En SQL, c’est la clause select de la requete qui exprime la projection. Le qualificatif distinct permetd’obtenir l’unicite des lignes du resultat (distinct porte sur toutes les colonnes de la projection) :
select distinct v.Departement, v.Population from Ville v ;
Si on ne met pas distinct, les doublons eventuels sont conserves :
select v.Departement, v.Population from Ville v ;
12 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
La restriction : WHERE
Pour ne conserver que les nuplets verifiant le predicat P .
σP (R) = {(x1, . . . , xk) | (x1, . . . , xk) ∈ R ∧ P (x1, . . . , xk)}
Par exemple, on veut les villes du nord :
σDpt=59
Id Nom Dpt Population
1 Lille 59 222.4007 Dunkerque 59 222.4002 Paris 75 2.200.0005 Paris-Texas
12 Lyon 69 420.000
=
Id Nom Dpt Population
1 Lille 59 222.4007 Dunkerque 59 222.400
En SQL, c’est la clause where de la requete qui exprime la restriction :
select * from Ville v
where v.Departement = 59 ; -- predicat de la restriction
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 meme schema.
R ∪ S = {(x1, . . . , xk) | (x1, . . . , xk) ∈ R ∨ (x1, . . . , xk) ∈ S}
Une requete select peut etre utilisee comme une table, on peut donc avoir des emboıtements derequetes.
1. La requete ensembliste (sans doublons) :
select nom, ’Etudiant’ as categorie from Etudiant
Union
select nom, ’Enseignant’ as categorie from Enseignant ;
2. ou, si on souhaite conserver les boublons :
select nom, ’Etudiant’ as categorie from Etudiant
Union All
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 a conditionde remplacer la clause values par une requete, par exemple :
create table Ville_Du_Nord (
id Number (5),
nom Varchar2 (50),
constraint Ville_Du_Nord_PK primary key (id)
) ;
insert into Ville_Du_Nord
select v.id, v.nom from Ville v where v.departement = 59 ;
Q. 4 Utiliser cette technique pour eviter d’utiliser l’operateur d’union dans les requetes 1 et 2.
2.5. L’ALGEBRE RELATIONNELLE ET LE LANGAGE DE REQUETE SQL 13
La difference : MINUS
R et S sont deux relations de meme schema.
R− S = {(x1, . . . , xk) | (x1, . . . , xk) ∈ R ∧ (x1, . . . , xk) 6∈ S}
Les villes dont le departement est connu :
select * from Ville MINUS select * from Ville where Departement is null ;
Q. 5 Ecrire plus simplement la requete precedente.
Nouveau jeu de donnees (figure 2.1)
Fig. 2.1 – Un exemple de valeur de table avec deux clefs etrangeres etu et mat dans la table Note.
Table Etudiant
nom id
Alfred 1Marc 2Julie 3
Table Note
Etudiant.id←�� ��etu note
�� ��mat →Matiere.id
1 12 11 14 23 15 2
Table Matiere
id nom coeff
1 BD 32 CL 5
Le produit cartesien : CROSS JOIN
Le produit cartesien est une fonction binaire dont les deux operandes sont des ensembles quelconqueset la valeur est l’ensemble des couples formes d’un element du premier operande et d’un element dusecond operande. Exemple : {b, f} × {e, i, o} = {(b, e), (b, i), (b, o), (f, e), (f, i), (f, o)}.Dans un couple (ou 2-uplet) l’ordre des elements est important : (b, e) 6= (e, b).Autre exemple : le produit cartesien de l’ensemble des etudiants de licence GMI avec l’ensemble desUE de licence GMI.
R× S = {(r1, . . . , rkr, s1, . . . , sks
) | (r1, . . . , rkr) ∈ R ∧ (s1, . . . , sks
) ∈ S}
Tous les couples etudiant, matiere (Oracle10, Postgres, SQL92) :
select *
from Etudiant etu
cross Join Matiere mat ;
on obtient 3× 2 nuplets.En Oracle 8 on devait ecrire (et en general en SQL on peut ecrire) le produit cartesien comme ceci :
-- Oracle8, Postgres, SQL92
select *
from Etudiant, Matiere ;
Si on ne veut afficher que la partie Etudiant de chaque element du produit cartesien, on peut prefixer* 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 requetes suivantes ont-elle la meme valeur, sous quelle conditionont-elle des valeurs differentes ?
14 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
select * from Etudiant ;
select distinct etu.*
from Etudiant etu cross join Matiere mat ;
2.5.3 Quelques operateurs supplementaires
Ils peuvent s’exprimer grace aux operateurs de base vus precedemment et ne sont donc theoriquementpas insdispensables, mais ils sont tellement pratiques qu’a la fois le relationnel et SQL leur attribuentune identite.
La jointure, produit cartesien et restriction : ... INNER JOIN ... ON <condition>
Elle permet de ne conserver que les elements pertinents d’un produit cartesien.
R ⊲⊳P S = σP (R× S)
ou P exprime la condition de conservation d’un element du produit cartesien.
Par exemple les couples (etudiant, matiere) si l’etudiant a une note dans cette matiere en se basantsur les contenu des tables de la figure 2.1 page 13 :
select e.nom as etudiant, m.nom as matiere
from Etudiant e
cross join Note n
cross join Matiere m
where e.id = n.etu
and n.mat = m.id ;
SQL2, PostgreSQL et Oracle 10 (et d’autres bien entendu) disposent d’un operateur de jointurespecifique <table> inner join <table> on <condition>. La requete precedente peut alors etrereecrite 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
Q. 7 Pourquoi Marc n’apparaıt-il pas dans le resultat ?
Le mot clef inner permet de distinguer cette jointure de la jointure dite externe (voir la section 2.12page 30) qui, elle, utilise le mot clef outer.
En Oracle 8 on devait ecrire :
select e.nom as etudiant, m.nom as matiere
from Etudiant e, Note n, Matiere m -- 1) produit cartesien
where e.id = n.etu -- 2) condition de
and n.mat = m.id ; -- la jointure
Q. 8 Que vaut la requete suivante ? Marc apparaıt-il ?
select e.nom, n.note
from Etudiant e inner join Note n on e.id != n.etu ;
2.5. L’ALGEBRE RELATIONNELLE ET LE LANGAGE DE REQUETE SQL 15
L’operateur != signifie different et peut aussi se noter <>.
On distingue plusieurs cas particuliers de jointures
Equi-jointure Egalite entre colonnes : c’est probablement la plus courante, tres souvent on testel’egalite entre la clef etrangere d’une table et la clef primaire d’une autre table. L’exemple precedentest une equi-jointure.
Jointure naturelle : attention danger Equi-jointure de R et S sur les colonnes de memes noms.En SQL92 et PostgreSQL on ajoute le mot clef natural.
La jointure naturelle est particulierement dangereuse : supposons une application qui utilise la jointurenaturelle entre deux tables T1 et T2. Si, plus tard, on ajoute a T1 et a T2 une colonne homonyme et dememe type alors ces deux colonnes participeront automatiquement a cette jointure naturelle, ce quin’est pas forcement ce que souhaite celui qui ajoute ces colonnes.
Auto-jointure Jointure d’une relation avec elle-meme. Par exemple, les employes qui sont chef d’aumoins un autre employe :
select distinct chef.*
from Employe emp
inner join Employe chef on chef.id = emp.mon_chef ; -- equi-jointure
Non equi-jointure Le predicat de la clause on d’une jointure n’est pas forcement une egalite :toute condition peut convenir.
Grace a l’ordre alter, on ajoute l’attribut sexe aux etudiants :
alter table Etudiant
add sexe Varchar2 (1)
default ’M’ -- valeur par defaut (discutable !)
check (sexe in (’M’, ’F’)) -- les 2 valeurs possibles
not null ; -- ne peut etre indefini
update Etudiant
set sexe = ’F’
where id = 3 ;
Q. 9 Ecrire la requete qui donne tous les binomes mixtes d’etudiant et sans redondance : si on obtientle binome (Alfred, Julie) on ne doit pas obtenir aussi le binome (Julie, Alfred).
Un autre exemple : on a une table F contenant des couples (x, y) d’une fonction y = f(x) definie surles entiers. On veut une requete contenant 0 lignes si la fonction stockee dans F est croissante (pourtout couple de lignes (x1, y1), (x2, y2) verifiant x1 < x2 on a f(x1) > f(x2)) et contenant au mois uneligne si elle est decroissante.
Q. 10 Pourquoi est-il logique que x soit la clef primaire de F ?
Q. 11 Ecrire cette requete.
Q. 12 En utilisant la fonction count (voir section 2.8 page 21) modifier la requete precedente pourqu’elle valle une seule ligne d’une colonne contenant le nombre de couple de lignes decroissant.
L’intersection
R et S sont deux relations de meme schema.R∩S = {(x1, . . . , xk) | (x1, . . . , xk) ∈ R∧(x1, . . . , xk) ∈ S}
Oracle ne propose pas d’operateur d’intersec-tion, mais on peut la realiser grace a l’egalite : R ∩ S = R− (R− S)
16 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
La division
Le schema de R englobe strictement celui de S, c’est a dire que R comporte toutes les colonnes de S(memes 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 meme valeur en CR et comportant toutes les lignes de S dans lescolonnes S.
R÷ S = {ΠCR(x)|x ∈ R ∧ S ⊆ ΠS(σΠCR
(x)=ΠCR(y∈R)(R))}
Autrement dit ΠCR(x) appartient a la division si les lignes de R ayant ces valeurs couvrent toutes les
lignes de S.
Par exemple :
A B C D
a b c da b e fb c e fe d c de d e fa b d e
÷C D
c de f
=
A B
a be d
La division peut s’exprimer grace aux autres operateurs :
R÷ S = ΠA,B(R)−ΠA,B(ΠA,B(R)× S −R) (2.1)
En effet, ΠA,B(ΠA,BR× S −R) sont les nuplets qui n’appartiennent pas a la division.
Par exemple l’ensemble des etudiants qui sont inscrits a toutes les UE peut etre calcule en divisantla jointure des etudiants avec leurs inscriptions par la table UE projetee sur sa colonne id. Maiscomme SQL ne dispose pas d’operateur de division, on est oblige de s’y prendre autrement en utilisantl’egalite (2.1).
select e.id, e.nom
from Etudiant e
Minus
select id_etu, nom_etu
from (-- La relation totale : Tous les couples etudiant, 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 a diviser est obtenue par une jointure.
-- Les couples etudiant, UE si l’etudiant 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 manieres plus simples d’obtenir le meme resultat, mais elles utilisent des fonctionsd’agregation (ici la fonction count(), voir 2.8 page 21) et eventuellement la partition des nuplets engroupes (group by, voir 2.11 page 27) qui ne font pas partie de l’algebre relationnelle :
Utiliser count() pour compter le nombre d’UE et le nombre d’inscriptions d’un etudiant :
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 INDEFINIES 17
On peut esperer que le calcul du nombre total d’UE nb_UE ne sera fait qu’une seule fois car lasous-requete qui fait ce calcul ne depend pas de la requete englobante, on dit que cette sous-requete est close ou autonome.
Attention : cette technique ne marche que si les tables disposent des contraintes necessaires :
create table Etudiant (
id Number (5) primary key,
nom varchar2 (20)
) ;
create table UE (
id Number (5) primary key,
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 definies et donc les co-lonnes clefs etrangeres de Inscrit sont forcement definies.
Creer un groupe par etudiant 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 represente une condition de conservation d’un groupe. Ici un groupe correspondaux concatenations d’une ligne etudiant avec chaque ligne d’inscription le concernant ainsi quele nombre total d’UE. Cette condition porte sur chaque groupe (ou etudiant) separement, ainsil’expression count (*) represente le nombre d’inscriptions d’un meme etudiant.
Pour resumer : la condition du where porte sur chaque ligne produite par la clause from et lacondition du having porte sur chaque groupe construit par le group by.
2.6 Le cas des valeurs indefinies
Dans la pratique il est souhaitable de pouvoir memoriser une nouvelle ligne dans une table, meme sicertaines colonnes ne peuvent etre renseignees du fait qu’on n’a pas forcement toute l’information.
Par exemple je veux quand meme pouvoir enregistrer un nouveau client meme si je ne connais pas sonnumero de telephone. Par exemple voici deux ordres equivalents qui ne renseignent pas le telephoned’un nouveau client :
Insert into Client (id, nom, tel) values (13, ’Tartampion’, null) ;
Insert into Client (id, nom) values (13, ’Tartampion’) ;
Et une maniere d’enregistrer le fait qu’on ne connaıt plus le nouveau numero du client 15 :
update Client set tel = null where id = 15 ;
La colonne telephone sera alors dite indefinie : elle n’a pas de valeur. On pourra tester si une colonne(et plus generalement une expression) est definie ou non avec le predicat booleen is [not] null :–
�� ��<expr> is null vrai ssi <expr> est indefinie, faux ssi <expr> est definie.
–�� ��<expr> is not null est equivalent a
�� ��not (<expr> is null)
Par exemple, les villes dont on ne connaıt ni la population ni le departement :
select v.nom
from Ville v
18 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
where v.population is null and v.departement is null ;
Paris-Texas
Q. 13 Quelle ambiguıte 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 departement du Nord ou dont le departement n’est pasrenseigne.
SQL permet qu’une colonne soit is null a condition qu’elle ne soit assujettie ni a la contrainte notnull ni a primary key.
2.6.1 Noter l’absence de valeur
Un operande n’ayant pas de valeur peut se noter explicitement avec le mot clef null, par exemplenull + 5.
Attention : ne pas interpreter ce null comme le pointeur null des langages de programmation nicomme le zero des entiers !
2.6.2 Comportement des operateurs et des fonctions a valeur non booleenne
La plupart des operateurs et des fonctions a valeur autre que booleenne sont indefinis si un de leursoperandes est indefini. Par exemple :
a b a + b
1 2 3is null 2 is null
0 0 00 is null is null
is null is null is null
Par exemple :�� ��(1 + n.note) is null ⇔�� ��n.note is null
2.6.3 Comportement des operateurs relationnels
Les operateurs relationnels (=, <, ... et x between a and b) sont a valeur booleenne. Quand unde leurs operandes est indefini, il ont vraiment une valeur qui est appelee unknown (je ne sais pas).
a b a = b, a != b, a <= b, ...
is not null is not null vrai ou fauxAu moins un des deux is null unknown
Par exemple, quel que soit l’etat de la colonne nom, les expressions null=null et nom!=null valentnecessairement unknown.
2.6.4 Comportement des operateurs logiques
Les operateurs logiques (not, or et and) travaillent donc en logique tri-valuee, c’est a dire que leursoperandes ont des valeurs prises dans un ensemble de trois valeurs : {vrai, faux, unknown}.
Quand aucun des operandes n’est unknown on a affaire a la logique binaire habituelle. Precisons cequi se passe quand un des operandes vaut unknown :
not vaut evidemment unknown.
and vaut faux si l’autre operande vaut faux, sinon unknown.
2.7. QUELQUES OPERATEURS ET FONCTIONS SCALAIRES DE SQL/ORACLE 19
or vaut vrai si l’autre operande vaut vrai, sinon unknown.
a b not b a and b a or b
unknown unknown unknown unknown unknownunknown faux vrai faux unknownunknown vrai faux unknown vrai
Q. 15 Que donnerait le ou exclusif xor qui n’existe pas en Oracle et en PostgreSQL ?
Q. 16 Donner une definition du predicat x between a and b en utilisant uniquement les operateurs<= et and.
Q. 17 Que donnerait l’operateur a between b and c si un de ses operandes est indefini ?
Q. 18 Definir le comportement que devrait avoir l’operateur ou exclusif (qui n’existe ni en Oracle nien PostgreSQL !).
2.6.5 Presomption d’innocence de la clause where
La clause where peut apparaıtre dans une requete (select) mais aussi dans une mise a jour de lignes(update) ou une suppression de lignes (delete).
Si la condition d’une clause where s’evalue a false ou unknown alors le nuplet correspondant n’estpas traite.Par exemple, pour le delete, l’idee est qu’on ne veut pas detruire un nuplet si on ne sait pas s’il verifiela condition de suppression (presomption d’innocence).
Q. 19 La requete suivante, censee lister les clients dont le nom n’est pas defini, est incorrecte, pour-quoi ? En donner une version correcte.
select * from Client c where c.nom = NULL ;
2.7 Quelques operateurs et fonctions scalaires de SQL/Oracle
2.7.1 between a and b
Les expressions a et b peuvent etre des nombres, des chaınes, des dates, tout type disposant d’unordre.
v.population between 1000 and 15000
v.nom between ’b’ and ’e’
2.7.2 Expression conditionnelle : case
case
when <predicat1> then valeur1
[ when <predicat2> then valeur2
...
when <predicatN> then valeurN ]
[ else valeurDefaut ]
end
Le premier predicat qui vaut vrai donne sa valeur au case, si aucun predicat ne vaut vrai c’est lavaleur par defaut du else et s’il n’y a pas de else la valeur est indefinie (is null).
select v.nom as nom,
case
when v.population >= 100000 then ’Grande Ville’
when v.population < 100000 then ’Petite Ville’
else ’Je ne sais pas : la population est indefinie !’
20 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
end as categorie
from Ville v ;
Q. 20 Donner une autre formulation equivalente au case precedent qui utilise le predicat is null.
2.7.3 Les fonctions nvl et coalesce
La fonction nvl a deux parametres et vaut la valeur du premier parametre s’il est defini (is not null),sinon elle vaut celle du second parametre.
select ’Bonjour ’ || nvl (upper (e.nom), ’Anonyme’)
from Etudiant e ;
La fonction coalesce, a au moins un parametre et vaut la premiere valeur definie en partant de lagauche et est indefinie si tous ses parametres le sont2.
Q. 21 Ecrire l’equivalent de nvl (upper (e.nom), ’Anonyme’) en utilisant l’operateur case.
Q. 22 Ecrire l’equivalent de coalesce (a, b, c) en utilisant l’operateur nvl.
2.7.4 Manipuler les chaınes
Les fonctions de chaıne (upper, lower)
Attention : Oracle confond les notions de chaıne vide (de longueur nulle) et de chaıne indefinie (unechaıne indefinie se comporte a peu pres comme une chaıne vide) ! Ce defaut devrait disparaıtre dansles versions futures. PostgreSQL n’a pas ce defaut !
Concatenation : || et reconnaissance de modele : like
Je ne dis bonjour qu’aux etudiants dont le nom contient un r qui n’est pas la derniere lettre :
select ’Bonjour ’ || e.nom from Etudiant e
where e.nom like ’%r_%’ ;
Bonjour Alfred
Bonjour Marc
Bonjour rene
Dans le modele de like :
– % correspond a un nombre quelconque de n’importe quel caracteres (eventuellement nul).– _ correspond a exactement un caractere quelconque.
Par exemple ’Alfred’ like ’%r_%’ est vrai et ’mer’ like ’%r_%’ est faux.
Q. 23 Ecrire le modele qui reconnaıt toute chaıne contenant un caractere x qui n’est ni le premier,ni le dernier de la chaıne.
Q. 24 Ecrire le modele qui reconnaıt toute chaıne contenant deux caracteres x separes par au moinsdeux caracteres.
Q. 25 Comment reconnaıtre les chaınes qui ont un caractere x en premiere et/ou en derniere position ?
Attention Note de la documentation Oracle 10 :
Oracle Database currently treats a character value with a length of zero as null. However,this may not continue to be true in future releases, and Oracle recommends that you donot treat empty strings the same as nulls.
2Postgres propose aussi la fonction coalesce avec la meme signification.
2.8. LES FONCTIONS D’AGREGATION COUNT, SUM, AVG, MIN, MAX 21
Mais comment distinguer entre la chaıne vide et le fait qu’une expression de type chaıne est indefiniepuisqu’Oracle lui-meme confond les deux ? ? ? ? ?
Toujours a propos des chaınes vides (et non pas indefinies) :
Although Oracle treats zero-length character strings as nulls, concatenating a zero-lengthcharacter string with another operand always results in the other operand, so null can resultonly from the concatenation of two null strings. However, this may not continue to be truein future versions of Oracle Database. To concatenate an expression that might benull, use the NVL function to explicitly convert the expression to a zero-lengthstring.
Autrement, bien qu’actuellement (version Oracle 10) on ait les egalites suivantes :
mon commentaire
’’ is null = vrai n’importe quoi ! on devrait avoir faux’’ || ’toto’ = ’toto’ c’est coherentnull || ’toto’ = ’toto’ n’importe quoi ! on devrait avoir indefininull = ’’ = unknown c’est coherent’’ = ’’ = unknown n’importe quoi ! on devrait avoir vrai
Oracle annonce que bientot il appliquera la norme, c’est a dire que la chaıne vide sera considereecomme definie. Pour garantir la portabilite du code il recommande d’utiliser systematiquement lafonction nvl() lors des concatenations :’Nom du client : ’ || nvl (client.nom, ’’).
En revanche PostgreSQL est parfaitement coherent sur la notion de chaıne vide qui est bien entenduparfaitement definie.
2.8 Les fonctions d’agregation count, sum, avg, min, max
Ces fonctions effectuent un calcul synthetique sur l’ensemble des nuplets fournis a la projection (clauseselect).
Par exemple sum calcule la somme des valeurs definies que prend son expression pour chacun desnuplets et min en calcule la plus petite.
Une requete dont la clause select comporte de telles fonctions dans ses expressions de projectionfournit exactement une ligne (sauf si la requete est munie d’une clause group by, voir la section 2.11).
sum, avg, min et max donnent un resultat indefini si l’expression argument n’est jamais definie,c’est en particulier le cas quand aucun nuplet n’est selectionne.
En revanche count, qui compte le nombre de fois que son expression a une valeur definie, a toujoursune valeur definie (eventuellement la valeur zero).
Par exemple count (e.id) donne le nombre de fois que l’attribut e.id est defini. Formes speciales :
– count (*) renvoie le nombre total de nuplets fournis.– count (distinct <expression>) nombre de valeurs differentes et definies que prend l’expression.
Q. 26 Donner d’autres formes de count (*) qui soient equivalentes.
Enfin, on ne peut pas demander a la clause select de fournir a la fois une information synthetique(exactement un nuplet) et une information individuelle (0, 1 ou plusieurs nuplets). Donc, des qu’unefonction d’agregation apparaıt dans la clause select, un nom de colonne ne peut apparaıtre que dansune expression argument d’une fonction d’agregation.
La requete 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 MODELE RELATIONNEL ET SQL
Differentesvaleurs dela table T
n c n*cn c n*c
? ? ?? ? ?
n c n*c
1 ? ?? 2 ?4 ? ?
n c n*c
? 1 ?3 2 64 3 123 ? ?
<expr>
Sum (n) ? ? 5 10Sum (2) ? 4 6 8
Sum (n*c) ? ? ? 18Max (n) ? ? 4 4
Max (15) ? 15 15 15
Count (n) 0 0 2 3Count (distinct n) 0 0 2 2
Count (*) 0 2 3 4
Fig. 2.2 – Un exemple ou on evalue la requete select <expr> from T pour differentesvaleurs de la table T. Un ? signifie que la valeur est indefinie (is null). La colonne n*cmontre que le produit d’un entier par un indefini est indefini. La premiere collection meten evidence la specificite de count par rapport aux autres fonctions d’agregation.
max (n.note - 5 + 5) as meilleure_note
from Note n ;
2 13.66 13.66 15
Et voici un exemple incorrect car il melange information synthetique et information individuelle :
select e.nom as nom
count (*) as nb_etudiants,
from Etudiant e ;
-- erreur Oracle
Le tableau suivant resume les differentes fonctions d’agregation count, sum, avg, min, max
fonction valeur si expr est toujoursindefinie ou queaucune ligne nelui est fournie
sum (expr) somme des valeurs definies de expr is nullavg (expr) moyenne des valeurs definies de expr is nullmin (expr) min des valeurs definies de expr is nullmax (expr) max des valeurs definies de expr is null
count (expr) nombre de valeurs definies de expr 0count (distinct expr) nombre de valeurs definies et differentes de expr 0
count (*) nombre de lignes 0 si aucune lignecount (1+2) nombre de lignes 0 si aucune lignecount (’abc’) nombre de lignes 0 si aucune ligne
Q. 27 Quel est le resultat de select count (distinct 1+5) from T pour chaque valeur de T de lafigure 2.2 page 22 ?
Q. 28 Evaluer l’expression Sum (n*c)/Sum (c) pour les valeurs de la figure 2.2 page 22. Si oninterprete n comme une note et c comme un coefficient, en quoi et pour quelle(s) collection(s) leresultat est-il incorrect, corriger l’expression en consequence.
2.8. LES FONCTIONS D’AGREGATION COUNT, SUM, AVG, MIN, MAX 23
Q. 29 Parmi les expressions de la figure 2.3 page 23, regrouper celles qui ont exactement le memecomportement (vous devriez obtenir 7 groupes).
count (*) count (e.nom) count (55 + 2*3.14)
sum (1) count (’coucou’) sum (e.note) / count (*)
count (e.id) count (upper (e.nom)) sum (case when e.nom is null then 0 else 1 end)
avg (e.note) count (e.nom is null) sum (e.note) / count (e.note)
Fig. 2.3 – Expressions a classer.
2.8.1 Evaluation d’une requete synthetique
Une requete synthetique produit toujours exactement une ligne (meme si le from where ne produitaucune ligne) en utilisant les fonctions d’agregation dans sa clause select.On veut calculer la moyenne ponderee par les coefficients de matiere de l’etudiant Alfred. Voici larequete et, conceptuellement, comment elle va etre evaluee (il est tres probable qu’un vrai moteurSQL ne fera pas l’evaluation de cette maniere) :
select Sum (n.note*m.coeff) /
Sum (case when n.note is null then 0 else m.coeff end) as moy_alfred
from Etudiant e
inner join Note n on e.id = n.etu
inner join Matiere m on n.mat = m.id
where e.nom = ’Alfred’ ;
--
-- 1) resultat de la jointure et de la restriction where :
--
NOTE| COEFF
--------|------
12| 3
14| 5
--
-- 2) calcul des expressions en argument des fonctions d’agregation :
--
N.NOTE*M.COEFF| CASE ...
--------------|------
36| 3
70| 5
--
-- 3) Calcul les sommes de chacune des deux colonnes :
--
SUM(N.NOTE*M.COEFF)|SUM(CASEWHENN.NOTEISNULLTHEN0ELSEM.COEFFEND)
-------------------|----------------------------------------
106| 8
--
-- 4) Enfin calcul de la moyenne d’Alfred (la division) :
--
MOY_ALFRED
----------
13.25
Les expressions arguments des fonctions d’agregation sont donc evaluees separement pour chaque nu-plet et les expressions externes aux fonctions d’agregation sont calculees en dernier.
24 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
Pour avoir cette meme moyenne pour chaque etudiant, il faudra utiliser la clause group by, voir lasection 2.11.
2.9 Les sous-requetes
2.9.1 sous-requete dans la clause from
Dans la clause from on peut ecrire un select entre parentheses a la place du nom d’une table. Parexemple : les villes dont la population est superieure ou egale a la moyenne des populations :
select v.nom
from Ville v
cross join (select AVG (v.population) as moyenne from Ville v) population
where v.population >= population.moyenne ;
Ou encore, les villes dont la population est superieure ou egale a la population moyenne par ville deleur departement :
select v.nom
from Ville v
inner join (select AVG (v.population) as moyenne,
v.departement as departement
from Ville v
group by v.departement) pop_par_dpt
on v.departement = pop_par_dpt.departement
where v.population >= pop_par_dpt.moyenne ;
Remarquer que la sous-requete calculant la moyenne de population par departement est close (auto-nome) : elle ne depend en rien de la requete englobante.
Une clause on ne peut mentionner que des alias de tables deja declares.
Une sous-requete dans la clause from ne peut pas mentionner des colonnes appartenant aux tablescette clause from : elle doit etre close ou autonome (idem en PostgreSQL). Autrement dit : une sous-requete dans une clause from ne peut pas etre correlee (ou dependante) avec une table ou une autresous-requete de la meme clause from.
L’exemple suivant est refuse par Oracle car la sous-requete 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
2.9.2 sous-requetes dans les clauses where et select
En general un operande dans une expression peut etre une sous-requete entre parentheses.
Si cette sous-requete produit :
– exactement une ligne d’une colonne, elle peut etre employee avec un operateur scalaire correspondantau type de la valeur.
2.9. LES SOUS-REQUETES 25
– un nombre quelconque de nuplets, elle devra etre utilisee avec un operateur ensembliste approprie(any, all, in, exists)
Dans where et select une sous-requete peut etre correlee si elle mentionne des colonnes appartenanta des tables de la clause from de la requete englobante.
sous-requete close, autonome ou non correlee
C’est une sous-requete qui ne depend pas du nuplet courant de la requete englobante, une sous-requete non correlee donnera donc toujours le meme resultat, l’optimiseur peut s’en rendre compte etne l’evaluer qu’une seule fois.Par exemple : les villes dont la population est superieure ou egale a la moyenne :
select v.nom from Ville v
where v.population >= (select AVG (v.population) from Ville v) ;
sous-requete correlee
Le resultat d’une sous-requete correlee depend du nuplet courant de la requete principale car ellementionne des colonnes de ce nuplet.
Par exemple les villes dont la population est superieure ou egale a la moyenne de leur departement :
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 matiere, nom d’un etudiant ayant la meilleure note dans cette matiere avecles deux techniques : sous-requete dans la clause from et sous-requete dans la condition. On a troistables : Etudiant, Note et Matiere.
2.9.3 Factorisation des sous-requetes non correlees
La clause with permet de factoriser une fois pour toutes les sous-requetes non correlees et de lesbaptiser avant d’ecrire la requete principale.
En voici la syntaxe :
with <query-name> as ( <subquery> ) { , <query-name> as ( <subquery> ) }
select ... ;
Une sous-requete factorisee peut mentionner les noms des sous-requetes factorisees qui la precedent.La requete principale peut evidemment utiliser tous les noms des sous-requetes factorisees.Interet : simplifier des requetes complexes contenant des sous-requetes non correlees.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 MODELE 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. 31 Combien de fois la sous-requete Dept Costs est-elle utilisee ?
Q. 32 Que calcule cette requete ?
Q. 33 Reecrire la requete principale precedente en utilisant une jointure.
Il est parfois possible de decorreler une sous-requete puis d’utiliser une clause with. Par exemple,soit :
select *
from Etudiant e
where not exists (
select *
from Matiere m
where not exists (
select * from Note n where n.etu = e.id
)
)
Q. 34 Quelle est la sous-requete correlee et en quoi l’est-elle ?
Q. 35 Pourquoi un etudiant ne peut-il avoir plus d’une note pour une matiere ? (voir la figure 2.1page 13)
Q. 36 Reecrire cette requete en evitant que la sous-requete soit correlee. Suggestion : deplacer lessous-requetes dans la clause from principale et utiliser un comptage.
Q. 37 En utilisant une clause with pour factoriser la sous-requete non correlee, donner deux requetesdifferentes qui calculent la meme chose.
2.9.4 Les operateurs/fonctions ensemblistes sur resultat d’une requete emboıtee
–�� ��<expr> < ALL (select ...) vrai si <expr> est strictement inferieure a toutes les valeurs pro-duites par le select.Valable aussi pour les operateurs = != < <= >=
–�� ��<expr> < ANY (select ...) vrai si <expr> est strictement inferieure a au moins une des valeursproduites par le select.Valable aussi pour les operateurs = != < <= >=
–�� ��[NOT] EXISTS (select ...) vrai ssi le select produit au moins un (aucun si NOT) nuplet.
–�� ��<expr> [NOT] IN (select ...) vrai ssi <expr> est egale a au moins une (aucune si NOT) desvaleurs produites par le select.Remarquer que les valeurs peuvent etre constituees de plus d’une colonne :
mat in (select mat from ...) ou (mat, etu) in (select mat, etu from ...)
L’operande droit de in peut aussi etre une liste de constantes explicites, par exemple :
note in (2, 3, 5, 7, 11, 13, 17, 19)
Pour chaque matiere, les etudiants qui ont la meilleure note :
2.10. ORDONNER LE LISTING DES NUPLETS : ORDER BY 27
select m.nom, e.nom
from Etudiant e
inner join Note n on e.id = n.etu
inner join Matiere m on n.mat = m.id
where n.note >= All (select n.note from Note n where n.mat = m.id) ;
BD Alfred
CL Julie
Q. 38 Recrire la requete precedente en utilisant not exists plutot que >= All.
Q. 39 any vaut faux si la sous-requete renvoie un ensemble vide, que vaut all dans ce meme cas ?
Q. 40 Pour chaque matiere, lister les etudiants qui n’ont pas la plus mauvaise note.
Q. 41 Donner un operateur ensembliste equivalent a�� ��expr IN (select ...)
2.10 Ordonner le listing des nuplets : order by
Cette clause order by permet d’indiquer dans quel ordre on souhaite obtenir les nuplets produits parla clause select.Obtenir les nuplets dans un certain ordre n’est utile que pour un lecteur humain (par exemple : lorsd’un jury on aime bien avoir la liste des etudiants par moyenne decroissante) ou pour un programmedont l’algorithme a besoin de recuperer les nuplets dans un ordre bien precis (par exemple si on veutverifier par programme que les numeros d’etudiants sont uniques et contigus le plus simple est d’ouvrirun curseur sur les numeros croissants, voir le chapitre PL/SQL).Cette clause d’ordre n’est donc utilisable que pour le select principal (elle etait interdite dans lessous-requetes en Oracle < 10).
En Oracle 10 cette regle n’est plus vraie : il est possible d’utiliser la clause order by dans unesous-requete.
Pour trier les villes par departements croissants, puis populations decroissantes, puis noms croissants :
select * from Ville v
order by v.Departement asc, v.Population desc, v.Nom ;
Par defaut l’ordre est asc (i.e. croissant), desc demande un ordre decroissant.On n’est evidemment pas oblige d’ordonner sur toutes les colonnes et on peut trier sur le resultatd’une expression :
select * from Ville v
order by upper (v.Nom) ;
-- On peut aussi ordonner sur une colonne de la projection :
select upper (v.Nom) as nom_MAJ from Ville v
order by nom_MAJ ;
La clause order by est toujours la derniere d’une requete.
2.11 La formation de groupes : group by
L’ensemble des nuplets produits par les clauses from et where peut etre partitionne en sous-ensemblesou groupes non vides et disjoints. La maniere de partitionner est indiquee par les expressions pa-rametres de la clause group by qu’on appellera clef de groupe : les nuplets ayant la meme valeur pourla clef de groupe font partie du meme groupe. Seules les expressions du group by peuvent figureren direct dans la projection du select, toute autre expression ou nom de colonne ne peut figurer
28 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
qu’en parametre d’une fonction d’agregation : cette fonction s’appliquera donc aux nuplets de chaquegroupes pris separement. Par exemple pour calculer la moyenne de chaque etudiant on utilise la clefde 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 requete peut constituer un nombre quelconque de groupes (eventuellement aucun groupe siaucun nuplet n’est retenu par le where) et elle produira autant de nuplets qu’il y a de groupes.Une maniere de visualiser ce regroupement est de remplacer la clause group by par une clause orderby 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 groupeid nom note
premier groupe 1 Alfred 121 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 interessant des qu’on veut obtenir une information synthetique sur chaquegroupe grace aux fonctions d’agregation (sinon on peut se contenter du qualificatif distinct de laclause select). Par exemple on souhaite connaıtre la moyenne de chaque etudiant :
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
-- Resultat de l’equi-jointure ordonne sur la clef de groupe :
-- -----------------------
-- nom id | etu note mat
-- -----------------------
-- Alfred 1 | 1 12 1
-- Julie 3 | 3 15 2
-- Alfred 1 | 1 14 2
group by e.id, e.nom ;
-- Resultat du regroupement (ou group by) :
-- -----------------------
-- 2 groupes | individus du groupe
-- nom id | etu note mat
-- -----------------------
-- Alfred 1 | 1 12 1
-- | 1 14 2
2.11. LA FORMATION DE GROUPES : GROUP BY 29
-- -----------------------
-- Julie 3 | 3 15 2
id nom moyenne nb_notes
1 Alfred 13 2
3 Julie 15 1
Et encore une maniere de lister, pour chaque matiere, les etudiants qui ont la meilleure note. Onremplace, dans la clause from, la table Matiere par la table (virtuelle) des notes maxi de chaquematiere :
select m_max.nom, e.nom
from Etudiant e
inner join Note n on e.id = n.etu
inner join (select -- meilleure note de chaque matiere
m.id as id,
m.nom as nom,
Max (n.note) as note_max
from Matiere m
inner join Note n on m.id = n.mat
group by m.id, m.nom) m_max on n.mat = m_max.id
where n.note = m_max.note_max ;
Q. 42 En supposant que chaque matiere soit dotee d’un coefficient coeff, calculer la moyenneponderee de chaque etudiant. On supposera que toutes les notes et coefficients sont renseignes (isnot null).
Q. 43 Que se passe-t-il si le coefficient d’une matiere est indefini ?
Q. 44 Comment calculer une moyenne correcte pour l’etudiant si certaines notes ne sont pas ren-seignees ? (si une note n’est pas renseignee, il faut ne pas la prendre en compte)
2.11.1 Selectionner des groupes : la clause having
La sous-clause having de group by est l’equivalent pour un groupe de la clause where pour uneligne. Elle permet de ne laisser passer que les groupes qui verifient sa condition. En dehors des fonc-tions d’agregation, elle ne peut donc mentionner que des expressions de la clef du group by.
Par exemple la moyenne des etudiants ayant au moins deux notes :
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
group by e.id, e.nom
having count (*) >= 2
-- Resultat 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 MODELE RELATIONNEL ET SQL
Q. 45 Moyenne ponderee des etudiants ayant une note renseignee dans chaque matiere.
Q. 46 Pour chaque etudiant, nombre de matieres pour lesquelles il a une note definie.
Q. 47 Quelle sera la valeur systematique d’une requete ayant un group by muni de la clause having
count (*) = 0 ?
Q. 48 Donnez une nouvelle version de la requete listant les etudiants inscrits a toutes les UE (voirsection 2.5.3).
2.11.2 group by et informations indefinies
Lors d’un group by sur une seule expression E, Oracle 10 considere que toutes les lignes pour les-quelles E est indefinie (is null) font partie du meme groupe (ce qui n’est pas plus evident que deconsiderer qu’elle forment autant de groupes differents). PostgreSQL 8.2.1 a la meme attitude.
Le mieux serait cependant d’expliciter la valeur indefnie :
select nvl (<expression>, ’inconnu’), ...
...
group by nvl (<expression>, ’inconnu’), ...
Attention : ’inconnu’ doit etre du meme type que <expression>.
Q. 49 Mettre en place une experience pour savoir comment se comporte votre SGBD favori dans cecas.
2.12 Les jointures externes : outer join
Dans l’exercice precedent, le probleme est qu’on ne voit pas Marc car n’ayant pas de notes il ne faitpas partie de la jointure (figure 2.1 page 13).On peut resoudre ce probleme grace a une jointure externe sur la table Etudiant : un etudiant n’ayantaucune note fera alors partie de la jointure mais toutes les colonnes relatives a la partie Note serontindefinies (Oracle10, Postgres, SQL92) :
select e.id, e.nom, n.note
from Etudiant e
left outer join Note n on e.id = n.etu ;
1 Alfred 12
1 Alfred 14
2 Marc <-- nuplet supplementaire grace a la jointure externe
3 Julie 15
Si un nuplet Etudiant n’a pas de note, le left outer join le concatene quand meme avec un nupletNote dont toutes les colonnes sont indefinies.Le left designe la table dont on veut conserver tous les nuplets : celle de gauche. Cette jointure externeest signalee par left outer join.
Q. 50 Dans la requete precedente, qu’obtiendrait-on avec une jointure externe conservant les lignesde la table de droite : right outer join ?
La jointure externe n’est pas une primitive car on peut l’exprimer grace aux operateurs precedents,voici l’equivalent de la requete precedente :
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’AGREGATION 31
Il suffit de rajouter le group by pour obtenir des informations synthetiques par etudiant (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 indefini pour Marc
3 Julie 1
La fonction count (expression) compte le nombre de fois que expression est definie. n.etu etantindefini pour Marc, son nombre de matieres vaut zero.
Les jointures sont (Oracle10, Postgres, SQL92) :
inner join : jointure classique (interne)
left outer join : jointure externe conservant les lignes de la table de gauche qui ne s’apparient avecaucune ligne de la table de droite,
right outer join : comme ci-dessus mais ce sont les lignes de la table de droite qui sont conservees,
full outer join : pour une jointure externe complete (conservation des lignes de gauche et de droite)
Exemple : liste des couples etudiant, matiere, meme pour les etudiants n’ayant aucune note et matiereest alors indefinie :select e.nom, nvl (m.nom, ’aucune matiere’)
from Etudiant e
left outer join Note n on e.id = n.etu
left outer join Matiere m on n.mat = m.id ;
donne
Alfred BD
Alfred CL
Julie CL
Marc aucune matiere
2.13 Contraintes sur l’usage des fonctions d’agregation
Une clause on ne peut mentionner aucune fonction d’agregation, elle s’applique a la construction d’uneconcatenation de lignes.
Une clause where ne peut mentionner aucune fonction d’agregation car elle s’applique a exactementune ligne de la clause from. Cependant elle peut contenir une sous-requete utilisant des fonctionsd’agregation car une sous-requete est un nouveau monde et n’a donc pas d’impact sur la clause where,par exemple pour avoir les notes des etudiants superieures a 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’agregation.
Une clause having peut mentionner des fonctions d’agregation mais avec une profondeur d’au plus 1.Les colonnes clef du group by peuvent apparaıtre en dehors ou a l’interieur de fonctions d’agregation,les autres colonnes doivent absolument apparaıtre a l’interieur de fonctions d’agregation.
La clause select d’une requete R peut :
1. si R n’a pas de clause group by :
– si R n’est pas une requete synthetique aucune fonction d’agregation n’apparaıt,
32 CHAPITRE 2. LE MODELE RELATIONNEL ET SQL
– si R est une requete synthetique, toute colonne provenant de sa clause from doit apparaıtredans une fonction d’agregation dont la profondeur est exactement de 1.En revanche des constantes ou des colonnes provenant d’une requete englobante peuvent ap-paraıtre en dehors des fonctions d’agregation, ou a l’interieur, car elles ont une valeur constantepour l’evaluation de R.
2. si R a une clause group by :– si R n’est pas une requete synthetique alors toute colonne ne faisant pas partie de la clef de
groupe doit apparaıtre dans une fonction d’agregation avec une profondeur de 1. Les colonnesclef de groupe peuvent apparaıtre a l’exterieur ou a l’interieur des fonctions d’agregation.
– si R est une requete synthetique alors toute colonne ne faisant pas partie de la clef de groupedoit apparaıtre dans un double emboıtement de fonctions d’agregation (profondeur de 2).Les colonnes clef de groupe doivent apparaıtre a une profondeur 1 ou 2 dans les fonctionsd’agregation.En revanche des constantes ou des colonnes provenant d’une requete englobante peuvent ap-paraıtre en dehors des fonctions d’agregation, ou a l’interieur, car elles ont une valeur constantepour l’evaluation 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 meme etudiant
Un autre exemple ou on suppose qu’un etudiant est inscrit a exactement un groupe : on veut connaıtrele 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 nombred’etudiants inscrits par groupe).
2.14 Emplacement des fonctions d’agregation
Une fonction d’agregation ne peut etre utilisee ni dans une clause on de jointure ni dans la clausewhere.
Il est possible d’emboıter des fonctions d’agregation dans le select d’une requete munie d’une clausegroup by, mais sans depasser une profondeur d’emboıtement de deux. Dans ce cas la requete donneune information synthetique des informations obtenues pour chaque groupe, par exemple la moyennedes moyennes des etudiants :
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 requete calcule la moyenne de chaque etudiant, puis la moyenne de ces moyennes.
Il est aussi possible d’utiliser des fonctions d’agregation dans l’expression du having mais avec uneprofondeur d’emboıtement de un : donc on ne peut y emboıter deux fonctions d’agregation. Parexemple si on veut la moyenne des moyennes superieures ou egales a 10 :
select Avg (Avg (n.note)) as moyenne_promo
from Etudiant e
2.15. POUR CONCLURE 33
inner join Note n on e.id = n.etu
group by e.id, e.nom
having AVG (n.note) >= 10 ;
2.15 Pour conclure
En conclusion, l’execution d’une requete se fait conceptuellement dans cet ordre :
1. from produit les nuplets du produit cartesien (eventuellement la jointure pour ANSI SQL etOracle 9, Postgres),
2. where applique une restriction (et condition de jointure dans Oracle 8) aux nuplets de la clausefrom,
3. group by construit des groupes avec sa clause optionnelle having de restriction,
4. select produit la projection de chaque groupe de nuplets provenant du group by ou de chaquenuplet du where s’il n’y a pas de group by,
5. order by ordonne les nuplets provenant du select.
On peut remarquer que l’ordre syntaxique et l’ordre conceptuel n’ont pas grand chose a voir l’un avecl’autre ! En particulier, la clause select n’est pas la premiere a etre executee.
Chapitre 3
Dependances fonctionnelles etnormalisation
Une relation universelle est l’unique relation formee de tous les attributs pertinents d’un probleme.A,B,C,D designent des attributs.
R,T,X, Y, Z designent des ensembles d’attributs (eventuellement vides).F un ensemble de dependances fonctionnelles (DF)
On notera indifferemment X ∪ Y ou XY .
3.1 Dependances fonctionnelles
Une DF est notee X → Y et exprime que dans toute extension de X ∪ Y les valeurs des attributs deX determinent de facon unique celles des attributs de Y . Autrement dit : si on connaıt une valeur deX alors on connaıt la valeur de Y lui correspondant.
Q. 51 Soit la table (numero-de-carte-etudiant, nom), que peut-on faire de numero-de-carte-etudiant ?
X → Y est elementaire si X = {C1C2 · · ·Ck} et que pour tout 1 ≤ i ≤ k on n’a pas X − {Ci} → Y .X → Y est triviale ssi Y ⊆ X, y compris pour Y vide.
Exemple dedependancesfonctionnelles :
{numero-insee} → {sexe,date-naissance} est elementaire,{numero-insee, sexe} → {sexe,date-naissance} n’est pas elementaire,{date-naissance, sexe} → {sexe} est triviale.
Soit la relation universelle LDF qui decrit une ligne d’une facture : LDF= {num-facture, date, client,produit, qte-produit, prix-produit}. Une facture (num-facture) est etablie a une date pour un client.Le prix d’un produit est constant. Une facture peut avoir plusieurs produits (i.e. plusieurs ligne). Unproduit apparaıt dans au plus une ligne d’une facture. Un client a au plus une facture par jour.
Q. 52 Donner l’ensemble des DF elementaires de LDF.
Q. 53 Donner quelques DF triviales et quelques DF non triviales et non elementaires de LDF.
Q. 54 Combien y a-t-il de dependances triviales dont le determinant est LDF?
3.2 La necessite de decomposer une relation en sous-relations
Motivation : eviter la repetition (redondance) d’information et l’impossibilite de representer certainesinformations tout en essayant de conserver les dependances fonctionnelles.
Q. 55 Sur l’exemple de la relation LDF, mettre en evidence plusieurs anomalies.
Q. 56 Quelles verifications un programme doit-il faire prealablement a l’ajout d’un tuple LDF.
Q. 57 Que doit-on faire pour modifier le prix d’un produit.
34
3.3. AXIOMES DE ARMSTRONG 35
On a donc souvent besoin de decomposer (normaliser) une relation en plusieurs sous-relations afind’eviter ces anomalies.
Q. 58 Proposer une telle decomposition de la relation Ligne-de-Facture et indiquer les dependancesfonctionelles qui sont conservees par les sous-relations.
3.3 Axiomes de Armstrong
Ils permettent de deduire de nouvellesdependances fonctionnelles a partir d’unensemble F de dependances fonction-nelles.
Axiomes de Armstrong(1) trivialite Y ⊆ X ⇒ X → Y(2) augmentation X → Y ⇒ XZ → Y Z(3) transitivite X → Y ∧ Y → Z ⇒ X → Z
Q. 59 De R = {A,B,C,D,E, F} muni de F = {AB → CD, B → F}, deduire {DE → E, AB →C, ABD→ ADF}. (l’axiome d’augmentation est precieux, ainsi que le fait que XX = X)
F+ est la cloture de l’ensemble de DF F obtenue par application des axiomes de Armstrong.
Q. 60 Calculer la cloture de F = {A→ B} sur R = {A,B}.
L’interet d’une telle cloture est qu’elle permet de definir l’equivalence entre deux ensembles de DF F1
et F2 portant sur la meme relation universelle : F1 est equivalent a F2 ssi F1+ = F2+.
Q. 61 Sans passer par la cloture, on veut montrer que sur R = {A,B,C}, F1 = {A→ B, B → C}est equivalente a F2 = {A→ BC, B → C}. Comment peut-on si prendre ? faites-le.
3 corollaires bien pratiques des axiomes de Armstrong(4) union / decomposition X → Y ∧X → Z ⇔ X → Y Z(5) pseudo-transitivite X → Y ∧ Y Z → T ⇒ XZ → T(6) augmentation bis X → Y ∧ Z → T ⇒ XZ → Y T
Q. 62 Prouver ces corollaires a l’aide des axiomes et des corollaires deja prouves.
Soit R = {A,B,C,D,E, F} munie de : F = {{A,B} → {C}, {C,D} → {E,F}, {E} → {F,D}}
Q. 63 Montrer que si on supprime la DF {E} → {F} on perd une information.
Q. 64 En revanche si on supprime la DF {C,D} → {F} montrer qu’on ne perd rien.
3.4 Calculer les cles candidates d’une relation
Une cle candidate d’une relation R vis a vis d’un ensemble de dependances fonctionnelles F , est unsous-ensemble minimal d’attributs de R qui determine tous les attributs de R.
Q. 65 Quelles sont les cles candidates de R munie de F = {} ?
Definition : tout ensemble d’attributs incluant strictement ceux d’une cle candidate est une super-cle.Cet algorithme determine l’ensemble des cles candidates d’une relation R munie d’un ensemble deDF :
1. On construit le graphe des dependances, y compris les attributs n’apparaissant dans aucunedependance et sont donc des sommets isoles dans le graphe.
2. Les sommets non cibles d’une fleche appartiennent a toutes les cles, on les note et les marque.
3. Tant qu’il existe un sommet S determine par des sommets marques, marquer S.
4. Effacer tous les sommets marques et les fleches qui en partent.
5. Tant qu’il existe un sommet S non source d’une fleche, effacer S qui n’appartient a aucune cle.
6. Les sommets restant sont forcement dans des cycles, considerer separement chacun d’eux commeappartenant a une des cles, le marquer puis recommencer en (3)
7. S’il ne reste pas de sommet, supprimer toutes les cles non minimales et c’est fini.
36 CHAPITRE 3. DEPENDANCES FONCTIONNELLES ET NORMALISATION
Voici le graphe de C = {Ville, Rue, Zip, D} muni de F = {{Ville,Rue } → Zip, Zip → Ville}.
Une cle non minimale est : {Ville, Rue, Zip, D}.Les 2 cles candidates sont : {{Ville, Rue, D}, {Rue, Zip, D}}
Ville
RueZip
D
Q. 66 Dessiner le graphe des dependances de Ligne-de-Facture (voir question Q.52).
Q. 67 Marquer les nœuds de ce graphe determines directement ou indirectement par (date, client,produit) puis montrer qu’on obtient le meme resultat en utilisant les DF et les axiomes et corollairesde Armstrong.
Q. 68 Donner les cles candidates de Ligne-de-Facture.
Q. 69 Donnerles cles de :
la relation munie des dependances fonctionnelles
R = {A,B,C,D,E, F,G,H, I} {A→ BC,C → D,BDE → A,F → AG,G→ H}
R = {A,B,C,D,E, F,G} {AC → B,B → C,C → DE,D → F,E → F,F → G}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}
Definitions des formes normales : BCNF ⇒ 3NF ⇒ 2NF ⇒ 1NF
Une forme normale permet de mesurer la qualite d’une relation munie de dependances fonctionnelles.Par exemple 2NF nous garantit que toutes les cles completes sont necessaires pour determiner lesattributs n’appartenant a aucune cle : cela permettra d’eviter des redondances.Par exemple Magasin = {Produit,Date, Prix, Producteur} muni de Regle = {{Produit,Date} →Prix, {Produit} → Producteur} a comme cles C = {{Produit,Date}}. Elle n’est donc pas 2NF.
Q. 70 Pourquoi Magasin n’est pas 2NF? Donner un exemple de redondance sur Magasin.
1NF Si tout attribut a une valeur atomique.
2NF Une relation est en 2NF si elle est 1NF et que tout attribut n’appartenant a aucune cle can-didate est en dependance elementaire ou (irreductible) avec chacune des cles. (contre-exemple :{A,B,C}, {B → C})
3NF Une relation est en 3NF si tout attribut A n’appartenant a aucune cle X depend de chacunedes cles par une DF directe de F+, autrement dit 6 ∃Y |A 6∈ XY,X → Y, Y 6→ X,Y → A, ouencore sans intermediaire possible qui ne serait pas une cle. Une relation 3NF est aussi 2NF.(contre-exemple : {A,B,C,D}, {AB → C,C → D}, 2NF ?)
BCNF : Boyce Codd Normal Form Une relation R est BCNF vis a vis d’un ensemble de DF F ,si toute DF non triviale de F+ a comme determinant une cle ou une super-cle de R .
Q. 71 Par exemple R = {cru, pays,region, qualite} munie de {{cru, pays} → {region, qualite},{region} → {pays}} n’est pas BCNF car {region} n’est pas une cle. Est-elle 2NF ? 3NF?
Q. 72 Normalite de LDF (voir Q.52) ?
Q. 73 Normalite de R = {A,B,C,D} munie de F = {AB → CD,BC → D,CD→ A} ?
Q. 74 Normalite de R = {A,B,C,D} munie de F = {A→ BC,B → C,C → B} ?
3.5 Decomposer une relation sans perte d’information
Quand une relation ne satisfait pas la normalite souhaitee, on la decompose en deux sous-relations.Si cette decomposition ne satisfait toujours pas la normalite souhaitee on pourra a nouveau lesdecomposer : le processus de decomposition est iteratif.Cette technique presque mecanique de decomposition risque de donner un resultat similaire a celuiobtenu par une approche plus intuitive comme par exemple la conception du MCD de Merise.
3.5. DECOMPOSER UNE RELATION SANS PERTE D’INFORMATION 37
Soient la relation R munie de F et R1, R2 une decomposition de R (i.e. R1 ∪R2 = R et R1 ∩R2 n’estpas vide). Cette decomposition est sans perte d’information vis a vis de F si toute extension r deR verifiant F est egale a ΠR1
(r) ⊲⊳ ΠR2(r) = r, cette jointure naturelle se faisant par egalite sur les
colonnes de R1 ∩R2.
Soit R = {A,B,C} munie de F = {A → C}. Pour l’exemple d’extension donne a droite,montrer que les decompositions suivantes de R :
Q. 75 R1 = {A,C}, R2 = {A,B} ne perd pas d’information.
Q. 76 R1 = {B,C}, R2 = {A,C} perd de l’information.
A B C
a1 b1 c1
a1 b2 c1
a2 b2 c1
Le principe de non perte d’information est evidemment incontournable lors d’une decomposition ! D’oul’importance du theoreme suivant.
Theoreme de decomposition sans perte d’information Soient R = {A1, A2, . . . , An} un schemarelationnel, F un ensemble de dependances fonctionnelles et X,Y,Z une partition de R telle queX → Y ∈ F+. Alors R1 = X ∪ Y,R2 = X ∪Z est une decomposition de R sans perte d’information1.
X,Y,Z est une partition de R⇔ (X ∪ Y ∪ Z = R) ∧ (X ∩ Y = ⊘) ∧ (X ∩ Z = ⊘) ∧ (Y ∩ Z = ⊘)
Demonstration : Soit r une valeur quelconque de R et r1 = ΠR1(r), r2 = ΠR2
(r). On montre d’abordque r1 ⊲⊳ r2 ⊆ r, pour cela on peut montrer que r1 ⊲⊳ r2 6⊆ r est une absurdite : 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 ete obtenuspar projection de r, c’est qu’il existe deux nuplets (xi, yi, z
′
i), (xi, y′
i, zi) appartenant a r, or X → Y ona donc yi = y′i et donc (xi, yi, zi) ∈ r. De la meme maniere on montre que r ⊆ r1 ⊲⊳ r2.
Q. 77 Montrer que la condition du theoreme est aussi necessaire, c’est a dire que si une decompositionest sans perte alors elle verifie necessairement la condition du theoreme. Suggestion : montrer que sion n’a ni R1 ∩R2 → R1 ni R1 ∩R2 → R2 alors la decomposition est avec perte, un exemple suffit.
Q. 78 En SQL, a quelles contraintes serait soumis X dans les tables R1 et R2 ?
L’ensemble des DF de Ri est la projection ΠRi(F+) = {X → Y ∈ F + |X ∪ Y ⊆ Ri}.
Une decomposition sans perte d’information ne preserve pas toujours les dependances fonctionnelles.
Exemple :
R = {A,B,C,D} munie de {AB → C,C → D}X = {A,B} Y = {C} Z = {D}
R1 = {A, B, C} munie de {AB → C} R2 = {A, B, D} munie de {AB → D}
mais la dependance {C → D} est perdue. On perd donc une contrainte d’integrite facilement expri-mable par une contrainte d’unicite ou de cle primaire. Il faudra programmer pour garantir que cettedependance est preservee lors des modifications de table.
Q. 79 Implanter R1 et R2 en SQL, comment garantir la dependance perdue C → D ?
Q. 80 Donner une autre decomposition de R qui preserve a la fois l’information et les DF.
Q. 81 On decompose la relation R de la question Q.73 en R1 = {A,B,C}, R2 = {A,B,D}. Cettedecomposition est-elle sans perte ? Quelles sont les DF conservees par cette decomposition ?
Q. 82 Decomposer LDF (voir Q.52) en sous-relations qui sont toutes BCNF, cette decompositionconserve-t-elle toutes les DF?
Remarque : pour un meme probleme R muni de F il peut y avoir plusieurs decompositions differentespermettant d’obtenir des sous-relations verifiant une forme normale.
Attention : une decomposition BCNF sans perte d’information peut perdre des dependances fonc-tionnelles (ce n’est pas le cas de 3NF).
1Autremrent dit : R1, R2 est sans perte d’information ssi R = R1 ∪ R2 et (R1 ∩ R2 → R1 ou R1 ∩ R2 → R2).
38 CHAPITRE 3. DEPENDANCES FONCTIONNELLES ET NORMALISATION
Application (emprunte au poly de Mireille Clerbout)
Soit la relation D = {depot, journal, titre, categorie, tx com, prix, adr depot, jour, quantite} muniedes dependances F :
{depot} → {adr depot} {categorie} → {tx com} {titre} → {journal}{depot, journal, jour} → {quantite} {journal} → {titre, prix, categorie, tx com}
Utilisez des diminutifs pour faire les questions, par exemple D pour depot, Jl pour journal, Jr pourjour . . ..
Q. 83 Determiner les cles de D munie de F et montrer qu’elle n’est pas BCNF (section 3.4).
Q. 84 Decomposer D par etapes 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 decomposition obtenue.
Q. 86 Ecrire les ordres SQL de creation des tables BCNF et leurs garnissages a partir d’une table D
deja peuplee.
Chapitre 4
SQL/DML les ordres de modificationdes tables
SQL signifie Structured Query Language
SQL = {DDL, DML, DCL}DML = Data Manipulation Language
4.1 insert : ajout de nouvelles lignes
Pour ajouter de nouvelles lignes.
insert into <nomTable> [(col1, ..., coln)] values (val1, ..., valn) ;
ou
insert into <nomTable> [(col1, ..., coln)] <requete> ;
Exemple :
– Insertion d’une ligne en explicitant la valeur de toutes les colonnes dans l’ordre de leurs declarations :insert into Client values (4, ’Durif’, ’Philippe’, 300) ;
On peut explicitement indiquer qu’une colonne n’est pas definie (is null) en mettant null poursignifier 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 mentionnees seront indefinies ou bien auront leur valeur par defaut eventuellementindiquee lors de la creation de la table (default).
– Insertion de toutes les lignes produites par une requete :insert into Client (num_client, nom, prenom)
select ref, nom, prenom
from Employe
where salaire > 1000 ;
Le mot clef default peut etre utilise en tant que valeur d’une colonne et indique que la colonne doitprendre sa valeur par defaut si elle en a une (voir create table section 5.1 page 41) ou etre indefiniesi elle n’en a pas.
4.2 update : la mise a jour de lignes existantes
Pour modifier des lignes existantes.
update <nomTable>
set affectation {, affectation}
[where condition] ;
39
40 CHAPITRE 4. SQL/DML LES ORDRES DE MODIFICATION DES TABLES
affectation ::= colonne = expression
| (col1, ..., colp) = (sous-requete-1-ligne-p-colonnes)
Attention : la sous-requete eventuelle ne doit pas porter sur la table en cours de modification sinonon aura une erreur de table mutante.Exemple, augmentation du solde des clients ayant un numero inferieur a 4 :
update Client set solde = solde + 100 where num_client < 4 ;
Exemple avec une liste de colonnes :
create table Departement (
deptno Number (5) primary key,
prefecture Varchar2 (10) not null unique
) ;
create table Employe (
id Number (5) primary key,
salaire Number (10, 2),
commission Number (10, 2),
deptno references Departement (deptno) -- clef etrangere
) ;
On veut deplacer sur Paris les employes des departements de Lille et Lyon en doublant leurs salaireset 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’)) ;
ou bien, de facon equivalente :
update Employe
set
salaire = 2 * Employe.salaire,
commission = 500.0,
deptno = (select d.deptno from Departement d where d.prefecture = ’Paris’)
where deptno in (select deptno from Departement
where prefecture in (’Lille’, ’Lyon’)) ;
4.3 delete : suppression de lignes existantes
delete from <nomTable> [where condition] ;
Exemple suppression des clients ayant un numero egal a 2 ou 5 :
delete from Client where num_client in (2, 5) ;
Suppression de tous les clients :
delete from Client ; -- vide la table
Chapitre 5
Contraintes d’integrite en SQL
DDL = Data Definition LanguageDes la declaration d’une table on peut fixer un certain nombre de proprietes sur les valeurs que peuventprendre les attributs.
5.1 Creation des tables
create [global temporary] table <nom-table>
( <liste-des-colonnes-et-contraintes-de-table> )
[on commit preserve rows | delete rows]
[ as <requete> ] ;
global temporary la table est temporaire et visible par toutes les sessions qui en ont le droit. Lesdonnees d’une telle table ne sont visibles que par la session qui les a inserees. Les donnees inserees nesurvivent pas a la fin de :– la transaction qui les a inserees si l’option on commit delete rows a ete precisee, c’est l’option
par defaut.– la session qui les a inserees si on commit preserve rows (une session est en general une sequence
de transactions par forcement contigues dans le temps).as permet d’initialiser le contenu de la table avec le resultat de la requete, dans ce cas il ne faut paspreciser les types des colonnes de la table et on ne peut pas donner une contrainte de clef etrangere(on pourra toujours ajouter cette derniere plus tard avec la commmande alter table add constraint...).
create table Client (
id Number (3),
nom Varchar2 (20) constraint Client_Nom_Defini not null,
prenom Varchar2 (20),
solde Number (6, 2) default 0.0,
constraint Client_PK primary key (id)
) ;
La clause default n’est pas une contrainte, elle provoque simplement l’introduction de la valeur pardefaut lors d’un insert ne precisant pas de valeur explicite.
5.2 Les types de donnees
SQL2 ne definit pas le type booleen (pourquoi ? ? ?).
Le mot cle BOOLEAN n’apparaıt meme pas dans l’index de l’ouvrage Oracle i SQL Reference Release3 (8.1.7) qui compte quand meme plus de mille pages !
41
42 CHAPITRE 5. CONTRAINTES D’INTEGRITE 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 role de unknown.
Les types definis par la norme ne sont malheureusement pas toujours respectes.
Numeriques Caracteres Binaires Dates, Intervalles
5.2.1 Types numeriques
Pour Oracle
Number (p, s) nombres en virgule fixe a p chiffres decimaux avec une precision de 10−s. L’in-tervalle de valeur est : [−(10p − 1)10−s, (10p − 1)10−s]
p, qui doit etre ∈ [1, 38], indique le nombre maximal de chiffres en base 10,
s ∈ [−84, 127], comme scale (echelle en francais) qui indique la precision : 10−s
si s = 2, la precision est de un centieme
si s = −2, la precision est de cent
Par exemple Number (5, 2) = [−999, 99, 999, 99], precision 0, 01
Un autre exemple :
create table Essai (
n Number (3, -2) -- de -99900 a 99900, precision 100
) ;
insert into Essai VALUES (-240) ;
select * from Essai ;
-200
update Essai set n = n + 25 ;
select * from Essai ;
-200
update Essai set n = n + 125 ;
select * from Essai ;
-100
drop table Essai ;
Number (p) nombre entier, qui signifie Number (p, 0)
Number nombre en virgule flottante avec 38 chiffres decimaux.
Pour norme ANSI/SQL (acceptes par Oracle)
NUMERIC (p, s) et DECIMAL (p, s) (Oracle Number (p, s))
INTEGER, INT et SMALLINT (Oracle Number (38))
FLOAT (b), DOUBLE PRECISION et REAL (Oracle Number)
La fonction predefinie mod :
select mod (24.66, 24) from dual ;
affiche 0.66.
5.2.2 Types caracteres
Pour Oracle
CHAR (n) chaınes de taille exactement egales a n (jusqu’a 2000 caracteres)
Varchar2 (n) chaınes de tailles variables inferieure ou egale a n (jusqu’a 4000 caracteres).
NCHAR et NVarchar2 : Unicode
CLOB et NCLOB (SQL3)
5.2. LES TYPES DE DONNEES 43
Pour norme ANSI/SQL
– CHARACTER (n) et CHAR (n) (Oracle CHAR (n))– NATIONAL CHARACTER (n), NATIONAL CHAR (n) et NCHAR (n) (Oracle NCHAR (n))– NATIONAL CHARACTER VARYING (n), NATIONAL CHAR VARYING (n) et NCHAR VA-
RYING (n) (Oracle NVarchar2 (n))
5.2.3 Types binaires
– RAW (size) (jusqu’a 2000 octets) obsolete (utiliser BLOB et BFILE)– LONG RAW (jusqu’a 2 Goctets) obsolete (utiliser BLOB et BFILE)– BFILE adresse d’un fichier binaire (BLOB en SQL3)
5.2.4 Types temporels
Pour Oracle
– DATE = siecle-annee-mois-jour-heure-minutes-seconde On dispose des fonctions– La fonction SYSDATE donne la date courante du systeme.– arithmetique (l’unite est le jour) et relation d’ordre sur les dates.– to_char pour passer de la representation interne a la representation externe
select SYSDATE from Dual ;
select SYSDATE from Dual ;
2003-02-12 11:41:42.0
select to_char (SYSDATE, ’dd mon yyyy’) from Dual;
12 fev 2003
select to_char (SYSDATE+21, ’dd/mm/yy hh:mi’) from Dual;
05/03/03 05:42
select ’Il est ’ || to_char (SYSDATE, ’hh:mi’) from Dual;
Il est 05:42
select to_char (SYSDATE,
’"Il est" hh24 "heures" mi "minutes" ss "secondes"’)
from Dual;
Il est 17 heures 42 minutes 27 secondes
– to_date pour passer de la representation externe a la representation interne.
select to_char(to_date(’7/2/04 21h15’,’dd/mm/yy hh24"h"mi’),’dd/mm/yy hh24"h"’)
from Dual ;
07/02/04 21h
select to_date (’19h27’, ’hh24"h"mi’) from Dual ;
2004-02-01 19:27:00.0
– La difference entre deux dates est exprimee en nombre de jours (nombre reel eventuellement negatif)et on peut ajouter un nombre de jours a une date.
– Months_Between (Date1, Date2) en gros : Date1 - Date2 en nombre de mois, donc positif siDate1 est posterieure a Date2. Le resultat est un reel, il n’est entier que si Date1 et Date2 sont lememe jour du mois (par exemple le 12/3/05 et le 12/11/03) ou le dernier jour du mois (par exemplele 28/2/06 et le 31/12/01).
– Pour avoir des dates sans prendre en compte l’heure de la journee, Oracle propose la fonctionTrunc (D in Date) qui renvoit la date D dont la partie heure est a zero. PostgreSQL propose lafonction date_trunc.
44 CHAPITRE 5. CONTRAINTES D’INTEGRITE EN SQL
Pour norme ANSI/SQL
– TIME et TIMESTAMP (Oracle DATE)
5.3 Les contraintes
Depuis SQL2.
Declarees a la creation de la table, puis verifiees automatiquement par le SGBD :
– programmation allegee– securite plus forte
Oracle ne verifie les contraintes qu’une fois l’instruction DML completement terminee (on peut eventuellementlui demander de ne les verifier qu’en fin de transaction, c’est a dire au moment ou les modificationsfaites par la transaction sont publiees par l’instruction commit).
Si une contrainte n’est pas verifiee en fin d’instruction DML, il y a annulation de la mise a jour avecmessage d’erreur. Plus precisement, la table est remise dans l’etat dans lequel elle etait avant le debutde l’instruction DML (fonctionnement en tout ou rien).
5.3.1 Baptisez vos contraintes !
Chaque contrainte peut etre baptisee (et on a toujours interet a le faire), elle pourra ensuite etremanipulee facilement par certaines commandes simplement en donnant sont nom.Le nom d’une contrainte est donne apres le mot-clef constraint :
constraint <nom-de-la-contrainte> <definition-de-la-contrainte> [[not] deferrable]
Par defaut l’attribut est not deferrable : la contrainte sera alors verifiee en fin de l’instructionmodifiant la table (insert, update ou delete).
Si la contrainte est deferrable alors il sera possible de demander qu’elle ne soit verifiee qu’en fin detransaction (lors du commit) avec la commande set constraint <nom-contrainte> deferred.
Postgres 8 dispose lui aussi de cette possibilite, mais uniquement pour les clef etrangeres (references).
5.3.2 Aspects syntaxiques
SQL distingue deux syntaxes pour decrire les contraintes : les contraintes de colonnes et les contraintesde table.
La seule contrainte qui ne peut etre decrite qu’en tant que contrainte de colonne est not null car ellequalifie toujours une seule colonne.
Une autre contrainte exprimable dans les deux syntaxe est unique pouvant s’applique a plusieurscolonnes.
Les autres contraintes peuvent etre decrites indifferemment en tant que contrainte de colonne oucontrainte de table ce sont unique, primary key, foreign key et check.
5.3.3 Liste des contraintes
not null l’attribut doit toujours avoir une valeur definie, c’est la seule contrainte qui ne peuts’ecrire qu’en contrainte de colonne.
primary key Aucune des colonnes de la clef primaire ne peut etre indefinie (Oracle cree unindex unique pour cette contrainte).
5.3. LES CONTRAINTES 45
Fig. 5.1 – Les deux manieres de declarer des contraintesSyntaxe contrainte de colonne Syntaxe contrainte de table
Une contrainte de colonne porte sur exactementune colonne (par exemple la contrainte not null)et est indiquee au moment de la declaration de lacolonne et on peut en mettre plusieurs :
create table Produit (
id Number (5)
constraint Produit_PK primary key,
nom Varchar2 (10),
stock Number (5) default 0
constraint Produit_stock_defini
not null
constraint Stock_Positif
check (stock >= 0)) ;
Deux contraintes portent sur la colonne stock.
Une contrainte de table peut porter sur plusieurs co-lonnes, elle est indiquee comme un element de la listedes colonnes de la table :
create table Commande (
produit Number (5), client Number (5),
quantite Number (5) default 0,
constraint Commande_PK
primary key (produit, client),
constraint Commande_Produit_FK
foreign key(produit) references Produit(id),
constraint Commande_Client_FK
foreign key(client) references Client(id),
constraint Quantite_Positive
check (quantite >= 0)) ;
default n’est pas une contrainte.
unique sur un attribut ou un groupe d’attributs dont la valeur, quand elle est definie, doit etreunique dans la table (Oracle cree un index unique pour cette contrainte).
Restriction Oracle 10 : contrairement a la norme SQL, Oracle considere que, dans une contrainted’unicite definie, les valeurs indefinies pour une meme colonne sont egales si d’autres colonnessont definies. 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 considerespar Oracle comme egaux et ne pourront donc pas coexister.En revanche si deux lignes sont indefinies sur toutes les colonnes d’unicite alors Oracle lesconsidere comme satisfaisant l’unicite, par exemple (null, null) et (null, null) sont considerescomme differents.
PostgreSQL respecte la norme SQL, c’est a dire qu’il considere (1, null) et (1, null) commedistincts.
check predicat portant sur les colonnes d’un meme nuplet
check (qte >= 0)
check (date_deb < date_fin)
check (couleur IN (’BLANC’, ’VERT’, ’ROUGE’))
En SQL2 la condition de check est presque equivalente a celle de where (y compris des sous-requetes)
Restrictions Oracle 10 et PostgreSQL 8.2 : le predicat doit porter uniquement sur la valeur de laligne courante, pas de sous-requete, de sequence, 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 (presomption d’innocence) la propriete estconsideree comme respectee et la mise a jour est acceptee.
Q. 87 A votre avis, le delete provoque-t-il la verification des contraintes not null et check ?
Q. 88 Ce meme delete a-t-il des verifications a faire quand il y a des contraintes primary key etunique, lesquelles ?
46 CHAPITRE 5. CONTRAINTES D’INTEGRITE EN SQL
Presomption d’innocence pour la contrainte check
Si la condition d’un check s’evalue a UNKNOWN alors la contrainte est consideree comme satisfaite.
Par exemple :
check (salaire > 0 or (salaire = 0 and commission > 0))
Q. 89 Montrer que si�� ��salaire is null la mise a jour est acceptee quel que soit l’etat de commission.
L’idee est qu’on ne peut pas empecher la creation d’un nuplet en l’absence d’information (presomptiond’innocence).
Q. 90 Si commission n’est pas definie, le salaire peut-il etre negatif ?
Q. 91 Corriger la contrainte pour garantir que le salaire et la commission ne sont jamais negatifs(une idee consiste a utiliser l’operateur is null, une autre idee a mettre plusieurs check).
Definition de nouveaux domaines (seulement en PostgreSQL)
En SQL2 et PostgreSQL oui, mais pas en Oracle :
create domain Quantite Integer default 0 check (value >= 0)
create table ... (
qte_produit Quantite,
...
) ;
Un exemple de domaine en PostgreSQL :
create domain Couleurs_Additives
as Text
default ’bleu’
constraint Couleurs_Additives_CHK
check (upper (value) in (’ROUGE’, ’VERT’, ’BLEU’)) ;
5.3.4 Contraintes d’integrite d’entite : clef primaire
Il s’agit des clefs primaires
create table Contient (
commande Number (3),
produit Number (3),
constraint Contient_PK primary key (commande, produit)
) ;
Les colonnes de la clef primaire doivent etre definies et les clefs primaires forment un ensemble (unicite).
Sous Oracle (et d’autres), un index unique est automatiquement cree sur la clef primaire, il prend lenom de la contrainte (Produit_PK dans l’exemple).
Relation sans clef
En theorie, une relation est un ensemble de nuplet, c’est a dire qu’un nuplet ne peut pas apparaıtreplus d’une fois dans l’extension d’une relation.De facon plus pratique, une relation a toujours une clef qui garantit l’unicite des nuplets.
En Oracle comme en PostgreSQL il est possible de definir une table sans clef :
5.3. LES CONTRAINTES 47
create table Sans_Clef (num Number (3)) ;
et on pourra y inserer plusieurs nuplets de meme valeur.
5.3.5 Contraintes d’integrite referentielle : clef etrangere
create table Etudiant (
id Number (5),
nom Varchar2 (20),
constraint Etudiant_PK primary key (id)
) ;
create table Note (
note Number (2),
etudiant Number (3),
constraint Note_Etudiant_FK foreign key (etudiant) references Etudiant (id)
) ;
Le fait que la colonne Note.etudiant est une clef etrangere implique que la table Note depend de latable Etudiant. Autrement dit Note ne peut etre creee que quand Etudiant existe.
Considerons une ligne de la table Note :
– si sa colonne etudiant est definie, il doit exister exactement une ligne de Etudiant dont le id estegal a etudiant.L’unicite de Etudiant.id est garantie puisque c’est justement la clef primaire.
– si sa colonne etudiant est indefinie (is null), c’est qu’elle ne reference aucune ligne de Etudiant.
La colonne Note.etudiant est alors appelee une clef etrangere, on peut aussi la comprendre commeun pointeur associatif qui n’est pas une adresse memoire mais une valeur permettant de retrouver laligne designee de la table Etudiant.
Une consequence du exactement une ligne de la table Etudiant est que la colonne id doit garantirl’unicite des lignes de Etudiant : id doit soit etre une clef primaire soit supporter une contrainted’unicite (unique).
Une clef etrangere peut-etre constituee de plusieurs colonnes : ces colonnes ne referencent une ligneque si elles toutes definies.
Une table peut se referencer elle-meme :
create table Employe (
id Number (3),
nom Varchar2 (20) constraint nom_not_null not null,
superieur Number (3),
constraint Employe_PK primary key (id),
constraint Employe_Superieur_FK
foreign key (superieur) references Employe (id)
) ;
Tres souvent une clef etrangere reference directement une clef primaire.
Il peut etre souhaitable et meme agreable de ne pas expliciter le type de la clef etrangere qui sera celuide 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’INTEGRITE EN SQL
) ;
– en contrainte de table :
create table Note (
note Number (2),
etudiant,
constraint Note_Etudiant_FK
foreign key (etudiant) references Etudiant (id)
) ;
Depuis surement assez longtemps MySQL accepte la syntaxe de declaration de clef etrangere, il n’enassure la semantique que depuis sa version 6 et uniquement dans InnoDB.
Suivent quelques manipulations dont certaines sont erronees.
On peut noter un etudiant non defini !
insert into Note (note) values (13) ;
-- OK ! une contrainte not null permettrait d’eviter ce DEFAUT !
On ne peut pas noter un etudiant qui n’existe pas
insert into Note (note, etudiant) values (13, 111) ;
ORA-02291: violation de contrainte (DURIF.NOTE_ETUDIANT_FK) d’integrite
- touche parent introuvable
On ne peut pas modifier la clef cible d’un etudiant note
update Etudiant set id = 666
where nom = ’dupont’ ;
-- OK car ’dupont’ n’a pas de note
update Etudiant set id = 444
where nom = ’durif’ ;
-- ’durif’ a au moins une note
ORA-02292: violation de contrainte (DURIF.NOTE_ETUDIANT_FK) d’integrite
- enregistrement fils existant
Modification de contrainte pour propager la mise a jour
Impossible en Oracle 10, mais possible en Postgres 8.
On ne peut pas supprimer un etudiant note
delete from etudiant where id = 666 ;
-- OK car 666 n’a pas de note
delete from etudiant where id = 333 ;
-- 333 a au moins une note
ORA-02292: violation de contrainte (DURIF.NOTE_ETUDIANT_FK) d’integrite
- enregistrement fils existant
5.4. LE DILEMME DE LA DEPENDANCE MUTUELLE 49
5.3.6 Clef etrangere et modifications de la table maıtre
SQL permet de maintenir automatiquement la coherence des clefs etrangeres lorsqu’on modifie la tablereferencee (ou table maıtre).
Pour cela il propose un certain nombre de comportements, qui ne sont pas tous implementes parOracle :
Oracle PostgreSQLSQL Commentaire (10.2) (8.1.3)
on delete|update no action (pardefaut)
Modification interdite (echec del’instruction).
par defaut par defaut
on delete cascade Suppression propagee : les nupletsreferencant sont supprimes
oui oui
on update cascade Modification propagee. non oui
on delete|update set null La reference devient indefinie. oui oui
on delete|update set default La reference est remise a sa valeurpar defaut.
non oui
Un tel comportement est indique lors de la declaration d’une clef etrangere, ainsi on peut avoir desclefs etrangeres ayant la meme cible et n’ayant pas le meme comportement. Ces comportements sontdes complements optionnels a ajouter a la definition d’une clef etrangere.
Redefinition de contrainte pour propager la suppression on delete cascade figure 5.1
alter table Note drop constraint Commande_Produit_FK ;
alter table Note add (constraint Commande_Produit_FK foreign key (produit)
references Produit (id) on delete cascade) ;
select n.note, Nvl (e.nom, ’anonyme’) as nom
from Note n
left outer join Etudiant e on n.etudiant = e.id ;
NOTE NOM
----------
13 durif
10 durif
13 anonyme
delete from etudiant where e.nom is null ;
-- OK
select n.note, Nvl (e.nom, ’inconnu’) as nom
from Note n
left outer join Etudiant e on n.etudiant = e.id ;
NOTE NOM
----------
13 durif
10 durif
5.4 Le dilemme de la dependance mutuelle
Par defaut Oracle ne verifie les contraintes qu’a la fin de l’execution de chaque instruction de mise ajour (insert, update et delete). Un update peut donc parfaitement faire passer la table modifiee
50 CHAPITRE 5. CONTRAINTES D’INTEGRITE EN SQL
par des etats intermediaires incoherents.
Un probleme apparaıt cependant quand la coherence a maintenir couvre plusieurs tables : il est alorspossible de differer en fin de transaction (lors du commit) les verifications de maniere a pouvoirmodifier les differentes tables.
Tout conducteur a exactement une voiture et toute voiture a exactement un conducteur :
create table Conducteur (
id Number (5),
nom Varchar2 (10),
voiture Number (5)
constraint Conducteur_Voiture_NOT_NULL not null,
constraint Conducteur_PK primary key (id)
) ;
create table Voiture (
id Number (5),
marque Varchar2 (10),
conducteur Number (5)
constraint Voiture_Conducteur_NOT_NULL not null,
constraint Voiture_PK primary key (id),
constraint Voiture_Conducteur_FK
foreign key (conducteur) references Conducteur (id)
) ;
alter table Conducteur
add (constraint Conducteur_Voiture_FK
foreign key (voiture) references Voiture (id) deferrable) ;
Ainsi il est impossible d’inserer un conducteur ou une voiture !
insert into Conducteur values (1, ’toto’, 6) ;
ORA-02291: violation de contrainte
(DURIF.CONDUCTEUR_VOITURE_FK) d’integrite - touche parent introuvable
insert into Conducteur (id, nom) values (1, ’toto’) ;
ORA-01400: impossible d’inserer NULL dans ("DURIF"."CONDUCTEUR"."VOITURE")
Remarquer qu’on a pris soin de dire, lors du alter table, que la contrainte Conducteur_Voiture_FK
est deferrable, car, par defaut, les contraintes ne sont pas differables. On peut alors demander adifferer la verification de cette contrainte en fin de transaction :
set constraint Conducteur_Voiture_FK deferred ;
insert into Conducteur values (1, ’toto’, 121) ;
insert into Voiture values (121, ’citron’, 1) ;
commit ; -- verification des contraintes differees
--
-- ici la contrainte Conducteur_Voiture_FK est de nouveau "immediate"
--
Les contraintes differees sont verifiees soit lors :
5.5. MODIFICATION DU SCHEMA 51
– d’un set constraint ... immediate,– de la validation et terminaison de la transaction courante, grace a l’instruction commit ou impli-
citement par une deconnexion normale.
Si elles ne sont pas verifiees, le prochain commit (ou la fin de session) effectuera un rollback quiannulera toutes les modifications faites depuis le debut de la transaction.
Enfin pour detruire ces deux tables interdependantes on peut commencer par supprimer les contraintesou bien faire tout simplement :
drop table Conducteur cascade constraint ;
-- Detruit la contrainte de clef etrangere Voiture_Conducteur_FK
-- puis detruit la table Conducteur.
drop table Voiture ;
5.5 Modification du schema
alter table permet :
ajouter/supprimer/modifier la definition d’une colonne
ajouter/supprimer des contraintes
activer/desactiver des contraintes
5.5.1 alter table
alter table <nom> add (<colonne-ou-contrainte> {, <colonne-ou-contrainte>}) ;
alter table <nom> modify (<colonne> {, <colonne>}) ;
alter table <nom> drop <colonne-ou-contrainte> ;
Ajouter une ou plusieurs colonnes et contraintes : add (...)
create table Client (id Number (5)) ;
alter table Client add (nom Varchar2 (20) not null,
tel Varchar2 (10) constraint tel_unique unique,
loc Varchar2 (15) default ’Lille’,
solde Number (10, 2), constraint Client_PK primary key (id)) ;
S’il n’y a pas de valeur par defaut, la nouvelle colonne est indefinie et cela peut entrer en conflit avecd’autres contraintes (par exemple si une nouvelle colonne est not null et que la table modifiee n’estpas vide).
alter table Client
add (constraint Client_PK primary key (id)) ;
Modifier la definition d’une colonne : modify (...)
On peut augmenter la taille d’une colonne, la diminuer si la table est vide, et, pour le type Varchar2
diminuer la taille uniquement si la nouvelle taille est suffisante pour les donnees deja stockees,
On ne peut changer de type que si la table est vide.
alter table Client
add (constraint Client_Solde check (solde >= 0))
modify (nom Varchar2 (30)) ;
On peut ajouter ou supprimer des contraintes, mais pas les modifier.
52 CHAPITRE 5. CONTRAINTES D’INTEGRITE EN SQL
Suppression de colonne et/ou contraintes : drop
Suppression d’une contrainte nommee Suppression d’une colonnealter table Client drop
constraint tel_unique ;alter table Client drop column tel ;
-- Suppression d’une contrainte anonyme
alter table Dept drop unique (dname, loc) ;
Activer/Desactiver les contraintes : enable/disable
Contrainte activee : elle est verifiee et est stockee dans le dictionnaire.
Contrainte desactivee : elle n’est pas verifiee, mais elle reste stockee dans le dictionnaire (evidemment !).
Pourquoi desactiver des contraintes : quand on veut faire des traitements qui peuvent, provisoirement,les violer, par exemple charger les tables une par une.
La reactivation d’une contrainte echoue tant qu’elle n’est pas verifiee, on est donc oblige de corrigerles donnees.
Par defaut les contraintes sont actives.
On peut les desactiver des leur definition, ou bien plus tard :
create table Emp (
empno Number (5) primary key disable,
...
)
alter table Autre
add primary key (num_autre) disable ;
alter table Dept
enable primary key,
enable unique (dname, loc) ;
alter table Dept
disable constraint dname_PK ;
Remarque : les contraintes primary key et unique creent des index sur la table qui sont reconstruitsa chaque reactivation de la contrainte.
5.5.2 Suppression d’une relation
Elle echoue si la table est referencee par des clefs etrangeres (meme si elle est vide).
drop table <nom> ;
Effets :
– enleve la definition de la table du dictionnaire,– tous les index et triggers associes sont detruits,– les sous-programmes PL/SQL qui dependent de cette table deviennent inutilisables (ils sont toujours
la !)– les vues et les synonymes qui dependent de cette table sont toujours la mais renvoient une erreur
quand on les utilise !– la place occupee par la table est restituee.
5.6. GENERATEUR D’ENTIERS : LES SEQUENCE 53
5.5.3 drop table ... cascade constraints
Le probleme des dependances dues aux clef etrangeres :
create table Maitre (
id Number (3) primary key) ;
create table Esclave (
id Number (3),
constraint Esclave_Vers_Maitre_FK foreign key (id) references Maitre (id)
) ;
La suppression
drop table Maitre ;
-- erreur oracle
ne marche pas : il faut d’abord supprimer les tables referencantes ou desactiver/supprimer certainescontraintes ou encore, plus simplement :
drop table Maitre cascade constraints; -- ok (supprime les contraintes referencantes)
qui supprime la contrainte Esclave_Vers_Maitre_FK qui fait reference a la table Maitre.
5.5.4 Vider une table sans la detruire (Oracle, PostgreSQL)
Ceci est plus efficace qu’un drop suivi d’un create, mais cette cette operation ne sera pas annuleelors d’une eventuelle annulation de la transaction (rollback).
truncate table <nom> ;
PostgreSQL permet la meme chose sur une liste de tables.
5.6 Generateur d’entiers : les sequence
Cela peut etre pratique pour fabriquer une valeur de clef primaire.
create sequence Id_Voiture ;
create table Voiture (
idv Number (5),
marque Varchar2 (20),
nbPlaces Number (1),
constraint Voiture_PK primary key (idv)
) ;
insert into Voiture values (Id_Voiture.nextval, ’Peugeot’, 5) ;
...
drop sequence Id_Voiture ;
1. Par defaut le premier entier d’une sequence produit par nextval sera 1, il y a moyen de modifierce comportement.
2. nextval renvoie la valeur courante puis fait passer la sequence a la valeur suivante.
3. currval renvoie la derniere valeur de nextval sans modifier l’etat de la sequence, ne peut etreconsultee qu’apres le premier appel a nextval.
create sequence Id_Voiture ;
select Id_Voiture.currval from dual ;
select Id_Voiture.nextval from dual ;
54 CHAPITRE 5. CONTRAINTES D’INTEGRITE EN SQL
drop sequence Id_Voiture ;
Chapitre 6
Complements Oracle SQL
6.1 Expression conditionnelle decode
decode (<expr>,
<search>, <result>
{, <search>, <result>}
[, <default>])
Renvoie le premier <result> tel que <expr> = <search>, sinon renvoie <default>, et s’il n’y a pasde <default> alors null. Attention : decode considere que deux valeurs indefinies sont egales (ce quiest 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 ?
6.2 Expression conditionnelle case
Il s’agit d’une expression conditionnelle pouvant avoir un nombre quelconque de branches when :
case
{when <cond> then <expr-r> }
[ else <expr-d> ]
end
Exemple donnant une mention :
case
when 0 <= note and note < 10 then ’Refuse’
when 10 <= note and note < 12 then ’Passable’
when 12 <= note and note < 14 then ’Assez bien’
when 14 <= note and note < 16 then ’Bien’
when 16 <= note and note <= 20 then ’Tres Bien’
else ’Bizarre ! note incorrecte ?’
end
Q. 93 Pourquoi n’utilise-t-on pas un between and ?
6.3 group by cube et group by rollup
cube et RollUp permettent de constituer, en plus des groupes fournis pas la clause group by, dessuper-groupes de ces groupes et d’en fournir pour eux aussi des informations synthetiques.
55
56 CHAPITRE 6. COMPLEMENTS ORACLE SQL
6.3.1 Clef de groupe
Rappelons que les expressions de la clause group by forment ce qu’on appellera la clef de groupe :tous les tuples qui ont la meme valeur pour les expressions de la clef de groupe appartiennent au memegroupe. Par exemple dans :
select Matiere.nom as Matiere,
sum (Note.note) / count (*) as Moyenne
from Note, Matiere
where Note.matiere = Matiere.id
group by Matiere.nom ;
MATIERE MOYENNE
---------------
BD 13
CL 13
SSM 14
la clef de groupe est constituee par le nom de la matiere.
6.3.2 Cube et RollUp
cube et RollUp sont des extensions de la clause group by : en plus de constituer les groupescorrespondant a la clef de groupe, elles produisent des ensembles de groupes correspondant a dessous-ensembles de la clef de groupe initiale. Puisque ces derniers groupes sont identifies par des clefsplus petites, ils contiendront donc plus de tuples et on les appellera des super-groupes. Donc unesous-clef de groupe genere des super-groupes.
En fait Cube et RollUp peuvent ne porter que sur une partie des clefs de groupre, par exemple lesdeux 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 parenthesesapres Cube, y compris l’ensemble vide : si la clef de groupe contient n expressions alors Cubegenerera les 2n ensembles de groupes correspondant chacun a une des 2n sous-clef de groupe.Si on se place dans le cas ou la clef de groupe comporte trois expressions x, y et z, cube introduiteffectivement les huit sommets d’un cube en trois dimensions.
Pour la requeteles 8 sous-clefs de groupesont :
qu’on peut voir comme les huit som-mets d’un cube :
select Max (d)
from ...
group by
Cube (a,b,c);
(a b c)(a b) (a c) (b c)(a) (b) (c)
()
{} {a}
{ac}{c}
{b}
{bc}
{ab}
{abc}
Q. 94 Si la clef de groupe comportait deux expressions entre parentheses, a quoi correspondraitcube ? et si elle en comportait quatre ?.
Q. 95 Combien de lignes produisent les requetes suivantes (dual contient une seule ligne) :
6.3. GROUP BY CUBE ET GROUP BY ROLLUP 57
select 1 from dual group by ’a’, ’b’, ’c’ ;
select 1 from dual group by ’b’, cube (’a’, ’c’) ;
select 1 from dual group by cube (’a’, ’b’, ’c’) ;
Q. 96 Sur le modele de la requete precedente, ecrire une requete qui imprime toutes les combi-naisons des trois lettres a, b et c (voir la fonction grouping section 6.3.3).
Q. 97 A quoi peut bien servir la possibilite de construire des cubes.
RollUp explore tous les prefixes de la clef de groupe entre parentheses : si la clef de groupe contientn expressions alors rollUp generera n + 1 sous-clefs de groupe.
Pour la requeteLes 4 sous-clefs de groupe seront les quatreprefixes possibles de abc :
select Max (d)
from ...
group by x, RollUp (a, b, c) ;
(x a b c) (x a b) (x a) (x)
rollUp correspond a une exploration purement hierarchique des groupes en super-groupes etpermet donc d’afficher des super-totaux. Par exemple (a b c) regroupe les individus qui ontla meme valeur en (a b c) ; l’intersection de deux groupes (a b c) differents est evidemmentvide. (a b) regroupe les individus qui ont la meme valeur en (a b), ainsi le groupe (a b) devaleur (v w) sera l’exacte union des groupes (a b c) dont (a b) = (v w) et c est quelconque :on voit bien se dessiner une hierarchie.
On constate donc que rollUp produit un sous-ensemble des sous-clefs produites par Cube.
Q. 98 Dans quel cas unique, cube et rollUp sont-ils equivalents ?
Q. 99 Dessiner comment group by rollup (CDM, CDP) groupe les lignes suivantes :
ENVOICDC CDP CDM QTE
1 A1 B1 C1 22 A1 B1 C4 73 A2 B3 C1 44 A2 B3 C4 55 A2 B3 C5 66 A3 B3 C1 27 A3 B4 C2 58 A5 B1 C4 39 A5 B2 C2 2
10 A5 B2 C4 111 A5 B3 C4 712 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 requetes suivantes (dual contient une seule ligne) :
select 1 from dual group by ’a’, ’b’, ’c’ ;
select 1 from dual group by ’b’, rollup (’a’, ’c’) ;
select 1 from dual group by rollup (’a’, ’b’, ’c’) ;
6.3.3 La fonction grouping
Pour exploiter correctement les regroupements fournis par cube et rollup, il est necessaire, dansla clause select de savoir a quel sous-clef de groupe on a affaire. C’est a cela que sert la fonction
58 CHAPITRE 6. COMPLEMENTS ORACLE SQL
grouping ().
Dans les clauses select et having la fonction de groupe�� ��grouping ( <expr> ) renvoie 0 si l’ex-
pression en parametre fait partie de la sous-clef du groupe actuellement traite et renvoie 1 sinon.L’expression
�� ��<expr> doit evidemment faire partie de la clef complete de groupe.
6.3.4 Exemples de RollUp
Si, en plus de la moyenne par matiere, on veut aussi voir la moyenne generale (c’est a dire la moyennede toutes les notes et non pas la moyenne des matieres) on insere simplement un RollUp et dansle select on teste la presence de l’expression Matiere.nom dans la sous-clef du groupe en cours detraitement :
select decode (grouping (Matiere.nom),
0, Matiere.nom, -- sous-clef = (Matiere.nom)
’Moyenne generale’) -- sous-clef = ()
as Matiere,
sum (Note.note) / count (*) as Moyenne
from Note
inner join Matiere on Matiere.id = Note.matiere
group by rollup (Matiere.nom) ;
MATIERE MOYENNE
------------------------
BD 13
CL 13
SSM 14
Moyenne generale 13.2
Ici, on utilise la fonction grouping() pour choisir le bon libelle de la premiere colonne. En revanche,la formule de calcul de la deuxieme colonne est la meme pour tous les super-groupes : c’est la moyennede toutes les notes des etudiants (ceci explique que la moyenne generale ne soit pas egale a la moyennedes moyennes de matiere).
Maintenant, on veut en plus voir les notes individuelles de chaque etudiant :
select
decode (grouping (Matiere.nom) + grouping (Etudiant.nom),
2, ’Moyenne generale’, -- sous-clef = ()
1, Matiere.nom, -- sous-clef = (Matiere.nom)
0, Matiere.nom || -- sous-clef = (Matiere.nom, Etudiant.nom)
’ et ’ || Etudiant.nom)
As libelle,
sum (Note.note) / count (*)
AS Note_ou_Moyenne
from Etudiant
inner join Note on Note.etudiant = Etudiant.id
inner join Matiere on Matiere.id = Note.matiere
group by rollup (Matiere.nom, Etudiant.nom) ;
LIBELLE NOTE_OU_MOYENNE
----------------------------
BD et Prevert 13
BD 13
CL et Prevert 12
CL et Sartre 15
CL et Vian 12
6.3. GROUP BY CUBE ET GROUP BY ROLLUP 59
CL 13
SSM et Prevert 14
SSM 14
Moyenne generale 13.2
Une impression plus fine qui malheureusement n’est pas possible :
select decode (grouping (Matiere.nom),
1, ’Toutes les matieres’ || count (distinct Matiere.nom),
Matiere.nom)
AS Matiere,
decode (grouping (Etudiant.id),
1, ’Tous les etudiants’,
Etudiant.id)
AS etudiant,
count (*) as effectif
from Etudiant, Note, Matiere
where Etudiant.id = Note.etudiant AND
Note.matiere = Matiere.id
group by ROLLUP (Matiere.nom, Etudiant.id) ;
NON car ORA-30480 : L’option distinct n’est pas autorisee avec group by cubeou rollup.
6.3.5 Exemples de Cube
Pour voir la difference entre Cube et RollUp, on reprend la derniere requete en remlacant le rolluppar un cube et on modifie la clause select en consequence :
select
decode (grouping (Matiere.nom) + 2 * grouping (Etudiant.nom),
3, ’Moyenne generale’, -- sous-clef = ()
2, Matiere.nom, -- sous-clef = (Matiere.nom)
1, Etudiant.nom, -- sous-clef = (Etudiant.nom)
0, Matiere.nom || -- sous-clef = (Matiere.nom, Etudiant.nom)
’ et ’ || Etudiant.nom)
As libelle,
sum (Note.note) / count (*)
AS Note_ou_Moyenne
from Etudiant, Note, Matiere
where Etudiant.id = Note.etudiant AND
Note.matiere = Matiere.id
group by cube (Matiere.nom, Etudiant.nom) ;
LIBELLE NOTE_OU_MOYENNE
----------------------------
BD et Prevert 13
BD 13
CL et Prevert 12
CL et Sartre 15
CL et Vian 12
CL 13
SSM et Prevert 14
SSM 14
Prevert 13
60 CHAPITRE 6. COMPLEMENTS ORACLE SQL
Sartre 15
Vian 12
Moyenne generale 13.2
6.3.6 Selectionner les super-groupes
La clause having et la fonction grouping() permettent d’eviter l’edition de certains super-groupes.Par exemple si on veut que seuls les super-groupes correspondant a des sous-clefs contenant l’expressionMatiere.nom soient edites :
select decode (grouping (Matiere.nom),
0, Matiere.nom,
’Impossible !!!’) as Matiere,
decode (grouping (Etudiant.nom),
0, Etudiant.nom,
’Moyenne promotion’)
AS etudiant_ou_promotion,
sum (Note.note) / count (*) as Moyenne
from Etudiant
inner join Note on Note.etudiant = Etudiant.id
inner join Matiere on Matiere.id = Note.matiere
group by rollup (Matiere.nom, Etudiant.nom)
having grouping (Matiere.nom) = 0 ;
MATIERE ETUDIANT_OU_PROMOTION MOYENNE
-------------------------------------
BD Prevert 13
BD Moyenne promotion 13
CL Prevert 12
CL Sartre 15
CL Vian 12
CL Moyenne promotion 13
SSM Prevert 14
SSM Moyenne promotion 14
Les premieres expressions du group by peuvent etre en dehors du cube ou du rollUp, elles fontalors partie de toutes les sous-clefs de groupe. La requete suivante est plus simple et donne le memeresultat que la precedente :
– par matiere et etudiant– par matiere et promotion
select Matiere.nom as Matiere,
decode (grouping (Etudiant.nom),
0, Etudiant.nom,
’Moyenne promotion’)
AS etudiant_ou_promotion,
sum (Note.note) / count (*) as Moyenne
from Etudiant
inner join Note on Note.etudiant = Etudiant.id
inner join Matiere on Matiere.id = Note.matiere
group by Matiere.nom, rollup (Etudiant.nom) ;
6.4. REQUETES HIERARCHIQUES, ORDRE PREFIXE ET MONO-TABLE 61
6.4 Requetes hierarchiques, ordre prefixe et mono-table
Quand les nuplets d’une table decrivent une structure de donnees hierarchique (ou plus generalementun graphe sans cycle), Oracle permet, grace aux requetes dites hierarchiques, l’exploration prefixeedes nuplets de cette structure.
Voici une table decrivant une telle hierarchie :
create table Employe (
id Number (5),
nom Varchar2 (20),
superieur Number (5),
constraint Employe_PK primary key (id),
constraint Mon_Superieur_PK foreign key (superieur) references Employe (id)
) ;
Une requete hierarchique est caracterisee par les deux clauses start with et connect by dont voicila syntaxe et la semantique :
select e.id, e.nom from Employe e where <condition>
start with <condition identifiant la (les) ligne(s) jouant le role de
racine(s) de la (des) hierarchie(s). Sous-requetes possibles>
connect by <condition etablissant la parente entre une ligne mere et ses
lignes enfants le mot clef prior identifie les colonnes de la
ligne mere. Pas de sous-requete>
La clause start with est optionnelle, la clause connect by est obligatoire. Si start with est absentealors toutes les lignes de la table sont utilisee en tant que racine.
Les nuplets de la hierarchie sont parcourus en ordre prefixe a partir du nuplet racine (la racine estprise avant ses enfants).
La clause where ne fait que retenir ou non les lignes produites par la requete hierarchique, maisne modifie pas l’ensemble des nuplets selectionnes par la requete hierarchique (where agit apres laproduction hierarchique des nuplets).
Une requete hierarchique ne peut pas fonctionner sur une jointure : il ne peut y avoir qu’une seuletable dans la clause from.
Suivent quelques exemples.
Exploration strictement hierarchiqueIci on liste l’employe 1 ainsi que tous ses subordonnes directs ou indirects :
select e.nom, e.id, e.superieur from Employe e
start with e.id = 1
connect by prior e.id = e.superieur ;
-- !!!!! * * ces 2 ’e’ sont ceux de deux lignes DIFFERENTES
Attention, les differents e de start with et connect by designent des nuplets differents :– dans start with, e est le nuplet racine de la hierarchie en cours d’exploration (c’est le premier
pere),– dans connect by, e.id est une colonne d’un nuplet de la hierarchie dont on recherche les fils car
il est qualifie de prior, en revanche, e.superieur est une colonne d’un nuplet dont on cherche asavoir s’il est un fils du nuplet prior.
Le nuplet prior est necessairement un descendant d’une des racines determinees par la clause startwith.
Mise en forme de l’affichageOn souhaite montrer le niveau hierarchique de chaque employe en indentant son nom en fonction dela profondeur de sa position dans la hierarchie.
62 CHAPITRE 6. COMPLEMENTS ORACLE SQL
Pour cela la valeur de la pseudo-colonne�� ��level est la distance a la racine plus 1 (pour la racine,
level vaut 1). La requete precedente pourrait alor s’ecrire :
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 hierarchie
6.4.1 Exploration d’un graphe sans cycle
Cet exemple est tres artificiel !
select e.id, e.nom
from Employe e
start with e.id = 1
connect by prior e.id < e.id ;
Autrement dit un employe est le pere ou l’ancetre de tous les employes qui ont un id strictementsuperieur au sien.
Deuxieme partie
Developpement serveur
63
Chapitre 7
Introduction a PL/SQL
PL/SQL = Programming Language with SQL.
Langage de programmation procedural inspire de Ada.
Langage proprietaire (Oracle), mais la norme SQL3 s’en inspire.
Permet d’inclure facilement des requetes SQL.
Ce langage est utilise :
Cote serveur pour definir des objets proceduraux eventuellement persistants :
– blocs d’instructions anonymes et non persistants– procedures, fonctions et paquetages stockes (donc persistants),– des paquetages (eux aussi persistants),– triggers (reflexes, ou declencheur : base de donnee actives)
Cote client pour developper le code des interfaces graphiques (Developper 2000 par exemple).
L’interet des sous-programmes stockes est qu’il sont executes sur le serveur de donnees et qu’il sontdonc proches de la base de donnees qu’ils exploitent : leurs traitements seront donc plus efficaces ques’ils etaient executes cote client.
7.1 Acces aux donnees : uniquement les ordres DML
Les ordres DML (insert, update et delete) s’ecrivent comme en SQL dans le source PL/SQL, onpeut meme y faire figurer des variables et des parametres du programme PL/SQL.
La seule exception concerne select qui, etant une expression, renvoie une valeur qu’il faudra affectera une variable PL/SQL avec la nouvelle clause obligatoire : into.
7.2 Les types de donnee disponibles en PL/SQL
On dispose des types :SQL Number, Varchar, Date, types objets (tous les types SQL)
PL/SQL Boolean, Positive, Natural, PositiveN, NaturalN, . . .definis par l’utilisateur (PositiveN et NaturalN ⇒ is not nul),types composes (record, tableau)
Si on a besoin d’effectuer des calculs numeriques, on a interet a utiliser les types numeriques specifiquesa PL/SQL car ils ont des representations plus adaptees.
Le N des types PositiveN et NaturalN indique que les valeurs ne peuvent pas etre indefinies (is null).
64
7.3. FONCTION STOCKEE 65
Tous les types SQL sont utilisables en PL/SQL, y compris ceux definis par le programmeurdans le contexte du relationnel-objet.
7.2.1 Exemples de declaration de variable
Num NUMBER (4) ; -- ’Num is null’ est une expression PL/SQL correcte
En_Stock Boolean := False ;
Limite constant Real := 5000.0 ;
Par defaut, les variables sont indefinies. On peut leur appliquer l’operateur is [not] null.
7.2.2 Le type Boolean
Il est muni des deux valeurs true et false.
Attention : le type Boolean n’existe pas dans le SQL d’Oracle, la consequence est qu’une fonctionbooleenne ne pourra etre utilisee nulle part dans un ordre DML, meme pas dans la clause where. Ellepourra seulement etre utilisee par un autre programme PL/SQL.
7.2.3 Les expressions
Les operateurs SQL sont disponibles en PL/SQL, par exemple le predicat is [not] null.
7.2.4 Les connecteurs logiques and et or
Contrairement a Ada, les connecteurs logiques and et or sont a court-circuit (il n’y a donc pas enPL/SQL d’operateur and then ou or else).
7.3 Fonction stockee
Une premiere fonction qui montre que PL/SQL est effectivement un langage de programmation :
create or replace function pgcd (a in PositiveN, b in PositiveN) return PositiveN is
-- On ne peut pas modifier les parametres "in" (comme en Ada).
ia PositiveN := a ; ib PositiveN := b ;
-- PLS_Integer PositiveN Natural ... + efficaces que Number pour calculer
begin
while ia <> ib loop -- <>, !=, ~=, ^=
if ia < ib then ib := ib - ia ;
else ia := ia - ib ;
end if ;
end loop ;
return ia ;
end pgcd ;
/
Le / indique a SQL/PLUS la fin du texte du sous-programme (ou du paquetage) qui est compile etstocke immediatement.
Un appel a une fonction est une expression ou un bout d’expression, il est donc possible, pour testerla fonction, d’en faire figurer un appel dans la clause select d’une requete :
SQL> select pgcd (7, 21) from Dual ; -- Dual : table predefinie d’une ligne
66 CHAPITRE 7. INTRODUCTION A PL/SQL
7.4 Procedure stockee
create or replace procedure ajouterClient
(id in Client.id%type, Nom in Client.nom%type) is
begin
insert into Client (id, Nom) values (id, Nom) ;
end ajouterClient ;
La notation id in Client.id%type s’appelle un typage implicite : le parametre id a le meme typeque la colonne id de la table Client, cela garantit une bonne coherence avec la table manipulee etoffre une meilleure lisibilite.
Le typage implicite est aussi utilisable pour les variables locales.
On voit aussi que les parametres de la procedure s’utilisent tout naturellement dans le insert.
7.4.1 Executer une procedure stockee
Elle pourra etre appelee dans tout autre sous-programme ou trigger ou dans un bloc anonyme :
SQL> begin ajouterClient (5, ’Tartempion’) ; end ; -- bloc anonyme
ou directement avec l’instruction call :
SQL> call ajouterClient (5, ’Tartempion’) ; -- marche sous JDBC et SQL*PLUS
ou encore avec l’ordre Execute de SQL/PLUS :
SQL> Execute ajouterClient (5, ’Tartempion’) -- marche sous SQL*PLUS
7.4.2 Une autre procedure : equilibrage des salaires
On peut vraiment se demander l’interet de la procedure ajouterClient precedente : elle ne realisepas vraiment un algorithme. L’interet d’une procedure est de realiser un algorithme correspondant aune operation plus ou moins complexe necessitant en general plusieurs acces a la base de donnees.
Par exemple, on veut automatiser le traitement social suivant : tous les employes ayant un salairesuperieur a un seuil passe en parametre voient leurs salaires ramenes a ce seuil. Le total de salaireainsi retranche est ensuite reparti equitablement entre tous les employes :
create or replace procedure Repartir (Seuil in Employe.salaire%type) is
total_a_repartir Employe.salaire%type ;
nb_employes NaturalN ;
begin
select Sum (case
when e.salaire > Repartir.Seuil then e.salaire - Repartir.Seuil
else 0
end), count (*)
INTO total_a_repartir, nb_employes
from Employe e ;
if total_a_repartir is not null and total_a_repartir <> 0 then
update Employe
set salaire = Repartir.Seuil
where salaire > Repartir.Seuil ;
update Employe
set salaire = salaire + total_a_repartir / nb_employes ;
end if ;
end Repartir ;
La clause into de la requete est obligatoire, elle permet d’affecter aux variables PL/SQL les valeursdes colonnes de l’unique ligne produite. Exception si 0 ou plus d’une ligne.
7.5. BLOC ANONYME 67
Q. 102 Pourquoi teste-t-on l’etat de definition de total a repartir?
Q. 103 Reecrire les deux update en un seul.
7.5 Bloc anonyme
Un bloc anonyme est compile, execute immediatement puis oublie.
La forme generale d’un bloc est :
bloc ::= [ declaredeclaration de variables, sous-programmes, . . .]
beginsequence d’instruction
[ exceptiontraitements d’exception ]
end ;
Les blocs sont bien pratiques pour tester vite fait des sous-programmes, et ils n’ont probablement pasd’autre utilite ! Par exemple, si on veut tester les deux sous-programmes precedents :
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 recursive (eventuellement croisee) et l’emboıtement de sous-programmes.
7.7 Modes des parametres formels : in (par defaut), out, in out
procedure Solde_De (id in NUMBER, Solde out Natural) is
Ou plutot :
procedure Solde_De (id in Client.id%type, Solde out Natural) is
Le type d’un parametre formel ne peut pas etre contraint, par exemple on ne peut pas definir unparametre par
�� ��Nom in VARCHAR (20) .
Les parametres peuvent etre de mode in, in out ou out et sont de mode in par defaut.
7.7.1 Passage sans copie : nocopy
Par defaut les parametres out et in out sont passes par copie. Pour demander le passage par adresseon utilise l’indication nocopy :
declare
type Platoon is Varray (200) of Soldier;
procedure reorganize (My_Unit in out nocopy Platoon) IS
68 CHAPITRE 7. INTRODUCTION A PL/SQL
Ceci n’est qu’une indication (hint) : le compilateur peut quand meme choisir le passage par copie.
Suivant que les parametres sont passes par copie ou par adresse, l’effet peut-etre tres different quand lesous-programme se termine par une exception non traitee. Lors d’un passage par copie, si la proceduremodificatrice (ici Incr_Copie) est abandonnee par une exception, les modifications des parametresformels ne sont pas reportees sur les parametres effectifs, comme le montre l’exemple suivant :
create or replace package Global is
Mon_Exception exception ;
end Global ;
create or replace procedure Incr_Copie (i in out Natural) is
begin
i := i + 1 ;
raise Global.Mon_Exception ;
end Incr_Copie ;
create or replace function Test_Copie (i in Natural) return Natural is
vi Natural := i ;
begin
begin
Incr_Copie (vi) ;
exception
when Global.Mon_Exception then
null ;
end ;
return vi ;
end Test_Copie ;
select Test_Copie (3) from Dual ;
TEST_COPIE(3)
----------------------------
3
En revanche si le passage se fait par adresse (nocopy), alors les modifications des parametres effectifsseront effectives :
create or replace procedure Incr_Adresse (i in out nocopy Natural) is
begin
i := i + 1 ;
raise Global.Mon_Exception ;
end Incr_Adresse ;
create or replace function Test_Adresse (i in Natural) return Natural is
vi Natural := i ;
begin
begin
Incr_Adresse (vi) ;
exception
when Global.Mon_Exception then
null ;
end ;
return vi ;
end Test_Adresse ;
select Test_Adresse (3) from Dual ;
7.8. TYPES COMPOSES : LES RECORDS 69
TEST_ADRESSE(3)
----------------------------
4
7.8 Types composes : les records
type Duree is record (h SmallInt, m SmallInt) ;
type Reunion is record (debut Date, d Duree, lieu VarChar2 (20)) ;
Dans un record, il n’y a que des types simples s’il doit correspondre a un nuplet d’une table rela-tionnelle, sinon on peut avoir des composants eux-memes composes (comme c’est le cas ici pour lecomposant d de Reunion qui est lui-meme un record).
Si on declare la variable R Reunion ; on pourra acceder a ses champs par une notation pointee, parexemple R.d.h pour manipuler le nombre d’heures de la duree.
7.9 Types composes : les collections
Les elements d’une collections sont tous du meme type et sont accessibles par leurs indices (entiers)
7.9.1 Tables a acces associatif (index-by)
En fait il s’agit de table de correspondance (les map de Java) pouvant etre indicees par des nombresou des chaınes de caracteres.
type <type_name> is table of <element_type> [not null] index by Binary_Integer;
7.9.2 Tables (emboıtees nested) a trous
Elles sont indicees par des entiers et peuvent contenir un nombre quelconque d’elements.
type <type_name> is table of <element_type> [not null] ;
Par exemple avec une initialisation litterale :
type Point is record (
X Number (5),
Y Number (5)
) ;
type Des_Points is table of Point ;
P Des_Points ;
P3 Des_Points := Des_Points((0, 0), (1, 0), (0, 1)) ;
nested tables (relationnel-objet) : indicees a partir de 1 et sa taille peut augmenter dynamiquement.peut comporter des trous lorsqu’on en a supprime des elements avec la methode delete : P.delete (3)
supprime l’element d’indice 3 et cree un trou : P.exists (3) devient faux. (la methode next permetde sauter les trous). Une nested table peut correspondre a la valeur d’un attribut de tables (relationnel-objet).Quelques methodes applicables a une table P :– P.count nombre d’elements contenus dans la collection : ne compte pas les trous (elements detruits
par exemple).– P.exists (i) vrai si le i-ieme element de la collection existe (pas un trou).– P.first et P.last sont indefinis si la collection est vide, sinon l’indice du premier/dernier element.– P.next (i) et P.prior (i) renvoient, a partir de la i-ieme case, l’indice de la prochaine/precedente
case garnie ou null si cette case n’existe pas.
70 CHAPITRE 7. INTRODUCTION A PL/SQL
– consultation du X de l’element d’indice 2 : P (2).X
– P.delete supprime tous les elements de P
– P.delete (3) supprime le troisieme element de P
– P.extend allonge la table P d’un element indefini, P doit avoir ete initialisee au prealable– P.extend (15) allonge la table P de 15 elements indefinisException : COLLECTION_IS_NULL, SUBSCRIPT_OUTSIDE_LIMIT.On peut voir une utilisation interessante de ces tables en section 7.21.1.
7.9.3 Tableaux dense : Varray
Vecteur de taille variable mais bornee lors de la declaration du type. Le premier indice vaut 1 et ledernier varie entre 0 et la taille maximum. Un VARRAY est toujours dense et conserve son indicagememe apres stockage dans une table (contrairement aux nested tables).
type <type_name> is {VARRAY | VARYING ARRAY} (<size_limit>)
OF <element_type> [not null];
En pratique on prefere les Varray pour les petites collections.
7.10 Les objets
Les types objets sont declares au niveau SQL, mais sont utilisables en PL/SQL. Nous les verrons plustard !
7.11 Typage implicite : %type et %rowtype
On peut demander que le type d’une variable ou d’un parametre soit le meme que celui d’une autrevariable, d’une colonne de table, de vue ou de curseur (attribut %type) ou du meme type recordque le record correpondant a un nuplet d’une table, d’une vue ou d’un curseur (attribut %rowtype).Curseurs : voir section 7.15 page 74.
un_client Client%rowtype ;
prenom Client.prenom%type ; -- Tuple de la table "Client"
nom un_client.nom%type ; -- Le record "un_client"
7.12 Structures de controle
if <predicat> then ... {elsif <predicat> then ...} [else ... ] end if ;
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 egal a <expr-ctr> est pris,
-- si aucun ’when’ on prend le ’else’
loop ... exit [when <predicat>] ; ... end loop ;
while <predicat> loop ... end loop ;
for V in [reverse] Min..Max loop ... end loop ;
-- L’intervalle Min..Max est evalue avant de commencer le for avec les
-- valeurs courantes de Min et Max. L’intervalle ne change pas, meme
-- si la boucle modifie Min ou Max, comme en Ada.
7.13. RESULTATS DE COMPILATION 71
Attention : l’ordre exit permet de continuer l’execution apres la boucle qui le contient (equivalentdu break de C et Java) : exit ne termine pas le sous-programme !
Quand un predicat est indefini (is null), l’aiguillage se fait comme si le predicat etait faux. Parexemple, si la condition d’un exit when est indefinie, on reste dans la boucle ( !).
7.13 Resultats de compilation
Pour voir les erreurs de compilation eventuelles sous SQL*PLUS :
show errors ; -- Commande SQL*PLUS : messages d’erreur de compilation
-- ou bien en accedant directement a la bonne vue du dictionnaire :
select * from user_errors ;
Pour voir les noms, types et etats de validite 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 ;
Et pour fabriquer les commandes permettant de faire le menage :
-- Fabriquer les commandes pour faire le menage
select ’drop ’ || Object_Type || ’ ’ || Object_Name || ’;’
from user_objects where Object_Type<>’INDEX’ ;
7.14 PL/SQL et le DML (select, insert, update, delete)
Il est tres facile d’integrer des ordres DML dans un programme PL/SQL : a chaque ordre DML cor-respond une instruction PL/SQL ayant exactement la meme syntaxe, (sauf pour l’instruction select,voir plus loin)
create procedure Augmenter (Categorie in Employe.categorie%type,
Augmentation in PositiveN) is
begin
update Employe set salaire = salaire + Augmentation
where categorie = Augmenter.Categorie ;
end Augmenter ;
On voit que les valeurs des parametres (ou des variables) PL/SQL s’utilisent tres naturellement dansl’ecriture de l’ordre DML.
7.14.1 Select expressions into variables PL/SQL from . . .
L’instruction select introduit la clause obligatoire into permettant d’affecter le resultat de larequete a des variables du programme.
create function Pourcentage (S in Employe.sexe%type) return Number is
total NaturalN ;
personnes NaturalN ;
begin
select count (*), count (case when sexe=Pourcentage.S then 1 else null end)
into total, personnes
from Employe ;
return (personnes / total) * 100 ;
end Pourcentage ;
72 CHAPITRE 7. INTRODUCTION A PL/SQL
7.14.2 Les exceptions de select into
La valeur d’une requete select into doit avoir exactement une ligne puisque la variable PL/SQL doitrecevoir exactement une valeur.sinon une exception predefinie sera declenchee :
exception signification
No_Data_Found si la requete n’a aucune ligne.Too_Many_Rows si la requete a plus d’une ligne.
Attention : No_Data_Found est gommee par un test fait dans la requete :
select <fonction-a-tester> from Dual ;
Si la fonction echoue avec l’exception No_Data_Found, celle-ci est recuperee par le select qui donnealors un nuplet dont l’unique colonne est indefinie ! En revanche No_Data_Found est bien visible quandon teste avec un bloc anonyme.L’exemple precedent (Nb_Employe) ne pose pas ce probleme car un select count (*) sans groupby 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 selectionne,
-- Too_Many_Rows si plus d’un nuplet est selectionne.
...
si aucun ou plus d’un client s’appelle toto.
Donc, pour s’assurer qu’un seul client s’appelle ’toto’ avant de le traiter, on preferera ecrire simple-ment :
declare
nb Natural ; Le_Client Client%rowtype ;
begin
begin
select * into Le_Client from Client where Client.nom = ’toto’ ;
exception
when No_Data_Found then
raise_application_error (-20111, ’Aucun client ne s’’appelle toto’) ;
when Too_Many_Rows then
raise_application_error (-20111, ’Plus d’’un client s’’appelle toto’) ;
end ;
Traiter (Le_Client) ;
end ;
raise_application_error genere une erreur SQL et arrete l’execution PL/SQL.plutot que d’ecrire la chose couteuse et compliquee suivante :
declare
nb Natural ;
Le_Client Client%rowtype ;
begin
select count (*) into nb from Client where Client.nom = ’toto’ ;
if nb = 0 then
raise_application_error (-20111, ’Aucun client ne s’’appelle toto’) ;
elsif nb > 1 then
raise_application_error (-20111, ’Plus d’’un client s’’appelle toto’) ;
end if ;
7.14. PL/SQL ET LE DML (SELECT, INSERT, UPDATE, DELETE) 73
select * into Le_Client from Client where Client.nom = ’toto’ ;
Traiter (Le_Client) ;
end ;
D’autant que le comportement de cette solution depend du niveau d’isolation de la transactionqui l’execute : en isolation read committed, le second select pourrait echouer avec une excep-tion No_Data_Found ou une exception Too_Many_Rows si, avant que cette requete ne commence sonexecution, une autre transaction a publie (par commit) une modification supprimant le client ’toto’ou ajoutant de nouveaux clients ’toto’.
7.14.3 Les noms des colonnes des tables peuvent cacher les variables/parametres
Reecrivons la fonction Nb_Employe en donnant au parametre formel le meme 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 probleme est alors dans la clause where de la requete la mention du parametre Categorie est enfait comprise comme la colonne Categorie de la table Employe1 ! Le test d’egalite vaudra toujoursvrai (sauf pour les employes dont la categorie est indefinie), et la fonction ne fait plus ce qu’elle estcensee faire.
Une solution consiste a donner aux variables et parametres PL/SQL des noms differents des noms descolonnes des tables manipulees comme cela est fait dans la premiere version de la fonction Nb_Employe.
Une autre solution, probablement plus fiable, consiste a prefixer le nom de variable ou de parametrepar le nom de la structure qui le declare, 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 ;
7.14.4 Une fonction ne devrait pas tenter de modifier la base de donnees
En general cela provoque une erreur d’execution.
Soit :
create table T (x Number(5)) ;
create or replace function F (x in Number) return Number is
begin
insert into t values (f.x) ;
return 2*x ;
1Lors de la compilation de l’instruction DML, on cherche d’abord si une des tables du from possede une colonnne dece nom avant de s’interesser aux variables locales et aux parametres.
74 CHAPITRE 7. INTRODUCTION A PL/SQL
end F ;
Un select ne peut se servir de cette fonction car elle tente de modifier la base de donnees
select f (5) from dual ;
ORA-14551: impossible d’effectuer une operation DML dans une interrogation
ORA-06512: a "DURIF.F", ligne 3
ORA-06512: a ligne 1
En revanche, dans un bloc anonyme, tout se passe bien :
declare
b number (5) ;
begin
b := f (5) ;
end ;
pour autant qu’il soit raisonnable d’avoir des fonctions a effet de bord.
7.15 Requetes a nombre inconnu de resultats : les curseurs
Toute instruction du DML (et seulement du DML) peut-etre ecrite directement a differents endroitd’un programme PL/SQL.
L’acces aux informations relatives a l’execution de ces instructions se fait soit par curseur implicitesoit par curseur explicite.
7.15.1 Curseurs explicites statiques : requete fixee a la declaration du curseur
Il s’agit ici de recuperer les nuplets fournis par une requete pouvant renvoyer un nombre quelconquede nuplets.
Un curseur explicite permet de balayer sequentiellement les nuplets obtenus par une requete. La requeteest fixee une fois pour toutes des la declaration du curseur, mais elle peut etre parametree.
Declaration :
cursor <cursor_name> [(parameter[, parameter]...)] [return <return_type>]
is <select_statement> ;
parameter ::= <parameter_name> [in] datatype [{:= | default} <expression>]�� ��Le return_type doit etre un record ou un %rowtype (un %rowtype est un record).
Deux exemples presque equivalents de curseurs sans parametre
type implicitement type explicitement
cursor Les_Nom_Prenom is
select nom, prenom
from Client
where id between 3 and 10;
type Nom_Prenom is record (
Nom Client.nom%type,
Prenom Client.prenom%type
) ;
cursor Les_Nom_Prenom return Nom_Prenom is
select nom, prenom
from Client where id between 3 and 10;
Exemple de curseur parametre avec Min et Max
cursor Les_Nom_Prenom_2 (Min in Number := 0, Max in Number := 100) is
select nom, prenom from Client where id between Min and Max ;
7.15. REQUETES A NOMBRE INCONNU DE RESULTATS : LES CURSEURS 75
C’est lors de l’ouverture du curseur (instruction open, voir 7.15.4, page 75) qu’on fixera les parametreseffectifs.
7.15.2 Comment baptiser les curseurs
Comme pour toute entite d’un programme, bien choisir le nom d’un curseur est important pour lalisibilite du programme — il n’y a rien de pire que d’appeler curseur un curseur.
Un curseur represente en fait un ensemble d’objets qu’il permet d’explorer sequentiellement et sanspossibilite de revenir sur un objet deja explore (on ne peut faire qu’avancer).
Une maniere de nommer un curseur pourrait alors etre Les_<nature des objets>, c’est exactementce qu’on a fait avec le curseur Les_Nom_Prenom.
7.15.3 Utiliser un curseur pour typer implicitement
Un curseur statique n’est pas une variable : on ne peut ni l’affecter ni le passer en parametre desous-programme. Pour faire cela il faut plutot utiliser des variables curseur, voir ?? page ??.
On peut utiliser l’attribut %ROWTYPE pour typer une variable a partir d’un curseur :
nom_prenom Les_Nom_Prenom%ROWTYPE ; -- nom_prenom.nom
7.15.4 Les operations et attributs des curseurs
Il y a trois operations :
open <curseur> [(parameter[, parameter]...)] ; c’est lors de l’ouverture qu’on fixe les va-leurs effectives des eventuels parametres formels du curseur. L’ouverture calcule immediatementle result set de la requete.
open Les_Nom_Prenom ; open Les_Nom_Prenom_2 (Max => 55) ;
fetch <curseur> into <variable> { , <variable> } ;
On peut recuperer le nuplet courant soit dans un record du meme type que le curseur soit, dansautant 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 ;
On peut ensuite reouvrir le curseur.
7.15.5 Exceptions deja attachees a des codes d’erreur Oracle
Chaque erreur Oracle possede un code d’erreur numerique.
Lorsqu’un ordre SQL embarque dans du PL-SQL provoque une erreur Oracle, cette erreur se materialisepar une exception soit anonyme, soit nommee si la configuration a associe un nom d’exception a cetteerreur Oracle.
Voici quelques-un des codes d’erreur Oracle qui sont deja associes a des exceptions predefinies :
76 CHAPITRE 7. INTRODUCTION A PL/SQL
Exception Code d’erreur Explicationpredefinie Oracle
Cursor_Already_Open -6511Dup_Val_On_Index -1 duplication d’une clef existant deja (insert ou update)Invalid_Cursor -1001No_Data_Found -1403 select ... intoToo_Many_Rows -1422 select ... into ou returning ... intoZero_Divide -1476
Value_Error non respect des intervalles numeriques, par exempleaffecter -1 dans une variable Natural
-02290 violation de contraintes check-02291 Clef etrangere : ligne referencee inexistante-02292 Tentative de suppression d’une ligne referencee
Un exemple recapitulatif ou le pragma Exception_init associe une exception a une erreur SQL :
create table Client (nom Varchar2 (20) primary key,
solde Number (5) constraint Solde_Positif check (0 <= solde)) ;
create or replace procedure Debiter (C in Client.nom%type, M in Client.solde%type) is
Insolvable exception ;
pragma Exception_init (Insolvable, -02290) ; -- violation de contraintes
begin
update Client set solde = solde - M where nom = C ;
exception
when Insolvable then
raise_application_error (-20111, ’Client ’||nom||’ non solvable’) ;
end Debiter ;
– Lorsqu’un ordre SQL echoue, Oracle genere une erreur Oracle identifiee par un numero negatifet un message approprie. Si cet ordre SQL est embarque dans du PL/SQL, alors il faut absolu-ment que le programme PL/SQL soit informe de cet echec et c’est effectivement ce qui se passe :l’erreur Oracle est automatiquement transformee en une exception PL/SQL qui pourra alors etreeventuellement traitee par le PL/SQL avec le mecanisme de traitement des exceptions.
Par exemple lors de la tentative d’insertion d’une clef dupliquee, Oracle genere l’erreur�� ��-1 , cette
erreur sera automatiquement traduite en l’exception predefinie Dup_Val_On_Index dans le codePL/SQL.
Voici un code naıf qui exploite cette exception pour trouver une clef satisfaisante (cette methodemarche mais il est clair qu’il serait deraisonnable de la mettre en exploitation !) :
SQL> create table T (id Number (5) primary key, nom Varchar2 (20)) ;
create procedure Ajouter (Le_Nom in T.nom%type) is
l_id T.id%type := 0 ;
begin
loop
begin
Insert into T (id, nom) values (l_id, Le_Nom) ;
exit ;
exception
when Dup_Val_On_Index then -- exception attachee a l’erreur SQL -1
l_id := l_id + 1 ;
end ;
end loop ;
7.15. REQUETES A NOMBRE INCONNU DE RESULTATS : LES CURSEURS 77
end Ajouter ;
Q. 104 Que fait la procedure Ajouter ? Est-ce une bonne idee ?
Q. 105 Que calcule la fonction F suivante :
create function F (A in NaturalN) return NaturalN is
I Natural := A ;
R Natural := 0 ;
begin
loop
begin
I := I - 1 ;
R := R + 2*I + 1 ;
exception
when Value_Error then
exit ;
end ;
end loop ;
return R ;
end F ;
Q. 106 Reecrire F pour eviter l’exception Value Error.
Tous les codes d’erreurs Oracle ne sont pas necessairement pre-attaches a une exception PL/SQL.Le programmeur PL/SQL peut alors realiser cet association grace au pragma Exception_Init (voirla section 7.15.6).
– Inversement, lorsqu’un code PL/SQL echoue a cause d’une exception non traitee, il faut que lemoteur Oracle soit informe de cet echec : l’exception PL/SQL est alors transformee en une erreurOracle (voir la section 7.15.7). Cette erreur pourra porter un numero et un message fixes par leprogrammeur PL/SQL en utilisant la procedure raise_application_error :
SQL> declare
Fonds_Insuffisants exception ;
procedure Bof is ... -- peut declencher Fonds_Insuffisants
begin
Bof ;
exception
when Fonds_Insuffisants then
raise_application_error (-20111, ’C’’est une erreur de comptabilite !’) ;
end ;
ORA-20111: C’est une erreur de comptabilite !
Si on n’utilise pas la procedure raise_application_error, c’est l’erreur Oracle -06510 qui seratransmise au moteur SQL :
begin Bof ; end ;
ORA-06510: PL/SQL : exception definie par l’utilisateur non traitee
7.15.6 Recuperer 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 mecanisme des exceptions.De plus, avec le pragma Exception_Init, on peut associer explicitement une exception a un coded’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 A 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 propagees vers SQL sous forme d’erreurs Oracle
Quand une exception n’est pas traitee par le code PL/SQL, elle est propagee vers oracle sous la formed’un code d’erreur accompagne d’un message :
declare
Mon_Exception exception ;
begin
raise Mon_Exception ;
end ;
ORA-06510: PL/SQL : exception definie par l’utilisateur non traitee
ORA-06512: a ligne 4
Propager un code d’erreur et un libelle explicites
Il est possible de choisir le code d’erreur et le message avec la procedure raise_application_error :
SQL> begin raise_application_error (-20101, ’employe encore a l’’essai’) ; end ;
ORA-20101: employe encore a l’essai
ORA-06512: a ligne 2
[−20999,−20000] est l’intervalle des numeros d’erreurs utilisables par le programmeur.
7.16 Les paquetages
Pour regrouper des types, des exceptions, des sous-programmes et des variables globaux.
La surcharge des noms de sous-programmes est possible.
Comme en Ada, on distingue la declaration de paquetage qui definit des entites utilisables de l’exterieuret le corps de paquetage qui implemente les sous-programmes annonces dans la declaration de paque-tage et peut definir ses propres entites privees (non visibles de l’exterieur du paquetage).
create or replace package Gestion_Client is
function nombre (bout in VARCHAR2) return Number ;
procedure ajouter (nom in Client.nom%type, prenom in VARCHAR2 := ’--’) ;
end Gestion_Client ;
create or replace package body Gestion_Client is
courant number (4) ;
7.17. PL/SQL ET L’INTERACTION HOMME-MACHINE 79
function nombre (bout in VARCHAR2) return Number is
nb Number (6) ;
begin
select count (*) into nb
from Client where upper(nom) like modele ’%’ || upper(bout) || ’%’ ;
return nb ;
end nombre ;
procedure ajouter(nom in Client.nom%type, prenom in VARCHAR2 := ’--’) is
begin
insert into Client values (courant, nom, prenom) ;
courant := courant + 1 ;
end ajouter ;
begin -- Initialisation des variables du paquetage
-- Sequence executee une et une seule fois, a chaque debut de session.
select nvl (max (id), 0) + 1 into courant from Client ;
end Gestion_Client ;
Q. 107 Aucune des exceptions No Data Found ou Too Many Rows ne peut etre declenchee par le selectde l’initialisation du paquetage, pourquoi ?
Remarques importantes : les variables globales ont une persistance limitee a la duree de la session :a chaque debut de session elles sont reinitialisees.De meme, la partie initialisation du corps de paquetage est executee une et une seule fois au debut dechaque session.
7.17 PL/SQL et l’interaction homme-machine
Un programme PL/SQL est fait pour travailler au cœur de la base de donnees, c’est a dire que saufpeut-etre en phase de test, il est destine a etre execute dans un environnement sans interaction homme-machine (on pourrait dire qu’il est execute en batch ou encore off-line).
C’est probablement pourquoi il est tellement penible de faire, en PL/SQL, de l’interaction homme-machine, meme de facon tres primitive.
Le paquetage predefini DBMS_OUTPUT permet, sous SQL*Plus, d’ecrire des messages dans une tablegeree 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’execution les messages de la table soient affiches a l’ecran il faut le demander aSQL*Plus :
SQL> set serveroutput on
SQL> Execute Imp_Pgcd (45, 129) ;
Pgcd = 3
Tous les messages du programme sont donc affiches d’un seul coup lorsque ce dernier se termine.
7.18 Les tables mutantes ne peuvent etre consultees ou modifiees
Pendant l’execution d’un ordre la modifiant (insert, update ou delete), une table est dite mutantec’est a dire que son etat est instable. Oracle interdit alors de modifier ou consulter cette table par
80 CHAPITRE 7. INTRODUCTION A PL/SQL
l’execution un ordre DML emboıte dans le premier.
Si on tente de le faire alors Oracle, logiquement, fait avorter l’ordre principal avec l’erreur ORA-04091.
Soit la fonction parfaitement correcte :
create table Etudiant (note Number (4, 2)) ;
insert into Etudiant values (15.0) ;
create function Moyenne return Etudiant.note%type is
M Etudiant.note%type ;
begin
select AVG (e.note) into M from Etudiant e ;
return M ;
end Moyenne ;
Si on tente d’executer :
delete from Etudiant where note < Moyenne ;
ORA-04091: la table ETUDIANT est en mutation ; la fonction ne peut la voir
ORA-06512: a "MOYENNE", ligne 4
La table Etudiant n’etant pas vide, Oracle declenche une erreur de table mutante, et c’est tant mieux !En effet ce delete n’aurait aucun sens s’il etait effectivement execute puisqu’apres la suppression d’unetudiant la fonction Moyenne ne renverrait probablement pas la meme valeur et ainsi ce delete neserait pas equitable pour tous les etudiants.
Q. 108 Reecrire correctement cette fonctionalite sous forme d’une procedure PL/SQL.
Q. 109 Pouvez-vous expliquer pourquoi le delete suivant fonctionne correctement ?
delete from Etudiant e
where e.note < (select AVG (note) from Etudiant) ;
7.19 Question de style, de surete et d’efficacite
Utiliser un traitement d’exception pour prendre en compte les erreurs de parametre pour les sous-programmes stockes donne un code souvent plus efficace et plus clair.
Par exemple, voici deux tables :
create table Mere (id_mere Number (5) primary key) ;
create table Fils (
id_fils Number (5) primary key,
id_mere Number (5) references Mere (id_mere)
) ;
La table Fils comporte une clef etrangere vers la table Mere.
Lors de l’insertion d’une ligne dans Fils, la pratique courante consiste souvent a verifier d’abord,grace a une requete la presence de la clef dans Mere.
Or ceci est inutile, car lors de l’insertion, si la clef etrangere n’apparaıt pas dans Mere alors Oracledeclenche l’erreur -02291 (touche parent introuvable) : il suffit de recuperer cette erreur sous formed’une exception.
Voici deux versions de la procedure d’ajout d’un fils qui ont, grosso modo, le meme comportement(sauf si on se place dans un contexte multi-transactionnel) :
7.20. RECUPERATION DES VALEURS PRODUITES PAS LE SGBD (DML RETURNING) 81
version 1 version 2
create procedure Ajouter_Fils (
f in Fils.id_fils%type,
m in Mere.id_mere%type)
is
begin
if m is not null then
-- Tester si la mere existe
declare
nb Natural ;
begin
select count (*) into nb
from Mere
where id_mere = m ;
if nb = 0 then
raise_application_error
(-20100, ’Mere inexistante’);
end if ;
end ;
end if ;
-- La mere existe ou est indefinie
insert into Fils values (f, m) ;
end Ajouter_Fils ;
create procedure Ajouter_Fils (
f in Fils.id_fils%type,
m in Mere.id_mere%type)
is
Mere_inexistante exception ;
pragma Exception_init
(Mere_inexistante, -02291) ;
begin
insert into Fils values (f, m) ;
exception
when Mere_inexistante then
raise_application_error
(-20100, ’Mere inexistante’);
end Ajouter_Fils ;
Q. 110 Quelle version preferez-vous ? pourquoi ?
La version 2 (avec traitement d’exception) est aussi moins complexe en terme d’ordres SQL (un seulordre au lieu de deux), elle sera certainement plus facile a prendre en compte dans un environnementtransactionnel. En particulier, si la transaction qui execute la version 1 est en read committed, il estpossible que le insert echoue bien que la verification prealable ait confirme la presence de la mere :en read committed une instruction SQL voit les modifications validees avant qu’elle ne commence :il se pourrait qu’une autre transaction detruise la mere juste apres la verification de son existence etqu’elle valide cette suppression juste avant la tentative d’insertion.
Q. 111 Ecrire une procedure stockee qui tente d’ajouter une commande d’une quantite d’un produita un client a condition que le client ait un solde suffisant pour payer toutes ses commandes.
7.20 Recuperation des valeurs produites pas le SGBD (DML retur-
ning)
Cette fonctionnalite est particulierement precieuse lors du developpement logiciel : elle simplifie leprogramme et le rend plus efficace. PostgreSQL, depuis sa version 8.2, propose lui aussi une fonction-nalite equivalente.
Lors d’une instruction DML les nouvelles valeurs d’une ligne inseree ou modifiee peuvent etre produitespar le SGBD lui-meme et donc inconnues de la procedure :
1. lors d’une insertion, la clef est obtenue grace a 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 employe, si un ensemble d’autres colonnes n’est pasaussi une clef.
2. c’est le update qui augmente le salaire :
update Employe
82 CHAPITRE 7. INTRODUCTION A PL/SQL
set salaire = salaire * 1.1
where id = 299 ;
La procedure stockee peut avoir besoin de connaıtre la nouvelle valeur du salaire, elle peut aussi vouloirconnaıtre les anciennes valeurs d’une ligne detruite.
Cela peut se faire grace a la clause returning disponible en fin de chacune des instructions DML(insert, update et delete) :
returning <expression> {, <expression>} into <variable> {, <variable>}
– lors du insertinsert into Employe (id, nom, salaire)
values (Generateur_De_Clef.nextval, ’Dupont’, 2000.0)
returning id, nom, salaire into id, nom, salaire ;
– lors du updateupdate Employe
set salaire = salaire * 1.1
where id = 299
returning salaire into nouveau_salaire ;
– lors du delete d’une ligne, on souhaite recuperer le contenu de cette ligne dans des variables de laprocedure. On pourrait ecrire :select nom, prenom into nom, prenom
from Employe
where id = 299 ;
delete from Employe
from Employee
where id = 299 ;
mais cette solution est erronee si la transaction est read committed et que la modification suivante :update Employe
set id = case when id=298 then 299 else 298 end
where id in (298, 299) ;
est validee par une autre transaction entre la requete et l’instruction delete puisque lors du delete,299 n’est plus le meme employe !
Q. 112 Comment corriger simplement ce probleme (voir le chapitre sur les transactions) ?
L’ecriture suivante est certainement bien plus elegante et fiable :delete from Employee
where id = 299
returning nom, prenom into nom, prenom ;
Seule l’exception Too_Many_Rows sera declenchee si plus d’une ligne est modifiee par l’ordre DML. Siaucune ligne n’est modifiee, les variables de into seront indefinies, l’expression SQL%rowcount s’averealors utile pour detecter ce probleme.
7.21 Amelioration des performances du code PL/SQL
Un des aspects couteux de PL/SQL est que l’execution de chaque ordre SQL demandee par PL/SQLrequiert de passer du monde PL/SQL au monde SQL.
Oracle propose (au moins) deux outils pour diminuer le nombre de passages d’un monde a l’autre :
– la structure de controle Forall qui transforme une suite d’iterations sous forme d’un travail batchqui ne necessitera qu’un seul passage d’un monde a l’autre au lieu d’autant de passage qu’il y ad’iterations dans une boucle normale,
– la clause bulk collect qui permet de recuperer d’un seul coup dans une ou plusieurs collectionsPL/SQL, un nombre inconnu a priori de lignes (mais probablement pas trop eleve) qui sont soit lavaleur d’une requete soit les nouvelles valeurs des lignes modifiees par un ordre DML muni de laclause returning ... into ....
7.21. AMELIORATION DES PERFORMANCES DU CODE PL/SQL 83
7.21.1 La clause bulk collect
Cette clause bulk collect permet de recuperer d’un seul coup dans une ou plusieurs collectionsPL/SQL :
– tous les resultats produits par une requete,– lors du fetch ... into ... d’un curseur,– ou la clause returning ... into ... d’un ordre DML.
Elle s’ecrit toujours immediatement avant le mot clef into.
Bien entendu les variables PL/SQL figurant apres into doivent alors etre des collections, par exempledes table (voir section 7.9.2).
Exemple avec select :
create or replace procedure Tranche (smin in Employe.salaire%type,
smax in Employe.salaire%type) is
type Des_Employes is table of Employe%rowtype ;
Les_Employes Des_Employes ;
begin
select * BULK COLLECT INTO Les_Employes
from Employe
where salaire between smin and smax ;
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) ;
end loop ;
end if ;
end Tranche ;
On peut aussi mettre une table par colonne fabriquee par la requete ou le curseur ou la clause retur-ning.
Exemple avec fetch, c’est a dire un curseur :
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 ;
begin
open Employes ;
fetch Employes BULK COLLECT INTO Les_Employes ;
close 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) ;
end loop ;
end if ;
end Tranche ;
Exemple avec update :
create or replace procedure Augmenter (smin in Employe.salaire%type,
84 CHAPITRE 7. INTRODUCTION A PL/SQL
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 oblige de preciser les colonnes (* ne convient pas).
7.21.2 Limiter le nombre de lignes recuperees par fetch ... bulk collect
Seulement avec un curseur (fetch) on peut specifier un nombre maximum de lignes a recuperer achaque fois, l’utilisation du curseur doit alors se faire a nouveau dans une boucle. La limite est donneeapres le mot clef limit. Voici une reprise de l’exemple precedent 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. AMELIORATION DES PERFORMANCES DU CODE PL/SQL 85
Q. 113 Ici il ne faut surtout pas utiliser Employes%notfound pour sortir de la boucle. Pourquoi avotre avis ?
Chapitre 8
Les triggers
DDL
Un trigger est un bout de code qui sera execute a chaque fois qu’un evenement particulier se produirasur une table particuliere. Un evenement correspond a la modification d’une table (insert, updateou delete).La programmation par trigger est donc une forme de programmation evenementielle.
Un trigger est une procedure compilee (en pcode) et stockee dans le dictionnaire, qui s’execute auto-matiquement chaque fois que l’evenement declenchant se produit.
Les triggers existent dans la plupart des SGBD (par exemple Oracle, PostgreSQL, MySQL 5.1 qui nepermet 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’ecrit en PL/SQL (on peut aussi utiliser C ou Java depuis Oracle 8).
Les triggers peuvent etre utilises pour garantir des proprietes que les contraintes declaratives (check)ne peuvent garantir. Un trigger qui echoue par une exception fait echouer l’ordre DML qui a provoquesont execution, la table est alors remise dans son etat d’origine.
Ils peuvent aussi servir a rendre la base plus dynamique ; par exemple, on peut grace au trigger, es-pionner les operations faites sur la table des salaires en enregistrant dans une autre table l’heure etl’identite de celui qui a tente la modification.
La programmation de triggers est une tache delicate puisqu’elle insere du code dans le fonctionnementnormal du moteur SQL.
8.1 Deux utilisations possibles des triggers
– Pour garantir qu’une propriete est verifiee, si on ne peut l’exprimer de facon declarative.L’algorithme du trigger teste la propriete, si elle est verifiee il n’y a rien d’autre a faire, si elle n’estpas verifiee le trigger appelle la procedure raise_application_error pour declencher une erreuret faire ainsi avorter l’ordre DML : la table sera automatiquement remise dans son etat initial.
Exemples :– garantir que le nombre d’etudiants inscrits a une unite d’enseignement est toujours inferieur a sa
capacite d’accueil.– garantir que le salaire d’un employe est inferieur a celui de son superieur.Attention : quand c’est possible, une contrainte declarative est toujours preferable a l’introductiond’un trigger.
– Pour automatiser des traitements lors de certains evenements, ce type de trigger permetde mettre en œuvre la notion de BD active.
Exemples :– on veut conserver la trace de toutes les modifications appliquees a 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
– creer une commande de produit a chaque fois que sa quantite en stock passe en dessous d’uncertain seuil.
8.2 Structure d’un trigger
create [or replace] trigger <Nom-du-Trigger>
<instant>
<liste-evenements> on <Nom-Table>
[for each row [when ( <Condition> ) ]]
<bloc-anonyme> ;
drop trigger <Nom-du-Trigger> ;
Le drop d’une table detruit automatiquement les triggers qui lui sont attaches.
<instant> ::= before | after
<liste-evenements> ::= <evenement> { or <evenement> }
<evenement> ::= delete | insert | update [ of <liste-colonnes> ]
<liste-colonnes> ::= <nom-colonne> { , <nom-colonne> }
8.2.1 before et after
Le trigger sera declenche avant ou apres la modification :– determiner si modification autorisee : before ou after,– si le trigger doit fabriquer une valeur a mettre dans la table : before,– si la modification doit d’abord etre terminee : after.
8.2.2 Les evenements
La liste d’evenement indique quels sont les ordres DML qui provoqueront le declenchement du trigger.On peut donner une liste de colonnes a l’evenement update. Il suffira qu’au moins une de ces colonnessoit modifiee par le update pour que le trigger soit declenche.
8.2.3 Granularite du trigger
Un trigger peut-etre destine a etre declenche soit :– exactement une fois avant (before) ou apres (after) l’execution complete de l’ordre DML l’ayant
provoque : il s’agit d’un trigger instruction voyant la BD avant toute modification si before ouapres toutes les modifications si after. Un tel trigger voit donc la BD dans un etat stable et peutdonc consulter toutes les tables y compris celle a laquelle il est attache.
– exactement une fois avant (before) ou apres (after) la modification de chaque ligne : il s’agitd’un trigger ligne. Autrement dit il sera declenche autant de fois qu’il y aura de lignes modifiees(eventuellement zero fois si aucune ligne n’est modifiee). Pour chaque ligne modifiee le trigger estexecute et dispose de l’ancienne (prefixe old) et nouvelle valeur (prefixe new) de la ligne. Un teltrigger etant execute pendant l’execution de l’instruction DML la table en cours de modificationest dans un etat instable (mutating table), le trigger ne peut donc pas la consulter (Oracle declencheune erreur SQL si on tente de le faire), en revanche il peut consulter toutes les autres tables de la BD.
PostgreSQL permet, de facon coherente, a un trigger ligne after de consulter la table en cours demodification en fait cela est coherent car les triggers ligne after ne sont declenches qu’apres que latable ait ete completement modifiee (voir la section 8.10).
trigger instruction : for each row absent
Si for each row est absente, c’est un trigger de niveau instruction DML : il sera appele exactementune fois, avant ou apres l’execution de l’instruction DML. Il n’y a alors pas de ligne courante (ni oldni new).
88 CHAPITRE 8. LES TRIGGERS
PostgreSQL a le merite de permettre de dire explicitement qu’il s’agit d’un trigger instruction avec lequalificatif for each statement. Cependant, comme en Oracle, si aucun des deux qualificatifs n’estdonne, il s’agit d’un trigger instruction.
trigger ligne : for each row present
for each row implique que le trigger est un trigger ligne, il sera declenche pour chaque tuple modifie :si on supprime 10 lignes, le trigger sera declenche 10 fois, si on supprime 0 ligne le trigger sera declenche0 fois.Dans un trigger ligne, la ligne qui fait l’objet de la modification peut etre consultee sur ses anciennesvaleurs (:old pour toute la ligne, :old.col pour une colonne particuliere) et sur ses nouvelles valeurs(:new pour toute la ligne, :new.col pour une colonne particuliere).Suivant l’instruction declenchante :old ou :new n’ont pas forcement de sens :
:old.col :new.col
insert is null valeur insereedelete valeur originale is nullupdate valeur originale nouvelle valeur ou valeur originale si pas de nouvelle valeur
:new et :old ont les memes valeurs, que le trigger soit before ou after mais une modification de :newn’aura d’effet que dans un trigger before.
:old et :new ne peuvent etre utilises que dans le bloc anonyme du trigger.
Pour insert et update, on peut reaffecter :new dans le trigger, mais seulement pour un trigger before.Un autre trigger ligne after verra les modifications apportees a :new par un trigger ligne before.
La clause when (Condition sur la ligne courante)
Uniquement pour les triggers ligne : le bloc anonyme ne sera execute que si la condition est vraie. Enparticulier si la condition du when est unknown le trigger n’est pas declenche.La condition ne peut utiliser de fonction PL/SQL ni contenir de sous-requete et on doit utiliser lesprefixes old. et new. pour acceder aux noms de colonnes de la ligne courante.L’interet de when est d’eviter le plus possible l’execution du bloc anonyme car cette execution necessitede passer du monde SQL au monde PL/SQL ce qui est couteux en temps CPU.
8.2.4 Le bloc anonyme
C’est du PL/SQL.
Pour les triggers ligne, utilisation obligatoire des prefixes :old. et :new. pour designer les colonnesen cours de modification.
8.2.5 Predicats utilisables dans le code PL/SQL
Pour l’ecriture du bloc anonyme, on dispose des predicats :
inserting deleting updating [ ( <nom-colonne> ) ]
Cela permet d’ecrire un seul trigger pour gerer plusieurs evenements.
8.3 Instants de declenchement des triggers instruction et ligne
Sur le fonctionnement decrit ci-apres on voit que les triggers instruction sont executes avant ou apresl’instruction de mise a jour, c’est a dire quand la table est dans un etat stable (non mutante).
8.4. EXEMPLES DE TRIGGERS GARANTISSANT LE RESPECT D’UNE PROPRIETE 89
En revanche, les triggers ligne sont executes pendant l’execution de l’instruction de mise a jour, c’esta dire a un moment ou la table n’est pas dans un etat stable (elle est dite mutating).
Etat stable de la table, elleest observable et modifiablepar les triggers instruction
1. Execution des triggers instruction before
2. Debut de l’instruction DML
Etat instable de la table(mutante), elle n’est ni ob-servable ni modifiable par lestriggers ligne
3. Pour chaque ligne selectionnee par la clause where
(a) Si update : calcul des valeurs new par la clause set
(b) Execution des triggers ligne before
(c) Si insert ou update, inscription de new dans la table, si delete,suppression de la ligne.
(d) Execution des triggers ligne after
4. Fin de l’instruction DML
Etat stable de la table, elleest observable et modifiablepar les triggers instruction
5. Execution des triggers instruction after
Verification des contraintes declaratives de la table (si non differees enfin de transaction)
Quand plusieurs triggers sont declenches par le meme evenement, ils sont executes sequentiellementdans un ordre quelconque.
On voit que les triggers ligne remettent en cause l’apparente atomicite des ordres DML en permet-tant d’injecter du code (celui des triggers ligne) qui sera execute pendant l’execution de l’ordre DML.
Si un trigger echoue en declenchant une erreur, quelle qu’elle soit, alors Oracle garantit que la baseest remise dans l’etat dans lequel elle etait avant l’execution de l’instruction ayant declenche ce ou cestriggers (l’effet des ces triggers est lui aussi gomme).
8.4 Exemples de triggers garantissant le respect d’une propriete
Garantir une propriete consiste a faire echouer toute modification qui casse la propriete a maintenir.Si un trigger declenche une erreur SQL, alors l’ordre DML est abandonne et la table est remise dansson etat d’origine.
Supposons que la base de donnees doive a tout moment verifier une propriete P . Si P ne peut etreexprimee de facon declarative (contrainte de table ou assertion), alors on peut mettre en place unsysteme de triggers qui feront echouer tout ordre DML (insert, update, delete) qui aurait pourconsequence de casser la propriete.
8.4.1 Un trigger instruction de contrainte : controle d’horaire
On veut empecher toute modification de la table Salaire en dehors des heures d’ouverture du service :
create table Salaire (nom VARCHAR (20), salaire Number (7, 2)) ;
create or replace trigger Controler
before insert or delete or update on Salaire
declare
h constant Natural := to_number (to_char (Sysdate,’HH24’)) ;
begin
if h < 8 or 17 <= h then
raise_application_error (-20111, ’modification interdite !’) ;
90 CHAPITRE 8. LES TRIGGERS
end if ;
end ;
Q. 114 Peut-on garantir cette propriete sans passer par un trigger ?
Q. 115 Pourquoi, syntaxiquement, Controler est-il un trigger instruction ?
Q. 116 Le trigger est-il toujours correct si on remplace before par after ?
update Salaire set salaire = 0 ; -- erreur detectee meme sur une table vide
8.4.2 Un trigger ligne de contrainte : salaires croissants dans le temps
On veut garantir (1) que le salaire d’un employe ne decroıt jamais et (2) qu’un salaire defini ne peutpas devenir indefini :
create or replace trigger Salaire_Croissant
before update of salaire on Salaire
for each row
when (old.salaire is not null and
(new.salaire is null or new.salaire < old.salaire))
begin
raise_application_error (-20111, ’nouveau salaire indefini ou decroissant !’) ;
end Salaire_Croissant ;
Q. 117 Peut-on garantir cette propriete sans passer par un trigger ?
Q. 118 Enrichir la condition de when pour n’executer le bloc anonyme qu’en cas d’erreur de salaire.
Q. 119 Ecrire un trigger qui garantit qu’une fois defini le salaire est constant.
8.5 Exemples de triggers rendant active la base
8.5.1 Mettre a jour une table de synthese : information redondante
Une entreprise se compose de services, un employe travaille dans exactement un service.
create table Service (
id Number (5) primary key,
intitule Varchar2 (20)
) ;
create table Employe (
id Number (5) primary key,
nom Varchar2 (20),
salaire Number (15),
service references Service (id)
) ;Il se trouve que l’equipe de direction consulte tres souvent pour chaque service le nombre d’employeset le salaire moyen. Pour rendre ces consultations plus efficaces il est possible de stocker les resultatsdans une table de synthese qui sera mise a jour, par des triggers, lors de chaque modification d’unedes deux tables.
create table Synthese (
id Number (5) primary key,
intitule Varchar2 (20),
effectif Number (5) default 0, -- nombre d’employes
som_sal Number (25) default 0, -- somme des salaires definis de ce service
nb_sal_def Number (5) default 0 -- nombre d’employes ayant un salaire defini
) ;
Un trigger ligne pour chacune des deux tables est necessaire :
8.5. EXEMPLES DE TRIGGERS RENDANT ACTIVE LA BASE 91
create or replace trigger Modif_Service
after insert or update or delete on Service
for each row
begin
if inserting then
insert into Synthese (id, intitule) values (:new.id, :new.intitule) ;
elsif updating then
update Synthese
set id = :new.id, intitule = :new.intitule
where id = :old.id ;
else -- deleting evidemment
delete from Synthese where id = :old.id ;
end if ;
end ;
create or replace trigger Modif_Employe
after insert or update or delete on Employe
for each row
declare
procedure Ajouter (Serv in Service.id%type, Sal in Employe.salaire%type) is
begin
if Serv is null then return ; end if ;
update Synthese
set effectif = effectif + 1,
som_sal = som_sal + nvl (Sal, 0),
nb_sal_def = nb_sal_def + case when Sal is null then 0 else 1 end
where id = Serv ;
end Ajouter ;
procedure Retirer (Serv in Service.id%type, Sal in Employe.salaire%type) is
begin
if Serv is null then return ; end if ;
update Synthese
set effectif = effectif - 1,
som_sal = som_sal - nvl (Sal, 0),
nb_sal_def = nb_sal_def + case when Sal is null then 0 else -1 end
where id = Serv ;
end Retirer ;
procedure Modifier (Serv in Service.id%type,
Old_Sal in Employe.salaire%type,
New_Sal in Employe.salaire%type) is
begin
update Synthese
set som_sal = som_sal + nvl (New_Sal, 0) - nvl (Old_Sal, 0),
nb_sal_def=nb_sal_def + case
when Old_Sal is null and New_Sal is null then 0
when New_Sal is null then -1
when Old_Sal is null then 1
else 0
end
where id = Serv ;
end Modifier ;
begin
92 CHAPITRE 8. LES TRIGGERS
if inserting and :new.service is not null then
Ajouter (:new.service, :new.salaire) ;
elsif updating then
if :old.service = :new.service then
Modifier (:new.service, :old.salaire, :new.salaire) ;
else -- 2 services differents ou 1 ou 2 indefinis
Retirer(:old.service, :old.salaire); Ajouter(:new.service, :new.salaire);
end if ;
else -- deleting evidemment
Retirer (:old.service, :old.salaire) ;
end if ;
end ;
Cette technique va ralentir les ordres DML (insert, update, delete), mais s’ils sont relativementrares et que les requetes de synthese sont tres frequentes, cela peut etre interessant.
Les modifications faites par un trigger sont annulees si l’instruction qui l’a declenche echoue.
Q. 120 Quel probleme se poserait pour le maintien du salaire maximum d’un service dans la tableSynthese si le salaire d’un employe peut decroıtre ?
8.5.2 Un trigger instruction d’audit
On souhaite maintenant garder trace de toutes tentative de modification de la table Salaire enenregistrant la date, l’utilisateur et le type de modification (insert, update ou delete) :
create table Audit (quand Date, qui Varchar2 (20), quoi Varchar2 (10)) ;
create or replace trigger Auditeur
after insert or update or delete on Salaire
begin
if inserting then insert into Audit values (sysdate, user, ’insert’) ;
elsif updating then insert into Audit values (sysdate, user, ’update’) ;
else -- deleting evidemment
insert into Audit values (sysdate, user, ’delete’) ;
end if ;
end Auditeur ;
Q. 121 Si le trigger Auditeur etait before, cela changerait-il quelque chose ?
Les triggers instruction s’executent soit avant (before) soit apres (after) l’instruction de mise a jour :la table sur laquelle ils s’appliquent n’est donc pas consideree comme mutante et ils sont autorises aconsulter ou modifier la table elle-meme.
8.5.3 Un trigger ligne pour cadrer les notes entre 0 et 20
Soit :
create table Les_Notes (mat Number (2), note Number (2)) ;
On souhaite que lors de la modification d’une note celle-ci soit eventuellement recadree 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. 122 Que se passe-t-il si new.note est indefinie ?
Q. 123 Peut-on remplacer impunement 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 matiere 2 par lacommande :
update Les_Notes set note = note + 1 where mat = 2 ;
contenu apres clause apres execution tupleinitial set du trigger inscrit
mat note :old.note :new.note :old.note :new.note mat note
1 13
2 7 7 8 trigger non declenche car when non satisfait
2 20 20 21 20 20 2 20
1 14
2 8 8 9 trigger non declenche car when non satisfait
1 9
Chronologiquement, voici ce qui se passe :
1 Debut de la commande update
2 Selection et lecture dans old du premier tupleLa clause set calcule new.note : 8Le trigger Cadrer_Note s’arrete sur when
Ecriture du tuple avec new.
3 Selection et lecture dans old du deuxieme tupleLa clause set calcule new.note : 21Execution du trigger Cadrer_Note
Ecriture du tuple avec new.
4 Selection et lecture dans old du troisieme et dernier tupleLa clause set calcule new.note : 9Le trigger Cadrer_Note s’arrete sur when
Ecriture du tuple avec new.
5 Fin de la commande update
On voit que lorsque le trigger s’execute la table Les_Notes est en cours de modification, on dit qu’elleest mutante ou mutating.Pour cette raison, un trigger ligne ne peut ni consulter ni modifier la table a laquelle il est attachesous peine d’un declenchement d’erreur de table mutante.
8.5.4 Un trigger ligne pour une BD active : commande automatique
Un magasin veut maintenir la disponibilite de ses produits en creant automatiquement une commandepour un produit dont la quantite en stock plus la quantite commandee devient inferieure a un seuilspecifique au produit.
create table Produit (
id Number (3) primary key,
q_stock Number (3),
q_seuil Number (3),
constraint QS_Naturel
check (q_stock >= 0 and q_seuil >= 0)
) ;
create table Commande (
produit Number (3)
primary key
references Produit (id),
quantite Number (3)
) ;
94 CHAPITRE 8. LES TRIGGERS
create or replace trigger Commande_Automatique
after insert or update of q_stock
on Produit
for each row when (new.q_stock is not null and new.q_seuil is not null and
new.q_stock < new.q_seuil)
begin
-- Ici on a : new.q_stock is not null et new.q_seuil is not null
update Commande
set quantite = :new.q_seuil - :new.q_stock
where produit = :new.id ;
if SQL%rowcount = 0 then
-- Il n’y avait pas de commande pour ce produit
insert into Commande values (:new.id, :new.q_seuil - :new.q_stock) ;
end if ;
end Commande_Automatique ;
Q. 124 Peut-on se passer des deux premiers tests de la clause when ?
Q. 125 La modification de quelle colonne a-t-on oublie de surveiller ?
Revoir la section 5.3.6 page 49 avant de resoudre la question suivante.
Q. 126 Quand un produit est supprime, on ne veut plus le commander. Implanter.
8.6 Table mutante (Mutating table)
La notion de table mutante n’a strictement rien a voir avec le fait que plusieurs transactions accedentsimultanement a la meme table. En effet Oracle garantit l’etancheite entre les transactions grace a desverrous et a un protocole de gestion de versions multiples d’un meme nuplet (voir le chapitre 13).
La notion de table mutante est strictement interne a une seule transaction : une table est mutantependant l’execution d’une instruction insert, update ou delete.
Pendant qu’une table est mutante elle ne peut ni etre consultee ni etre modifiee de facon emboıtee.Si on tente de le faire alors Oracle, logiquement, fait avorter l’ordre principal avec l’erreur ORA-04091.
Ce probleme peut apparaıtre notamment avec l’utilisation des triggers ligne puisque ceux-ci sontexecutes pendant l’execution de l’instruction qui les declenchent. Il peut aussi apparaıtre avec desfonctions stockees, par exemples si elles sont appelees dans la clause where d’un update et qu’elletente de consulter la table modifiee par le update.
La raison de cette erreur est qu’une table mutante est dans un etat intermediaire probablementincoherent et que cela n’aurait alors aucun sens de la consulter.
Voici un trigger tres simple qui est errone 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 propriete Psalaires egaux :
Psalaires egaux ≡
�
�Tous les salaires sont egaux et un salaire indefini est considere comme
egal a n’importe quelle autre valeur.
8.6. TABLE MUTANTE (MUTATING TABLE) 95
Tout d’abord on remarque que seule la table Employe est impliquee dans le maintien de Psalaires egaux.
Q. 127 L’ordre delete peut-il casser Psalaires egaux ?
Analyse des cas :
– delete : ne peut evidemment pas casser Psalaires egaux– insert :
– new.id ne peut casser Psalaires egaux– new.salaire s’il est indefini ne casse pas Psalaires egaux– new.salaire s’il est defini peut casser Psalaires egaux
– update– new.id ne peut casser Psalaires egaux– new.salaire s’il est indefini ne casse pas Psalaires egaux– new.salaire s’il est defini peut casser Psalaires egaux
On decide donc d’ecrire un trigger ligne errone qui fera la verification pour chaque employe modifie :
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 egaux !’) ;
end if ;
end ;
On remarque que la requete du trigger utilise la table Employe qui est cours de modification parl’ordre insert ou update qui a declenche le trigger. Par exemple, l’ordre suivant qui tente d’augmenterles salaires de 10 unites conserve evidemment Psalaires egaux et pourtant il echouera a cause de laconsultation d’une table mutante :
update Employe set salaire=salaire+10; -- echec : table mutante dans le trigger
Si Oracle ne declenchait pas cette erreur de table mutante, le comportement serait bien pire : avantde modifier le salaire du premier employe, le trigger detecterait que le nouveau salaire est different deceux presents dans la table et declencherait a tort l’erreur de salaires inegaux.
En revanche PostgreSQL (version 7.3.4) ne connaıt pas la notion de table mutante, du coup, pour lememe exemple :
– avec un trigger ligne before il declencherait incorrectement une erreur de salaires inegaux !– en revanche cela marche bien pour les triggers ligne after car ces triggers sont executes quand
la modification de la table est completement terminee. Les valeurs de :new sont celles presentedans la table et les valeurs :old sont (tres probablement) celles memorisees par le multiversion(ou l’historique) des valeurs de chaque ligne (voir la partie sur les transactions, section 13.9.1 et 14pages 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 des la compilation ? La raison est que dans certains casun trigger peut legitimement consulter ou modifier la table sur laquelle l’evenement declenchant a eulieu. Le cas principal est celui ou le trigger est du type instruction, en effet un trigger instructions’execute avant ou apres l’instruction declenchante, il travaillera donc sur une table non mutante , voirla section 8.3 page 88.
96 CHAPITRE 8. LES TRIGGERS
Une solution, pour garantir Psalaires egaux, consiste donc a confier la verification de la propriete aun trigger instruction after.
Q. 128 Pour resoudre le probleme de table mutante, remplacer le trigger ligne Salaire Egaux par untrigger instruction after qui lui peut consulter la table Employe apres modification.
Attention : un probleme de table mutante peut aussi se produire pour un trigger instruction dans lecas d’une cascade de declenchements.
Q. 129 Donner un exemple ou un trigger instruction echoue pour cause de table mutante.
8.7 Conception d’un trigger garantissant une propriete
Prealablement a l’utilisation de triggers il faut s’assurer que la propriete ne peut vraiment pas etreexprimee de facon declarative : les triggers introduisent en general une complexite qui peut rendredelicate la maintenance de la base de donnees.
C’est pourquoi, si la technique des triggers semble incontournable, il est important de faire une analysestructuree avant de les implanter.
Le probleme est : en quoi une modification de la BD peut-elle casser la propriete.
1. faire l’inventaire des tables pour lesquelles une modification pourrait casser la propriete,
2. construire un tableau a deux entrees : en lignes les tables, en colonnes les evenements (insert,update, delete) et, pour chaque case, en quoi l’evenement se produisant sur la table est sus-ceptible ou non de casser la propriete. Il est aussi interessant d’y faire figurer les colonnes de latable intervenant dans le maintien de la propriete.
3. utiliser les informations precedentes pour savoir si fonctionnellement un ou des triggers ligne ouinstruction peuvent ou doivent etre mis en place.
Le choix entre trigger ligne ou instruction n’est pas forcement evident :
– le trigger ligne verifie que la modification de chaque ligne conserve la propriete, il peut etre interessantsi tres peu de lignes sont modifiees a chaque mise a jour de la BD.
– l’avantage du trigger instruction est qu’il travaille toujours sur une BD stable (non mutante), ce-pendant il peut etre couteux si a chaque modification d’une table il verifie que ses 10 millions delignes verifient toujours la propriete alors qu’une seule ligne a ete modifiee !
8.8 Exemple de conception de trigger
Appliquons cette demarche sur un exemple non trivial :
____________________ _________________________
v | | v
Produit(id, prix >= 0) Achat(p, c, quantite >= 0) Client(id, solde)
-- ---- --
La propriete Psolde suffisant a garantir est :�� ��le solde d’un client est soit indefini soit superieur ou egal au total de ses achats .
D’abord on ne peut garantir cette propriete Psolde suffisant de facon declarative : verifier Psolde suffisantnecessite d’observer l’etat global des trois tables Produit, Achat et Client grace a une requete quicalcule la somme des achats de chaque client. Or Oracle ne permet pas d’evaluer une requete dans unecontrainte check et ne dispose pas des assertions definies par la norme SQL.
Donc l’usage de triggers est inevitable !
L’inventaire nous donne les trois tables et on obtient le tableau :
8.8. EXEMPLE DE CONCEPTION DE TRIGGER 97
insert update deleteProduit ♥ Si le prix a augmente ♥
AchatUn nouvel achat peutcasser Psolde suffisant
Un changement de produit et/ou de client et/ouune augmentation de la quantite peuvent casserPsolde suffisant
♥
Client ♥ Si le solde a decru ♥
Un cœur (♥) dans une case indique que l’evenement sur la table ne peut pas casser la propriete.Il va donc etre necessaire d’ecrire au moins 3 triggers !Le tableau ne dit pas comment s’y prendre pour verifier la propriete, et ce n’est pas son role. La suiteconsidere independamment chaque case du tableau susceptible de casser la propriete :–
�� ��table Client : seul un ordre update sur Client peut casser la propriete. C’est le cas le plussimple : il suffit de verifier Psolde suffisant pour chaque client modifie dont le solde a decru ou vientd’etre defini. Cela peut se faire avec un trigger ligne car le calcul du montant des achats d’un clientn’a besoin d’explorer que les tables Produit et Achat et on n’aura donc pas de probleme de tablemutante.
create or replace trigger Maj_Solde_Client
before update of solde
on Client
for each row when (new.solde is not null and
(old.solde is null or new.solde < old.solde))
declare
Somme_Des_Achats Natural ;
begin
select Sum (a.quantite * p.prix) into Somme_Des_Achats
from Achat a inner join Produit p on a.p = p.id
where a.c = :new.id ;
if Somme_Des_Achats > :new.solde then
raise_application_error (-20111, ’Solde client insuffisant’) ;
end if ;
end ;
Ici on a adopte l’approche du check : si le nouveau solde est indefini on considere que Psolde suffisantest verifiee (presomption d’innocence).
Q. 130 Reecrire plus simplement la clause when en utilisant la fonction nvl.
–�� ��table Produit : un ordre update peut casser la propriete. Pour cet evenement, un trigger lignen’est pas approprie car il a besoin de la table Produit pour calculer la somme des achats d’un clientet on aurait donc un probleme de table mutante. Le plus simple est probablement de mettre enplace un trigger instruction after qui declenche une erreur s’il existe au moins un client pour lequelPsolde suffisant n’est plus vraie. La procedure suivante declenche une erreur si la propriete n’estpas verifiee :
create or replace procedure Verifier_Soldes_Suffisants is
Nb_Clients_Insolvables Natural ;
begin
select Count (Count (*)) into Nb_Clients_Insolvables
from Client c
inner join Achat a on c.id = a.c
inner join Produit p on a.p = p.id
group by c.id, c.solde
having Sum (a.quantite * p.prix) > c.solde ;
if Nb_Clients_Insolvables != 0 then
raise_application_error (-20111, ’Solde client insuffisant’) ;
end if ;
end Verifier_Soldes_Suffisants ;
98 CHAPITRE 8. LES TRIGGERS
Remarquer Count (Count (*)) afin de compter le nombre de groupes, chaque groupe corresponda un client insolvable a cause de la clause having.A nouveau, un client dont le solde est indefini n’est pas considere comme un mauvais client.Cette procedure qui verifie tous les clients est la chose a faire apres un update :
create or replace trigger Maj_Prix_Produit
after update of prix
on Produit
begin
Verifier_Soldes_Suffisants ;
end ;
Cette solution n’est pas terrible car meme si la modification consiste a diminuer les prix des produitsconcernes (ce qui implique que Psolde suffisant ne peut pas etre cassee) on va quand meme verifiertoute la base !
–�� ��table Achat : les deux ordres insert et update peuvent casser Psolde suffisant. A nouveau untrigger ligne provoquerait un probleme de table mutante car il aurait besoin de consulter la tableAchat a la fois pour insert et update.On va donc de nouveau utiliser un trigger instruction after :
create or replace trigger Verifier_Achat
after insert or update
on Achat
begin
Verifier_Soldes_Suffisants ;
end ;
Cette solution a le meme inconvenient que precedemment : elle reverifie tous les clients, memeceux qui ne sont pas concernes par les nouveaux achats ou les achats modifies ! Par exemple, si latable Achat contient 1 million d’achats, alors le trigger va traiter effectivement 1 million d’achats.Supposons que le insert n’ait cree qu’un seul nouvel achat pour un client disposant deja de 100achats, alors, idealement, il suffirait de faire la somme des prix de seulement 101 achats au lieudu million d’achats traites par le trigger instruction Verifier_Achat. La verification serait en gros1000 a 10.000 fois plus rapide !
Une meilleure solution consiste donc a ne verifier que les clients concernes par les nouveaux achatscrees par le insert. Souvenons-nous qu’un insert peut inserer plus d’une ligne avec la forme sui-vante :
insert into Achat select ... ;
Pour cela, il est necessaire de memoriser les clients a verifier pendant l’execution du insert, onva donc introduire la table de travail CAV destinee a memoriser ces clients. Cette table sera garnie,pendant le insert, grace a un trigger ligne (Garnir_CAV_Insert). CAV qui joue le role d’une variableglobale, doit bien entendu etre initialisee a vide avant le debut de chaque ordre insert, ce sera le roledu trigger instruction before Vider_CAV. Enfin, comme precedemment, on a besoin d’un triggerinstruction after pour verifier la propriete pour chacun des clients memorises dans CAV, c’est le roledu trigger instruction Verifier_CAV.
8.8. EXEMPLE DE CONCEPTION DE TRIGGER 99
Cette figure illustre cette mise en place :
Voici la table de travail :
-- Clients a verifier
create global temporary table CAV (
id Number (5) not null
) on commit preserve rows ;
create unique index CAV_PK on CAV (id) ;
instruction before instruction after
ligne before
le client concerné par l’achatmémorise dans CAV le
CAV
1 3
2
Achat
vide la table CAV vérifie les clientsde CAV
Vider_CAV
Garnir_CAV
Verifier_CAV
Le global temporary fait qu’une session (connexion) ne voit que les modifications qu’elle a faitessur CAV, elle ne voit pas les modifications faites sur CAV par d’autres sessions. Le on commitpreserve rows signifie que les modifications faites sur CAV lors de la session disparaissent quandla session se termine. Toute nouvelle session voit la table CAV vide. Une telle table ne peut disposerd’une clef primaire.Il est aussi possible que le contenu de CAV disparaisse a la fin de la transaction courante avec laclause on commit delete rows qui est l’option par defaut.et les trois triggers attaches a la table Achat :
-- TRIGGER INSTRUCTION execute avant le debut de insert
create or replace trigger Vider_CAV
before insert on Achat
begin
delete from CAV ;
end ;
-- TRIGGER LIGNE execute pour chaque ligne inseree dans Achat
create or replace trigger Garnir_CAV_Insert
before insert on Achat
for each row when (nvl (new.quantite, 0) != 0)
begin
insert into CAV values (:new.c) ;
exception
when Dup_Val_On_Index then -- le client y etait deja !
null ;
end ;
-- TRIGGER INSTRUCTION execute apres la fin de insert
create or replace trigger Verifier_CAV
after insert on Achat
declare
Nb_Clients_Insolvables Natural ;
begin
select Count (Count (*)) into Nb_Clients_Insolvables
from CAV cv
inner join Client c on cv.id = c.id
inner join Achat a on c.id = a.c
inner join Produit p on a.p = p.id
group by c.id, c.solde
having Sum (a.quantite * p.prix) > c.solde ;
if Nb_Clients_Insolvables != 0 then
100 CHAPITRE 8. LES TRIGGERS
raise_application_error (-20111, ’Solde client insuffisant’) ;
end if ;
end ;
Dans le trigger Garnir_CAV_Insert il est inutile de verifier que new.c et new.p sont definis puisqu’ilsfont partie de la clef primaire.L’equijointure de Verifier_CAV debute par la table CAV, elle ne prend donc en compte que lesclients concernes par de nouveaux achats.
Cette architecture de solution est probablement utilisable dans pas mal de cas ou on a des problemesde table mutante avec les triggers ligne.
La partie la plus critique est probablement de bien concevoir :
1. ce que doit memoriser la table de travail,
2. le trigger ligne qui garnit la table de travail : il doit minimiser le nombre de donnees a verifiersans en oublier aucune !
Nous en sommes a 5 triggers !Lors d’un update sur Achat on peut utiliser la meme technique qu’au point precedent : recenser lesclients qu’il faut absolument verifier puis les verifier apres l’update de Achat. Pour cela on modifieles deux triggers instruction precedents pour qu’ils soient aussi declenches par update :
create or replace trigger Vider_CAV
before insert or update
on Achat
begin -- comme avant
end ;
create or replace trigger Verifier_CAV
after insert or update
on Achat
declare -- comme avant
end ;
C’est la selection de ces clients qui constitue le cœur du travail :– si la new.quantite est indefinie : il n’y a rien a verifier,– sinon il faudra verifier new.c si :
– on a change de client et que new.quantite est strictement positive,– sinon si on a change de produit et que new.quantite est strictement positive,– sinon si on a augmente la quantite ou bien que l’ancienne etait indefinie et que la nouvelle est
strictement positive.
create or replace trigger Garnir_CAV_Update
before update on Achat
for each row when (new.quantite is not null and
(nvl (old.quantite, 0) < new.quantite or
(old.p != new.p and new.quantite != 0) or
(old.c != new.c and new.quantite != 0)
))
begin
insert into CAV values (:new.c) ;
exception
when Dup_Val_On_Index then -- le client y etait deja !
null ;
end ;
Ici c’est la clause when de Garnir_CAV_Update qui est critique puisque c’est elle qui choisit les
8.9. CONCLUSION 101
clients a verifier : il ne faut pas qu’elle en oublie et il serait souhaitable qu’elle ne prenne pas ceuxpour lesquels une verification est inutile.
Finalement on s’en sort avec six triggers.
8.9 Conclusion
Losqu’un trigger echoue par une exception ou une erreur Oracle, il est abandonne, ainsi que l’instruc-tion qui l’avait declenche : tout se passe comme si l’instruction n’avait pas ete executee (principe dutout ou rien sur les instructions DML).
Aucune instruction DDL (create table par exemple) ou relative au controle de transaction (com-mit, rollback, savepoint) ne peut etre executee par un trigger, que ce soit directement dans lesinstructions du trigger ou indirectement en appelant une procedure PL/SQL (reflechissez et vouscomprendrez pourquoi !).
Si on a plusieurs triggers associes a une table et susceptibles d’etre declenches par un meme evenement,on sait juste que Oracle execute les triggers d’un meme type avant d’executer ceux d’un autre type(autrement dit on ne sait pas grand chose sur l’ordre dans lequel seront executes les triggers, cela estassez classique en programmation evenementielle).
Ne pas abuser des triggers :– ils introduisent un cout non negligeable (la clause when des triggers ligne permet cependant de
minimiser ce cout).– interdependance entre triggers : un trigger qui insere, detruit ou modifie des lignes peut provoquer
le declenchement d’autres triggers qui peuvent eux-memes en declencher d’autres . . .Cela induit unecomplexite qui peut devenir difficile a maıtriser.
Les proprietes d’une base de donnees peuvent aussi etre garanties en ne donnant acces qu’a desprocedures stockees programmees pour conserver ces proprietes.Oracle 8 propose deux nouvelles sortes de triggers :
triggers instead of ce sont des triggers de vues qui permettent de programmer explicitement lamodification des tables sous-jacentes de la vue lorsqu’on demande a modifier la vue (section 10.5page 114),
triggers systemes pour l’administrateur...
8.10 Deux mots a propos de PostgreSQL
Voici ce que PostgreSQL propose :– PostgreSQL ne connaıt pas le concept de table mutante ! il n’y a donc pas de garde fou en Post-
greSQL.– Les triggers lignes before voient la table dans un etat instable sans qu’aucune erreur ne soit
declenchee (ce qui ne semble pas tres serieux !).– en revanche, et la c’est beaucoup mieux, les triggers lignes after travaillent sur une table stable dont
la valeur est celle obtenue apres execution complete de l’instruction de mise a jour ; le fait que cestriggers disposent a la fois de l’ancienne et de la nouvelle valeur de chacune des lignes modifiees (oldet new) repose probablement sur le fait que PostgreSQL (comme Oracle) gere plusieurs versions dechaque ligne d’une table (protocole multi-versions, voir les chapitres 13 et 14).
Troisieme partie
Schema externe
102
103
La notion de schema externe exprime le fait que plusieurs utilisateurs ont des fonctions differentes surune meme base de donnees, autrement dit chaque fonction aura besoin de son schema externe de cettebase de donnees. Pour chacune de ces fonctions il faudra ne lui permettre que les consultations et mo-difications qui correspondent a ses besoins et pour lesquelles cette fonction assume ses responsabilites.Bien entendu les deux chapitres qui suivent, privileges et vues, ne sont pas les seuls outils permettantde materialiser un schema externe. Les procedures stockees ainsi que le developpement d’applicationsclientes peuvent y participer.
Chapitre 9
Privileges et roles
Objectif : pouvoir limiter au strict necessaire ce qu’un utilisateur peut faire sur la base de donnees.
Celui qui cree un objet (table, vue, procedure stockee, . . .) en est proprietaire et initialement seul luipeut le manipuler.
Afin qu’un autre utilisateur puisse manipuler ces objets il faut que le proprietaire lui accorde directe-ment (ou indirectement avec l’option grant option) des privileges. Il y a deux sortes de privileges :
– les privileges objet (table, vue, sous-programme, . . .) permettent de manipuler des objets existant :consultation et modification d’une table ou vue, execution d’un sous-programme stocke,
– les privileges systeme permettent de modifier la structure de la base en creant ou detruisant desobjets,
Un role est un assemblage de privileges necessaires pour assumer une fonction. On pourrait com-prendre la notion de role comme une casquette que l’on porte pour accomplir une fonction particuliere.Comme il est possible de porter plusieurs casquettes, on peut assumer plusieurs roles simultanement,et on peut aussi abandonner un role comme on enleve une casquette. Un role correspond donc a desprivileges temporaires.
9.1 Qu’est qu’un objet
Toute entite creee par l’ordre create est un objet : table, vue, procedure stockee, trigger, index, . . ..
Pour pouvoir manipuler un objet, il faut bien sur qu’il ait un nom, la plupart du temps on utilisesimplement le nom local de l’objet (exactement comme en Unix un simple nom de fichier ou derepertoire designe un objet du repertoire courant).
Il est bien entendu possible de designer de facon plus absolue un objet en donant son nom complet :
– en Oracle, le nom complet d’un objet est le nom de l’objet prefixe par le nom du schema dans lequelil a ete cree, par exemple durif.Employe ou GMI13.Client. Le nom de schema est homonyme ducompte utilisateur disposant de ce schema.
– en PostgreSQL, le nom complet d’un objet est le nom de l’objet prefixe par le nom du schemacontenant cet objet lui-meme prefixe par le nom de la base de donnees contenant ce schema, parexemple annuaire.public.Date_Admin ou annuaire est le nom de la base de donnees et public
est le schema par defaut1. Contrairement a Oracle, PostgreSQL distingue clairement les notionsd’utilisateur et de schema.
Avec un nom complet il est donc possible de designer un objet cree par un autre utilisateur ou setrouvant dans un autre schema. Par defaut un utilisateur n’a aucun droit sur les objets qu’il n’a pascrees.
La commande create schema d’Oracle est une facilite fonctionnelle, mais elle ne cree pas de nou-veau schema (le nom du schema doit etre celui de l’utilisateur executant cette commande) : c’estcertainement pourquoi la commande symetrique drop schema n’existe pas.
1Au moins pour l’instant (PostgreSQL 8.2) le prefixe nom de la base de donnees doit etre le nom de la base surlaquelle on est connecte, autrement dit ce prefixe ne permet pas d’acceder a un objet d’une autre base de donnees.
104
9.2. LES UTILISATEURS ET LES PRIVILEGES 105
9.2 Les utilisateurs et les privileges
Les privileges sont de deux sortes :– les privileges, dit objet, qui gouvernent les operations possibles sur le contenu des objets de la base,– les privileges, dit systeme, qui gouvernent les operations concernant les contenants : creation, mo-
dification de la description (alter) et destruction d’objet.
9.2.1 Les privileges objet
Les privileges objet permettent de consulter ou modifier l’etat d’un objet particulier, ou de l’executer,mais sans pouvoir le detruire ou en creer de nouveaux (ces operations correspondent a des privilegessysteme). Chacun de ces privileges fait reference a un objet particulier de la base :
privileges types d’objet
select table, view et sequenceupdate, delete, insert table et viewalter table et sequenceexecute sous-programme et paquetagereferences (possibilite de definir une cle etrangere) tableindex (possibilite de definir un index) table commande create index
. . . . . .
Certains types d’objets n’ont pas de privileges associes, par exemple primary key, unique et lestriggers, car ils seront toujours actifs.
Q. 131 Pourquoi les privileges index et references ont-ils un sens ?
Les synonymes (create synonym) sont transparents autant pour determiner les privileges de l’utili-sateur sur l’objet designe par le synonyme que pour gerer les privileges de ce meme objet (i.e. on peutindifferemment utiliser le synonyme ou le vrai nom de l’objet).
Tout objet est la propriete de l’utilisateur qui l’a cree. Le proprietaire a tout pouvoir sur ses objets,y compris celui de donner a d’autres utilisateurs des privileges sur ses objets, puis de les revoquer.
Un sous-programme pour lequel on a le privilege execute peut executer d’autres sous-programmespour lesquel on n’a pas ce privilege et manipuler des tables et des vues pour lesquelles on n’a pas deprivilege. Par defaut, un sous-programme s’execute avec les privileges de celui qui l’a compile. Cecipermet de limiter tres precisement ce qu’un utilisateur peut faire sur un ensemble de tables.
Il en va de meme pour les vues : inutile d’avoir des privileges sur les tables ou vues sous-jacentes.
La destruction d’un objet supprime les privileges associes, meme si l’objet est ensuite recree.
Un utilisateur peut manipuler des objets dans la mesure ou il dispose des privileges objet correspon-dant. Des privileges peuvent etre accordes soit par le proprietaire de l’objet soit par un utilisateurles ayant recus avec l’option grant option, tous les deux peuvent ensuite les revoquer (commanderevoke).
9.3 Gestion des privileges objet
9.3.1 Donner des privileges objet : grant
L’utilisateur qui donne des privileges objet a un autre utilisateur doit soit etre proprietaire de l’objetsoit avoir lui-meme obtenu ces privileges d’un autre utilisateur avec l’option with grant option.
grant <liste-de-privileges-objet> on <objet> to
<liste-utilisateurs-et-ou-roles> | PUBLIC
106 CHAPITRE 9. PRIVILEGES ET ROLES
[ with grant option ] ;
<liste-de-privileges-objet> ::= <privilege> { , <privilege> }
| all [privileges] [ ( <liste-de-colonnes> ) ]
<privilege> ::= alter | delete | execute | index | select | read
| insert [ ( <liste-de-colonnes> ) ]
| references [ ( <liste-de-colonnes> ) ]
| update [ ( <liste-de-colonnes> ) ]
<liste-de-colonnes> ::= <colonne> { , <colonne> }
Le privilege objet references autorise a creer des cles etrangeres qui referencent la table.
La table User_Tab_Privs (Owner, Grantor, Grantee, Table_Name, Privilege, Grantable) per-met a Oracle de se souvenir comment ont ete accordes les privileges sur une table et qui les a donnes.En particulier si un privilege a ete accorde a Toto par plusieurs donateurs (grantor), on s’en souvient.
9.3.2 Revoquer des privileges objet : revoke
Seul celui qui a donne un privilege objet peut le revoquer, avec une revocation en cascade si ce privilegeavait ete donne avec grant option.
La revocation :
revoke <liste-de-privileges-objet> on <objet>
from <liste-utilisateurs-et-ou-roles> [ cascade constraint ] ;
<privileges> ::= <privilege> { , <privilege> }
| all [privileges]
Seul le donateur d’un privilege peut le revoquer. Si cet utilisateur (notons-le U) avait obtenu ceprivilege with grant option, le privilege est aussi revoque a tous les utilisateurs auxquels U l’avaitaccorde et le meme processus est repete recursivement a ces derniers s’ils avaient recu ce privilegewith grant option.
On ne peut pas revoquer un privilege pour un sous-ensemble de colonnes : on supprime globalementle privilege.
Un utilisateur ne peut se revoquer des droits a lui-meme (sauf indirectement par un cycle de withgrant option !).
L’option cascade constraint est necessaire pour revoquer le privilege references car il faut suppri-mer les contraintes de cle etrangere referencant la table.
Si un utilisateur a obtenu le meme privilege depuis plusieurs donateurs, il se peut qu’il le conservememe si le privilege est revoque par un des donateurs. La figure 9.1 page 107 en donne un exemple.
Q. 132 En accord avec la figure 9.1, dessiner le graphe qui explicite comment, en phase 3, les privilegesont ete obtenus, puis revoques.
Q. 133 D’apres la figure 9.1 et en partant de la phase 3, comment gmi51 peut-il s’y prendre pourrevoquer a tout le monde, sauf evidemment a gmi52 qui est proprietaire, le privilege select sur Livre ?
9.4 Les roles
Les roles sont tres utiles pour regrouper les privileges necessaires a l’exercice d’une fonction sur lesysteme d’information.
Un role est un ensemble nomme de privileges et/ou d’autres roles. La constitution d’un role en sous-roles forme un graphe oriente acyclique (DAG, sinon erreur Oracle ORA-01934).Quand il est active, un role accorde tous les privileges presents dans le DAG dont il est la racine.On peut ensuite :
– accorder ou revoquer ce role a un utilisateur, exactement comme on le fait avec un privilege,
9.4. LES ROLES 107
gmi52 gmi51 gmi50
create table Livre ...;
User Tab Privs vue par gmi52 (phase 1)
OWNER GRANTOR GRANTEE TABLE NAME PRIVILEGE GRANTABLE
grant select on Livre
to gmi50, gmi51
with grant option;
User Tab Privs vue par gmi52 (phase 2)
OWNER GRANTOR GRANTEE TABLE NAME PRIVILEGE GRANTABLE
gmi52 gmi52 gmi50 LIVRE SELECT YESgmi52 gmi52 gmi51 LIVRE SELECT YES
grant select on gmi52.Livre
to gmi49;
grant select on gmi52.Livre
to gmi49;User Tab Privs vue par gmi52 (phase 3)
OWNER GRANTOR GRANTEE TABLE NAME PRIVILEGE GRANTABLE
gmi52 gmi52 gmi50 LIVRE SELECT YESgmi52 gmi52 gmi51 LIVRE SELECT YESgmi52 gmi50 gmi49 LIVRE SELECT NOgmi52 gmi51 gmi49 LIVRE SELECT NO
revoke select on Livre
from gmi51 ;
User Tab Privs vue par gmi52 (phase 4)
OWNER GRANTOR GRANTEE TABLE NAME PRIVILEGE GRANTABLE
gmi52 gmi52 gmi50 LIVRE SELECT YESgmi52 gmi50 gmi49 LIVRE SELECT NO
Fig. 9.1 – Exemple ou gmi49 a obtenu le meme privilege depuis plusieurs donateurs. gmi49 conservele privilege bien qu’il ait ete revoque a gmi51. Ceci est une bonne chose dans la mesure ou le chef deservice gmi50 dispose toujours de ce privilege et qu’il souhaite que son collaborateur gmi49 continued’en disposer.
– un meme utilisateur peut disposer de plusieurs roles (plusieurs fonctions) qu’il n’est pas obliged’assumer tout le temps (set role),
– ajouter ou supprimer des privileges a un role meme si ce role est deja accorde a des utilisateurs.
Un utilisateur se voit accorde un certain nombre de roles, parmi ceux-ci il y a les roles dit par defautet ceux qui ne le sont pas :
– les roles par defaut d’un utilisateur et les privileges qui lui sont directement accordes sont actifs desla connexion.
– pour beneficier des privileges associes aux roles qui ne sont pas par defaut, l’utilisateur doit lesendosser explicitement (commande set role) et pourra ensuite les desactiver.
Un role est un objet :
create role <nom-de-role> [ identified by <mot-de-passe> ] ;
alter role <nom-de-role> [ identified by <mot-de-passe> ] ;
drop role <nom-de-role> ;
108 CHAPITRE 9. PRIVILEGES ET ROLES
9.5 Gestion des privileges systemes et des roles
9.5.1 Les privileges systeme
Ils correspondent a des operations permettant de modifier la structure de la base de donnees, parexemple creer une table, detruire une vue, . . .. Aucun de ces privileges ne fait reference a un objetprecis de la base.
createalter [any]drop [any]
index pour optimiser des requetesproceduresequencesession connexion au SGBDsynonymtabletriggertypeuserviewcluster, context, database, rolerollback segment, tablespace...
Du point de vue de grant et revoke, les roles se comportent comme des privileges systemes.
9.5.2 Donner des roles et/ou des privileges systemes : grant
grant <privileges-systeme|roles> to
<liste-utilisateurs-et-ou-roles> | public
[ with admin option ] ;
<privileges-systeme|roles> ::= <privilege|role> {, <privilege|role>}
| all privileges
<privilege> ::= create table|create view|create procedure|create role|...
| drop table | ...
Pas de memorisation du donateur.
L’option with admin option autorise le beneficiaire a transmettre le privilege a n’importe qui d’autre.Si le privilege est un role, il pourra aussi le revoquer a un autre utilisateur, le modifier et le supprimer.
9.5.3 Revoquer des roles et/ou des privileges systemes : revoke
La revocation :
revoke <privileges-systeme|roles> from <liste-utilisateurs-ou-roles> ;
Contrairement a ce qui se passe pour les privileges objet, la revocation des privileges systeme n’estpas transitive.
9.5.4 Les privileges juste apres la connexion
Les privileges disponibles des la connexion sont ceux donnes explicitement ainsi que ceux des roles pardefaut fixes par :
alter user <nom> default role <liste-de-roles> | all | none ;
Pour voir les roles actuellement actives par gmi52 :
9.5. GESTION DES PRIVILEGES SYSTEMES ET DES ROLES 109
select * from Session_Roles ;
ROLE
----------------------------
GMI
CONNECT
RESOURCE
9.5.5 Gerer ses roles
Un role correspond a une fonction dans l’entreprise : un role n’est necessaire que quand l’utilisateurassume cette fonction. Une regle de securite : ne disposer a tout moment que des privileges strictementnecessaires a la tache en cours.
Des sa connexion, un utilisateur dispose des privileges correspondant a ses roles par defaut.
L’utilisateur doit pouvoir activer un des roles qui lui ont ete attribues et choisir de le desactiver quandil n’en a plus besoin :
set role <liste-de-roles-identifies> ;
| none ;
| all [ except <liste-de-roles> ] ;
<liste-de-roles-identifies> ::= <role-identifie> { , <role-identifie> }
<role-identifie> ::= <nom-de-role> [ identified by <mot-de-passe> ]
<liste-de-roles> ::= <nom-de-role> { , <nom-de-role> }
none desactive tous les roles, y compris ceux par defaut.
Attention : set role n’est pas cumulatif (ou differentiel), il reinitialise l’ensemble des roles actifs avecuniquement ceux qui sont mentionnes.
set role ne peut etre embarque dans du PL/SQL, dommage (ni meme de facon dynamique) !
9.5.6 Particularites des roles
Un privilege ne peut etre attribue a un role avec with grant option.
Dans la mesure ou un role n’est pas toujours actif pour l’utilisateur qui en beneficie, un role ne devraitpas comporter de privileges qui n’ont de sens que s’il sont toujours actifs. Par exemple les privilegesreferences et execute sont dans ce cas et ne devraient jamais etre attribues via un role.
Par exemple, le privilege execute donne via un role ne permet pas de compiler une procedure appelantla procedure sur laquelle porte ce privilege car, lors de son execution, le code compile ne verifie pas sion a le privilege d’executer la procedure.
9.5.7 Exemple
Sur la BD des clients, produits et achats.
create role G_Client ; grant update (solde) on Client to G_Client ;
create role G_Produit ; grant update (prix) on Produit to G_Produit ;
create role G_Achat ; grant insert, update (quantite) on Achat to G_Achat ;
-- Un super-role :
create role Gerer_Tout ;grant G_Client, G_Produit, G_Achat to Gerer_Tout ;
Plus tard on peut modifier le contenu d’un des roles :
revoke update on Achat from Gerer_Achat ;
revoke Gerer_Client from Gerer_Tout ;
110 CHAPITRE 9. PRIVILEGES ET ROLES
9.6 Exemple
administrateur durif utilisateur gmi25 effet
create table Salaire;
select * from durif.Salaire ; Table ou vue inexistantecreate role X ;
grant select
on Salaire to X ;
grant X to gmi25 ;
select * from durif.Salaire ; Table ou vue inexistanteset role X ; active le role X
select * from durif.Salaire ; successet role NONE ; desactive le role X
select * from durif.Salaire ; Table ou vue inexistanteset role X ; active le role X
select * from durif.Salaire ; succesdelete from durif.Salaire ; privileges insuffisants
grant delete
on Salaire to X ;
delete from durif.Salaire ; succesrevoke X from gmi25 ;
select * from durif.Salaire ; succes : le role reste actif
soit set role NONE;
set role X ; le role ’X’ n’est pasaccorde ou n’existe pas
soitdrop role x;
select * from durif.Salaire ; Table ou vue inexistante
La modification d’un role actif a un effet immediat.
La revocation d’un role actif n’a pas un effet immediat !
9.7 Privileges et sous-programmes stockes
Le code compile d’un sous-programme stocke ne verifie plus les droits a la volee (cela certainementpour des raisons d’efficacite). Donc la compilation, echouera si elle n’est pas capable de garantir que,lors des futures executions, les acces a des objets de la base ou des appels a d’autres sous-programmesdefinis par ailleurs seront toujours autorises.
Dans le cas de l’appel a un sous-programme (par exemple Dbms_Lock.Sleep()) dont le compilateurne peut garantir qu’il sera toujours autorise, le message d’erreur est assez deroutant : il annonce queDbms_Lock n’a pas ete declare.
Par defaut, un sous-programme stocke s’execute avec les droits de celui qui a compile le sous-programme(on peut aussi le dire explicitement avec authid definer dans l’ordre create).
Les droits necessaires pour que la compilation se passe bien doivent donc etre garantis toujours actifspour l’utilisateur qui effectue la compilation. Autrement dit, ces droits ne doivent pas etre octroyesvia des roles, car un utilisateur peut a tout moment endosser ou abandonner un de ses roles. Les droitsnecessaires doivent donc etre attribues directement a l’utilisateur.
9.7. PRIVILEGES ET SOUS-PROGRAMMES STOCKES 111
Bien entendu, si un de ces droits est ensuite revoque le resultat de compilation deviendra invalide,car Oracle se souvient (independamment du code compile) des droits necessaires a l’execution de toutsous-programme.
Connexion de durif Connexion de gmi52create function Un return Number is
begin
return 1 ;
end Un ;
create function Deux return Number is
begin
return durif.Un + durif.Un ;
end Deux ;
Cette compilation donne l’erreur :
l’identificateur ’DURIF.UN’
doit etre declare
Mais la fonction Deux est bien compilee et on latrouve dans user_objects dans l’etat invalide.
grant execute on Un to gmi52 ;
La fonction Deux est toujours invalide, on n’acependant pas besoin de la recompiler, on peuttout de suite l’evaluer :
select Deux from dual ;
qui nous donne bien la valeur 2 et main-tenant Deux est notee comme valide dansuser_objects.
revoke execute on Un from gmi52 ;
La fonction Deux est maintenant invalide dansuser_objects.
On peut voir qu’Oracle adopte une attitude paresseuse2 quant a la validation d’un objet : c’est seule-ment quand on tente d’utiliser un objet invalide pour cause de droits manquants qu’Oracle va tenterde le revalider en fonction de l’etat actuel des droits de l’utilisateur. En l’occurrence c’est lors duselect Deux from dual ; qu’Oracle, voyant que Deux est invalide, va la remettre dans l’etat validecar gmi52 a maintenant le droit d’executer durif.Un.
La meme experience, mais en utilisant un role pour transmettre le droit d’execution a gmi52 ne marchepas.
2L’adjectif paresseux n’est pas a prendre dans son sens pejoratif, il signifie ici qu’on ne fait les choses que quand celaest necessaire ! L’attitude paresseuse d’Oracle ou de certains logiciels peut s’averer tout a fait efficace.
Chapitre 10
Les vues
En premiere approche, une vue est un objet qui associe un nom a une requete. Une fois creee, onpourra consulter cette vue comme si c’etait une table :
create view Bon_Client (id, nom, solde) as
select id, nom, solde
from Client
where solde > 1000
with check option ;
select *
from Bon_Client ;
select *
from Bon_Client
where lower (nom) like ’%gold%’ ;
En general, une utilisation particuliere d’une base de donnees ne necessite pas de voir toutes les donneesde la base de donnees, ceci pour des raisons de confidentialite mais aussi tout simplement pour ne paspolluer l’utilisateur avec des informations qui ne le concernent pas.
Par exemple les etudiants qui concoivent l’annuaire des anciens GMI ne peuvent pas voir le salaire in-dividuel que certains anciens renseignent, mais il peuvent en obtenir une moyenne. Ainsi ces etudiantsn’auront aucun droit sur la table Ancien mais disposeront d’une vue correspondant a la table Ancien
amputee de la colonne salaire et d’une vue calculant le salaire moyen.
Pour mettre en place une vision limitee et appropriee a la mission de l’utilisateur de la base de donnees,les vues sont un des outils majeurs (le systeme de privileges intervient lui aussi).
Les vues constituant le cadre juste necessaire a une utilisation particuliere de la base de donnees sontun des outils permettant de realiser un schema externe.
Quelques usages des vues :
– Pour obtenir simplement une information synthetique.– Pour eviter de divulguer certaines informations (nominative par exemple) : une vue peut restreindre
le nombre de colonnes consultables, l’utilisateur concerne pourra consulter la vue mais pas la ou lestables d’ou elle tire sa valeur.
– Pour assurer l’independance du schema externe vis a vis du schema interne : on peut esperer qu’unemodification des tables qui implantent la base de donnees permettra de modifier les requetes desvues sans changer le sens des informations qu’elles fournissent.
Une vue est evaluee a chaque consultation.
Oracle en definit un grand nombre pour faciliter la consultation de son dictionnaire, par exemple :tab, user_objects, . . .
112
10.1. LE LDD D’UNE VUE 113
Si le schema externe d’une utilisation n’est constitue que de vues, on aurait tendance a penser quecette utilisation est incapable de modifier la base, ce qui serait parfois tres embetant !
En fait, comme on le verra, Oracle et PostgreSQL disposent de moyens permettant de modifier la basede donnees via les vues d’un schema externe.
10.1 Le LDD d’une vue
En Oracle une vue est potentiellement l’equivalent d’une table, c’est a dire que, si la requete de la vueest assez simple, on pourra mettre a jour la vue (insert, update, delete) ce qui en fait mettra a jourla table sous-jacente.
Si la requete de la vue est trop complexe (group by par exemple), il est quand meme possible demodifier la base via une vue en lui attachant un trigger instead of.
create [or replace] view <nom-de-vue>
[ ( <liste-alias> ) ]
as <requete-select>
[ <with-clause> ] ;
<with-clause> ::= with read only
| with check option [ constraint <nom-de-contrainte> ]
drop view <nom-de-vue> ;
with read only interdit toute tentative de modification de la vue (insert, update, delete) ainsique l’attachement de trigger instead of.
with check option pour garantir que les insert et update sur la vue ne seront acceptes que s’ilsproduisent des lignes que la vue peut selectionner. Cette option n’a pas de sens si :– la requete de la vue ou de toute sous-vue utilisee pour construire cette vue contient une
sous-requete– ou bien si les instructions insert, update, delete sont programmee grace a un trigger instead
of associe a cette vue.
10.1.1 Un exemple
Soit la base de donnees :
create table Client (
id Number (5) primary key,
nom Varchar2 (20),
solde Number (6, 2) default 0.0) ;
create table Commande (
client references Client (id),
montant Number (6, 2) default 0.0) ;
La vue qui donne la liste des clients avec le montant moyen des commandes qu’il a effectuees
create view Client_Moyenne (id, nom, montant_moyen) as
select Cl.id as id, Cl.Nom as nom, Avg (Co.montant)
from Client Cl
inner join Commande Co on Co.client = Cl.id
group by Cl.nom
with read only ;
Comme une table, une vue peut etre mentionnee dans la clause from d’une requete.
Si une des tables utilisees par la vue est detruite, cette derniere devient inutilisable.
114 CHAPITRE 10. LES VUES
10.2 Vues Oracle modifiables
Certaines vues peuvent etre l’objet de mise a jour par les instructions insert, update, delete, maispour cela il faut que Oracle soit capable de deduire les modifications a faire sur les tables et ce n’estpas toujours possible. Voici les restrictions imposees par Oracle sur la requete de la vue afin que celle-cisoit modifiable :– pas d’operateurs ensemblistes– pas de fonction d’agregation– pas de clause group by ou order by– pas de sous-requete– pas de collection dans un select (objet-relationnel).Si la vue comporte une jointure, les instructions DML sont tres restreintes et ne peuvent concernerqu’une seule table de base.
Q. 134 Les vues Bon Client et Client Moyenne sont-elles modifiables ?
10.3 Vue modifiable avec with check option
Avec l’option with check option on ne peut inserer que des lignes selectionnables par la vue :
Insert into Bon_Client values (15, ’martin’, 2000) ; -- OK
Insert into Bon_Client values (20, ’dupont’, 500) ; -- echec : solde > 1000
Q. 135 L’insertion suivante est-elle acceptee ? pourquoi ?
Insert into Bon_Client (id, nom) values (33, ’durant’) ;
10.4 Vue modifiable sans with check option
Sans l’option with check option toute insertion est possible, mais ne sera pas forcement visible viala vue :
create view Mauvaise_Vue (id, nom, solde) as
select id, nom, solde
from Client
where solde > 1000 ;
insert into Mauvaise_Vue values (45, ’dupont’, 500) ; -- OK
select * from Mauvaise_Vue ; -- on ne voit pas ’dupont’
update Mauvaise_Vue
set solde = 300
where id = 45 ; -- aucune ligne mise a jour
delete from Mauvaise_Vue
where id = 45 ; -- aucune ligne supprimee
Les procedures stockees permettent aussi de resoudre ce probleme en permettant d’exprimer les trai-tements a mettre en place sur les tables pour mettre a jour la vue.
10.5 Vue non modifiable : trigger instead of
Si la vue n’est pas modifiable a cause de la complexite de sa requete, on peut lui attacher des triggersinstead of qui s’executeront a la place de l’ordre DML.
10.5. VUE NON MODIFIABLE : TRIGGER INSTEAD OF 115
Les procedures stockees permettent aussi de resoudre ce probleme en permettant d’exprimer les trai-tements a mettre en place sur les tables pour mettre a jour la vue.Les triggers instead of sont forcement des triggers ligne, c’est a dire que lors d’un update et d’undelete ils disposent du contenu d’origine du nuplet courant de la vue (old) et du nouveau contenu dece nuplet (new) mais celui-ci n’est pas modifiable par le trigger. C’est cela qui permet de comprendrepourquoi l’exemple suivant fonctionne.before et after n’ont pas de sens pour les triggers instead of.On ne peut pas attacher un trigger instead of sur une vue with read only.
create table Etudiant (
id Number (5) primary key,
nom Varchar2 (20)
) ;
create table Note (
etudiant references Etudiant (id),
note Number (5)
) ;
Soit la vue non modifiable a cause, entre autres, de l’utilisation de avg :
create view Moyenne (id, nom, moyenne) as
select e.id, e.nom, nvl (to_char (avg (n.note)), ’pas de note’)
from Etudiant e left outer join Note n on e.id = n.etudiant
group by e.id, e.nom ;
On veut qu’un ordre DML sur la vue Moyenne se traduise par un ordre DML similaire sur la tableEtudiant :– un insert insere simplement le nouvel etudiant dans la table Etudiant,– un update met a jour uniquement le nom de l’etudiant (on pourrait aussi tenter de mettre a jour
son id mais cela poserait des problemes a cause de la clef etrangere de Note)– un delete supprime les notes de l’etudiant old.id puis l’etudiant.
create trigger DML_sur_Moyenne
instead of insert or delete or update
on Moyenne
for each row
begin
if inserting then
insert into Etudiant values (:new.id, :new.nom) ;
elsif updating (’nom’) then
update Etudiant set nom = :new.nom where id = :old.id ;
elsif deleting then
delete from Note where etudiant = :old.id ;
delete from Etudiant where id = :old.id ;
end if ;
end ;
Soit la vue non modifiable :
create or replace view Tout (etudiant, nom, note) as
select e.id as etudiant,
e.nom as nom,
n.note as note
from Etudiant e
left outer join Note n on n.etudiant = e.id ;
Q. 136 Dans la clause select, pourquoi a-t-on pris soin d’ecrire e.id as etudiant et non pasn.etudiant as etudiant?
On veut que :
un insert ajoute si necessaire l’etudiant et systematiquement la note,
un update mette a jour uniquement le nom de l’etudiant,
116 CHAPITRE 10. LES VUES
un delete n’ait aucun effet.
Q. 137 Implanter le trigger qui fait ce travail.
10.6 Deux mots a propos de Postgres 8.2.1
En Postgres, on peut modifier les tables sous-jacentes aux vues en creant une regle (create rule). Uneregle permet d’executer des commandes supplementaires lorsqu’une commande donnee est executeesur une table ou une vue donnee (also) ou a la place de la commande (instead).
create rule Creer_Etudiant as
on insert to Moyenne
do instead Insert into Etudiant values (new.id, new.nom) ;
create rule Modifier_Etudiant as
on update to Moyenne
do instead update Etudiant set nom = new.nom where id = old.id ;
create rule Supprimer_Etudiant as
on delete to Moyenne
do instead (delete from Note where etudiant = old.id ;
delete from Etudiant where id = old.id ) ;
Quatrieme partie
Optimisations
117
Chapitre 11
Optimisations
11.1 Organisation physique d’un SGBD
La durabilite d’une base de donnees est assuree par son enregistrement sur un disque magnetique(c’est probablement actuellement la technique la plus utilisee).
L’unite atomique de lecture/ecriture sur un disque est le secteur ou le bloc (plusieurs secteurs conti-gus). La taille d’un secteur peut etre de 512 ou 1024 octets voire 4096.
Ecrire ou lire un secteur prend un temps enorme par rapport a la meme operation en memoire centrale.Cela est du principalement a l’aspect mecanique de l’acces au secteur :
1. le bras supportant la tete de lecture/ecriture doit d’abord etre deplace radialement sur la pistedu secteur
2. il faut ensuite attendre que le secteur se presente sous le bras grace a la rotation du disque,
3. enfin il faut lire ou ecrire le secteur, la duree de cette operation depend elle aussi de la vitessede rotation du disque.
Oracle organise ses acces au disque de la facon suivante :– le bloc est la plus petite unite de l’ecriture/ecriture dont la taille est fixee par la constante DB_BLOCK_SIZE,
par exemple 2 kilo-octets.– l’extent est l’unite suivante. Un extent est constitue d’un certain nombre de blocs contigus, ce qui
garantit un acces physique efficace.– le segment est une collection d’extents qui constitue en general un seul objet de la base, par exemple
le segment de donnee d’une table ou le segment d’un index.
11.2 Optimisations algebriques
11.2.1 Introduction
On s’interesse a reduire le plus possible le nombre d’entrees/sorties sur le disque, les mesures de per-formances se feront en nombre de lectures ou ecritures sur le disque.
Nous allons nous interesser particulierement aux transformations algebriques et a la recherche dechemins d’acces (utilisation d’index par exemple). Les possibilites offertes par Oracle seront ensuiteexaminees.Soit par exemple :
create table Etudiant (
id Number (5),
nom Varchar2 (20),
constraint Etudiant_PK primary key (id)
) ;
118
11.2. OPTIMISATIONS ALGEBRIQUES 119
create table Inscription (
etudiant Number (5) references Etudiant (id),
matiere Varchar2 (3),
constraint Inscription_PK primary key (matiere, etudiant)
);
Pour connaıtre le nom des etudiants inscrit en ’BDD’ on peut ecrire la requete :
select e.nom
from Etudiant e
inner join Inscription i on e.id = i.etudiant
where i.matiere = ’BDD’ ;
Supposons qu’il y a 1.000 etudiants (100 par bloc), 10.000 inscriptions (200 par bloc) et 100 etudiantsinscrits en ’BDD’.
Voici quelques manieres de calculer cette requete.
Approche naıve On effectue d’abord l’equi-jointure (sans se servir des index), puis la restriction etenfin la projection.
1. Construire sur disque le resultat de la jointure : lire chacun des etudiants (1.000 lectures)et pour chacun retrouver toutes ses inscriptions (1.000×10.000 lectures), on obtient 10.000elements dans la jointure qu’on ecrit sur le disque (10.000 ecritures).
2. lire les 10.000 lignes de la jointure pour ne conserver que celles de ’BDD’ et en faire laprojection.
Le nombre total d’entrees sorties est donc de 10.021.000.
Utiliser la semi commutativite de la restriction sur la jointure On se rend compte que la res-triction sur la matiere BDD pourrait etre faite avant la jointure.
1. Calculer la restriction de Inscription sur ’BDD’ : 10.000 lectures et 100 ecritures.
2. Calculer l’equi-jointure entre Etudiant et la restriction deja calculee : 1.000 lectures d’etudiantet pour chacun 100 lectures d’inscription et faire la projection.
Le nombre total d’entrees sorties est donc de 111.100. On a gagne un facteur de 90 !
Exploiter les index A chaque clef primaire ou contrainte d’unicite est associe un index. Un indeximplante par une structure ordonnee (Barbre par exemple) permet de retrouver une clef et saligne en logm(n) avec m ≥ 2.
1. matiere etant le poids fort de la clef primaire de Inscription, il est possible, grace a l’indexInscription_PK, de retrouver les 100 inscriptions en ’BDD’ en au plus log2(10.000) + 2×100 = 214 lectures si les aiguillages du Barbre menant en feuille peuvent etre conserves enmemoire (voir 11.4.5 page 125) puis de les stocker avec 100 ecritures.
2. Plutot que faire la jointure par rapport aux etudiants, on peut la faire par rapport auxinscriptions (la jointure est commutative) : on lit chacune des 100 inscriptions et, pourchacune on retrouve l’etudiant grace a l’index Etudiant_PK en au plus log2(1.000) = 10lectures.
Le nombre total d’entrees sorties est donc de 1.414. On gagne un facteur d’environ 7.000 parrapport a l’approche naıve !
Remarquer que ces ameliorations sont le fruit de proprietes de l’algebre relationnelle appliqueesen connaissant la taille des tables.
11.2.2 Optimiser par des manipulations algebriques
Principalement : (Gardarin [8] p.315)
1. σP (A×B) = A ⊲⊳P B
120 CHAPITRE 11. OPTIMISATIONS
2. Commutativite des jointures : R ⊲⊳P S = S ⊲⊳P R
3. Associativite des jointures : (R ⊲⊳P S) ⊲⊳Q T = R ⊲⊳P (S ⊲⊳Q T )
4. Fusion des projections : ΠA1,...,Ak(ΠB1,...,Bl
(R)) = ΠA1,...,Ak(R)
5. Regroupement ou degroupement et commutativite des restrictions : σP (σQ(R)) = σP∧Q(R) =σQ∧P (R) = σQ(σP (R))
6. Quasi-commutativite des restrictions et projections : ΠA1,...,Ak(σP (R)) = σP (ΠA1,...,Ak
(R)) si lesattributs A1, . . . , Ak de R forment un sur-ensemble de ceux utilises dans le predicat P
7. Quasi-commutativite des restrictions et jointures : σP (R ⊲⊳Q S) = (σP (R)) ⊲⊳Q S si le predicatP porte uniquement sur des attributs de R
8. Distributivite des restrictions sur les unions, intersections ou differences : σP (R1∪R2) = σP (R1)∪σP (R2)
9. Quasi-commutativite des projections et jointures : ΠA1,...,Ak(R ⊲⊳P S) = ΠA1,...,Ak
(R) ⊲⊳P S si lesattributs A1, . . . , Ak de R forment un sur-ensemble de ceux utilises dans le predicat de jointureP .
10. Commutativite des projections avec les unions.
Q. 138 Dessiner l’arbre relationnel de la requete suivante puis le transformer pour qu’il soit plusefficace (p(x) ∧ (x = y)) ≡ (p(x) ∧ (x = y) ∧ p(y)) :
select e.nom, i.matiere
from Etudiant e
inner join Inscription i on e.id = i.etudiant
where e.id between 20 and 50
and e.nom like ’%rr%’
and i.matiere in (’BDD’, ’CL’, ’SYS’) ;
11.3 Acces aux donnees sans index
Rappel : la page est l’unite d’entree/sortie sur disque, c’est a dire que, quelle que soit la taille dela valeur qu’on souhaite lire ou ecrire sur le dique, le systeme (si la page n’est pas deja en memoiretampon) lira ou ecrira completement la page qui contient cette valeur.Une page est en general constituee d’un certain nombre de secteurs disque. Par exemple en Oracleune page fait 4 kilo-octets.Soit la table :
create table Employe (
id Number (5) primary key,
nom Varchar2 (20),
salaire Number (10, 2),
dpt Number (5)
) ;
dont la clef primaire est constituee de la colonne id.Suposons qu’un SGBD naıf implemente cette table par un simple fichier lineaire contenant la listedes employes et le fait qu’id etant la clef primaire doit etre unique, mais aucune autre information.Supposons que Employe contienne n lignes et qu’une page contiennent en moyenne p employes (lenombre de pages de la table Employe sera alors de ⌈n/p⌉).Lors de la requete suivante :
select e.nom from Employe e where e.id = 16 ;
il faudre lire sequentiellement le fichier jusqu’a trouver l’employe d’id 16. Des qu’on l’a trouve on peutarreter l’exploration puisqu’on sait qu’id est unique.
11.4. NOTIONS DE BASE SUR LES B+-ARBRES A CLEFS UNIQUES 121
Par exemple, pour retrouver l’employe 16 il faudra lire en moyenne ⌈n/2p⌉ pages car cet employe peutse trouver, de facon equiprobable, n’importe ou dans la table (ce qui donne 5.000 lectures de page sin = 1.000.000 et p = 100).
Pire : lors d’une insertion d’un nouvel employe, il faudra d’abord verifier que son id n’apparaıt pasdeja dans la table et donc faire une exploration exhaustive de celle-ci, c’est a dire lire les ⌈n/p⌉ pages(10.000 lectures de page si n = 1.000.000 et p = 100).
Cela sera un peu plus complique suite a un update de la colonne id qui a pu modifier un nombrequelconque de lignes.
Q. 139 Comment pourrait-on s’y prendre pour verifier qu’un update conserve l’unicite de la clefprimaire ?
D’ou l’interet de gerer une structure supplementaire permettant de trouver rapidement un employegrace a sa clef et de garantir efficacement l’unicite des clefs. Cette structure s’appelle un index. Il ya au moins deux sortes d’index : les B-arbres et les tables de hachage, nous n’envisagerons que lesB-arbres.
11.4 Notions de base sur les B+-arbres a clefs uniques
Voir “Introduction a l’algorithmique” de T. Cormen, C. Leiserson et R. Rivest chez Dunod.
La fonctionnalite principale d’un B+-arbre est celle d’une table (ou map en anglais) permettant detrouver rapidement l’adresse de la ligne1 d’une table ayant une valeur particuliere de certaines colonnes.On appellera clef du B+-arbre ces colonnes. L’interet du B+-arbre est qu’il est bien adapte a la gestionsur disque ou en fait un nœud correspond a un bloc disque dont la taille va de 512 octets a 4 Koctets,le nombre maximum de clefs stockables par nœud depend evidemment du nombre d’octets necessaireau stockage d’une clef.
Un autre interet est qu’il est parfaitement equilibre : toutes ses feuilles sont a la meme profondeur.
La structure d’un B+-arbre est basee sur le fait que les clefs qui y definissent des aiguillages disposentd’un ordre complet. On peut voir une clef comme un nombre ayant autant de chiffres que la clef a decolonnes, les colonnes de gauche etant celles de poids fort, comme dans notre notation des nombresen base 10.
Par exemple la clef (34, ’jaune’) est strictement plus petite que la clef (34, ’vert’) a cause de lacolonne de poids faible indiquant la couleur et de l’ordre lexicographique.
L’exemple le plus classique est celui ou la clef du B+-arbre est la clef primaire de la table.
Ses caracteristiques principales sont :
– en terme de stockage : le B+-arbre est stocke sur disque, il est donc persistant et dispose d’unegrande capacite.
– en terme d’organisation : c’est une generalisation de l’Arbre Binaire de Recherche (ABR) : c’est unarbre m-aire avec m ≥ 2 (tout nœud interne a au moins deux sous-arbres non vides) equilibre quipermet donc des recherches par clef efficaces (en logm(n) acces disque, ou n est le nombre d’elementsdu B+-arbre).
Une petite difference des B+-arbres tels que presentes ici avec les ABR : les couples (clef, adressede ligne) sont stockes dans les feuilles et les nœuds internes ne contiennent que des clefs (ce sont depurs aiguillages)2 .
1Oracle utilise le mot rowid pour designer une adresse de ligne.2D’autres imlementations des Barbres ressemblent plus aux ABR, soit en stockant les elements complets plutot que
simplement leurs clefs dans les nœuds internes, soit, si ces elements sont de trop grande taille en associant a chaque clefun pointeur permettant de retrouver l’element possedant cette clef. Ces deux solutions permettent lors de la recherche
122 CHAPITRE 11. OPTIMISATIONS
– chaque nœud du B+-arbre occupe une page du systeme de fichiers (une page correspond en generala un, deux ou quatre secteurs disque), l’idee est que la page (on dit parfois aussi bloc) est l’uniteatomique de lecture/ecriture.
Un nœud interne (ou aiguillage) ne contient que des clefs et des adresses d’autres nœuds du B+-arbre.Une adresse est en fait le numero de page du nœud ou de la feuille pointe. Chaque nœud interneconstitue un aiguillage permettant de trouver le chemin menant a la feuille contenant la clef chercheeet sa valeur, voir la figure 11.1.
C1 C2 Cn
B1 B2 B3 Bn Bn+1
espace libre........C3
Fig. 11.1 – Nœud interne (page disque) constituant un aiguillage : on a C1 < C2 < . . . < Cn,l’element de clef C telle que Ci−1 < C ≤ Ci ne peut se trouver que dans le sous-arbre Bi. Si C ≤ C1,C doit se trouver dans B1. Si Cn < C, C doit se trouver dans Bn+1. On remarque que ce nœud internen’est pas sature et qu’il pourrait donc accueillir d’autres clefs et sous-arbres.
Un nœud feuille contient des elements (clef, adresse de ligne), voir la figure 11.2.
espace libre........C1 C2 Ck
Fig. 11.2 – Feuille : Ci−1 < Ci. L’adresse associee a Ci est celle du tuple dans la table ayant la valeurCi dans ses colonnes (on parle aussi de rowid plutot que d’adresse).
La taille des nœuds internes et des feuilles etant fixee par le systeme, l’arite des nœuds internes et lenombre d’elements stockables dans une feuille dependront des tailles physiques maximales necessairesa l’ecriture sur disque de toute valeur de clef (taille Sclef) et d’element (taille Selem).
11.4.1 Proprietes invariantes d’un B+-arbre
1. L’ensemble des clefs est muni d’une relation d’ordre total qui sert a maintenir la structure duB+-arbre.
2. La capacite T en nombre de clefs d’un nœud interne doit etre impaire avec T = 2K +1 et K ≥ 1.Un nœud interne doit pouvoir accueillir au moins 3 clefs.
3. tout nœud interne (sauf la racine) pouvant accueillir 2 ∗K + 1 clefs doit toujours contenir aumoins K clefs (et donc K + 1 sous-arbres).
4. la racine n’a pas cette contrainte dans la mesure ou elle peut etre l’unique feuille et contenir uneseule clef avec son rowid.
5. De plus l’equilibre du B+-arbre (toutes les feuilles sont a la meme profondeur) est du au faitque le B+-arbre croıt ou decroıt en hauteur par sa racine.
6. Deux clefs successives Ci et Ci+1 d’un nœud interne ou d’une feuille verifient Ci < Ci+1
7. Les clefs C du sous-arbre situe a gauche de Ci verifient Ci−1 < C ≤ Ci.
d’un element par sa clef de trouver l’element sans descendre forcement jusqu’aux feuilles du Barbre (comme c’est le casavec un ABR), ce qui semble etre un avantage, mais on verra qu’en general il vaut mieux ne stocker qu’un minimumd’information dans les nœuds internes, c’est a dire uniquement la clef, de maniere a ce que l’arite, ou la largeur, desaiguillages soit la plus grande possible ce qui a pour consequence de diminuer la profondeur de l’arbre et donc le nombrede pages a lire pour acceder a un element.
11.4. NOTIONS DE BASE SUR LES B+-ARBRES A CLEFS UNIQUES 123
Si T = 101 on a au moins K = 50 clefs par aiguillage. (il en va de meme pour les feuilles qui doiventetre au moins a moitie remplies mais pas forcement avec la meme valeur de T .)
Q. 140 Supposons que la page fasse 4 kilo-octets, qu’un pointeur de page necessite 16 octets et que leSGBD utilise 4 octets de chaque aiguillage pour en gerer le contenu. Donner les valeurs de T = 2K +1lorsque la taille maximale d’une valeur de clef vaut respectivement 10 octets, 100 octets et 1000 octets.
Q. 141 Nombres minimaux et maximaux de sous-arbres d’un aiguillage avec T = 51 clefs ?
Q. 142 Une clef Ci d’un aiguillage peut-elle apparaıtre dans les aiguillages de Bi ?
Q. 143 Que peut-on dire de Ci par rapport a son sous-arbre gauche Bi ?
11.4.2 Algorithme de recherche d’un element connaissant sa clef C
Pour trouver la feuille susceptible de contenir une clef C donnee (ou se rendre compte que C n’existepas dans le B+-arbre), la recherche commence par la racine du B+-arbre. Soit x la variable contenantla page disque correspondant au nœud courant :
1. x← lire (racine)
2. tant que x est un aiguillage : x← lire (Bi) avec Ci−1 < C ≤ Ci,
3. x est une feuille : soit C s’y trouve : il faut lire le bloc table pour y trouver la ligne, soit C n’estpas dans le B+-arbre et est donc absent de la table.
Cout dans le pire des cas : au plus 1 + ⌈logK+1(⌈n/2⌉)⌉ lectures de page.Les proprietes 3 et 5 page 122 garantissent que, dans le pire des cas — i.e. la racine contient uneclef et a donc deux fils et chaque nœud interne contient K clefs et a donc K + 1 fils — l’acces a unelement de clef donnee se fera en au plus 1+⌈logK+1(⌈n/2⌉)⌉ lectures de page. Voici quelques exemplespour differentes valeurs de K et n (le nombre d’elements de la table), a comparer aux 5.000 lecturesnecessaires si on ne dispose pas d’index !
Nombre de pages lues ou profondeur du B+-arbre, n est le nombre de lignespire des cas meilleur des cas
nœud le moins plein possible nœud le plus plein possibleK n = 1.000 n = 1.0002 n = 1.0003 n = 1.000 n = 1.0002 n = 1.0003
1 10 20 303 6 11 167 4 8 11
31 3 5 763 3 5 6
127 3 4 6
Fig. 11.3 – n est le nombre de lignes de la table.
Quelques rappels sur les fonctions logarithme :
propriete exemple d’application
log(ab) = log(a) + log(b) log(500.000) = log(2) + log(250) + log(1000)log(ap) = p log(a)loga(n) = logb(n)/ logb(a) logK(n) = log2(n)/ log2(K)⌈log2(1.000)⌉ = 10
Q. 144 Donner la formule donnant le nombre de lectures de page dans le meilleur des cas — i.e. tousles aiguillages sont pleins et ont donc chacun 2K + 2 fils.
Q. 145 Completer le tableau de la figure 11.3.
124 CHAPITRE 11. OPTIMISATIONS
11.4.3 Un algorithme simple d’insertion
Une technique assez simple, mais peut-etre pas tres optimale, pour inserer d’un nouvel element (clef,valeur) consiste, lors de la descente dans l’arbre pour trouver la feuille d’insertion, a exploser chaquenœud plein (i.e. contenant donc T = 2K + 1 clefs) en deux nœuds a moitie pleins (contenant chacunK clefs), la clef du milieu CK+1 remonte dans le nœud pere :
explosion
B1
C1
B2
...
BK
CK
BK+1
libre
BK+2
CK+2
BK+3 BT+1
...
BT
CT libre
PI PI+1CK+1PI PI+1
père non plein
BK
CK
BK+1
CK+1
BK+2
CK+2
BK+3
... CT
BT+1BT
...
B2
C1
B1
fils plein
Si le nœud explose n’est pas la racine, alors il a bien un pere qui peut accueillir CK+1 (le pere ne peutpas etre plein, sinon il aurait ete explose lors de la descente).
Si le nœud explose est la racine alors on alloue un nouvel aiguillage vide qui va accueillir CK+1 etdevenir la nouvelle racine du B+-arbre.
Donc :
1. cette insertion conserve bien au moins K clefs par aiguillage,
2. la hauteur du B+-arbre n’augmente qu’a chaque fois qu’on explose la racine, car il est alorsnecessaire de creer une nouvelle racine au dessus des deux nœuds obtenus par explosion. Puisquele B+-arbre croıt par la racine, toutes les feuilles restent a egale distance de la racine : l’equilibredu B+-arbre est bien conserve (voir la propriete 5 page 122).
Q. 146 Lors d’une explosion, combien de nouvelles pages faut-il allouer dans les deux cas possibles ?
11.4.4 Un exemple avec une clef atomique
Voici un exemple de B+-arbre associe a une table ayant des lignes du genre (34, "nom") ou 34 est laclef.
30 44
(10, oo) (4, bof) (55, ii) (66, ii) (44, oo)(31, rr) (30, toto)
30 31 44
TABLE
66554 10
B+−ARBRE (ou INDEX)
Un autre algorithme plus efficace consiste a ne faire exploser un nœud que quand c’est indispensable :dans ce cas les explosions se font en remontant le chemin vers la racine : la pile des nœuds peres saturesest alors necessaire dont le fond est le dernier nœud pere non sature rencontre s’il en existe un. Si tousles nœuds de la pile sont satures alors le nœud en fond de pile est forcement la racine, c’est le cas oule B+-arbre verra sa profondeur augmenter de 1.
Q. 147 Quelle est la valeur de K ? Est-ce bien un B+-arbre ?
Q. 148 Comment retrouver la feuille contenant la clef 44 ? etiqueter les blocs lus avec une *
11.4. NOTIONS DE BASE SUR LES B+-ARBRES A CLEFS UNIQUES 125
Q. 149 Comment retrouver les feuilles contenant toutes les clefs ∈ [25, 44] ? etiqueter les blocs lusavec un +
Q. 150 Les deux utilisations precedentes du B+-arbre paraissent-elles interessantes ? ne vaudrait-ilpas mieux parcourir directement la table sans passer par son B+-arbre ?
Q. 151 A quelle condition l’utilisation du B+-arbre pourrait-elle devenir interessante en particulierpour la question Q.148 ?
Q. 152 Dessiner le nouvel etat apres insertion dans la table de (45, ”truc”) puis (7, ”truc”).
Q. 153 Dessiner l’etat qu’on aurait obtenu si on avait permute les deux insertions precedentes.
Q. 154 Donner un algorithme efficace pour retrouver toutes les feuilles pouvant contenir des clefs∈ [a, b].
11.4.5 Un exemple avec une clef composee
Ici les lignes de la table sont de la forme (matiere, enseignant, volumeHoraire) et la clef (matiere,enseignant) est composee des deux premiers attributs d’un element. Par exemple (BD, 22) est la clefde l’element (BD, 22, 45).
Voici un B+-arbre contenant ces informations avec K = 1 :
CL3
BD22
CL5 22
CL CL30
CL33
IA3
BD5
BD7
AI32
BD3
IA5
PI1
PI3
BD, 7 IA, 3
BD, 3 CL, 3 CL, 22 CL, 30 PI, 1
Pour ordonner deux clefs multi-colonnes, plus une colonnes est a gauche plus elle est de poids fort.Par exemple, pour (m1, e1) et (m2, e2) on compare d’abord les colonnes m1 et m2 et, seulement sielles sont egales on compare les colonnes e1 et e2.
Q. 155 D’apres la figure, a-t-on (CL, 22) < (PI, 3) ? Quel est l’attribut de poids fort de la clef.
Q. 156 Donner un algorithme efficace pour retrouver tous les elements dont le poids fort de la clefest egal a une valeur donnee, par exemple BD.
On appellera sous-clef une clef incomplete constituee d’au moins une des colonnes de poids fort.
Q. 157 Meme question pour retrouver tous les elements dont la matiere ∈ [MInf,MSup].
Q. 158 Y a-t-il un algorithme aussi efficace dans le cas ou on cherche les elements dont le poids faiblede la clef est egal a une valeur donnee, par exemple les elements d’enseignant 3 ? Expliquer.
Q. 159 Dessiner un B+-arbre contenant les memes elements que dans l’exemple mais dont la clef al’enseignant en poids fort.
Q. 160 Soit des elements de la forme (a, b, c, d, e) dont la clef est constituee des attributs {a, d, e}et que l’on sache qu’on fera des acces uniquement sur les sous-clefs {d}, {a, d} et {a, d, e}, dans quelordre a-t-on interet a declarer les colonnes de la clef du B+-arbre ? (Oracle et PostgreSQL exploitenteffectivement cet ordre)
Q. 161 Dans le cas precedent, comment pourrait-on faire une recherche relativement efficace sur lasous-clef {d, e} ?
126 CHAPITRE 11. OPTIMISATIONS
Q. 162 Quelle caracteristique interessante ont les feuilles d’un B+-arbre ? en deduire un ajout d’in-formation permettant d’eviter de trier pour certaines clauses order by.
11.4.6 B+-arbre a clef non unique ou index categoriel
Les index permettant de retrouver efficacement plusieurs elements appartenant a une meme categoriesont eux aussi tres utiles.
On peut, par exemple, regrouper des employes par service, des disques par editeur ou des etudiantspar annee de naissance.
Q. 163 Donner les proprietes d’un B+-arbre acceptant la multiplicite des clefs en s’inspirant de cellesdu B+-arbre a clef unique.
Q. 164 L’algorithme d’insertion par explosion a priori des nœuds pleins (voir la section 11.4.3page 124) est-il toujours applicable et conserve-t-il les proprietes de ce B+-arbre ?
Q. 165 Reprendre les elements du B+-arbre precedent pour les indexer par la categorie matiere.
Q. 166 Algorithme pour retrouver les elements de clef C.
Q. 167 Soit un index multiple sur les couleurs, que peut-on dire du sous-arbre compris entre la clefjaune a gauche et jaune a droite ?
11.4.7 Plusieurs index B+-arbre sur une meme table
Il est bien entendu possible d’avoir plusieurs index B+-arbre associes a une table.
11.4.8 SQL et index
Les contraintes primary key et unique demandent implicitement au SGBD de creer les index uniquescorrespondant.
Par ailleurs la commande SQL create index permet de creer explicitement des index uniques ou non.
Par defaut, en Oracle et en Postgres, les index sont implementes par des B+-arbres.
Dans ses index implantes en B+-arbres, Oracle chaıne les feuilles dans l’ordre croissant de la clef,dans les deux sens. Par ailleurs les feuilles contiennent des couples (clef, rowids), un rowid est l’adressed’une ligne de table.
11.5 Oracle et les plans d’execution : Explain plan
Pour chaque requete (ou instruction DML), Oracle va utiliser un certain nombre de techniques d’opti-misation pour calculer un plan d’execution qui soit le meilleur possible (mais pas forcement optimal).
Le resultat de cette optimisation depend, entre autres, des index disponibles, des contraintes d’integrite(par exemple le fait qu’une colonne soit une clef etrangere autorise a utiliser l’index de clef lors d’uneequi-jointure se faisant sur cette colonne et la table referencee) et aussi des connaissances statistiques(par exemple si une des tables d’une jointure est tres petite on peut la charger une fois pour toutes enmemoire centrale et lire une seule fois l’autre table) dont dispose le SGBD au moment ou il calcule leplan d’execution.
Le but de la commande explain plan est principalement de permettre au programmeur de voir dequelle maniere le SGBD va executer un ordre DML et donc de voir les defauts eventuels de ce pland’execution. Le programmeur peut ameliorer les choses par une reconception des index, des suggestionsd’optimisation (hint) faites au SGBD, une reecriture des requetes, . . .
11.5. ORACLE ET LES PLANS D’EXECUTION : EXPLAIN PLAN 127
11.5.1 Table sans index
create table Employe (
id Number (5),
nom Varchar2 (20),
salaire Number (10, 2),
dpt Number (5)
) ;
pierre3 2
jules6 2sophie4 1paul2 3marc1 2léa7 3marie5 2
Employe
nomid dpt
La requete�� ��select * from Employe e where e.id = 4 ; doit explorer completement la table pour
retrouver tous les employes dont l’id vaut 4. Voici son plan d’execution :
Id Operation Name Rows Bytes Cost (%CPU)0 SELECT STATEMENT 1 25 2 (0)
*1 TABLE ACCESS FULL EMPLOYE 1 25 2 (0)1 - filter("E"."ID"=4)
L’operation TABLE ACCESS FULL signifie que l’execution consiste a balayer toutes les lignes de latable Employe. En effet id n’est pas une clef de Employe, plusieurs, voire tous les employes peuventavoir le meme id.
Cout en nombre d’acces disque :
– Le cout est principalement lie aux acces disque– Ne nombre de nuplets de la table Employe
– Ep nombre moyen de nuplets employe par page (bloc disque)– ⌈Ne/Ep⌉ nombre d’acces disque.
Si Ne = 100.000, Ep = 20 alors le nombre d’acces disque est de 5000.
11.5.2 Table avec index unique sur la clef primaire
Par defaut pour une clef, Oracle cree un index en B+arbre, la colonne la plus a gauche de la clef estcelle de poids fort, la plus a droite est celle de poids faible (comme dans les notations numeriques).
alter table Employe add
(constraint Employe_PK
primary key (id)) ;
pierre3 2
jules6 2sophie4 1paul2 3marc1 2léa7 3marie5 2
Employe
nomidEmploye_PK
4
dpt
La requete�� ��select * from Employe e where e.id = 4 ; utilise maintenant l’index de clef pri-
maire pour acceder rapidement a l’employe d’id 4. Voici son plan d’execution :
Id Operation Name Rows Bytes Cost(%CPU)0 SELECT STATEMENT 1 25 1 (0)1 TABLE ACCESS BY INDEX ROWID EMPLOYE 1 25 1 (0)*2 INDEX UNIQUE SCAN EMPLOYE_PK 1 1 (0)2 - access("E"."ID"=4)
Les operations les plus decalees vers la droite sont celles qui sont executees en premier. On voit doncque le plan consiste d’abord a utiliser l’index de clef primaire (EMPLOYE_PK) pour retrouver l’adresse(ou rowid) de la ligne contenant l’employe d’id egal a 4 ; ce rowid est ensuite utilise pour retrouver
128 CHAPITRE 11. OPTIMISATIONS
directement la ligne de l’employe 4 dans la table Employe.
Cout en nombre d’acces disque :
– Ne nombre de nuplets de la table Employe
– K nombre minimum de clefs par nœud du Barbre de l’index– 1 + ⌈logK+1(⌈Ne/2⌉)⌉ nombre de nœud d’index a lire pour obtenir le rowid de l’employe 4– 1 nombre de page a lire pour obtenir l’employe dont on a obtenu le rowid.– 2 + ⌈logK+1(⌈Ne/2⌉)⌉ nombre d’acces disque.
Si Ne = 100.000,K = 50 alors le nombre maximum d’acces disque est de 5. Ce qui est nettementmeilleur que precedemment ! Notons que les performances sont identiques quel que soit l’employe re-cherche (la valeur de e.id pourrait n’etre connue qu’a l’execution).
11.5.3 Table avec index non unique sur le departement
Evidemment, la requete�� ��select * from Employe e where e.dpt = 2 ; doit explorer toute la table.
Voici son plan d’execution :
Id Operation Name Rows Bytes Cost(%CPU)0 SELECT STATEMENT 1 51 2 (0)
*1 TABLE ACCESS FULL EMPLOYE 1 51 2 (0)1 - filter("E"."DPT"=2)
Creation d’un index :
create [unique] index <nom-index> on <nom-table> (<liste-colonnes-ou-expression>)
Si unique n’est pas mentionne l’index acceptera des occurrences multiples de la meme clef. Pourchaque colonne on peut choisir l’ordre croissant (asc) ou decroissant (desc).
Pour introduire un index pour des raisons d’optimisation, Oracle recommande d’utiliser explicitementcreate [unique] index plutot que d’introduire une contrainte d’unicite.
PostgreSQL propose les memes fonctionnalites.
On ajoute l’index non unique Employe_Dpt_Index sur les departements.
create index Employe_Dpt_Index
on Employe (dpt) ;
sophie4 1paul2 3marc1 2léa7 3marie5 2
Employe
nomid
1
2
3
4
Employe_Dpt_IndexEmploye_PK
pierre3 2
jules6 2
dpt
Les deux requetes�� ��select * from Employe e where e.dpt = 2 ; et�� ��select * from Employe e where e.dpt between 2 and 10 ; exploitent l’index non unique sur
la colonne dpt. Elles ont le meme plan d’execution :
Id Operation Name Rows Bytes Cost(%CPU)0 SELECT STATEMENT 34 1734 4 (0)1 TABLE ACCESS BY INDEX ROWID EMPLOYE 34 1734 4 (0)
*2 INDEX RANGE SCAN EMPLOYE_DPT_INDEX 34 1 (0)2 - access("E"."DPT"=2)
11.5. ORACLE ET LES PLANS D’EXECUTION : EXPLAIN PLAN 129
Le plan consiste maintenant a retrouver efficacement les rowid des employes du departement 2, puisa faire des acces direct dans la table Employe.
Pour obtenir ce plan d’execution, il a fallut inserer 10000 lignes dans Employe.
Q. 168 Quel est le plan d’execution de : select * from Employe e where e.dpt between 2 and
7 ?
Q. 169 Quel est le plan d’execution de : select * from Employe e where e.dpt in (2, 7, 11) ?
On peut aussi creer un index dont la clef est formee d’expressions portant sur les colonnes de la tableindexee, par exemple pour ne pas distinguer les minuscules des majuscules :
create index Emp_Nom on Employe (upper (nom)) ;
ou nom est bien sur une colonne de la table Employe.
Attention pour que cet index Emp_Nom soit utilise par l’optimiseur il faudra, dans les requetes, utiliserles memes expressions, par exemple :
select *
from Employe
where upper (nom) between ’C’ and ’H’ ;
La creation d’un index utilise la table triee par rapport a la clef d’indexation : on obtient donc unBarbre particulierement compact et efficace.
Attention, si un index non unique existe deja sur les memes colonnes que celles utilisees dans unecontrainte de clef primaire creee ensuite, alors la contrainte de clef primaire utilisera cet index multiple !
11.5.4 Table avec index unique sur une clef candidate
Il s’agit de la contrainte Unique.
11.5.5 L’ordre des colonnes d’un index a son importance
Que l’index soit unique ou non, l’ordre dans lequel on ecrit les colonnes constituant sa clef peu avoirdes consequences sur les performances.
Soit par exemple :
create table X (
a1 Number (5),
a2 Number (5),
a3 Number (5),
a4 Number (5),
constraint X_PK primary key (a1, a2, a3, a4)
) ;
Pour toute clause where (ou on pour les jointures) :
– si la condition porte au moins sur la colonne a1, Oracle utilisera l’index pour retrouver efficacementles tuples.
– si la condition ne porte pas au moins sur la colonne a1, Oracle ne pourra pas utiliser l’index.
Ceci s’explique par le fait que, pour Oracle (ainsi que pour Postgres), le poids des colonnes constituantla clef d’acces de l’index decroıt de gauche a droite.
Moralite : lorsqu’on declare les contraintes de clef ou d’unicite et les index on a interet a savoircomment seront utilisees les colonnes y participant.
130 CHAPITRE 11. OPTIMISATIONS
11.5.6 Relativiser l’importance des index
En simplifiant, on peut dire que les index sont particulierement important pour les grosses tables.En effet les petites tables susceptibles de tenir completement en memoire centrale n’ont peut-etre pasbesoin d’index.
Ne pas oublier qu’un index coute en temps de mise a jour et en place memoire. A chaque modificationd’une table, il faut aussi mettre a jour tous ses index.
11.6 Representation graphique
L’optimisation d’une BD est un sujet extremement important, elle peut permettre d’accelerer considerablementl’execution de certaines instructions DML.
L’algorithme utilise pour executer une instruction DML s’appelle un plan d’execution. Un plan estune decomposition hierarchique d’une instruction DML en operations plus elementaires, les plans sontproduits par l’optimiseur SQL.
Voici une requete et son plan (toutes les clefs primaires ont ete declarees dans les tables) :
-------------------- ---------------
v | | v
Client (cdc, nom) Envoi (cdc, cdp) Produit (cdp, couleur)
--- -------- ---
select c.nom, p.libelle, p.couleur
from Client c inner join Envoi e on c.cdc = e.cdc
inner join Produit p on p.cdp = e.cdp ;
Id Operation Name Rows Bytes Cost(%CPU)0 SELECT STATEMENT 1 37 4 (0)1 NESTED LOOPS 1 37 4 (0)2 NESTED LOOPS 1 19 3 (0)3 TABLE ACCESS FULL ENVOI 1 8 2 (0)4 TABLE ACCESS BY INDEX ROWID CLIENT 1 11 1 (0)
*5 INDEX UNIQUE SCAN CLIENT_PK 1 0 (0)6 TABLE ACCESS BY INDEX ROWID PRODUIT 1 18 1 (0)
*7 INDEX UNIQUE SCAN PRODUIT_PK 1 0 (0)5 - access("C"."CDC"="E"."CDC"), 7 - access("P"."CDP"="E"."CDP")
Q. 170 Quelles sont les contraintes qui peuvent expliquer le TABLE ACCESS FULL sur la table ENVOI ?
Q. 171 A quoi correspondent les lignes d’Id 3, 4 et 5 dans la requete ?
Q. 172 Pourriez-vous donner une approche plus efficace si on suppose que les index de ENVOI etCLIENT sont des B+arbre et en supposant que la colonne cdc de la clef primaire de ENVOI est celle depoids fort.
11.6.1 Comment lire un tel plan d’execution
Un plan d’execution correspond a une hierarchie de phases d’evaluation. La profondeur d’une lignedans cette hierarchie est proportionnelle a son indentation.
Tout d’abord, une ligne du plan d’execution est precedee de l’evaluation des lignes plus indentees quila suivent jusqu’a la prochaine ligne indentee de la meme maniere.
Voici un exemple d’ordre d’evaluation fonction de cette indentation :
11.6. REPRESENTATION GRAPHIQUE 131
6
3
1
2
5
4
On remarque que pour deux lignes filles d’une meme ligne c’est la premiere qui est evaluee en premieret la seconde qui est evaluee ensuite, enfin c’est la ligne mere qui est evaluee.On peut alors mieux comprendre le plan precedent et lui associer de la semantique :
Table access full : Envoi
Table access by index rowid : Client
Index unique scan : Client_PK
Nested loops
Nested loops
Select statement
Table access by index rowid : Produit
Index unique scan : Produit_PK
1
2
3
4
5
6
7
8
concaténer C et E en (C, E)
prendre chaque envoi E
trouver le rowid du client C de E
prendre les informations de C
prendre les informations de P
trouver le rowid du produit P de E
concaténer (C, E) et P en (C, E, P)
faire la projection du triplet (C, E, P)
Il est aussi possible de representer graphiquement cette hierarchie, voir la figure 11.4.
Fig. 11.4 – Representation graphique du plan d’execution
Nested Loops
1 Envoi
FullTable Access
By Index RowIdTable Access
IndexUnique Scan
4
5rowid
Nested Loops(cdc,cdp)
Table AccessBy Index RowId
3rowid
IndexUnique Scan
2 Client_PK
Client
Produit
Produit_PK
(cdc,cdp, nom)
Utiliser ce ROWID pour retrouverrapidement le client C dans la table Client et fournir la concatenation (E, C) a l’etage superieur.
On voit que les index des clefs sont utilises, a chaque fois que c’est possible, pour constituer la jointure.La seule table parcourue completement est Envoi, pour les autres le plan utilise l’index de cle primairede la table.
En reprenant la requete precedente mais en precisant qu’on s’interesse au client A3 on obtient un pland’execution different :
select c.nom, p.libelle, p.couleur
from Client c inner join Envoi e on c.cdc = e.cdc
inner join Produit p on p.cdp = e.cdp where e.cdc = ’A3’ ;
132 CHAPITRE 11. OPTIMISATIONS
Id Operation Name Rows Bytes Cost(%CPU)0 SELECT STATEMENT 1 37 3 (0)1 NESTED LOOPS 1 37 3 (0)2 NESTED LOOPS 1 19 2 (0)3 TABLE ACCESS BY INDEX ROWID CLIENT 1 11 1 (0)
*4 INDEX UNIQUE SCAN CLIENT_PK 1 1 (0)*5 INDEX RANGE SCAN ENVOI_PK 1 8 1 (0)6 TABLE ACCESS BY INDEX ROWID PRODUIT 1 18 1 (0)
*7 INDEX UNIQUE SCAN PRODUIT_PK 1 0 (0)4 - access("C"."CDC"=’A3’), 5 - access("E"."CDC"=’A3’)
7 - access("P"."CDP"="E"."CDP")
Q. 173 Pourquoi ce plan ne part-il plus pas de la table Envoi mais de la table Client ?
Q. 174 Pourquoi l’id 5 indique-t-il un INDEX RANGE SCAN sur ENVOI PK ?
Q. 175 Dessiner la hierarchie d’operations de ce plan d’execution.
Q. 176 Quel serait le nouveau plan d’execution si la projection devenait select p.libelle, p.couleur
11.6.2 Quelques operations d’un plan d’execution
11.7. EXEMPLES DE PLAN D’EXECUTION 133
operation option description
SORT GROUP BYclause group by regrouper les elements du meme groupepar un tri sur les valeurs des expressions definissant le re-groupement
JOINUn tri des nuplets d’une relation prealable a une jointurepar fusion : MERGE JOIN
ORDER BY clause order by
UNIQUEtri afin d’eliminer les doublons (clause distinct parexemple).
AGGREGATE application d’une fonction d’aggregation
VIEW calcul d’une sous-requete
FILTER par exemple les where et having
INDEX FULL SCANclause ORDER BY sur un index : il suffit de balayer l’indexpour obtenir les nuplets dans l’ordre : peut eviter un tri.
FAST FULL SCANn’accede pas a la table sous-jacente, rapide mais pas dansun ordre particulier
RANGE SCAN
recherche sur un index non unique ou sur lespremieres colonnes d’un index (pas toutes, sinon ils’agit de UNIQUE SCAN). Les clefs identiques sont trieessur leurs ROWIDs croissants.
UNIQUE SCAN recherche sur toutes les colonnes d’un index a clef unique
MERGE JOINjointure par fusion de listes ordonnees au prealable (quandla condition de jointure ne porte pas sur des colonnes clef),voir l’operation de type set SORT JOIN
CARTESIANQuand une des tables jointes n’a aucune condition de join-ture
HASH JOIN
construction en memoire d’un hachage sur les clefs de join-ture de la plus petite relation de la jointure (la premierefille), l’autre relation est ensuite balayee completement.
NESTED LOOPScalcul d’une jointure : la premiere table (boucle externe)est balayee integralement, la seconde (interne) est accedeeefficacement par exemple par une de ses clefs.
PROJECTION sous produit de UNION, MINUS et INTERSECTION
TABLE ACCESS BY INDEX ROWIDon trouve le nuplet connaissant son adresse (ou ROWID) :datafile, page, position
FULLbalayage complet de la table, peut etre efficace si la tableest petite.
UNION operation UNION
UNION-ALL operation UNION ALL
11.7 Exemples de plan d’execution
11.7.1 Une vue est integree dans la requete qui l’utilise
Soit la vue Bons_Clients qui calcule les clients ayant un envoi pour chaque produit :
create view Bons_Clients_1 as
select c.cdc, c.loc
from Client c
inner join Envoi e on c.cdc = e.cdc
cross join (select Count (*) as Nb_Produits from Produit) p
group by c.cdc, c.loc, p.Nb_Produits
134 CHAPITRE 11. OPTIMISATIONS
having Count (distinct e.cdp) = p.Nb_Produits ;
Voici une requete qui reproduit le contenu de la vue Bons_Clients_1 et son plan d’execution quandla table Client contient 4 clients :
select * from Bons_Clients_1 ;
Id Operation Name Rows Bytes Cost (%CPU)0 SELECT STATEMENT 10 380 9 (23)*1 FILTER
2 SORT GROUP BY 10 380 9 (23)*3 HASH JOIN 10 380 8 (13)4 MERGE JOIN CARTESIAN 10 210 4 (0)5 VIEW 1 13 2 (0)6 SORT AGGREGATE 17 INDEX FAST FULL SCAN PRODUIT_PK 3 2 (0)8 INDEX FAST FULL SCAN ENVOI_PK 10 80 2 (0)9 TABLE ACCESS FULL CLIENT 4 68 3 (0)
1 - filter(”P”.”NB PRODUITS”=COUNT(DISTINCT ”E”.”CDP”))3 - access(”C”.”CDC”=”E”.”CDC”)
On remarque qu’effectivement la definition de la vue est integree dans la requete (le plan d’executionn’utilise pas l’objet Bons_Clients_1).Pour compter le nombre de produits, Oracle utilise l’index de clef primaire Produit_PK plutot que latable Produit.
Q. 177 Dessiner la hierarchie d’operations de ce plan d’execution.
Q. 178 Pourquoi ce plan n’utilise-t-il pas la table Envoi mais seulement son index Envoi PK ?
Modifions legerement la vue Bons_Clients_1 en remplacant le select c.cdc, c.loc par select c.cdc
et en simplifiant le group by en consequence :
create or replace view Bons_Clients_2 as
select c.cdc
from Client c
inner join Envoi e on c.cdc = e.cdc
cross join (select Count (*) as Nb_Produits from Produit) p
group by c.cdc, p.Nb_Produits
having Count (distinct e.cdp) = p.Nb_Produits ;
Le plan de la requete select * from Bons_Clients_2 ; est :
Id Operation Name Rows Bytes Cost (%CPU)0 SELECT STATEMENT 10 250 5 (20)*1 FILTER
2 SORT GROUP BY 10 250 5 (20)3 NESTED LOOPS 10 250 4 (0)4 MERGE JOIN CARTESIAN 10 210 4 (0)5 VIEW 1 13 2 (0)6 SORT AGGREGATE 17 INDEX FAST FULL SCAN PRODUIT_PK 3 2 (0)8 INDEX FAST FULL SCAN ENVOI_PK 10 80 2 (0)*9 INDEX UNIQUE SCAN CLIENT_PK 1 4 0 (0)1 - filter("P"."NB PRODUITS"=COUNT(DISTINCT "E"."CDP"))
9 - access("C"."CDC"="E"."CDC")
Q. 179 Dessiner la hierarchie d’operations de ce plan d’execution.
Q. 180 En quoi ce nouveau plan est-il meilleur que le precedent ?
Voici une requete plus complexe et son plan d’execution :
11.7. EXEMPLES DE PLAN D’EXECUTION 135
select distinct l.ville
from Localite l
natural join Bons_Clients_1
where l.dpt = 59 ;
Id Operation Name Rows Bytes Cost (%CPU)0 SELECT STATEMENT 9 702 12 (17)1 SORT UNIQUE 9 702 12 (17)*2 FILTER
3 SORT GROUP BY 9 702 12 (17)*4 HASH JOIN 9 702 11 (10)5 MERGE JOIN CARTESIAN 20 1220 7 (0)6 MERGE JOIN CARTESIAN 2 106 5 (0)7 VIEW 1 13 2 (0)8 SORT AGGREGATE 19 INDEX FAST FULL SCAN PRODUIT_PK 3 2 (0)
*10 TABLE ACCESS FULL LOCALITE 2 80 3 (0)11 BUFFER SORT 10 80 4 (0)12 INDEX FAST FULL SCAN ENVOI_PK 10 80 1 (0)13 TABLE ACCESS FULL CLIENT 4 68 3 (0)
2 - filter("P"."NB PRODUITS"=COUNT(DISTINCT "E"."CDP"))
4 - access("L"."LOC"="C"."LOC" AND "C"."CDC"="E"."CDC")
10 - filter("L"."DPT"=59)
Ici aussi la definition de la vue et la requete principale se melangent.
11.7.2 Les instructions DML
Les instructions de mise a jour sont aussi l’objet d’un plan d’execution. Par exemple la mise a jourqui augmente le bonus des bons clients :
update Client c
set bonus = bonus + 10
where exists (select b.cdc from Bons_Clients_2 b where c.cdc = b.cdc) ;
Id Operation Name Rows Bytes Cost (%CPU)0 UPDATE STATEMENT 4 84 9 (23)1 UPDATE CLIENT
*2 HASH JOIN SEMI 4 84 9 (23)3 TABLE ACCESS FULL CLIENT 4 68 3 (0)4 VIEW VW_SQ_1 10 40 5 (20)5 VIEW BONS_CLIENTS_2 10 40 5 (20)
*6 FILTER
7 SORT GROUP BY 10 250 5 (20)8 NESTED LOOPS 10 250 4 (0)9 MERGE JOIN CARTESIAN 10 210 4 (0)
10 VIEW 1 13 2 (0)11 SORT AGGREGATE 112 INDEX FAST FULL SCAN PRODUIT_PK 3 2 (0)13 INDEX FAST FULL SCAN ENVOI_PK 10 80 2 (0)
*14 INDEX UNIQUE SCAN CLIENT_PK 1 4 0 (0)2 - access("C"."CDC"="CDC")
6 - filter("P"."NB PRODUITS"=COUNT(DISTINCT "E"."CDP"))
14 - access("C"."CDC"="E"."CDC")
Ici la vue Bons_Clients_2 est effectivement utilisee telle quelle.
136 CHAPITRE 11. OPTIMISATIONS
11.8 Prise en compte des statistiques
Soit la requete :
update Client c set bonus = bonus + 10
where c.cdc between ’A1’ and ’A10’ ;
nombre de lignesde Client plan d’execution
4Operation + Options Objet TypeUPDATE CLIENT
TABLE ACCESS FULL CLIENT TABLE
1003Operation + Options Objet TypeUPDATE CLIENT
INDEX RANGE SCAN CLIENT_PK INDEX UNIQUE
11.9 Astuces
Ces astuces sont principalement liees a Oracle, certaines sont cependant assez generales.
Eviter de cacher les clefs dans des expressions : L’utilisation des index peut-etre conditionneepar la maniere d’ecrire les expressions de la clause where :
Soit la table :
create table Employe (
id Number (5) primary key,
nom Varchar(50),
salaire Number (7, 2)
) ;
La requete suivante peut-elle utiliser l’index de la clef primaire e.id ?
select e.nom, e.salaire from Employe e where abs (e.id) = 7 ;
Non, car ne connaissant pas la semantique de la fonction abs, l’optimiseur ne peut en deduire laou les valeurs que doit avoir e.id pour que le predicat abs (e.id) = 7 soit verifie. Il ne pourradonc pas utiliser l’index de clef primaire et effectuera un parcours complet de la table Employe !
Q. 181 Reecrire la requete afin que l’acces par clef puisse etre effectue.
Attention aux conversions implicites dans les clauses where, on : l’expression colChar = 27
est comprise comme TO_NUMBER(colChar) = 27 et si colonneChar est une clef primaire, sonindex ne sera pas utilise !
Introduire un index peut accelerer les choses . . .MAIS Soit la requete :
select e.nom, e.salaire
from Employe e
where e.salaire between 1000.0 and 2000.0 ;
En l’etat l’optimiseur n’a pas d’autre choix que de parcourir completement la table Employe.
Si cette requete est (tres) frequente on a interet a introduire un index non unique sur la colonnesalaire :
create index Employe_Salaire_Index on Employe (salaire) ;
Attention quand meme : l’index doit etre mis a jour a chaque fois que la table est mise a jour,ce qui introduit un cout supplementaire lors des modifications. Si on multiplie inutilement lesindex on consomme inutilement de la place memoire et du temps CPU lors des modifications
11.10. LES COMMANDES ORACLE ET POSTGRES 137
(Oracle : la mise a jour d’un index prend en moyenne trois fois plus de temps que la mise a jourdans la table. Une mise a jour d’une table munie de trois index sera environ dix fois plus longueque s’il n’y avait pas d’index). La conception des index suppose au prealable une connaissanceprecise des requetes qui seront executees sur la base de donnees.
Attention a l’ordre des colonnes d’un index :
Quand un index — unique ou non — comporte plus d’une colonne :
create table T (A Number (5), B Number (5), C Number (5), D Number (5),
constraint T_PK primary key (A, B, C)) ;
La premiere colonne joue le role de poids fort et la derniere celle de poids faible. En l’occurrence,A est le poids fort, B est le poids intermediaire et C est le poids faible de la clef qui va servira ordonner l’index. C’est a dire que toutes les clefs ayant la meme valeur en A sont rangees defacon contigue dans l’index et on pourra donc les retrouver efficacement ; il en va de meme pourles clefs ayant les memes valeurs en A et en B. En revanche les clefs ayant la meme valeur en C
sont dispersees dans l’index et il faudra faire une exploration exhaustive de la table (plutot quede l’index) pour retrouver les lignes ayant une certaine valeur en C !
La requete suivante ne pourra donc pas utiliser l’index :
select * from T where B = 3 and C between 5 and 100 ;
Q. 182 Redefinir la clef primaire afin que l’index puisse etre utilise.
Q. 183 Cela changerait-il quelque chose si B etait compare a la valeur d’une variable ?
Q. 184 Pourquoi l’ordre (C, B, A) serait-il moins bon pour cette requete ?
Eviter les requetes et les vues a tout faire Il vaut mieux ecrire plusieurs requetes ou vues cha-cune adaptee a un usage particulier que de mettre en place peu de requetes ou vues a tout fairequi risquent de s’averer inefficaces pour certains usages.
Un index peut eviter de devoir trier Si une requete a une clause order by et qu’il existe unindex de type B-arbre sur la table a trier dont les colonnes sont les memes et qu’elles sontdonnees dans le meme ordre que dans la clause order by alors le tri est deja fait !
Eviter les connexions/deconnnexions trop frequentes.
Utiliser les curseurs et les variables de liaisons cela evite des compilations repetees de la memerequete.
Charger les donnees dans les tables avant de creer les index .
Les triggers peuvent couter cher !
11.10 Les commandes Oracle et Postgres
11.10.1 Oracle : Explain Plan for ...<ordre DML>
Permet de ranger dans une table le plan d’execution adopte par le moteur SQL pour executer l’ordreDML passe en parametre.
Le programmeur peut ensuite etudier a loisir ce plan d’execution et tenter de l’ameliorer par le biaisd’index, de suggestions explicites d’optimisation (hint voir 11.10.4 page 138) ou encore en modifiantl’ecriture de ses ordres DML.
Sous SQL+, la commande set autotrace on explain fait que le plan d’execution sera affiche apreschaque ordre DML. La commande set autotrace off explain permet d’arreter cet affichage.
11.10.2 Postgres : Explain <ordre DML>
Ressemble a ce que propose Oracle.
138 CHAPITRE 11. OPTIMISATIONS
11.10.3 Les statistiques
Elles influencent les choix de l’optimiseur : les choix ne seront generalement pas les memes suivantque l’on pense travailler avec de petites ou de grandes tables, voir 11.8 page 136. Il faut donc qu’ellessoient a jour.
La constitution des statistiques est faite explicitement par l’administrateur :
Oracle ANALYZE mais les statistiques utilisees par l’optimiseur sont produites par le paquetageDBMS_STATS, par exemple :
begin DBMS_STATS.GATHER_TABLE_STATS (’durif’, ’essai’) ; end ;
begin DBMS_STATS.GATHER_Schema_STATS (’durif’) ; end ;
select TABLE_NAME, NUM_ROWS, BLOCKS, LAST_ANALYZED
from User_Tables ;
select INDEX_NAME, INDEX_TYPE, TABLE_NAME, LEAF_BLOCKS,DISTINCT_KEYS
from User_Indexes ;
en Postgres ANALYZE
11.10.4 Oracle : suggestions d’optimisation faites par le programmeur (hints)
Le programmeur peut donner des indications d’optimisation sous forme d’un commentaire suivantimmediatement le nom de l’ordre DML.Par exemple la suggestion FULL demande une exploration complete de la table mentionnee en (Table Access Full).
Voici une requete sans suggestion d’optimisation et son plan d’execution :
select c.nom, cm.nom, p.libelle, p.couleur
from Client c
cross join Camion cm
cross join Produit p
inner join Envoi e
on e.cdc = c.cdc and cm.cdm = e.cdm and p.cdp = e.cdp ;
Id Operation Name Rows Bytes Cost (%CPU)0 SELECT STATEMENT 10 520 13 (16)*1 HASH JOIN 10 520 13 (16)*2 HASH JOIN 10 410 9 (12)*3 HASH JOIN 10 230 6 (17)4 TABLE ACCESS FULL CAMION 2 22 3 (0)5 INDEX FAST FULL SCAN ENVOI_PK 10 120 2 (0)6 TABLE ACCESS FULL PRODUIT 3 54 3 (0)7 TABLE ACCESS FULL CLIENT 4 44 3 (0)1 - access("E"."CDC"="C"."CDC")
2 - access("P"."CDP"="E"."CDP")
3 - access("CM"."CDM"="E"."CDM")
et la meme avec une suggestion qui demande a effectuer la jointure en respectant l’ordre d’apparitiondes tables dans la clause from (hint ORDERED), le plan d’execution est alors different de celui obtenuprecedemment et il est plus cher :
select /*+ ORDERED */ -- conserve l’ordre de jointure
c.nom, cm.nom, p.libelle, p.couleur
from Client c cross join Camion cm
cross join Produit p
inner join Envoi e
on e.cdc = c.cdc and cm.cdm = e.cdm and p.cdp = e.cdp ;
11.11. LES GROUPES (CLUSTERS) : GROUPEMENT DE TABLES PRE-JOINTES 139
Id Operation Name Rows Bytes Cost (%CPU)0 SELECT STATEMENT 10 520 19 (0)1 NESTED LOOPS 10 520 19 (0)2 MERGE JOIN CARTESIAN 24 960 19 (0)3 MERGE JOIN CARTESIAN 8 176 9 (0)4 TABLE ACCESS FULL CLIENT 4 44 3 (0)5 BUFFER SORT 2 22 6 (0)6 TABLE ACCESS FULL CAMION 2 22 2 (0)7 BUFFER SORT 3 54 18 (0)8 TABLE ACCESS FULL PRODUIT 3 54 1 (0)
*9 INDEX UNIQUE SCAN ENVOI_PK 1 12 0 (0)9 - access("E"."CDC"="C"."CDC" AND "P"."CDP"="E"."CDP" AND "CM"."CDM"="E"."CDM")
On voit qu’on effectue d’abord le produit cartesien des clients, camions et produits avant de faireun acces par cle a l’index de Envoi, on remarque aussi qu’on n’accede pas a la table Envoi car laprojection n’a besoin d’aucune de ses colonnes.
Il y a beaucoup d’autres suggestions possibles, en voici quelques-unes :– ALL_ROWS optimise la consommation globale de ressources : approprie pour des executions en arriere-
plan (batch).– FIRST_ROWS (<entier n>) optimise le temps de reponse pour produire les n premiers nuplets :
convient plutot a une utilisation interactive.– FULL (<nom-de-table-ou-alias>) demande a ne pas utiliser l’index eventuel (d’ou un parcours
complet de la table).– INDEX (<nom-de-table-ou-alias>) demande a utiliser l’index de la table (s’il y a plusieurs index,
on peut preciser les noms de ceux qu’on souhaite utiliser).– ORDERED demande a effectuer la jointure en respectant l’ordre d’apparition des tables dans la clause
from.
11.11 Les groupes (clusters) : groupement de tables pre-jointes
Pour regrouper des tables souvent inter-jointes et ayant des colonnes de meme semantique (des clefsetrangeres vers des clefs primaires par exemple).
Un cluster est principalement defini par sa clef, formee d’au moins une colonne :
create cluster <nom-cluster> ( <dcl-colonne> { , <dcl-colonne> } ) index ;
Chaque declaration de table du cluster doit indiquer quelles sont ses colonnes qui correspondent auxcolonnes clef du cluster :
create table <nom-table> (...)
cluster <nom-cluster> (<colonne> { , <colonne> }) ;
La correspondance entre les colonnes de la table et celles de la clef du cluster se fait par position : lesnoms de colonnes n’ont pas besoin d’etre identiques, en revanche leurs types et dimensions doiventl’etre.
Les lignes des tables qui auront la meme valeur pour les colonnes clef du cluster seront stockees dansles memes blocs disques, sachant que la valeur de la clef cluster n’est stockee qu’une seule fois dansun bloc.Ainsi on gagne en place et les equi-jointures faites sur la clef du cluster risquent d’etre tres efficaces.
Il faut ensuite, avant de pouvoir manipuler les tables, creer l’index du cluster :
create index <nom-index-cluster> on cluster <nom-cluster> ;
cet index utilise toutes les colonnes de la clef du cluster.Par exemple on sait qu’on va souvent faire des equi-jointures entre les tables Adherant et Livre :
140 CHAPITRE 11. OPTIMISATIONS
1. creer le cluster :
create cluster Emprunt (adherant Number (5)) index ;
2. creer les tables dans le cluster Emprunt :
create table Adherant (
id Number (5),
nom varchar2 (20),
constraint Adherant_PK primary key (id)
) cluster Emprunt (id) ;
create table Livre (
id Number (5),
titre varchar2 (20),
emprunteur Number (5) references Adherant (id),
constraint Livre_PK primary key (id)
) cluster Emprunt (emprunteur) ;
3. creer l’index du cluster :
create index Index_Emprunt on cluster Emprunt ;
une fois cet index cree, on peut maintenant manipuler le contenu des tables.
4. Par exemple si les tables contiennent :id nom id titre emprunteur
. . . 3 Louis XI 1111 toto 5 Galilee 11
ces lignes figureront dans un seul bloc disque : 11 toto 3 Louis XI 5 Galilee
On ne peut detruire un cluster qu’apres avoir detruit toutes les tables qu’il contient :
drop table Livre ;
drop table Adherant ;
drop cluster Emprunt ;
Q. 185 En quoi cet exemple n’est peut-etre pas tres approprie pour illustrer les clusters ?
11.12 Enrichir un index avec des colonnes d’autonomie
Ajouter dans l’index toutes les colonnes referencees par les requetes, ainsi on n’a plus besoin d’accedera la table !Mais attention cela peut faire diminuer le nombre d’elements par feuille et faire grossir l’index ennombre de blocs.
11.13 Tables organisees en index (IOT) : lignes ordonnees
C’est l’approche extreme de celle definie en 11.12
create table Adherant (
id Number (5),
nom varchar2 (20),
constraint Adherant_PK primary key (id)
) organization index ;
L’organization par defaut est heap (tas) qui indique que les lignes sont stockees sans ordre particu-lier.L’organization index fait que les lignes sont dans un index defini sur la clef primaire. La contraintede clef primaire est donc obligatoire dans ce cas. Cela peut rendre les requetes utilisant cet ordre bien
11.13. TABLES ORGANISEES EN INDEX (IOT) : LIGNES ORDONNEES 141
plus efficace, mais on peut imaginer qu’en revanche les modifications de la table couteront plus qu’avecune organisation en heap
Q. 186 Verifier qu’une table organisee en index est ordonnee sur sa clef primaire, contrairement aune table qui ne l’est pas. Cela se voit bien en supprimant une ligne puis en la recreant.
Postgres propose l’instruction cluster qui consiste a trier une table dans l’ordre de l’un de ses index.Mais cette commande n’a pas d’effet sur les futures evolutions de la table : il faudra la relancerregulierement.
Cinquieme partie
Les transactions
142
Chapitre 12
Les transactions
12.1 Notion de transaction
Une transaction est une (la plus petite) unite logique de travail qui fait passer la base d’un etat correctdans un nouvel etat correct, par exemple le virement d’une somme d’un compte a un autre comptedans la meme banque ne doit pas changer la somme des comptes. En general une transaction prendpeu de temps pour s’executer (ajouter un achat, changer le nom d’un client, faire un virement ban-caire de compte a compte), sauf dans le cas de transactions destinees a fabriquer une synthese de l’etatcomplet de la base (decisionnel).
Une transaction a un debut et une fin : le debut correspond souvent au debut de l’execution de lapremiere instruction SQL, la fin correspond a une validation du travail qui rend publiques les mo-difications faites (instruction commit ou lors d’une deconnexion normale) ou a une annulation quiannule toutes les modifications faites (instruction rollback ou lors d’une deconnexion anormale) encas de probleme.
Une transaction est l’execution d’un code (en general une procedure) et non pas le code lui-meme :plusieurs transactions peuvent executer un seul et meme code. On retrouve ici quelque chose de tressimilaire a la distinction entre les notions de processus et de programmes : un processus (ou une tache)est une execution d’un programme.
Une transaction est aussi l’unite de reprise (en cas de panne : il faut pouvoir lors du redemarrage duSGBD annuler les effets partiels des transactions non terminees au moment de la panne et retrouverles effets des transactions terminees au moment de la panne) et l’unite de concurrence (si le SGBDautorise l’execution simultanee ou quasi-simultanee de plusieurs transactions).
Une bonne pratique consiste a mettre en place des transactions courtes : peu d’ordres DML et uneexecution rapide. La gestion des reprises consomme alors moins d’espace et les problemes dus a laconcurrence (blocage, non serialisabilite) sont moins probables.
Exemple de code faisant passer la base d’un etat correct a un nouvel etat correct et pouvant fairel’objet d’une transaction : le virement de compte a compte qui a la propriete de conserver la sommedes soldes :
update Compte
set solde = solde - :somme
where id_compte = :debite ;
update Compte
set solde = solde + :somme
where id_compte = :credite ;
Ce programme est parametre par les deux numeros de compte (debite et credite) et la somme a virer
143
144 CHAPITRE 12. LES TRANSACTIONS
(somme). Une transaction executant ce code disposera evidemment d’une valeur precise pour chacunde ces parametres.
12.2 Notion de session
On appellera session la periode qui commence a la connexion d’un utilisateur ou d’un programmeet se termine lors de sa deconnexion. Pendant une session plusieurs transactions seront executees ensequence, mais pas forcement de facon contigue : cas d’une session interactive ou l’utilisateur prend letemps de reflechir ou discuter entre deux actions sur la BD.
Les SGBD permettent en general plusieurs sessions simultanees et donc autorisent la concurrence destransactions.
Une session peut-etre tres courte et donner lieu a l’execution de peu d’ordres (par exemple si elle estcommandee par un programme) ou etre tres longue et donner lieu a l’execution de nombreux ordres,c’est le cas des seances de TP : 2 heures ou de l’employe qui se connecte le matin et se deconnecte le soir.
Dans ce dernier cas il ne serait pas raisonnable qu’une session corresponde a une seule transaction (caralors, comme on le verra dans la suite avec le niveau d’isolation serialisable, un employe effectuant desreservations de trains ne pourrait pas voir les reservations faites ses collegues et, pire, le travail d’unejournee pourrait alors se voir annule lors de la deconnexion).
En fait une session est la periode d’existence d’une connexion au SGBD permettant de lancer destransactions successives.
Enfin, dans le cas d’une session interactive, l’utilisateur passe certainement plus de temps a ne pasfaire travailler le SGBD qu’a le faire travailler : la duree de sa session est certainement bien superieurea la somme des durees des transactions dont il a demande l’execution au cours de cette session.
T1 T3 T4 T6 T8C DSession 2
temps
T2 T7 T9C DSession 1 T5
Fig. 12.1 – On a deux sessions qui se recouvrent partiellement dans le temps. L’identification destransactions se fait dans l’ordre chronologique de leurs demarrages, independamment de la sessionpour le compte de laquelle elles s’executent. La derniere transaction de la session 1 (T9) se termine acause de la deconnexion de l’utilisateur (fin de session). Toutes les autres transactions se terminentpar un des deux ordres specifiques commit ou rollback.
12.3 Modele d’execution des transactions (figure 12.2)
12.4 Proprietes que doivent respecter les transactions : ACID ou
CADI
C comme correction Une transaction preserve la semantique de la BD
Une transaction doit faire passer la base d’un etat correct dans un nouvel etat correct. Cettenotion de correction a donc beaucoup a voir avec le fait que le programme execute par la tran-saction est correct. Ce point ne sera donc pas developpe dans la suite.
Dans l’exemple la correction pourait consister a garantir que la somme des soldes des comptesest invariante lors d’un virement.
A comme atomicite Execution en tout (commit) ou rien (rollback) des transactions
12.4. PROPRIETES QUE DOIVENT RESPECTER LES TRANSACTIONS : ACID OU CADI 145
��������
��������
UniqueProcesseur
A B
CO:
CO:
code
Base de données
Mém
oire
Cen
tral
e
T2
T1
variables
a b
b
variables
a
Fig. 12.2 – Modele d’execution des transactions. Chacune des deux transactions T1 et T2 disposede son propre espace de travail en memoire centrale (CO : compteur ordinal, des variables a et bet probablement une pile d’execution). Le processeur n’execute qu’une seule instruction a la fois,ici il travaille pour le compte de T1. Une transaction qui veut modifier un objet (un nuplet parexemple) de la base doit (1) en lire une copie dans sa memoire de travail, (2) modifier cette copie,(3) reecrire dans la base cette copie comme nouvelle valeur de l’objet. A tout moment de l’executionde cette sequence, la transaction peut etre temporairement suspendue par le systeme pour laissertravailler une autre transaction. A et B sont les objets de la base susceptibles d’etre modifies parles transactions. En tant qu’objets de la base ils sont accessibles par n’importe quelle transaction,c’est donc l’etat de ces objets qui risque de devenir incoherent si des protocoles de synchronisationet de cooperation inter-transactions ne sont pas mis en place. Un dernier point : on voit que deuxtransactions concurrentes peuvent parfaitement executer le meme code, la meme procedure stockee parexemple, d’ou la distinction entre programme qui correspond a du code et transaction qui corresponda l’execution d’un code.
une transaction doit s’executer en tout ou rien : soit elle reussit et la base se trouve dans unnouvel etat correct, soit elle echoue et la base doit etre remise dans son etat correct de depart,c’est a dire que tout se passe comme si la transaction n’avait jamais eu lieu.
Dans l’exemple de virement, entre les deux update la base est dans un etat incorrect, si lesecond update echoue (par exemple parce que le compte 572 n’existe pas ou bien que le SGBDse plante) alors il faut annuler l’effet du premier update.
D comme durabilite Meme en cas de panne logicielle voire materielle
Les effets sur la BD d’une transaction reussie doivent etre conserves durablement, meme si leSGBD se plante avant d’avoir eu le temps d’ecrire sur disque le nouvel etat de la BD (ce quiest tout a fait possible puisque le SGBD utilise un systeme de cache memoire lui permettantd’optimiser les acces a la memoire secondaire : les effets de la transaction sont inscrits dans lecache, l’ecriture du cache sur le disque ne se faisant qu’a un moment ulterieur que la transactionne maıtrise pas).
Pour rendre durables ces effets, on valide la transaction (commit). En Oracle, on peut aussicomprendre commit comme la publication des modifications faites par la transaction, car c’estseulement a partir de ce moment que les autres transactions pourront eventuellement1 voir cesmodifications, sauf dans le cas de l’isolation read uncommitted defini par la norme SQL, maisOracle ne propose pas ce niveau de non isolation.
Les SGBD disposent en general de deux mecanismes pour garantir cette durabilite : 1) desfichiers journaux (ou log) permettent de prendre en compte des pannes logicielles ou materiellesn’affectant pas les supports de stockage du SGBD, 2) des sauvegardes completes de la base pourle cas ou un disque est detruit.
I comme isolation Deux transactions concurrentes n’interferent pas sur les donnees qu’elles lisent
1Eventuellement car une transaction serialisable demarree avant cette publication ne verra pas ces modifications.
146 CHAPITRE 12. LES TRANSACTIONS
ou modifient
La plupart des SGBD permettent a plusieurs utilisateurs de travailler simultanement sur labase. Chaque utilisateur interagit avec la base par le biais de transactions. Deux transactionssimultanees (ou quasi-simultanees) peuvent potentiellement chercher a modifier le meme objet(nuplet) de la base de donnees, les interferences qui en decoulent peuvent mettre la base dansun etat incorrect.
Exemple de deux transactions qui interferent sur le compte 537 :
Instant d’execution Transaction 1 : T1 Transaction 2 : T2
Ici le solde de 537 vaut 1001 s := lire_solde (537)
2 s := s + 20
3 s := lire_solde (537)
4 ecrire_solde (537, s)
5 s := s + 10
6 ecrire_solde (537, s)
Ici le solde de 537 vaut 110 alors qu’il devrait valoir 130
Chaque transaction a son propre jeu de variables locales pendant son execution.
Q. 187 A quel ordre SQL correspond ce qui est fait par T1 et T2 ? L’interference entre T1 et T2
a pour consequence que tout se passe comme si T1 n’avait pas eu lieu !
Dans cet exemple, il faut bien comprendre que chacune des deux transactions T1 et T2 dispose desa propre variable de travail s : le modele d’execution repose sur un processeur unique executanten temps partage chacune des transactions, chaque transaction disposant de son propre espacepour stocker ses parametres et ses variables comme l’illustre la figure 12.2 page 145.
L’idee est alors que le SGBD doit fournir des outils permettant de garantir les transactions contrece genre de probleme. SQL propose deux outils : 1) le verrouillage d’objet qui permet d’obligerune autre transaction a attendre que l’objet soit deverrouille avant de pouvoir y acceder, et 2) leniveau d’isolation d’une transaction T qui dit dans quelle mesure elle pourra voir les modificationsfaites par les autres transactions, par exemple le niveau d’isolation SQL serializable fait que latransaction T ne verra aucune des modifications faites par les transactions non terminees quandT a commence : elle aura l’impression d’etre la seule a utiliser la base de donnees (ce qui n’estpas necessairement la solution a tous les problemes !).
Les transactions sont gerees par un processus du SGBD : le moniteur transactionnel.
La procedure de virement de compte a compte qui evite les interferences entre transactions peuts’ecrire en PL/SQL Oracle comme indique a la section 15.5, page 174.
12.5 Transaction et atomicite : comment cela marche ?
12.5.1 Au niveau du programmeur : instructions de validation et d’annulation
En fonctionnement normal, une transaction se termine obligatoirement soit par :
une validation de ses effets : toutes les modifications qu’elle a faites sont rendues permanentes et,au moins pour Oracle et Postgres, ne peuvent devenir accessibles aux autres transactions qu’a cemoment la (suivant leurs niveaux d’isolation). Une validation est la plupart du temps demandeeexplicitement grace a l’instruction SQL commit.
une annulation de ses effets : les nuplets qu’elle a detruits sont reinseres, les nuplets qu’elle a modifiesreprennent leurs valeurs initiales, les nuplets qu’elle a inseres disparaissent. Une annulation estla plupart du temps demandee explicitement grace a l’instruction SQL rollback.
12.6. TRANSACTION ET DURABILITE : COMMENT CELA MARCHE ? 147
12.5.2 Au niveau du SGBD
Le probleme principal est de pouvoir remettre la base dans son etat initial si la transaction est annulee(rollback) ou qu’elle ne peut se terminer du fait d’une panne du systeme.
Pour gerer l’annulation d’une transaction (instruction rollback), le SGBD memorise dans des seg-ments d’annulation (rollback segments), les valeurs initiales des nuplets modifies. En cas d’annulationde la transaction il est alors possible, grace aux segments d’annulation, de remettre les objets modifiespar la transaction dans leurs etats initiaux.
Pour permettre, lors de la reprise apres une panne systeme ou une coupure electrique, d’annuler lestransactions non terminees lors d’une panne, le SGBD memorise dans un journal (fichier log) les etatsavant et apres des objets modifies par les transactions.
12.6 Transaction et durabilite : comment cela marche ?
La durabilite stipule que les effets des transactions validees (commit) doivent etre permanents2.
Le probleme est que les modifications de la base ne sont pas ecrites immediatement sur disque : leSGBD utilise un systeme de cache en memoire centrale permettant d’eviter de trop nombreuses etcouteuses entrees-sorties sur le disque. Cette memoire cache contient des copies de blocs du disqueet c’est sur ces copies qu’ont lieu les mises a jour. Plus tard, au moment opportun, ces copies serontcopiees sur le disque pour mettre la base a jour.
Que se passe-t-il alors si une panne de courant vient effacer le contenu de la memoire cache ?– d’une part des modifications faites par des transactions validees n’ont probablement pas ete ecrites
sur le disque, une partie des effets de ces transactions est donc definitivement perdu– encore pire, la base a toutes les chances d’etre dans un etat incorrect. En effet, pour les transactions
validees au moment de la coupure, seule une partie de leurs modifications ont pu etre ecrites sur ledisque.
Pour eviter ce genre de probleme (du a une coupure de courant ou a un ecroulement du systeme),les SGBD mettent en place des mecanismes de reprise qui repose sur un journal de reprise (fichierlog sur disque dont on a deja parle a la section 12.5.2 page 147) contenant les informations sur lesmises a jours effectuees par les transactions. Ce journal est mis a jour physiquement lors de plusieursevenements :– avant toute mise a jour physique de la base de donnees avec les caches memoire, les informations
de reprise de ces mises a jour sont ecrites physiquement dans le journal,– lors d’une validation (commit) toutes les mises a jour de la transaction qui n’ont pas ete ecrites
physiquement dans le journal doivent l’etre ainsi qu’une information indiquant la validation de latransaction. Seulement quand ces informations ont ete physiquement ecrites, l’operation de valida-tion (commit) peut se terminer et le programme reprendre son cours.
– cycliquement le SGBD effectue un point de controle qui consiste a :
1. ecrire physiquement des caches memoire dans la base de donnees (ce qui implique une ecritureprealable des informations de reprise dans le journal de reprise),
2. ecrire physiquement dans le journal de reprise un point de controle contenant l’identitificationde toutes les transactions en cours d’execution.
Principe de restauration : lors de la reprise le SGBD effectue :– d’abord un parcours du journal de reprise en arriere en annulant les effets des transactions non
validees au moment de la defaillance et ce jusqu’au dernier point de controle enregistre,– puis ils effectue, a partir de ce dernier point de controle, un parcours en avant du journal en rejouant
les modifications des transactions validees au moment de la defaillance.
2Cette section s’inspire fortement de [5].
Chapitre 13
Gestion de la concurrence destransactions
Les SGBD sont pour la plupart multi-utilisateurs, c’est a dire que plusieurs utilisateurs doivent pou-voir simultanement consulter et modifier une meme base de donnees.
Sachant qu’une modification de la base de donnees sous-entend en general la modification de plusieurstables faisant passer la base d’un etat correct dans un nouvel etat correct, on appelle transactionl’execution de toute suite d’operations elementaires permettant soit d’obtenir une information perti-nente sur l’etat de la base soit de faire passer la base d’un etat correct a un nouvel etat correct.
Le postulat de base est donc qu’un utilisateur n’interagit avec la base que via des transactions, ce quiest d’ailleurs vrai en Oracle et Postgres (on parle de bases de donnees transactionnelles).
Le but du jeu est que chaque transaction ait l’impression d’etre la seule a utiliser la base (et non paschaque utilisateur : l’employe qui fait des reservations de train — chaque execution d’une reservationrepresentant une transaction — doit voir les reservations faites par les autres employes !), c’est cequ’on appelle l’isolation. Le SGBD est, dans certains cas, incapable de garantir cette isolation destransactions (pour des raisons conjoncturelles tout a fait valables et qui ne remettent pas forcement encause la correction des programmes executes par les transactions, on le comprendra mieux plus tard) ;en revanche il est toujours capable de se rendre compte de cette incapacite. Quand il se rend comptede cette incapacite, il le signale a la transaction (en Oracle sous forme de l’erreur Oracle ORA-08177).Celle-ci peut (doit) alors decider de se terminer en annulant toutes les modifications qu’elle a effectueesgrace a l’instruction SQL d’annulation rollback, et elle peut tenter de mener a bien le travail qu’elleetait censee faire en se relancant (grace a une boucle).
Les SGBD fournissent en general deux outils pour gerer les interactions entre transactions : d’une parton peut specifier pour chaque transaction un niveau d’isolation plus ou moins etanche, d’autre part onpeut effectuer des verrouillages explicites d’objets de la base (en general on peut verrouiller les tableset les nuplets) pour synchroniser les transactions qui accedent a des objets communs.
13.1 Notion d’ordonnancement
Comme on l’a vu, le processeur partage son temps entre les differentes transactions en cours d’execution :il execute quelques instructions d’une transaction, puis quelques instructions d’une autre transaction,puis revient a la premiere, . . ..
Un ordonnancement est la trace chronologique des instructions executees par les transactions.
Un ordonnancement est dit entrelace si les instructions (li comme lecture et ei comme ecriture faitespar la transaction i) des transactions sont melangees : (l2l1l1e2l2e1) est un ordonnancement entrelacedes deux transactions T1 = (l1l1e1), T2 = (l2e2l2).
Quand le processeur est peu charge, il se peut que chaque transaction soit executee de bout en bout
148
13.2. DES PROBLEMES DUS A LA CONCURRENCE 149
sans etre interrompue par une autre transaction, on parle alors d’ordonnancement sequentiel.Interet d’une execution quasi-parallele par rapport a une execution purement sequentielle :– transaction longue/transaction courte : meme si la transaction courte commence apres le debut de
la transaction longue, elle pourra se terminer avant,– transaction en attente d’entree/sortie sur disque : une autre transaction peut alors prendre la main.
13.2 Des problemes dus a la concurrence
Pour illustrer les problemes, on reprend l’exemple du virement de compte a compte en distingant lesoperations de lecture et d’ecriture dans la base :
a := lire (A) ;
a := a - S ;
ecrire (A, a) ;
b := lire (B) ;
b := b + S ;
ecrire (B, b) ;
13.2.1 Perte de mise a jour
T1 virer (A, B, 100) T2 virer (A, B, 200)
a := lire (A) ;
a := lire (A) ; a := a - 200 ;
a := a - 100 ;
ecrire (A, a) ;
b := lire (B) ; b := b + 100 ;
ecrire (A, a) ;
ecrire (B, b) ;
b := lire (B) ; b := b + 200 ;
ecrire (B, b) ;
Perte de mise a jour : la premiere ecriture de A par T1 est perdue, et pire : la base est devenueincorrecte !Remede : Si tout acces a un nuplet commencait par le verrouiller, alors le verrouillage de T1 obligeraitT2 a attendre que T1 deverrouille A. C’est, en gros, ce que font automatiquement Oracle et Postgresdans le cas ou la mise a jour du solde est faite par un ordre update (update verrouille les nupletsconcernes par la mise a jour).
13.2.2 Dependance non validee
T1 virer (A, B, 100) T2 virer (A, C, 200)
a := lire (A) ; a := a - 100 ;
ecrire (A, a) ;
a := lire (A) ; a := a - 200 ;
b := lire (B) ; b := b + 100 ;
ecrire (B, b) ;
erreur : compte B saturerollback ;
ecrire (A, a) ;
b := lire (C) ; b := b + 200 ;
ecrire (C, b) ;
commit ;
Dependance non validee : l’annulation de T1 est provoquee par l’echec de l’ecriture du compte B,mais T2 ne s’en rend pas compte et utilise la valeur de A modifiee par T1. Au final A est decrementede 300 au lieu de 200. Il aurait fallu que T2 attende la fin de T1.
150 CHAPITRE 13. GESTION DE LA CONCURRENCE DES TRANSACTIONS
Remede : comme dans l’exemple precedent, mais en plus il faut que T1 ne deverrouille A qu’apres sonannulation, ainsi T2 verra la valeur originale de A. Ce protocole de deverrouillage s’appelle verrouillagedeux phases rigoureux et est automatiquement garanti par Oracle et Postgres.
13.2.3 Analyse incoherente
T1 analyser (A, B) T2 virer (A, B, 200)
a := lire (A) ; a := a - 200 ;
ecrire (A, a) ;
a := lire (A) ;
b := lire (B) ;
b := lire (B) ; b := b + 200 ;
ecrire (B, b) ;
afficher (a + b) ;
Analyse incoherente : la somme affichee n’est pas correcte car A est lu apres modification et B l’estavant d’etre modifie. Si initialement A vaut 500 et B vaut 1000 alors la transaction T1 affiche 1300 aulieu d’afficher 1500.Remede : il suffirait de se souvenir de la valeur originale du nuplet A : T1 utiliserait alors des valeurs deA et B qui sont en phase. C’est ce que propose le protocole multi-versions mis en place automatiquementpar Oracle et Postgres. (On pourrait aussi s’en sortir en utilisant des verrouillages).
13.2.4 Lectures non reproductibles
T1 T2
a := lire (A) ;
ecrire (A, 1000) ;
a := lire (A) ;
Lectures non reproductibles. : la meme transaction T1 voit deux valeurs differentes pour le memeobjet A.Remede : exactement le meme que dans l’exemple precedent.
Q. 188 Donner un ordonnancement entrelace correct de l’exemple 13.2.1 page 149.
13.2.5 Moralite
On remarque que les differents problemes vus precedemment sont toujours dus aux operations de lec-ture et d’ecriture qui peuvent provoquer des interferences entre transactions quand elles s’appliquentaux memes objets de la base.
C’est pourquoi, dans la suite, on ne s’interessera plus qu’a ces operations de lecture et d’ecriture.
13.3 Approche theorique : la serialisabilite
L’idee est de caracteriser formellement les ordonnancements corrects.
Si un ordonnancement entrelace est equivalent a (au moins) un ordonnancement sequentiel alors ilest correct car tout ordonnancement sequentiel est correct du point de vue de la concurrence destransactions.Un ordonnancement entrelace est serialisable (autrement dit : correct) s’il est equivalent a au moinsun ordonnancement sequentiel des memes transactions.Tout d’abord on notera :�� ��ei(o) l’operation d’ecriture de l’objet o par la transaction Ti.�� ��li(o) l’operation de lecture de l’objet o par la transaction Ti.
13.3. APPROCHE THEORIQUE : LA SERIALISABILITE 151
Comment determiner la serialisabilite d’un ordonnancement ?
L’idee est que :
– si deux transactions T1 et T2 ecrivent le meme objet et que, dans l’ordonnancement considere,l’ecriture de T1 precede celle de T2, alors, dans l’ordonnancement sequentiel equivalent T1 doitpreceder T2.
– de meme si T1 lit un objet et que T2 ecrit ce meme objet :– si la lecture par T1 precede l’ecriture faite par T2, alors, dans l’ordonnancement sequentiel equivalent
T1 doit preceder T2.– si l’ecriture faite par T2 precede la lecture par T1, alors, dans l’ordonnancement sequentiel equivalent
T2 doit preceder T1.
Par exemple, voici un cas d’ordonnancement non serialisable�� ��. . . e1(o) . . . e2(o) . . . e1(o) . . . . On
voit qu’on ne peut trouver aucun ordonnancement sequentiel de T1 et T2. En effet il faudrait que, dansla sequence, T1 precede T2 et que T2 precede T1, ce qui est evidemment impossible !
Dans un ordonnancement, deux operations executees sur le meme objet par deux transactions differentesinduisent un ordre des deux transactions si l’une est une ecriture et l’autre une ecriture ou unelecture1. Par exemple l’ordonnancement (e2(o, 501) l1(o)) implique que T2 doit preceder T1 dans unordonnancement sequentiel equivalent.
Inversement, deux operations de lecture d’un meme objet par deux transactions n’induisent pas d’ordredes deux transactions.
Autrement dit, deux operations induisent un ordre si leurs effets sur l’objet ou sur le calcul effectuerisquent de ne pas etre les memes suivant l’ordre dans lequel on les execute, en voici un exemple tressimple : (e1(o, 501) e2(o,−61)) et (e2(o,−61) e1(o, 501)).
On notera p→ q si p et q induisent un ordre et que p precede q dans l’ordonnancement considere.
Un ordonnancement est serialisable ⇔ le graphe suivant ne comporte pas de cycle :
– les sommets sont les transactions de l’ordonnancement,– un arc va de la transaction Ti a la transaction Tj ssi on a pi → pj dans l’ordonnancement.
Q. 189 En quoi l’ordonnacement suivant n’est-il pas serialisable : l1(a) l2(a) e2(a) e1(a)
Par exemple l1(o) l2(o) e1(o) e2(o) n’est pas serialisable car on a l1(o) → e2(o) qui implique que T1
doit preceder T2 et l2(o)→ e1(o) qui implique que T2 doit preceder T1.
Bien sur, on peut avoir plus d’un ordonnancement sequentiel equivalent a un ordonnancement entre-lace :
– si l’ordonnancement ne contient pas de couples d’operations induisant un ordre (par exemple lestransactions travaillent sur des objets differents)
– soit l’ordonnancement entrelace de 3 transactions T1, T2 et T3 :�� ��l3(a) e1(b) e2(a) l2(b) e3(c) . Les
couples d’operations induisant un ordre sont : l3(a)→ e2(a) et e1(b)→ l2(b) qui indiquent que dansles ordonnancements sequentiels equivalents T3 et T1 doivent preceder T2 mais n’impose aucun ordreentre T3 et T2. On a alors deux ordonnancements sequentiels equivalents : T1, T3, T2 et T3, T1, T2.
Q. 190 Combien (e3(c) l2(a) e4(b) e1(d) e2(a)) a-t-il d’ordonnancements sequentiels equivalents ?
Q. 191 Appliquer aux exemples precedents : 13.2.1, 13.2.2, 13.2.3, 13.2.4 et a l’ordonnancementcorrect
Q. 192 L’ordonnancement suivant est-il correct ? est-il serialisable ? qu’en conclure sur la theorie dela serialisabilite ?
1Certains auteurs parlent, dans ce cas, d’operations incompatibles.
152 CHAPITRE 13. GESTION DE LA CONCURRENCE DES TRANSACTIONS
T1 virer (A, B, 100) T2 virer (A, C, 200)
a := lire (A) ; a := a - 100 ;
ecrire (A, a) ;
a := lire (A) ; a := a - 200 ;
ecrire (A, a) ;
b := lire (C) ; b := b + 200 ;
ecrire (C, b) ;
b := lire (B) ; b := b + 100 ;
ecrire (B, b) ;
Remarques
– Il existe d’autres manieres d’induire un ordre qui sont plus sophistiquees et reconnaissent plusd’ordonnancements comme etant serialisables que celle presentee ici (qui a le merite de la simplicite).Par exemple, si on considere des operations de plus haut niveau que de simples lectures et ecritures :x1(o, 10) et x2(o, 20) n’induisent pas d’ordre si l’operation x consiste a (1) lire la valeur de o, (2)ajouter a cette valeur celle du deuxieme parametre et (3) ecrire cette valeur comme nouvelle valeur deo. Notre definition de l’induction d’ordre ferait que l’ordonnancement (x1(o, 10) x2(o, 20) x1(o, 30))serait declare non serialisable alors qu’avec cette nouvelle definition on se rend compte qu’il estparfaitement serialisable.
– Cette etude theorique de la serialisabilite suppose que toutes les transactions participant a l’or-donnancement sont terminees. En pratique il se peut que, sur un SGBD charge, les transactionss’entrelacent de facon permanente, les ordonnancements a etudier pourraient donc etre de lon-gueurs illimitees et une telle approche n’est donc pas applicable pratiquement. Les SGBD mettentdonc en œuvre des protocoles de prevention (par verrouillage des donnees) et de detection de nonserialisabilite (par estampillage des donnees et des transactions) qui sont plus contraignantes quela theorie (elles empecheront certains ordonnancements bien qu’il soient serialisables) mais qui sontrealisables techniquement.On verra qu’Oracle prend le meilleur de ces deux types de protocole (prevention et detection) pourcorriger les defauts de l’une avec les qualites de l’autre.
13.4 Approche pratique : les techniques proposees par les SGBD
On verra principalement le verrouillage et la gestion de versions multiples d’un meme objet qui sonta la base des techniques proposees par Oracle et Postgres (entre autres tres probablement).
13.5 La technique preventive du verrouillage
Le verrouillage d’objet est un outil fourni par le SGBD qui permet d’empecher que deux transactionspuissent acceder simultanement au meme objet en obligeant une des deux transactions a attendreque l’autre deverrouille l’objet. On dit aussi que le verrouillage est un outil (de tres bas niveau, enfait le seul outil d’encore plus bas niveau est l’instruction test and set de certains microprocesseurs)pour synchroniser les transactions. Les methodes synchronized de Java relevent de la technique duverrouillage en empechant que deux taches puissent executer en meme temps une des methodes syn-chronized d’un meme objet, pas forcement la meme (une des deux taches est mise en attente jusqu’ace que l’autre ait termine d’executer sa methode).
Chaque donnee (nuplet) de la base peut-etre verrouillee, utilisee puis deverrouillee par une transaction(le deverrouillage est fait automatiquement en fin de transaction : voir le protocole V2PR section 13.8page 155).
Une transaction ne manipule une donnee que si elle l’a prealablement verrouillee dans le mode appro-prie (ceci est garanti par le SGBD puisque c’est lui qui implicitement verrouille les donnees). Quandune transaction demande a verrouiller une donnee deja verrouillee par d’autres transactions dans un
13.6. UN PROTOCOLE NAIF DE VERROUILLAGE 153
mode incompatible, elle est mise en attente jusqu’a ce que tous les verrouillages incompatiblessoient leves. Il y a deux modes de verrouillage :
verrouillage partage : S comme Shared si la transaction ne souhaite que lire la donnee. L’operationde verrouillage est lockS (o), celle de deverrouillage unlockS (o).
Typiquement ce mode de verrouillage est effectue automatiquement par le SGBD sur chaquenuplet selectionne par une requete (select).
Plusieurs transactions peuvent utiliser ce mode de verrouillage simultanement sur la memedonnee, d’ou son nom de partage.
En revanche ce mode est incompatible avec le mode exclusif.
Attention : Oracle et Postgres (depuis la version 6.5) ne disposent pas de ce mode de verrouillage,ils preferent utiliser un systeme de multi-versions des nuplets permettant de ne jamais bloquerles transactions en lecture seule.
verrouillage exclusif : X comme eXclusive si la transaction souhaite modifier la donnee. L’operationde verrouillage est lockX (o), celle de deverrouillage unlockX (o).
Typiquement ce mode de verrouillage est effectue automatiquement par le SGBD sur chaquenuplet faisant l’objet d’une mise a jour (update, insert, delete) ou d’une selection pour misea jour ulterieure (select ... for update : permet au programmeur de verrouiller explicitementdes nuplets sans pour autant les modifier section 16.3).
Ce verrouillage est incompatible avec toute autre demande de verrouillage.
Oracle et Postgres verrouillent en X automatiquement les nuplets modifies ou selectionnes pourmodification.
Le tableau suivant resume les compatibilites entre les deux modes de verrouillage :
etat de verrouillagenon verrouille S X primitives
demande Shared : lecture + + - lockS (), unlockS ()
de verrou eXclusive : modification + - - lockX(), unlockX()
13.6 Un protocole naıf de verrouillage
Ce protocole consiste a verrouiller l’objet que l’on souhaite mettre a jour, puis a le deverrouiller desla fin de cette mise a jour.
Ce protocole resout le probleme de perte de mise a jour de la section 13.2.1 page 149 :
154 CHAPITRE 13. GESTION DE LA CONCURRENCE DES TRANSACTIONS
T1 virer (A, B, 100) T2 virer (A, B, 200)
lockX (A) ;
a := lire (A) ; a := a - 100 ;
lockX (A) ;
attente . . .ecrire (A, a) ;
unlockX (A) ;
a := lire (A) ;
lockX (B) ;
a := a - 200 ;
b := lire (B) ; b := b + 100 ;
ecrire (A, a) ;
unlockX (A) ;
lockX (B) ;
attente . . .ecrire (B, b) ;
unlockX (B) ;
b := lire (B) ; b := b + 200 ;
ecrire (B, b) ;
unlockX (B) ;
13.6.1 Ce protocole naıf ne resout pas tous les problemes
Exemple d’un verrouillage naıf qui peut aboutir a une analyse incoherente de l’etat de la base :
T1 virer (A, B, 100) T2 afficher (A + B)
lockX (A) ;
lockS (A) ;
a := lire (A) ; a := a - 100 ; ecrire (A, a) ; attente . . .unlockX (A) ;
a := lire (A) ;
unlockS (A) ;
lockS (B) ;
lockX (B) ;
attente . . . b := lire (B) ;
unlockS (B) ;
afficher (a + b) ;
b := lire (B) ; b := b + 100 ; ecrire (B, b) ;
unlockX (B) ;
Ici, il faudrait que T2 attende la fin de T1 pour commencer son execution. Ceci est parfaitement possiblesi on s’arrange pour que le deverrouillage de A par T1 se fasse apres le verrouillage de B (verrouillagedeux phases).
C’est pourquoi on introduit le protocoles de verrouillage deux phases dont l’objectif est degarantir la serialisabilite.
13.7 Le verrouillage deux phases (V2P)
Dans la premiere phase la transaction ne peut que verrouiller les donnees, dans la seconde phase elle nepeut que relacher les verrouillages. Bien entendu ces operations de verrouilage puis de deverrouillagepeuvent etre melangees dans les instructions de la transaction.
Q. 193 Verifier que V2P resout le probleme precedent (section 13.6.1).
13.8. LE VERROUILLAGE DEUX PHASES RIGOUREUX (V2PR) 155
13.7.1 V2P : probleme des cascades d’annulations
Exemple d’un verrouillage deux phases ou l’annulation de T1 oblige a annuler aussi T2. Supposons queles soldes de la base n’aient pas le droit d’etre negatifs. Supposons aussi que le virement de A vers B
rende negatif le solde de A, lors de la tentive d’ecriture du nouveau solde une erreur sera declencheemenant a l’annulation de la transaction :
T1 virer (A, B, 100) T2 crediter (B, 200)
lockX (A) ;
a := lire(A); a := a-100;
lockX (B) ;
b:=lire(B); b:=b+100; ecrire(B,b); lockX (B) ;
unlockX (B) ; attente . . .b:=lire(B); b:=b+200; ecrire(B,b);
unlockX (B) ;
ecrire(A, a); ⇒ annulation car a<0 ⇒ annulation en cascade
Q. 194 Pourquoi l’annulation de T1 implique-t-elle celle de T2 ?
L’annulation en cascade pose un serieux probleme : si T2 est deja validee au moment ou T1 est annuleealors on ne peut plus annuler T2 et la base de donnees passe dans un etat incoherent puisque T2 autilise un etat de B qui a ete annule !Le probleme vient du fait que T2 utilise B sans etre sur que sa nouvelle valeur sera validee. La solutionconsiste donc a faire attendre T2 jusqu’a ce que T1 soit validee ou annulee : les verrous ne doivent etrerelaches par T1 qu’apres sa validation ou son annulation. C’est exactement ce que fait le protocolede verrouillage deux phases rigoureux.
13.8 Le verrouillage deux phases rigoureux (V2PR)
Pour eviter des problemes d’annulation en cascade, on interdit tout deverrouillage explicite (la primi-tive unlockX n’existe tout simplement pas !) : les deverrouillages sont effectues automatiquement parle SGBD en fin de transaction (apres commit ou rollback). Quand une transaction se debloque elleest sure de trouver les donnees dans le bon etat car la validation ou l’annulation de leurs modificationsa deja ete faite.
Q. 195 Reecrire l’exemple precedent (section 13.7.1) avec le V2PR : l’annulation de T2 est-ellenecessaire ?
Ce protocole garantit la serialisabilite et l’absence de cascades d’annulations. Il est automatiquementmis en œuvre en Oracle et PostgreSQL.
13.8.1 Le probleme du V2PR : l’interblocage
Malheureusement le V2PR peut donner lieu a des interblocages (encore nommes etreintes fatales ou
deadlock) entre transactions :
T1 T2
lockX (A) ;
lockX (B) ;
lockX (B) ;
bloque par T2
lockX (A) ;
bloque par T1
En fin de tableau, T1 attend que T2 deverrouilleB et T2 attend que T1 deverrouille A ce quitraduit par un interblocage entre T1 et T2 : ila un cycle dans le graphe d’attente :
T1 T2
attend
attend
Q. 196 Donner un exemple d’interblocage mettant en jeu trois transactions.
Q. 197 Caracteriser graphiquement un interblocage concernant n transactions T1, . . . , Tn
156 CHAPITRE 13. GESTION DE LA CONCURRENCE DES TRANSACTIONS
Dans la question precedente, n ne peut pas etre egal a 1, c’est a dire qu’une meme transaction nepeut pas s’interbloquer avec elle-meme : les SGBD (et les systemes de synchronisation en general)sont assez malins pour permettre a une meme transaction de verrouiller plusieurs fois le meme objet(par exemple, en Java, une methode synchronized peut parfaitement etre recursive !).
Comme en medecine pour les maladies, il y a deux manieres de gerer les interblocages : la prevention quiconsiste a s’arranger pour qu’il n’y ait jamais d’interblocage, la detection qui laisse les interblocages seproduire puis les detecte et les corrige en annulant une des instructions qui participe a cet interblocage.
Certaines techniques de prevention (comme le wait/die et le wound/wait) necessitent l’annulation dela transaction susceptible de participer a un interblocage.
Pour la technique de detection on n’annule aucune transaction mais plutot l’instruction d’une destransactions qui participe a l’interblocage (c’est ce que fait Oracle en provocant une erreur pour cetteinstruction).
Ainsi le code d’une transaction doit envisager l’echec de ses intructions pour cause d’interblocage.Le plus simple est d’effectuer un rollback brutal, d’attendre un peu que les choses se calment et derelancer une nouvelle transaction sur le meme code pour tenter de faire quand meme le travail, car engeneral un interblocage ne correspond pas a une erreur de programmation mais plutot a un manquede chance.
Une solution plus subtile consiste a rester dans la meme transaction en effectuant eventuellement unrollback partiel (rollback to savepoint).
Q. 198 Java previent-il ou detecte-t-il les interblocages ? (Ada non plus)
Oracle detecte les interblocages en provocant une erreur dans une des transactions y participant.
13.8.2 Detection puis resolution de l’interblocage (cas de Oracle et Postgres)
Detection d’interblocage : un cycle dans le graphe d’attente entre les transactions. Les transactionsforment les sommets du graphe, un arc Ti → Tj indique que Ti est bloque en attente d’une ressourceverrouillee par Tj.
Les SGBD detectent periodiquement les interblocages et les denouent en faisant echouer l’instructionen cours d’une des transactions participant a l’interblocage (en Oracle c’est l’erreur -00060). Commentchoisir cette transaction :
– celle qui est la plus proche de sa fin (comment le savoir ?)– celle qui a fait le moins de modifications– la plus jeune (elle vieillira et deviendra de moins en moins sujette a avortement)– en Oracle, il semble qu’il n’y ait pas de critere particulier.
Le fait que les verrous ne soient relaches qu’en fin de transaction (commit ou rollback) garantit quelors d’un tel echec aucune autre transaction n’a pu lire une donnee produite par la transaction choisie,ainsi la resolution d’un interblocage ne produira jamais d’avortements en cascade.
13.8.3 Prevention des interblocages
– Par norme de programmation : on verrouille les objets toujours dans le meme ordre (si toutes lestransactions verrouillent les objets dans le meme ordre, aucun interblocage n’est plus possible), oubien pose prealable d’un verrou global et unique (ceci etant evidemment penalisant).
– Par verrouillage en tout ou rien : soit on arrive a verrouiller tous les objets necessaires et l’executionpeut continuer, soit on n’y arrive pas et la transaction attend que tous ces objets soient disponibles.
– Par un protocole ad hoc, par exemple les protocoles wait/die et wound/wait qui seront vus en TD.Ces deux protocoles necessitent l’annulation d’une transaction en cas de possibilite d’interblocage.
13.9. MULTI-VERSIONS ESTAMPILLEES MVE, PROTOCOLE D’ISOLATION 157
Granularite des objets verrouillables en general deux grains : la table et le nuplet (Oracle etPostgres ont ces deux grains).
13.8.4 Inconvenients du V2PR
Le V2PR est plus strict que la theorie de la serialisabilite : a cause du verrouillage qui force l’attentede certaines transactions, certains ordonnancements serialisables ne peuvent plus se produire en V2PR.
Le verrouillage deux phases rigoureux bloque toute transaction qui tente de lire une donnee en coursde modification et inversement. Le protocole multi-versions permet d’assouplir cela en permettantque les lectures ne soient jamais bloquees et qu’elles ne bloquent jamais les ecrivains : le mode deverrouillage partage (SHARE) n’est plus necessaire. C’est ce que proposent Oracle et Postgres.
13.9 Multi-versions estampillees MVE, protocole d’isolation
Contrairement au V2PR, le protocole MVE ne bloque aucune transaction mais effectue une verificationde la serialisabilite a posteriori (protocole curatif). Il assure aussi l’isolation des transactions.
Objectifs et avantages de MVE :
– assure l’isolation inter-transaction, c’est a dire qu’une transaction ne verra pas les modificationsfaites par d’autres transactions plus recentes
– lecteurs jamais bloques,– les lecteurs ne bloquent pas les ecrivains.
Inconvenients de MVE :
– seule une operation d’ecriture peut donner lieu a annulation,– il peut y avoir des cascades d’annulations.
Ici, il faut bien distinguer les notions d’objet, qui est en general une ligne de table, et de valeur. Unevaleur est une constante alors qu’un objet peut posseder des valeurs differentes au fil du temps.
13.9.1 Introduction informelle a MVE
L’idee est de conserver et exploiter l’historique des valeurs qui ont ete affectees a chaque objet demaniere a ce qu’une transaction ne voit pas les modifications faites par d’autres transactions.
On parle d’objet, sachant que dans une base de donnees l’objet est une ligne de table (ou nuplet).Chaque objet aura plusieurs versions : chaque changement de valeur de l’objet produit une nouvelleversion.
Pour distinguer les versions d’un meme objet, chaque version V d’une ligne sera estampillee commececi V r
c ou :
1. c est l’estampille de creation de cette version, c’est la date de demarrage de la transaction ayantproduit cette version. Cette estampille est constante.
2. r est l’estampille de lecture de cette version, c’est la date de demarrage de la transaction la plusrecente ayant lu cette version. Cette estampille est variable (elle ne peut que croıtre).
3. A la creation d’une nouvelle version on a V cc ou c est l’identifiant de la transaction productrice.
On supposera que deux transactions ne pourront jamais avoir la meme date de demarrage (c’est facilesi on utilise la valeur d’un compteur pour dater les transactions, le compteur etant incremente apreschaque demarrage d’une transaction).
Une transaction Th ne pourra lire que la version V rc telle que c est la plus grande estampille ≤ h.
L’effet de cette lecture modifiera l’estampille de lecture comme ceci :Vmax(r,h)c . Cette mise a jour de
l’estampille de lecture permettra de faire echouer une tentative d’ecriture par la transaction Tk telle
158 CHAPITRE 13. GESTION DE LA CONCURRENCE DES TRANSACTIONS
que c ≤ k < max(r, h) car cela oterait toute signification a la lecture faite par Th.
Par exemple si la ligne lue par la transaction T11 a les versions V 64 V 9
7 V 1512 c’est la version V 9
7 qui seralue et la liste de versions deviendra V 6
4 V 117 V 15
12 . Si T11 creee une version on obtient : V 64 V 11
7 V 1111 V 15
12 .
Voici un exemple ou deux transactions T9 et T10 incrementent l’entier d’une meme ligne et ou tout sepasse bien :
versions de la ligne T9 T10
566
demarragelire donne 56
6
596
ecrit (699)
596 69
9
demarragelire donne 69
9
596 610
9
ecrit (71010)
596 610
9 71010
Q. 199 Que lira T10 apres avoir ecrit 7 dans la ligne ?
Le meme exemple ou les choses se passent moins bien :
versions de la ligne T9 T10
566
lire donne 566
596
lire donne 596
5106
ecrit (699) ⇒ echec car lu par T10
5106
La tentative d’ecriture de T9 echoue car on se rend compte que cette version a ete lue par unetransaction plus recente puisqu’elle est dans l’etat 510
6 : le 10 indique qu’une transaction plus recentea lu cette valeur et il ne faut pas que T9 puisse changer ce passe.
Cet echec de T9 montre que T10 est bien isolee des modifications faites par d’autres transactions.
On en deduit que :
– la tentative par Th de creer une nouvelle version dans . . . V rc V r′
c′ . . . avec c ≤ h < c′ doit verifierr ≤ h (sinon echec), et on obtient . . . V r
c V hh V r′
c′ . . .– une lecture peut allonger l’intervalle [c, r] de la version qu’elle lit mais sans chevaucher l’intervalle
de la version suivante dont l’estampille de creation est forcement strictement superieure a celle dela transaction
Du coup, pour les versions V r1
c1V r2
c2V r3
c3V r4
c4d’une ligne on est sur d’avoir la propriete : c1 ≤ r1 ≤
c2 ≤ r2 ≤ c3 ≤ r3 ≤ c4 ≤ r4.
Q. 200 La transaction T10 peut-elle donner une nouvelle valeur a la version V 1010 ? cela pourrait-il
deranger une autre transaction qui aurait lu cette version ?
Q. 201 La transaction T10 peut-elle donner une nouvelle valeur a la version V 1510 ?
La figure 13.1 page 159 donne une approche graphique de MVE.
Q. 205 Montrer que sur l’historique Q75 de Q, MVE n’accepte que l’ordonnancement [l9(Q) e9(Q) l10(Q) e10(Q)].
Q. 206 Montrer que sur l’historique Q75 de Q, [l10(Q) l9(Q) e10(Q) l9(Q)] est accepte et surtout
correct : T9 lit-elle toujours la meme valeur ?
13.9. MULTI-VERSIONS ESTAMPILLEES MVE, PROTOCOLE D’ISOLATION 159
Fig. 13.1 – Voici une representation plus graphique des versions 1473 1011
10 211614 d’une ligne contenant
un entier et quelles versions vont voir les transactions representees. Sur cette figure les instants ontune largeur non nulle de maniere a pouvoir montrer la valeur de la ligne.
3 7 10 14
T14, T50
16
14 2110
11
T3, T6, T7, T8, T9 T10, T12, T13
Q. 202 Donner les transactions dont on est sur qu’elles ont ecrit une valeur sur cette ligne et cellesdont on est sur qu’elles ont lu cette ligne.
Q. 203 Donner les transactions dont on est sur qu’elles n’ont jamais lu la ligne.
Si T8 lit cette ligne, elle obtient la version 1473 et l’etat des versions devient :
3 10 14
T14, T50
16
14 2110
11
T3, T6, T7, T8, T9 T10, T12, T13
8
Si T8 augmente cette valeur de 5 puis l’ecrit dans la meme ligne, le nouvel etat des versions sera :
3 10 14
T14, T50
16
14 2110
11
T10, T12, T13
8
19
T3, T6, T7 T8, T9
Q. 204 Qu’est-ce qui explique que l’ecriture faite pat T8 ne gene pas T9 ?
13.9.2 Definition precise de MVE
Chaque transaction T est estampillee de facon unique avec l’instant auquel elle a commence, parexemple T9. Donc Th1 est plus ancienne que Th2 si ⇔ h1 < h2.
Pour chaque donnee (nuplet) Q, on maintient plusieurs versions notees Qrc ou c est l’estampille
constante de la transaction qui a cree cette version et r celle de la transaction la plus recente quia lu cette version.
Toutes les versions d’une ligne sont des constantes.
Le protocole est alors le suivant : lors d’une tentative de lecture ou d’ecriture de Q par la transactionTh, on choisit la version Qr
c de plus grand c avec c ≤ h. Puis s’il s’agit d’une :
– lecture : on met a jour le r de Qrc avec max(r, h) et sa valeur est utilisee.
– ecriture, il y a trois cas :– si c = r = h : aucune autre transaction n’a encore lu cette version produite par Th : la valeur de
Qrc est remplacee par la valeur ecrite sans qu’il y ait creation d’une nouvelle version.
– si c = r < h ou c < r ≤ h : la nouvelle version Qhh est creee.
– si c ≤ h < r : la transaction Tr plus recente que Th, a deja lu la donnee : Th ne doit pas modifierQr
c et etre annulee afin de garantir l’isolation de Tr.Cela a pour consequence que Th verra les ecritures qu’elle a faites.
Un grand interet de ce protocole est de garantir aux transactions, meme si certaines sont tres longues,qu’elles liront toujours la meme valeur d’une ligne de table.
160 CHAPITRE 13. GESTION DE LA CONCURRENCE DES TRANSACTIONS
Q. 207 Supposons que T6 ait produit avec succes la nouvelle version P 66 de l’objet P , puis que la
transaction T8 ait lu P 66 qui devient P 8
6 , puis que T6 echoue en tentant d’ecrire une nouvelle versionde Q7
4. T6 est donc annulee, mais que devrait-il se passer en plus de cette annulation ? Cela paraıt-ilraisonnable ? (c’est le probleme de ce protocole qui sera resolu par l’utilisation du verrouillage)
Pratiquement une operation de lecture isolee correspond a une requete (select) tandis qu’une lecturesuivie d’une ecriture d’un meme objet correspond a une mise a jour d’un nuplet (update). Dans lesdeux cas l’objet preexiste a l’operation.
Le cas de la creation d’un nouveau nuplet (insert) par T6 peut etre pris en compte par la creationd’un nouvel objet O dont l’unique version est O6
6.
Q. 208 Supposons que T4 tente une lecture de Q ayant les versions Q117 Q15
11. Comment interpreter cecas ?
L’interpretation est exactement la meme dans le cas d’une mise a jour (update).
Q. 209 Montrer qu’une transaction qui ne fait que des lectures de Q lira toujours la meme valeur.(on peut montrer qu’il n’est pas possible qu’elle obtienne deux valeurs differentes)
Exemple, etudions l’evolution des versions du nuplet Q :Le nuplet Q dispose initialement de deux versions : Q13
6 Q1513
T14 lit Q : version Q1513 : Q13
6 Q1513
T16 lit Q : version Q1513 dont r est mis a 16h : Q13
6 Q1613
T17 ecrit une nouvelle valeur de Q : creation de Q1717 : Q13
6 Q1613 Q17
17
T17 ecrit une nouvelle valeur de Q : mise a jour de Q1717 : Q13
6 Q1613 Q17
17
T16 ecrit une nouvelle valeur de Q : creation de Q1616 : Q13
6 Q1613 Q16
16 Q1717
Q. 210 En fin de tableau, que se passe-t-il si T15 tente d’ecrire Q ?
Q. 211 Que se passe-t-il si T13 est annulee ? Annuler une transaction revient a supprimer les versionsqu’elle a creees.
Q. 212 Reprendre le tableau precedent en remarquant que l’attribution d’une nouvelle version a unobjet correspond a un update, or un update commence toujours par lire la version correspondant ala transaction pour pouvoir calculer la nouvelle version. Par exemple augmentation de 10% du salaired’un employe.
Suppression des versions inutiles On peut montrer que des versions anciennes ne seront plusjamais utilisees par aucune transaction presente ou future. Soient h l’estampille de la plus anciennetransaction encore active et Qr
c et Qr′
c′ deux versions de Q, telles que c < c′ ≤ h. La version Qrc peut
etre supprimee. Exemple :
Soit : Q136 Q18
13 Q2118
La plus ancienne transaction active est T14 : suppression de Q116 : Q18
13 Q2118
13.9.3 Ordonnancements non acceptes par le protocole Multi-versions estam-pillees
On represente chaque version d’un objet Q par Qrc, et on supposera qu’on dispose initialement de
l’unique version Q66.
13.9. MULTI-VERSIONS ESTAMPILLEES MVE, PROTOCOLE D’ISOLATION 161
Ordonnancement dont on ne sait pas (encore) s’il est ou non serialisable
versions de Q T9 T10
Q66
lire (Q) donne Q6
Q96
lire (Q) donne Q6
Q106
ecrire (Q) ⇒ annulation
Si, apres l’ecriture de Q par T9, T10 ne tente pas de lire ou d’ecrire Q l’ordonnancement est serialisable.Mais au moment de l’annulation de T9 on ne le sait pas encore et cette annulation est peut-etre abusive,mais necessaire du point de vue du protocole multi-versions.
Ordonnancement clairement non serialisable detecte par MVE
versions de Q T9 T10
Q66
lire (Q) donne Q6
Q96
lire (Q) donne Q6
Q106
ecrire (Q)Q10
6 Q1010
ecrire (Q) ⇒ annulation
Ici le protocole multi-versions colle bien a la theorie de la seriabilite.
Ordonnancement serialisable rejete par MVE !
versions de Q T9 T10
Q66
lire (Q) donne Q6
Q106
ecrire (Q)Q10
6 Q1010
lire (Q) donne Q6
Q106 Q10
10
ecrire (Q) ⇒ annulation
Cet ordonnancement serialisable est pourtant refuse par le protocole multi-versions.
Q. 213 Montrer que cet ordonnancement est pourtant serialisable (13.3 page 150).
13.9.4 Ordonnancement non serialisable mais accepte, a juste titre, par MVE
Un ordonnancement non serialisable peut etre accepte, a juste titre, par ce protocole, ceci grace auxversions multiples d’un meme objet. Un exemple ou T9 lit toujours la meme valeur :
162 CHAPITRE 13. GESTION DE LA CONCURRENCE DES TRANSACTIONS
versions de Q T9 T10
Q66
lire (Q) donne Q6
Q106
lire (Q) donne Q6
Q106
ecrire (Q)Q10
6 Q1010
lire (Q) donne Q6
Q106 Q10
10
lire (Q) donne Q10
Q106 Q10
10
Q. 214 Montrer que cet ordonnancement n’est pas serialisable (13.3 page 150).
Cela montre simplement que la definition de la serialisabilite que nous utilisons (car il y en a d’autres)n’est pas absolue et ne prend donc pas en compte les possibilites de MVE : la theorie de la serialisabiliteest une simplification du monde.
En conclusion : soit l’ensemble Os des ordonnancements serialisables et Omve celui des ordonnance-ments acceptes par le protocole multi-versions, on a : Os ∩Omve 6= ∅, Os 6⊆ Omve et Omve 6⊆ Os.
13.9.5 Inconvenient du protocole multi-versions : Les cascades d’annulations
versions de Q versions de A T9 T10 T11
Q66 A6
6
lire (A) -> A6
Q66 A9
6
ecrire (A)Q6
6 A96 A9
9
lire (A) -> A9
Q66 A9
6 A119
ecrire (A)Q6
6 A96 A11
9 A1111
lire (Q)Q10
6 A96 A11
9 A1111
lire (Q)Q10
6 A96 A11
9 A1111
ecrire (Q) ⇒ annulation⇒ annulation
L’annulation de T9 oblige a annuler T11 car T11 a lu A9 qui a ete produite par T9, on a donc unecascade d’annulation.
Si par malheur T11 est deja validee alors c’est foutu : on peut mettre la base de donnees a la poubelle !Solution : faire attendre, grace a un verrou, l’ecriture de T11 jusqu’a ce que T9 soit validee ou annulee(⇒ V2PR).Le protocole multi-versions ne peut donc se passer d’un protocole garantissant l’absence de cascadesd’annulations : le V2PR paraıt bien convenir.
13.9.6 Avantages du protocole multi-versions
Ce protocole n’est pas bloquant puisqu’il n’utilise pas de verrouillage, cette qualite sera conservee pourles transactions en lecture seule malgre l’ajout du protocole V2PR permettant de corriger le problemedes cascades d’annulations.
Q. 215 Completer les versions de Q et indiquer les valeurs des lire(Q) dans le tableau suivant :
13.9. MULTI-VERSIONS ESTAMPILLEES MVE, PROTOCOLE D’ISOLATION 163
versions de Q T7 T9 T10 T11
Q66
————– lire(Q) →
————– lire(Q) →
————– lire(Q) →
————– ecrire(Q)
————– lire(Q) →
————– ecrire(Q)
————– lire(Q) →
————– ecrire(Q)
————– lire(Q) →
————– lire(Q) →
Q. 216 En fin de l’ordonnancement precedant, on suppose que toutes les transactions d’estampille< 10 sont terminees, donner les versions que l’on peut supprimer.
13.9.7 Ce qui est troublant dans MVE
Chaque version peut vivre sa vie : de nouvelles versions peuvent etre produites par des transactionsanciennes et ces versions ne seront jamais vues par les futures transactions. Par exemple :
versions de QQ10
6
T12 ecrit QQ10
6 Q1212
T11 ecrit QQ10
6 Q1111 Q12
12
T18 ecrit QQ10
6 Q1111 Q12
12 Q1818
T21 lit QQ10
6 Q1111 Q12
12 Q2118
Les versions Q106 , Q11
11 et Q1212 ne peuvent etre vues et manipulees que par les transactions d’estampilles
inferieures a 18h et vivent leurs vies. Comment comprendre cela ?
Une reponse consiste a faire l’hypothese que l’ecriture d’une nouvelle version est forcement precedeepar une lecture, c’est effectivement ce qui se passe lors d’un update. Dans ce cas il est impossible decreer une nouvelle version dont l’estampille de creation serait inferieure a la plus grande estampille delecture et les anciennes versions ne peuvent alors plus vivre leur vie L’exemple precedent devient :
164 CHAPITRE 13. GESTION DE LA CONCURRENCE DES TRANSACTIONS
versions de QQ10
6
T12 lit QQ12
6
T12 ecrit QQ12
6 Q1212
T11 lit QQ12
6 Q1212
T11 echoue en tentant d’ecrire QQ12
6 Q1212
T18 lit QQ12
6 Q1812
T18 ecrit QQ12
6 Q1812 Q18
18
T21 lit QQ12
6 Q1812 Q21
18
On voit qu’il n’y a plus de trous entre les versions successives et les anciennes transactions disposenttoujours des versions qui les concernent. C’est exactement ce que font Oracle et Postgres.
Chapitre 14
Elements d’implantation destransactions
14.1 Multi-versions et V2PR (Oracle, PostgreSQL et MySQL/InnoDB)
Oracle ne permet de modifier que la version la plus recente d’une ligne.– Pas de cascades d’annulations grace au verrouillage deux phases rigoureux.– Les lectures ne sont jamais bloquees et ne bloquent pas les ecrivains grace aux versions multiples.
14.1.1 Verrouillage 2 phases rigoureux en Oracle
Oracle propose deux granularites de verrouilage : les nuplets et les tables. Pour les nuplets on nedispose que du verrouillage exclusif note X (pas de verrou partage note S sur les nuplets). Pour lestables on dispose des verrouillages effectifs X et S et des verrouillages d’intention RS, RX et SRX quipermettent de savoir rapidement si des nuplets de la table sont susceptibles d’etre en cours de lecturepour modification ulterieure (RS pose par select...for update voir section 16.3), de mise a jour (RXpose par insert, update, delete) ou de lecture par plusieurs transactions et mise a jour par uneseule transaction (celle qui a verrouille la table en SRX, seuls les verrouillages RS faits par les autrestransactions ne sont pas bloques par SRX).
Ici on ne s’interesse qu’au verrouillage niveau nuplet introduit par Oracle 6.
Trois composants fondamentaux dans la memoire globale d’Oracle :
– le SCN (System Change Number) de la prochaine transaction a demarrer. Le SCN est un entierqui identifie de facon unique chaque transaction vivante ou terminee, les SCN sont alloues de faconcroissante.
– chaque nuplet de la table conserve les versions encore utiles aux transactions en cours. La versionla plus recente est dans la table, les autres sont dans le segment de recouvrement et chaque versionest etiquetee avec le SCN de la transaction qui l’a creee,
– la table des transactions actives (nommee TTA dans la suite) contient les SCN de toutes les tran-sactions actives (ni validees ni annulees).
Un nuplet est verrouille de facon exclusive si et seulement si l’etiquette SCN de sa version la plusrecente (celle qui est dans la table) est presente dans la TTA. En fin de transaction, le SCN de latransaction est supprime de la TTA, ce qui deverrouille implicitement tous les nuplets verrouilles parcette transaction.Le verouillage d’une ligne n’empeche pas une autre transaction de lire la version appropriee de cetteligne.
14.1.2 Multi-versions en Oracle : principes generaux
Oracle implemente une version simplifiee de la gestion de multi-version : chaque version d’un nupletest une valeur qu’a prise ce n-uplet etiquetee avec le SCN de la transaction qui a produit cette version
165
166 CHAPITRE 14. ELEMENTS D’IMPLANTATION DES TRANSACTIONS
(il n’y a pas d’estampille de lecture). La version la plus recemment produite est dans la table alorsque les versions plus anciennes sont memorisees dans le segment de recouvrement (rollback segment).
Les versions sont dans une liste ordonnee de la version la plus recemment creee a la plus ancienne. Pourcreer une nouvelle version V ′, un update doit modifier la version la plus recente V (celle en table) etV ′ sera inseree en tete de la liste des versions poussant ainsi V dans le segment de recouvrement, saufsi c’est la meme transaction qui produit V et V ′, dans ce cas V ′ se substitue a V .
Si une transaction tente de modifier une version de ligne qui n’est pas la plus recente, une erreur seraproduite.
Les versions du segment de recouvrement ne peuvent qu’etre lues, elle ne peuvent pas servir a calculerune nouvelle version (update) ou a supprimer la ligne (delete).
Un etat possible du systeme
Memoire Globale
La transaction concernee SCN = 5025Les transactions actives lors de la creation de 5025 : CTTA = (1455, 2033, 4001)Les transactions actuellement actives : TTA = (5025, 1455, 4001)
table T segment de recouvrementligne scn valeur scn valeur scn valeur
1 4 (1, B)2 3999 (2, B) → 17 (2, A)3 17 (3, B)4 4001 (4, E)5 2033 (5, C) → 2000 (5, B) → 17 (5, A)
Q. 217 Que peut-on dire des lignes 4 et 5 ?
Q. 218 Quelles sont les transactions validees (commit) parmi celles qui ont manipule la table T ?
Q. 219 Quelles sont les lignes verrouillees ?
14.1.3 Demarrage d’une transaction : read only et serializable
Les transactions read only et serializable voient pendant toute leur vie la base dans l’etat devalidation dans lequel elle etait quand elles ont commence.
– Une transaction read only ne peut faire aucune mise a jour.– Une transaction en isolation serializable peut faire des mises a jour et ne voit que les modifications
qu’elle a faites.
Soit T la transaction read only ou serializable. En debut d’execution T est identifiee par le prochainSCN (plus grand que tous les autres deja attribues a d’autres transactions) et elle prend une copieCTTA de la TTA qu’elle ne modifiera pas. La CTTA permettra a T de connaıtre les transactionsqui etaient actives quand elle a demarre ce qui lui permettra d’eviter de voir les modifications faitespar ces transactions (meme si elles sont commises). Elle ignorera aussi les modifications faites par lestransactions plus recentes qu’elle.
On peut essayer de voir grahiquement ce qui se passe a l’instant t : soit l’etat suivant de la memoireglobale du SGBD et les etats locaux des deux transactions actives :
Etat 1
Memoire Globale Memoires locales des transactions
TTA = (15, 13) Transaction serializable 15 : CTTA = (14, 13, 12)prochain SCN = 17 Transaction serializable 13 : CTTA = (12, 10)
Q. 220 Le diagramme temporel de la figure 14.1 donne un historique coherent avec les donneesci-dessus (Etat 1), trouvez la petite erreur du diagramme !
14.1. MULTI-VERSIONS ET V2PR (ORACLE, POSTGRESQL ET MYSQL/INNODB) 167
10 11 12 13 15 16
T10
T11
T12 T16
T13
T14
T15
T9
temps
session 1
session 2
session 3
session 4
14 T Etat 1
Fig. 14.1 – Diagramme temporel possible des transactions de 4 sessions (erreur par rapport a Etat1). Les boıtes en pointilles correspondent aux transactions terminees a l’instant t. On voit par exempleque T10 existait encore quand T13 a demarre mais etait terminee au demarrage de T15. Quant a T16
elle est deja terminee (le prochain SCN global est 17). On aurait aussi pu representer les transactionsde SCN inferieurs a 10, mais il est sur qu’elles sont toutes terminees quand T13 demarre.
Q. 221 En utilisant la figure 14.1 donner l’etat correspondant a l’instant T.
Q. 222 La CTTA d’une transaction read only ou serializable peut-elle contenir un SCN superieurau sien ?
Q. 223 L’Etat 2 suivant est incorrect. Un diagramme temporel pourrait aider a trouver l’incoherence.
Etat 2
Memoire Globale Memoires locales des transactions
TTA = (20, 17) Transaction serializable 17 : CTTA = (7, 2)prochain SCN = 25 Transaction serializable 20 : CTTA = (18, 14)
14.1.4 La nouvelle transaction serialisable 5025 Demarre
Voici les contenus de la memoire globale et de la nouvelle transaction 5025 :
Memoire Globale Memoires locales des transactions
TTA = (1455, 2033, 4001) Transaction serializable 5025 : CTTA = (1455, 2033, 4001)prochain SCN = 5026
14.1.5 Lecture d’une ligne : transaction read only et serializable��
��
L’objectif est le suivant : pour une ligne, Ta obtient la version qu’elle a produite elle-meme ou,sinon, la version la plus recente produite par une transaction Tb deja validee quand Ta a demarre(principe d’isolation de read only et serializable), autrement dit b < a ∧ b 6∈ CTTA de Ta.
Q. 224 Montrer que, si la version obtenue par T est la plus recente (celle qui est dans la table), alors,si la ligne est verrouillee c’est forcement par T (montrer que les autres cas sont impossibles).
L’algorithme est alors le suivant : T obtient la version la plus recente (c’est a dire en parcourant laliste des versions en partant de celle qui est dans la table) dont le SCN est inferieur ou egal a son SCNet n’est pas dans sa CTTA.
Si aucune version acceptable n’existe c’est que le nuplet (la ligne) n’existait pas au demarrage de Tet T ignore donc ce nuplet.
Q. 225 Que lit la transaction 5025 si elle explore la table donnee en 14.1.2 p.166 ?
14.1.6 Mise a jour d’une ligne : transaction serializable
Si Ta est serializable et qu’elle tente un update ou un delete elle va travailler avec la version Vobtenue par une lecture ; plusieurs cas peuvent se produire :
168 CHAPITRE 14. ELEMENTS D’IMPLANTATION DES TRANSACTIONS
1. V est dans la table (donc version la plus recente) : le nuplet est verrouille par Ta puis la misea jour est faite dans la table avec mise de V dans le segment de recouvrement si son SCN n’estpas egal a celui de Ta.
2. V est dans le segment de recouvrement et le nuplet n’est pas verrouille : on en deduit que laversion la plus recente (celle qui est dans la table) a ete fabriquee par une transaction Tb dejavalidee et soit Tb est plus jeune que Ta soit le SCN de Tb est dans la CTTA de Ta. Dans lesdeux cas Ta cherche a modifier une version trop ancienne : une erreur de non serialisabilite estdeclenchee (ORA-08177), provoquant l’abandon du update ou du delete1.
3. V est dans le segment de recouvrement et le nuplet est verrouille par une autre transaction Tb :Ta va etre bloquee jusqu’a la fin de Tb. Tb peut se terminer de deux facons :
– Tb est validee (commit), le verrou est relache et on se trouve dans la situation 2 precedente :une erreur de serialisabilite est declenchee pour le update de Ta.
– Tb est annulee (rollback), le verrou est relache et Ta se retrouve soit dans le premier cas avecun succes, soit dans le deuxieme avec un echec.
Deux exemples du troisieme cas ou T10 serializable tente de modifier l’unique ligne de T. La versionvue par T10 est entouree :
– D’abord un succes :Memoire Globale Memoires locales des transactions
TTA = (5, 10) Transaction serializable 10 : CTTA = (2, 4, 5)prochain SCN = 11 Transaction serializable 5 : CTTA = (4)
table T segment de recouvrementligne scn valeur scn valeur scn valeur
1 5 (1, B) →�� ��3 (1, C)
T10 tente de mettre a jour laligne 1 : elle est bloquee par lamodification de T5. Si T5 effec-tue un rollback, la table re-prend son etat initial :
table T segment de recouvrementligne scn valeur scn valeur scn valeur
1�� ��3 (1, C)
du coup T10 est debloquee et reussit samise a jour car T3 etait terminee quandT10 a commence.
– Maintenant un echec : au debut T10 est bloquee par la modification de T5 :table T segment de recouvrement
ligne scn valeur scn valeur scn valeur
1 5 (1, B) → 4 (1, C) →�� ��3 (1, D)
A nouveau T5 effectue un rollback,la table reprend son etat initial :
table T segment de recouvrementligne scn valeur scn valeur
1 4 (1, C) →�� ��3 (1, D)
du coup T10 est debloquee, mais la versionqu’elle tente de modifier n’est pas la plusrecente : l’erreur de non serialisabilite estdeclenchee (ORA-08177).
Q. 226 Donner un ordonnancement qui fasse que la version lue par T30 ne soit pas la plus recentedu segment de recouvrement (par exemple c’est la troisieme de la liste des versions).
14.1.7 La transaction serialisable 5025 est en cours
Plus tard, la transaction 5025 commence a lire la table, l’etat de la TTA et de la table ont pu changer,mais pas celui de la CTTA :
Memoire Globale Memoires locales des transactions
TTA = (1455, 4001, 5025, 5555) Transaction serializable 5025 : CTTA = (1455, 2033, 4001)prochain SCN = 5556
1En fait il ne s’agit pas a proprement parler d’une erreur de serialisabilite, mais plutot d’une indication disantqu’Oracle ne peut garantir que cette execution est serialisable.
14.1. MULTI-VERSIONS ET V2PR (ORACLE, POSTGRESQL ET MYSQL/INNODB) 169
table T segment de recouvrementligne scn valeur scn valeur scn valeur scn valeur
1 4 (1, B)2 3999 (2, B) → 17 (2, A)3 5026 (3, C) → 17 (3, B)4 4001 (4, E)5 5555 (5, D) → 2033 (5, C) → 2000 (5, B) → 17 (5, A)6 5555 (6, A)
Q. 227 Que lit la transaction 5025 si elle explore la table ci-dessus ? L’insensibilte de la transac-tion serialisable aux modifications faites par les autres transactions est-elle effective ? (voir la ques-tion Q.225)
Q. 228 Nouvel etat de la ligne 1 si 5025 tente de la modifier avec (1, X) ?
Q. 229 Que se passe-t-il si 5025 tente de modifier le 3ieme nuplet ?
Q. 230 Que se passe-t-il si 5025 tente de modifier le 5ieme nuplet ?
Q. 231 Pour quelle raison peut-on etre sur que 2033 a ete validee (commit) ?
Q. 232 Comment prendre en compte la suppression du 2ieme nuplet par 5025 ? conserver les versions !
Q. 233 Que doit faire le systeme pour valider (commit) 5555 ? conclusion ?
sur cet aspect uniquement (car une validation doit aussi mettre a jour les fichiers journaux) que peut-on en conclure sur l’efficacite de l’instruction commit en Oracle ?
Q. 234 Que doit faire le systeme pour annuler (rollback) 5555 ?
Attention : les segments de rollback qui stockent les anciennes versions sont, comme toute ressource,d’une capacite limitee. Il se peut qu’ils se saturent et alors les anciennes transactions ne disposerontpas des versions dont elles ont besoin (erreur ORA-1555 snapshot too old (rollback segment too small)).Deux solutions : augmenter la taille des segments de rollback ou utiliser un verrouillage explicite poureviter la multiplication des versions.
14.1.8 Autre niveau d’isolation : read committed
En isolation read committed, chaque demarrage d’une instruction SQL commence par rechargerla CTTA avec la TTA actuelle, cette instruction pourra donc voir les modifications validees par uneautre transaction avant son demarrage.
Si une instruction read committed est bloquee par un verrou, lors de son deblocage elle rechargerasa CTTA avec la TTA actuelle. Elle pourra donc voir les modifications faites par la transaction qui labloquait.
Dans une transaction en isolation read committed, chaque instruction DML voit les versions publiees(commit) par d’autres transactions avant qu’elle ne commence.
Lorsqu’une instruction read committed est debloquee d’un verrou elle recommence depuis le debutson traitement en voyant toutes les modifications publiees par d’autres transactions avant son redemarrage.
Q. 235 Proposer un protocole pour l’isolation read committed.
Q. 236 L’etat suivant peut-il etre atteint si 14 etait serializable ? peut-il l’etre si 14 etait readcommitted.
Memoire Globale
TTA = (25, 26), prochain SCN = 27
170 CHAPITRE 14. ELEMENTS D’IMPLANTATION DES TRANSACTIONS
table T segment de recouvrementligne scn valeur scn valeur scn valeur
1 14 (2, B) → 17 (2, A) → 3 (2, X)
Chapitre 15
Les niveaux d’isolation des transactions
15.1 Delimitation des transactions sous SQL/Oracle
Suivant le type d’instruction executee, il y a deux cas de delimitation :
1. l’execution d’une instruction DDL ou DCL constitue une transaction : un commit est fait,puis l’instruction DDL ou DCL est executee, puis :
– si elle s’est bien passee un commit est fait qui valide et clot cette transaction,– sinon rien de plus n’est fait et on reste dans la transaction commencee pour cette instruction
DDL ou DCL.
2. en revanche une transaction peut etre l’execution d’autant d’instructions DML que l’on veut,il faudra la terminer explicitement par une validation (commit) ou une annulation (rollback).
Dans les deux cas, la transaction commence en meme temps que l’execution de la premiere instructionSQL. PostgreSQL ne connaıt que le second cas pour DDL et DCL.
Transaction Oracle Transaction PostgresEn Oracle, toute execution fait partie d’unetransaction. Une transaction commence avec lapremiere instruction DML ou set transaction
qui suit :– la connexion (debut de session)– une instruction DDL reussie– une validation (ordre commit)– une annulation (ordre rollback)Une transaction se termine juste apres– une validation (ordre commit)– une annulation (ordre rollback)– deconnexion normale ⇒ validation– deconnexion anormale ⇒ annulation
En PostgreSQL une transaction commence avecl’instruction start transaction ... (ou begin)et se termine comme en Oracle.Si on n’utilise pas l’instruction start transac-tion ... (ou begin) alors, par defaut, chaqueinstruction DML est executee comme une tran-saction complete, on parle alors de fonctionne-ment en auto commit (un peu comme le modepar defaut de JDBC).
15.1.1 Niveaux d’isolation : set transaction SQL et PL/SQL
Cette instruction, la premiere de la transaction (appelons la T ), regle le niveau d’isolation de T parrapport au reste du monde. Plus precisement, un niveau d’isolation indique dans quelle mesure T verrales modifications validees par d’autres transactions. En revanche le niveau d’isolation ne permet pasde restreindre la visibilite des modifications qui seront faites par T . En quelque sorte l’isolation n’estpas symetrique : elle permet de dire ce qu’on veut qu’une transaction puisse voir du monde exterieurmais elle ne permet pas d’empecher les autres de voir les modifications qu’elle effectue, par exempleune autre transaction en non isolation (read uncommitted de SQL2) verra toutes les modificationsmeme celles qui ne sont pas validees ! Ce niveau de non isolation n’est disponible ni en Oracle ni enPostgreSQL meme si PostgreSQL le reconnaıt syntaxiquement, voir le tableau un peu plus loin.
171
172 CHAPITRE 15. LES NIVEAUX D’ISOLATION DES TRANSACTIONS
set transaction <option> ;
<option> ::= read only | isolation level <niveau-d-isolation>
| read write | use rollback segment <rollback_segment>
<niveau-d-isolation> ::= serializable | read committed
Cette declaration (optionnelle) doit etre la premiere instruction de la transaction. La valeur d’isolationpar defaut est positionnable dans une variable qui est initialisee a read committed.Le mot read committed est d’ordre technique et signifie qu’une instruction peut voir toute modifi-cation validee avant qu’elle ne demarre.PostgreSQL utilise l’instruction start transaction ....
options
serializable
(SQL92, Oracle, PostgreSQL) transaction serialisable (isolation par defaut enSQL92). Une transaction serialisable voit la base telle qu’elle etait validee quandelle a commence, autrement elle ne voit aucunes des modifications validees pard’autres transactions apres son demarrage. Bien entendu elle voit ses propresmodifications.
�� ��2 erreurs possibles : non serialisabilite, interblocageCes deux erreurs son dues a pas de chance et ne devraient donc pas etre in-terpretees comme des bogues, mais plutot comme des circonstances empechantl’aboutissement de la transaction. Si la transaction serialisable T tente de modifierun nuplet modifie par une autre transaction validee apres le debut de T , l’ins-truction DML correspondante echoue : ORA-08177 : Can’t serialize access
for this transaction. Attention : cette erreur a lieu aussi si la transaction va-lidee a simplement effectue un select for update, meme si elle n’a pas modifieles lignes ainsi verrouillees.
readcommitted
(SQL92) (isolation par defaut en Oracle et en PostgreSQL) : chaque instruc-tion DML de la transaction read committed voit ce qui est valide au momentou l’instruction a commence a s’executer ou bien quand ele est debloquee.�� ��1 erreur possible : interblocage Si cette instruction DML tente de ver-rouiller un nuplet deja verrouille par une autre transaction, elle est bloquee jus-qu’au deverrouillage ; quand elle se debloque elle reevalue completement laselection des nuplets. Cette erreur est due a pas de chance et ne devrait donc pasetre interpretee comme un bogue, mais plutot comme une circonstance empechantl’aboutissement de la transaction.
readuncommitted
(SQL92) (absent de Oracle) : a ce niveau, la transaction voit toutes les modifi-cations, meme celles non validees, elle n’est donc aucunement isolee. Present enPostgreSQL8 mais la documentation dit clairement que c’est implante par duread committed : When you select the level Read Uncommitted you really getRead Committed !
read only
(Oracle) transaction-level read consistency. La transaction ne voit que les chan-gements commis avant son debut (commandes autorisees : select, manipulationde curseurs, lock table, set role, alter session, alter system, commit etrollback)L’interet de read only par rapport a serializable est certainement de faire deseconomies sur les ressources allouees a la transaction puisqu’on sait qu’elle nepourra pas modifier la base de donnees.
Q. 237 Oracle permet-il a une transaction d’observer des modifications non validees ?
15.2. POSITIONNER L’ISOLATION PAR DEFAUT : 173
En Oracle ou Postgres, tant qu’elle est vivante, une transaction est la seule a voir les modificationsqu’elle a effectuees.
Oracle ne dispose que du verrouillage exclusif (X) des nuplets, il ne dispose pas de verrouillage partage(S)1. C’est grace a la gestion des multiples versions des nuplets qu’Oracle peut se passer de ces verrousS tout en garantissant que les lectures ne sont jamais bloquees ni bloquantes par/pour les ecrituresfaites par d’autres transactions.
15.2 Positionner l’isolation par defaut :
Ce niveau d’isolation par defaut peut etre redefini pour la session en cours avec la commande altersession.
alter session set isolation_level = {serializable | read committed}
15.3 Les commandes intra-transaction SQL et PL/SQL
savepoint <nom-de-point-de-sauvegarde> ] ;
commit [ work ] ;
rollback [ work ] [ to [ savepoint ] <nom-de-point-de-sauvegarde> ] ;��
�
Attention, contrairement a l’instruction rollback, l’instruction rollback to savepointne termine pas la transaction en cours.
15.3.1 savepoint
Pose un point de reprise intermediaire dans la transaction courante, ce qui permettra de faire unrollback partiel de la transaction mais sans terminer cette transaction, on peut ensuite retenter letraitement annule sans devoir creer une nouvelle transaction.
15.3.2 commit
Termine la transaction et tous les changements effectues par la transaction deviennent permanents.Les eventuels points de reprise intermediaires poses depuis le debut de la transaction sont oublies ettous les verrous poses par la transaction sont relaches.
15.3.3 rollback
Forme rollback work, qui utilise le segment de rollback, annule le travail fait depuis le debut de latransaction, relache tous les verrous et oublie tous les points de reprise. La transaction est terminee.
15.3.4 Exemple de rollback work to savepoint lors d’une erreur de notation
Forme rollback work to savepoint xxx ; annule le travail fait depuis le point de reprise mentionne(qui appartient bien sur a la transaction courante). Tous les points de reprise poses apres le point dereprise mentionne sont oublies. Le point de reprise mentionne est conserve, les verrous obtenus depuisle point de sauvegarde sont relaches mais :
– les transactions deja bloquees sur ces verrous restent bloquees jusqu’a la fin de cette transaction– les autres transactions qui n’avaient pas encore demande ces verrous peuvent les obtenir.
La transaction n’est evidemment pas terminee, voici un exemple :
1D’autres SGBD permettent le verrouillage des nuplets en mode Share, Postgres par exemple.
174 CHAPITRE 15. LES NIVEAUX D’ISOLATION DES TRANSACTIONS
update Etudiant set note = 14 where nom = ’Durand’ ;
savepoint Durand_note ;
update Etudiant set note = 18 where nom = ’Dupont’ ;
savepoint Dupont_note ;
-- oups! ce n’est pas Dupont mais Dupire qui a 18 :
rollback to savepoint Durand_note ;
update Etudiant set note = 18 where nom = ’Dupire’ ;
commit ;
Un rollback to savepoint ne termine pas la transaction.
15.4 Echec d’une transaction
Une transaction doit echouer (rollback) si une erreur du SGBD s’est produite pendant son execution.Ce qu’il faut faire en reponse a cet echec varie en fonction de la nature de l’erreur :
semantique l’erreur est due au fait que la transaction a tente de casser la coherence de la base dedonnees, cette transaction est donc inappropriee et on ne devrait pas tenter de relancer la mememodification,
pas de chance l’erreur est un interblocage ou le fait qu’Oracle ne peut garantir la serialisabilite,cette erreur ne remet pas en cause la pertinence de la modification qui a echoue : il seraitpeut-etre interessant d’attendre un peu (DBMS_LOCK.Sleep) puis de relancer automatiquementla modification dans une nouvelle transaction2.
15.5 Le virement de compte a compte en PL/SQL
Cet exemple ne reprend pas tous les points du squelette de la section 16.5 page 181 :
create table Compte (
id Number (5) primary key,
solde Number (5) constraint Solde_Positif check (solde >= 0)
) ;
La procedure Virer doit laisser inchangee la somme des soldes quitte a ne rien faire si ce n’est paspossible :
create procedure Virer(D in Compte.id%type,C in Compte.id%type,S in PositiveN) is
Interblocage exception ;
pragma Exception_Init (Interblocage, -00060) ; -- deadlock (interblocage)
Solde_Negatif exception ;
pragma Exception_Init (Solde_Negatif, -02290) ; -- Solde_Positif viole (check)
begin
set transaction isolation level read committed ;
update Compte set solde = solde - S where id = D ;
if SQL%rowcount = 0 then
rollback ; raise_application_error (-20111, ’Compte a debiter inexistant’) ;
end if ;
update Compte set solde = solde + S where id = C ;
if SQL%rowcount = 0 then
rollback ; raise_application_error (-20111, ’Compte a crediter inexistant’) ;
end if ;
commit ;
exception
2Doc Oracle : To minimize the performance overhead of rolling back transactions and executing them again, try toput DML statements that might conflict with other concurrent transactions near the beginning of your transaction.
15.6. TRANSACTIONS AUTONOMES : PRAGMA AUTONOMOUS TRANSACTION 175
when Interblocage then
rollback ; raise_application_error (-20111, ’Interblocage’) ;
when Solde_Negatif then
rollback ; raise ;
end Virer ;
Q. 238 Montrer que deux executions simultanees de Virer peuvent s’interbloquer.
Q. 239 Reecrire la procedure Virer pour qu’elle reprenne le traitement en cas d’interblocage.
Ici l’isolation read committed est adaptee :
Q. 240 Montrer que l’isolation serializable pourrait provoquer inutilement des erreurs de serialisabilite.
Regle : les transactions qui modifient la base ne doivent pas etre trop longues, par exemple, au lieude faire une seule transaction qui effectue N virements, il vaut probablement mieux en faire N quieffectuent chacune un virement.
Q. 241 Trouver la betise dans le code suivant, puis la corriger.
create procedure Betise (D in Compte.id%type) is
Interblocage exception ;
pragma Exception_Init (Interblocage, -00060) ; -- deadlock
Serialisabilite_Non_Garantie exception ;
pragma Exception_Init (Serialisabilite_Non_Garantie, -08177) ;
begin
set transaction isolation level serializable ;
loop
begin
update Compte set solde = solde * 1.1 where id = D ;
commit ;
exit ;
exception
when Interblocage or Serialisabilite_Non_Garantie then
-- attendre un peu que les choses se calment :
DBMS_Lock.Sleep (3.14) ; -- en secondes
when others then
rollback ; raise ;
end ;
end loop ;
end Betise ;
15.6 Transactions autonomes : pragma AUTONOMOUS TRANSACTION
On a parfois envie qu’une transaction dite mere puisse provoquer l’execution d’une autre transactiondite fille. Certains SGBD permettent de faire cela mais avec des semantiques tres differentes.
En Oracle cette semantique est tres simple : les transactions mere et fille sont completement independantes,c’est pourquoi on parle de transaction autonome pour une transaction fille.
La seule relation entre mere et fille est que la fille est executee completement avant que sa mere nereprenne son execution : en fait la fille peut-etre consideree comme l’execution d’un sous-programmedevant se terminer par un commit ou un rollback.
– une fille ne voit pas les modifications faites par sa mere, pourquoi ?– la fille ne beneficie d’aucun des verrous poses par sa mere.– le succes ou non de la fille n’a aucun effet sur celui de sa mere et inversement.– les modifications d’une transaction autonome sont publiees des son commit sans attendre la fin de
sa mere,
176 CHAPITRE 15. LES NIVEAUX D’ISOLATION DES TRANSACTIONS
– une transaction autonome peut lancer d’autres transactions autonomes
Pour disposer de transactions autonomes, il suffit d’utiliser le pragma AUTONOMOUS_TRANSACTION dansla partie declarative de la procedure ou du bloc anonyme realisant cette transaction.
Par exemple, un trigger d’audit doit inscrire des informations dans une table de facon persistante,meme si l’instruction DML qui a declenche le trigger echoue :
create table Memoire (auteur Varchar2 (20), message Varchar2 (50)) ;
create table Salaire (
id Number (5) primary key,
sal Number (7, 2) constraint Sal_Pos check (sal >= 0)) ;
create procedure Auditer (U in Memoire.auteur%type, M in Memoire.message%type) is
begin
insert into Memoire values (U, M) ;
end Auditer ;
create trigger Auditeur
before update on Salaire
for each row
declare
pragma AUTONOMOUS_TRANSACTION ;
begin
Auditer (user, ’modif salaire ’ ||
’old =(’ || to_char(:old.id) || ’,’ || to_char(:old.sal) || ’),’ ||
’new =(’ || to_char(:new.id) || ’,’ || to_char(:new.sal) || ’)’) ;
commit ; -- interdit dans un trigger non autonome
end ;
insert into Salaire values (1, 7000) ;
insert into Salaire values (2, 5000) ;
update Salaire set id = id + 1, sal = sal + 50 ;
select * from Memoire ;
AUTEUR MESSAGE
----------------------------
DURIF modif salaire old = (1, 7000), new = (2, 7050)
DURIF modif salaire old = (2, 5000), new = (3, 5050)
select * from Salaire ;
ID SAL
----------------------------
2 7050
3 5050
update Salaire set id = id + 1, sal = sal - 6000 ;
ORA-02290: violation de contraintes (DURIF.SAL_POS) de verification
select * from Memoire ;
AUTEUR MESSAGE
----------------------------
DURIF modif salaire old = (1, 7000), new = (2, 7050)
DURIF modif salaire old = (2, 5000), new = (3, 5050)
15.6. TRANSACTIONS AUTONOMES : PRAGMA AUTONOMOUS TRANSACTION 177
DURIF modif salaire old = (2, 7050), new = (3, 1050)
DURIF modif salaire old = (3, 5050), new = (4, -950)
select * from Salaire ;
ID SAL
----------------------------
2 7050
3 5050
Malgre l’erreur pendant le second update, toutes les inscriptions faites par le trigger sont la !
Le pragma peut aussi etre mis dans la procedure, mais cela la specialise et n’est probablement pasune bonne idee.
Q. 242 Que se passe-t-il si une transaction autonome se bloque sur une des lignes verrouillees par satransaction mere ?
Chapitre 16
Synchronisation des transactions
La synchronisation est une technique permettant a une transaction de bloquer d’autres transactionstant qu’elle n’a pas termine son travail par un commit ou annule sont travail par un rollback.
Les niveaux d’isolation ainsi que le verrouillage automatique des lignes modifiees par une transactionne suffisent pas toujours a garantir la coherence de la base de donnees.
Il est parfois necessaire de synchroniser explicitement les acces des transactions aux donnees. Pourcela la technique classique est celle d’un verrouillage explicite qui en Oracle ainsi qu’en PostgreSQLpeut se faire a deux niveaux :
– verrouillage explicite de lignes d’une table avec la commande select ... for update– verrouillage explicite de table avec la commande lock table ...
16.1 Un exemple de non synchronisation de transaction
Voici un exemple de probleme qui ne sera resolu qu’en utilisant une synchronisation explicite. Ondispose des tables :
create table Equipe (
id Number (5) primary key,
budget_salarial Number (10)
) ;
create table Membre (
id Number (5) primary key,
salaire Number (10),
equipe references Equipe (id) not null
) ;
La propriete P de la base de donnee est :
�
�la somme des salaires des membres d’une equipe doit etre inferieure
ou egale au budget salarial de cette equipe.
La procedure Augmenter augmente le salaire d’un membre en tentant de conserver P :
create procedure Augmenter (M in Membre.id%type, A in Membre.salaire%type) is
l_equipe Equipe.id%type ;
le_budget_salarial Equipe.budget_salarial%type ;
somme_salaires Equipe.budget_salarial%type ;
begin
set transaction isolation level read committed ;
select equipe into l_equipe from Membre where id = Augmenter.M ;
select e.budget_salarial, nvl (Sum (m.salaire), 0)
into le_budget_salarial, somme_salaires
from Equipe e inner join Membre m on m.equipe = e.id
where m.equipe = Augmenter.l_equipe
group by e.id, e.budget_salarial ;
if somme_salaires + A > le_budget_salarial then
rollback ; raise_application_error (-20111, ’Budget depasse’) ;
end if ;
update Membre set salaire = salaire + Augmenter.A where id = Augmenter.M ;
commit ;
178
16.2. COMMENT ASSURER UNE SYNCHRONISATION 179
exception
when No_Data_Found then
rollback ; raise_application_error(-20111,’Membre inexistant’);
when others then
rollback ; raise ;
end Augmenter ;
On peut alors montrer que la propriete P peut etrecassee lors de l’execution de la procedure Augmenter
par deux transactions concurrentes. Etat initial destables :
Equipe Membre
budget_salarial id
2000 51
equipe id salaire
51 1 90051 2 1000
Augmenter (1, 100) Augmenter (2, 50)
set trans ... read committed
set trans ... read committed
select equipe into l_equipe ... → 51select e.budget_salarial ... → 2000, 1900update Membre ...
select equipe into l_equipe ... → 51select e.budget_salarial ... → 2000, 1900
commit ;
update Membre ...
commit ;
nouvel etat des tables, P est cassee !
Equipe Membre
budget_salarial id
2000 51
equipe id salaire
51 1 100051 2 1050
Q. 243 Cela se passerait-il mieux si le niveau d’isolation etait serializable ?
On peut imaginer d’autres ordonnancements qui casseraient P .
Q. 244 Donner le nombre d’ordonnancements pouvant casser P .
Une solution consiste a bloquer une des deux transactions jusqu’au commit de l’autre de facon a cequ’elle soit obligee de voir la modification faite par l’autre transaction. Ici l’isolation read committedest la seule appropriee car elle permettra a la transaction bloquee de voir, lorsqu’elle sera debloquee,les modifications validees par l’autre.
Apres une presentation des outils de verrouillage les sections 16.6 et 16.7.1 proposent de les utiliserpour resoudre le probleme de synchronisation de la procedure Acquerir. Ces outils devraient se trouverdans la plupart des SGBD, en tous les cas dans Oracle et PostgreSQL.
16.2 Comment assurer une synchronisation
L’outil de base est la possibilite de poser des verrous : lorsqu’un verrou est pose sur une donnee, lesautres transactions tentant de modifier cette donnee seront bloquees jusqu’a ce que la donnee soitdeverrouillee pas la transaction qui l’avait verrouillee. Ce verouillage correspond bien a la possibilitede verrouiller les toilettes qu’on utilise afin d’etre sur que personne d’autre ne peut entrer.
Lors du deverrouillage de la donnee, une seule transaction sera debloquee et pourra modifier cettedonnee car elle prendra soin de poser elle aussi un verrou sur cette donnnee.
16.3 Verrouillage fin avec select ... for update
Cette instruction permet de verrouiller en mode exclusif toutes les lignes selectionnees par la requete.Cela peut etre tres pratique pour resoudre des problemes de synchronisation ou mettre en place des ap-
180 CHAPITRE 16. SYNCHRONISATION DES TRANSACTIONS
plications interactives.Attention : la clause forupdate ne peut pasetre utilisee :
– dans une sous-requete,– dans un curseur PL/SQL,– si la clause select contient distinct ou une fonction d’agregation (coun– si la clause group by est presente,
Si la requete comporte une jointure, on peut completer for update avec of suivi de noms de colonnespermettant de savoir les lignes de quelle(s) table(s) il faut verrouiller, par exemple :
select ...
from Client c inner join Commande m on m.client = c.id
where c.id between 20 and 56
FOR UPDATE ; -- verrouille les lignes selectionnees de Client et Commande
verrouillera des lignes de Client et celles de Commande leur correspondant. En revanche :
select ...
from Client c inner join Commande m on m.client = c.id
where c.id between 20 and 56
FOR UPDATE OF c.id ; -- ne verrouille que les lignes selectionnees de Client
ne verrouillera que des lignes de Client ayant au moins une commande.
16.4 Comportement des instructions DML
Une requete select ne pose pas de verrou, n’est jamais bloquee ni bloquante (grace a Multi-versions).Les seules instructions posant un verrou et pouvant etre bloquees par un verrou sont insert, update,delete et select ... for update.
Chaque instruction DML est atomique (en tout ou rien) mais ce n’est pas une transaction (pas decommit).
16.4.1 Deblocage
Un deblocage correspond forcement au fait que la transaction bloquante vient d’etre validee ou annulee(protocole V2PR).
Lors d’un deblocage en isolation read committed, les instructions insert, update, delete et select... for update reevaluent completement la selection des nuplets.
En revanche, en isolation serializable, une erreur de serialisabilite est declenchee (ORA-08177), saufsi la transaction bloquante est annulee. Attention : cette erreur a lieu aussi si la transaction bloquantea simplement effectue un select for update, meme si elle n’a pas modifiee les lignes ainsi verrouillees.
Exemple d’erreur -08177 (seriabilite non garantie) causee par un select for update :
Tbloquante Tbloqueeset transaction isolation
level read committed
set transaction isolation
level serializable
select * from Client
where id = 4
for update
update Client
set solde = solde - 12
where id = 4
bloqueecommit
ORA-08177 :Impossible de serialiser
16.5. SQUELETTE DE PROCEDURE PL/SQL REALISANT UNE TRANSACTION 181
Q. 245 Pourquoi Oracle declenche-t-il cette erreur de serialisabilite ?
16.5 Squelette de procedure PL/SQL realisant une transaction
Supposons qu’une procedure P realise completement le traitement d’une transaction, dans ce cas voiciune possibilite de squelette de cette procedure :
procedure P (...) is
begin
set transaction isolation level ... ;
--------------------------------------------------------
-- Verrouillages eventuels permettant de garantir une bonne
-- synchronisation des differentes transactions :
--------------------------------------------------------
select id into x from T where ... for update ; -- No_Data_Found eventuel
lock table ... -- un peu brutal a priori
--------------------------------------------------------
-- Les traitements peuvent maintenant etre effectues.
-- 1) Detection du non maintien de certaines proprietes :
--------------------------------------------------------
if propriete non maintenue then
rollback ; raise_application_error (-20111, ’propriete non maintenue’) ;
end if ;
--------------------------------------------------------
-- 2) Si les proprietes sont garanties :
--------------------------------------------------------
modification(s) de la base
update, delete -- SQL%rowcount pour connaıtre le nombre de lignes manipulees
--------------------------------------------------------
-- Validation des modifications et fin de la transaction
--------------------------------------------------------
commit ;
exception
when No_Data_Found then rollback ; raise_application_error (-20111, ’...’) ;
when others then rollback ; raise ;
end P ;
16.6 Augmenter : solution fine avec select ... for update
Cette solution est fine car elle ne verrouillera qu’une ligne de la table Voiture, ainsi elle ne bloquerapas d’autres transactions s’interessant a une autre voiture.
create procedure Augmenter (M in Membre.id%type, A in Membre.salaire%type) is
l_equipe Equipe.id%type ;
le_budget_salarial Equipe.budget_salarial%type ;
somme_salaires Equipe.budget_salarial%type ;
begin
set transaction isolation level read committed ;
-- Verrouiller l’equipe concernee par l’augmentation. No_Data_Found si M n’existe pas
select m.equipe into l_equipe
from Membre m inner join Equipe e on e.id = m.equipe
where m.id = Augmenter.M FOR UPDATE OF e.id ;
select e.budget_salarial, nvl (Sum (m.salaire), 0)
into le_budget_salarial, somme_salaires
from Equipe e inner join Membre m on m.equipe = e.id
182 CHAPITRE 16. SYNCHRONISATION DES TRANSACTIONS
where m.equipe = Augmenter.l_equipe
group by e.id, e.budget_salarial ;
if somme_salaires + A > le_budget_salarial then
rollback ; raise_application_error (-20111, ’Budget depasse’) ;
end if ;
update Membre set salaire = salaire + Augmenter.A where id = Augmenter.M ;
commit ;
exception
when No_Data_Found then rollback; raise_application_error(-20111,’Membre inexistant’);
when others then rollback ; raise ;
end Augmenter ;
En reprenant un ordonnancement similaire a celui de la section 16.1 voyons ce qui va se passer :
Augmenter (1, 100) Augmenter (2, 50)
set trans ... read committed
set trans ... read committed
select equipe into l_equipe ... → 51l’equipe 51 est verrouilleeselect e.budget_salarial ... → 2000, 1900update Membre ...
select equipe into l_equipe ...
transaction bloqueecommit ;
l’equipe 51 est deverrouilleedeblocage et reevaluation de la requete precedanteselect e.budget_salarial ... → 2000, 2000rollback ;
l’equipe 51 est deverrouilleeraise_application_error
Un select ... for update doit etre simple : pas de group by de fonction d’agregation, . . .(section 16.3)
Q. 246 Pour le meme debut d’ordonnancement, que se passerait-il si le niveau d’isolation etaitserializable ?
16.7 Verrouillage de table en Oracle
Rappel : Oracle verrouille en mode eXclusive chaque nuplet modifie par une transaction (voir la sec-tion 14.1.1 p.165) ou selectionne par une requete munie de la clause for update. La gestion de cesverrouillages respecte le protocole de verrouillage 2 phases rigoureux.
Les verrous presentes ci-dessous portent sur les tables.
Il y a deux sortes de verrouillage de table : d’une part le verrouillage effectif dont les modes sontshare et exclusive, d’autre part le verrouillage intentionnel dont les modes sont row share, rowexclusive ; share row exclusive est a la fois effectif et intentionnel.
L’interet du verrouillage intentionnel est le suivant : supposons qu’une transaction T1 non termineeait modifie des nuplets d’une table, chacun de ces nuplets est donc verrouille en mode exclusif parcette transaction. Une autre transaction T2 souhaite verrouiller en mode exclusif cette meme table afinde s’assurer d’etre la seule a modifier cette table. T2 doit evidemment etre bloquee a cause des ver-rouillages de nupplet effectues par T1. Le probleme est que pour se rendre compte que T1 a verrouilledes nuplets il faut explorer les nuplets de la table ce qui risque d’etre tres couteux. C’est ce problemede cout que resout le verrouillage intentionnel de table : avant meme de commencer a modifier les
16.7. VERROUILLAGE DE TABLE EN ORACLE 183
nuplets de la table et donc de verrouiller ces nuplets, T1 va automatiquement verrouiller la table enmode row exclusive. Maintenant quand T2 tente de verrouiller la table en mode exclusive, elle serend compte tres rapidement que la table est deja verrouillee en row exclusive et elle se bloqueimmediatement sans avoir a explorer les nuplets de la table.
��
��
Le verrouillage intentionnel permet simplement d’ameliorer les performances d’executiondes transactions, fonctionnellement il n’apporte rien et, en theorie, on pourrait parfaite-ment s’en passer.
Certains verrouillages comme share peuvent etre poses simultanement pas plusieurs transactions.D’autres comme exclusive ne peuvent etre poses que par une transaction a la fois.
Certains verrouillages comme row share et row exclusive ne bloquent aucune operation de mise ajour, ils bloquent seulement la pose d’autres verrous de table.
Les operations de mise a jour posent automatiquement, en plus des verrous nuplet, un verrou rowexclusive sur la table a modifier (meme pour un update qui ne modifiera rien !).
Tous les verrouillages (table et nuplets) poses par une transaction sont relaches lors du prochain com-mit ou rollback : Oracle respecte le verrouillage deux phases rigoureux.
lock table <nom-de-table> in row share mode [ nowait ] ;
-- erreur Oracle du nowait : -54
-- erreur PostgreSQL du nowait : the transaction is aborted (?).
Un verrouillage de table n’empeche jamais une autre transaction d’effectuer des requetes sur cettetable et une requete ne verrouille jamais de table.
Voici les modes de verrouillage dans l’ordre croissant d’exigence :�� ��row share (RS) : bloque uniquement la pose de verrou exclusive pour les autres transactions,
il est pose automatiquement par un select ... for update.�� ��row exclusive (RX) : ce verrouillage est pose automatiquement par un ordre DML de modi-
fication de la table (update, delete, insert) meme si aucun nuplet de la table n’est modifie. Ilpermet de bloquer la pose de verrou exclusive, share row exclusive et share qui ne peuventpas etre poses tant qu’une transaction modificatrice n’est pas validee.�� ��share (S) la table est en lecture seule : la transaction qui a pose ce verrou ne peut pas tenter
de modifier la table ou de faire un select...for update. Plusieurs transactions peuvent biensur positionner ce verrou. Les transactions tentant de poser des verrous exclusive, SRX ou RX(insert, update et delete) sur la table sont bloquees.�� ��share row exclusive (SRX) la somme de share et row exclusive. La transaction diposant
de ce verrou et la seule a pouvoir modifier la table. D’autres transactions peuvent lire la tableet ne sont pas bloquees. D’autres transactions peuvent aussi executer un select for update.�� ��exclusive (X) la transaction possedant ce verrou peut tout faire sur la table, les autres tran-
sactions ne peuvent que la lire (mises a jour et verrouillages bloques).
Table d’incompatibilite (un - indique que les deux verrous sont exclusifs, un + indique que les deuxverrous peuvent etre poses en meme temps). Remarquez que cette matrice est symetrique.
Transactiondisposantdu verrou
Transaction demandeuseX SRX S RX RS mot clef verrou intentionnel
X - - - - - exclusive nonSRX - - - - + share row exclusive
S - - + - + share nonRX - - - + + row exclusive oui update, delete, insertRS - + + + + row share oui select ... for update
184 CHAPITRE 16. SYNCHRONISATION DES TRANSACTIONS
select ne verrouille rien et n’est jamais bloquee (cela grace au multi-versions).select ... for update verrouille la table en row share (RS), puis verrouille en X tous les nupletsselectionnes par le select. La table peut encore etre verrouillee en mode share car le select ... forupdate n’a modifie aucun nuplet.update, insert et delete verrouillent d’abord la table en row exclusive (RX), puis verrouillent enexclusive (X) tous les nuplets touches par la modification.
Inversement : si une table est verrouillee en mode share alors un update, un insert ou un deleteseront bloques jusqu’au deverrouillage.
L’instruction lock table est utilisable en SQL et PL/SQL.
16.7.1 Augmenter : solution brutale avec lock table ...
Cette solution est brutale car elle verrouille carrement la table Equipe en mode exclusive qui est leseul a convenir. Sa brutalite est due au fait que si d’autres transactions s’interessent a d’autres equipes,elles seront quand meme bloquees ! Une telle technique risque donc de faire baisser les performancestransactionnelles du systeme par rapport a la solution plus fine donnee en 16.6.
create procedure Augmenter (M in Membre.id%type, A in Membre.salaire%type) is
l_equipe Equipe.id%type ;
le_budget_salarial Equipe.budget_salarial%type ;
somme_salaires Equipe.budget_salarial%type ;
begin
set transaction isolation level read committed ;
-- Blocage eventuel puis verrouillage de la table Equipe
LOCK TABLE Equipe IN EXCLUSIVE MODE ;
select equipe into l_equipe from Membre where id = Augmenter.M ;
select e.budget_salarial, nvl (Sum (m.salaire), 0)
into le_budget_salarial, somme_salaires
from Equipe e inner join Membre m on m.equipe = e.id
where m.equipe = Augmenter.l_equipe
group by e.id, e.budget_salarial ;
if somme_salaires + A > le_budget_salarial then
rollback ; raise_application_error (-20111, ’Budget depasse’) ;
end if ;
update Membre set salaire = salaire + Augmenter.A where id = Augmenter.M ;
commit ;
exception
when No_Data_Found then rollback; raise_application_error(-20111,’Membre inexistant’);
when others then rollback; raise ;
end Augmenter ;
Q. 247 Pourquoi le mode share de verrouillage de la table ne conviendrait-il pas ?
Q. 248 Quel autre mode de verrouillage de la table pourrait convenir ?
16.8 PL/SQL
Un bloc anonyme est execute en tout ou rien, par exemple a l’issue du code suivant, la table TT estvide :
create table TT (id Number (5) primary key) ;
begin
insert into TT values (1) ;
insert into TT values (2) ;
16.8. PL/SQL 185
insert into TT values (1) ;
end ;
mais la transaction commencee par un bloc anonyme qui echoue n’est pas terminee !Utiliser for update (pour les programmes interactifs, par exemple)
declare
Employe_Bloque exception ;
pragma Exception_Init (Employe_Bloque, -54) ;
begin
select salaire into le_salaire
from Employe
where id = l_id and emploi = ’vendeur’ and 1000 > salaire
for update nowait;
exception
when Employe_Bloque then
-- faire autre chose ?
when No_Data_Found then
rollback ;
raise_application_error (-20111, ’Cet employe gagne >= 1000.’) ;
end ;
Lors du open, determine les nuplets selectionnes et les verrouille. nowait est optionnel :– si absent : transaction bloquee jusqu’a ce que tous les nuplets puissent etre verrouilles– si present : si des nuplets sont deja verrouilles par ailleurs, le controle est rendu au programme (via
l’erreur Oracle -54) qui peut faire autre chose avant de recommencer.Les nuplets seront deverrouilles lors du prochain commit ou rollback . Un curseur for update nepeut donc plus etre utilise apres un commit.Si le curseur utilise une jointure, il faut utiliser la forme for update of <colonne> pour ne verrouillerque les nuplets de la (des ?) table(s) possedant la (les ?) colonne(s)
Sixieme partie
Developpement client/serveur
186
Chapitre 17
Developper une application BD
Si on y reflechit, la plupart des logiciels necessitent la memorisation persistante d’informations. Unsimple editeur de texte procure cette persistance en utilisant directement le systeme de fichiers.
Mais pour peu que le volume de donnees soit important, et surtout que les donnees entretiennent entreelles des relations complexes, on a alors tout interet a utiliser un SGBD pour faire persister ces donnees.
De plus, on en tire plusieurs avantages lies aux fonctionnalites classiques des SGBD :– facilite de description des contraintes sur les donnees– facilite d’interrogation et de manipulation complexe des donnees (DML),– facilite de partage coherent des donnees entre plusieurs activites concurrentes (transaction)– facilite de restauration des donnees lors de pannes logicielles ou materielle– facilite de gestion des droits d’acces aux donnees (DCL)– . . .Pour conclure : pour la plupart des logiciels, il ne serait pas tres pertinent de se refuser a utiliser unSGBD.
17.1 Client serveur
Il s’agit maintenant de mettre en place des applications permettant un acces aise a une base dedonnees, soit dans un contexte multi-machines (client/serveur), soit dans un contexte mono-machine(l’application reside sur le serveur lui-meme) :
O interaction _____________ ordres/donnees SQL _____________
/|\ <-----------> | Application |<-------------------->| Application |
| | | | SGBD |
/ \ | CLIENTE | RESEAU | SERVEUR |
Les fonctionnalites principales de ces applications :– Interrogation de la base (les trains au depart de telle heure a telle heure)– Mise a jour de la base (reservation de billet, annulation de reservation, ajout de voitures a un train,
. . .)– Edition de rapports (taux d’occupation des trains en fonction de l’heure de depart, . . .)Pour cela les editeurs de SGBD ou des editeurs tiers proposent trois types d’outils, en allant du plusrudimentaire au plus sophistique :
API les interfaces de programmation (API Application Programming Interface, ou CLI Call LevelInterface). On peut distinguer deux sortes d’API :
concrete : specifique a un SGBD particulier : applications efficaces mais difficilement portablessur un autre SGBD.
abstraite : independante de tout SGBD particulier : applications moins efficaces mais plusportables. Une API abstraite, par exemple ODBC ou JDBC, necessite un driver specifiqueau SGBD utilise.
187
188 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
le SQL embarque dans des langages classiques comme Cobol, C, Ada, Java, . . ., l’outil majeur estalors un precompilateur.
AGL des environnements de developpement d’applications graphiques, d’edition de rapports
Les problemes sont :
1. de faire coexister l’aspect declaratif de SQL et procedural des langages d’accueil (PL/SQL endonne une bonne idee),
2. l’adequation entre les types de donnee SQL et ceux du langage d’accueil.
3. la repartition du code entre le poste client et le SGBD.
4. garantir l’independance du code client par rapport au SGBD effectivement utilise (passer pardes standard). Par exemple un meme code client utilisant JDBC ou ODBC ne depend quasimentpas du SGBD effectivement utilise que ce soit Oracle, PostgreSQL, MySQL, . . .
17.2 Les API concretes
Une API concrete est une bibliotheque permettant d’acceder a un SGBD bien particulier (OCI pourOracle, libpq pour Postgres, voire PHP, . . .).
Sources langage hote + appels à la bibliothèque
Fichiers objetsBibliothèque
Exécutable
Editeur de liens
Compilateur standardnonconnecté
connecter déconnecter
connecté
ordresSQL
Fig. 17.1 – API concrete : le programme effectue explicitement des appels aux primitives d’accesau SGBD proposees par une bibliotheque (API). La nouveaute par rapport aux procedures stockeesest la necessite de se connecter au SGBD pour pouvoir l’utiliser, puis de s’en deconnecter.�
�Une application utilisant une API concrete est prevue pour un SGBD particulier, il sera tres penible
de la modifier pour la porter sur un autre SGBD.
17.2.1 Principe
Le developpeur utilise le langage de son choix et utilise une bibliotheque d’acces au SGBD fournie parl’editeur ou un tiers.
Bibliotheque :
ouvrir, fermer une connexion au SGBD
demander l’execution d’un ordre SQL (statement)
recuperer les resultats d’une requete (resultset)
gerer les transactions (commit, rollback)
17.2.2 Avantages
– l’application a un controle tres fin de la manipulation de la base– l’application peut construire dynamiquement les instructions SQL
17.3. LES API ABSTRAITES 189
17.2.3 La mise en œuvre
Compilateur du langage hote et bibliotheque d’acces au SGBD.
17.2.4 OCI : l’API concrete de Oracle
(Oracle Call Interface) programmee en C, la bibliotheque OCILIB s’utilise par compilation puis editiondes liens. Les avantages : controle fin du fonctionnement, supporte le SQL dynamique, possibilited’execution asynchrone d’ordre SQL (l’application n’a pas a attendre la fin de l’ordre SQL pourcontinuer a travailler)
On peut ecrire :
select e.nom from Employees e where e.id = :idDemande
ou :idDemande est une variable de liaison dont la valeur est fournie par le programme applicatif. Onpeut aussi ecrire du code PL/SQL.
17.2.5 libpq : l’API concrete de Postgres
17.3 Les API abstraites
Ces API se veulent independantes de tout SGBD. Les deux qui sont presentees correspondent princi-palement au modele relationnel.
Elles reposent sur l’utilisation cachee de pilotes (driver) qui sont specifiques aux SGBD.�
�Une application utilisant une API abstraite doit theoriquement pouvoir fonctionner avec n’importe
quel SGBD, pour peu qu’on dispose du pilote approprie.
Ainsi il est relativement facile, sans rien modifier (ou presque) a une application Access prevue initia-lement pour fonctionner avec la base Access de la faire fonctionner avec une base Postgres : il suffitd’installer le pilote ODBC de Postgres et de remplacer toutes les liaisons aux tables Access par desliaisons reseau aux tables Postgres.
Pilote Oracle
Pilote Postgres
Pilote MySQL
Pilote DB2Application
Gestionnairede
Pilotes
StandardInterface
Connexion
donne1
2
Fig. 17.2 – Principe general des API abstraites. Ici, l’application a demande au gestionnaire depilotes une connexion utilisant le pilote Postgres. Le gestionnaire de pilotes possede deux visages :cote application, il offre une interface standard quel que soit le SGBD utilise ; cote SGBD, il gere lesdifferents pilotes permettant l’acces a autant de SGBD differents. Le seul moment ou l’applicationa conscience du SGBD particulier qu’elle souhaite utiliser est celui de la connexion : elle doit, parexemple en JDBC, fournir une url permettant d’identifier entre-autres le driver a utiliser pour cetteconnexion. Et encore, cette url pourrait n’etre connue qu’a l’execution car fournie par l’utilisateur.
17.3.1 ODBC : l’API abstraite de Microsoft
appli(s) <--> gestionnaire ODBC <---> pilotes specif des SGBD
190 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
17.3.2 JDBC : l’API abstraite de JavaSoft
JDBC est base sur le X/Open SQL Call Level Interface (SQL92 Entry Level).
JDBC permet principalement le SQL dynamique (Statement) souple mais couteux, il autorise aussila preparation d’instructions parametrables (PreparedStatement et CallableStatement) interessantdans le cas ou elles sont executees de nombreuses fois car elles peuvent etre precompilees et executeesplus efficacement (ceci en fonction des capacites du pilote utilise).
On verra que le preprocesseur SQLJ lui est complementaire en permettant le SQL statique (et doncefficace).
17.3.3 Architectures JDBC : voir figure 17.3
Le standard propose 4 types de pilotes JDBC :
Type 1pont JDBC-ODBC, l’application doit etre installee sur le client (pas d’applet), cette ar-chitecture considere ODBC comme un pilote. L’interet est de pouvoir porter sans aucunedifficulte toute application JDBC (Sun) sur la technologie ODBC (Microsoft).
Type 2le pilote utilise des methodes natives d’une bibliotheque proprietaire ecrite dans un autrelangage (en C par exemple). Cette bibliotheque doit etre installee sur le poste client. Onobtient des applications moins portables, mais plus performantes.
Type 3 pur Java en utilisant une API reseau generique et un middleware ( ?)
Type 4 pur Java en utilisant le protocole reseau du SGBD (application ou applet).
Les pilotes proposes par Oracle :
Type 4 Thin JDBC (100% pur Java, applet), implemente en Java le protocole Oracle SQL*Net audessus des sockets Java. On obtient alors un code 100% Java et la possibilite de realiser desapplets a condition que le SGBD soit sur la meme machine que le serveur WEB.
Type 2 OCI JDBC (Java + API cliente OCI), necessite l’installation de la bibliotheque OCI, on nepeut donc pas faire d’applet.
Type 2 JDBC Server driver : de Type 2, pour les applications s’executant sur le serveur
Client
SGBD
JDBC Server Driver
Application
Serveur
ThinJDBC
Application
Javaseulement des applications
OCI libApplicationpilote II
C à installersur le client
OCIJDBC
pilote IV
Tout Java : application et applet
Client
Fig. 17.3 – Architectures possibles Oracle JDBC : les pilotes de type 4 (thin) et 2 (OCI)
17.3.4 Fonctionnalites JDBC : java.sql voir figure 17.4
Toutes les methodes declenchent java.sql.SQLException?
Q. 249 Pourquoi la plupart des types de JDBC sont-il de simples interfaces ?
17.3. LES API ABSTRAITES 191
'
&
$
%
Connection Connection Connection
ResultSetMetaData
java.sql
interface
classe concrète
donne un
implémente
hérite
ResultSet
DriverManager OracleDataSource javax.sql.DataSource
Statement
PreparedStatement
CallableStatement
SQLException
Fig. 17.4 – Architecture generale des types JDBC.
SQLException �� ��public String getMessage()�� ��public int getErrorCode()�� ��public String getSQLState()
DriverManager gere les differents pilotes (driver) JDBC connus.Il faut tout d’abord charger ces pilotes Le driver correspondant a l’URL doit etre charge auprealable, par exemple avec :
Class.forName("oracle.jdbc.driver.OracleDriver") ;
Class.forName("org.postgresql.Driver") ;
La principale methode de DriverManager est statique :�� ��static Connection getConnection (String url, String user, String pwd)
Exemple d’URL (protocole, SGBD, sous-protocole, adresse serveur, port, nom de la base)
"jdbc:oracle:thin:@//ma-machine.fil.univ-lille1.fr:3333:mabase"
"jdbc:postgresql://localhost/fil"
– jdbc:oracle:thin identifie le pilote a utiliser,– @//ma-machine.fil.univ-lille1.fr:3333 identifie le serveur et le port de communication
TCP/IP,– mabase identifie la base de donnees ou service sur lequel ouvrir une session.
Dans l’URL Oracle, on peut remplacer thin par oci pour utiliser un pilote de type 2.
DataSource Depuis JDK 1.4, on dispose de l’interface javax.sql.DataSource dont chaque instanceest une fabrique de connexions a la source de donnees physiques qu’elle represente. L’interetde DataSource est un certain nombre de fonctionnalites supplementaires (pool de connexions,transactions distribuees) ainsi que de decoupler un peu plus le code applicatif des informationsde type URL.
L’interface DataSource est implementee par un editeur de pilote (driver)
Connection getConnection (String username, String password)
Q. 250 Implementer un DataSource rudimentaire a l’aide du DriverManager et de l’URLd’Oracle.
Connection correspond a une session (plusieurs transactions successives). Les instructions sont executeeset les resultats sont renvoyes dans le contexte d’une connexion.
Par defaut une connexion est en mode auto-commit c’est a dire que chaque instruction SQL estterminee automatiquement par un commit. On peut changer cela avec une des trois methodes
192 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
suivantes qui doivent etre appelees avant le debut d’une transaction :��void setAutoCommit (boolean autoCommit)
vrai par defaut : chaque ordre SQL est commis.�� ��void setReadOnly (boolean readOnly) faux par defaut�� ��void setTransactionIsolation (int level) level defini dans java.sql.Connection
permet, en debut de transaction, de specifier un des 4 niveaux d’isolation de SQL2 :
TRANSACTION_READ_UNCOMMITTED
TRANSACTION_READ_COMMITTED
TRANSACTION_REPEATABLE_READ
TRANSACTION_SERIALIZABLE�� ��Statement createStatement(int RSType, int RSConcurrency, int RSHoldability) On
peut fixer 3 comportements possibles pour les result set (RS) produits par ce statement :
– RSType : TYPE_FORWARD_ONLY (par defaut) on ne peut qu’avancer dans le result set (next()),TYPE_SCROLL_INSENSITIVE on peut avancer ou reculer (previous()) dans le result set quiest insensible aux modifications faites par d’autres, transactions) ou TYPE_SCROLL_SENSITIVE
comme le precedent et le result set peut etre sensible aux modifications faites par d’autres.– RSConcurrency : CONCUR_READ_ONLY (par defaut) en lecture seule, CONCUR_UPDATABLE on
peut mettre a jour la base de donnees via le result set, ces mises a jour seront effectives avecinsertRow(), deleteRow() et updateRow().
– RSHoldability : CLOSE_CURSORS_AT_COMMIT le result set sera ferme lors du prochain commit()
sur la connexion, HOLD_CURSORS_OVER_COMMIT le result set n’est pas ferme lors d’un commit().�� ��Statement createStatement() les result set seront TYPE_FORWARD_ONLY et CONCUR_READ_ONLY.�� ��PreparedStatement prepareStatement(String sql) dans la chaıne sql les ’?’ indiquentles parametres in�� ��CallableStatement prepareCall (String sql) Pour appeler une procedure stockee. Il fau-dra fixer une fois pour toutes les types des parametres out et in out ou du resultat de la fonctionavec registerOutParameter(), puis avant un appel on positionnera les valeurs des parametresin et in out et apres l’appel on peut recuperer les valeurs des parametres out et in out. Dansla chaıne sql, les ’?’ indiquent les parametres.�� ��void commit()�� ��void rollback()�� ��void close()
Statement objet utilise pour executer une instruction SQL et recuperer son resultat sous la formed’un ResultSet. Un seul ResultSet par Statement peut etre ouvert a la fois.�� ��boolean execute (String sql) Pour executer n’importe quel ordre SQL ou une procedure
stockee qui peut renvoyer plus d’un resultat. Renvoie vrai si le premier resultat est un resultset et faux s’il s’agit d’un nombre de mises a jour ou qu’il n’y a pas de resultat. Les troismethodes suivantes permettent de recuperer ces resultats.�� ��ResultSet getResultSet() suite a un execute () qui vaut vrai.�� ��int getUpdateCount() suite a un execute () qui vaut faux.�� ��boolean getMoreResults() pour obtenir les resultats suivants d’un execute ().�� ��ResultSet executeQuery(String sql)
Pour un select, renvoie un seul result set.�� ��int executeUpdate(String sql)
Pour une instruction insert, update ou delete ou une instruction SQL qui ne renvoie rien(DDL par exemple). La valeur renvoyee est le nombre de lignes affectees ou zero pour lesinstructions SQL qui ne renvoient rien.�� ��void close()
17.3. LES API ABSTRAITES 193
ResultSet le resultat d’une requete. Un curseur, initialement avant la premiere ligne. On peut avoirdes ResultSet balayables dans les deux sens, insensibles aux modifications faites par d’autrestransactions, et meme modifiables (voir Connection.createStatement())�� ��boolean next() une fois pour la 1iere ligne, faux s’il n’y a plus de lignes�� ��XXX getXXX (int/String) par exemple : int id = resultset.getInt ("id") Acces
par indice de colonne (a partir de 1) ou par nom de colonne (sans distinction minus-cules/majuscules). XXX peut etre : Boolean, Date, Float, Int, String et meme Object
quand on ne connaıt pas precisement le type de la colonne designee.
Pour preserver la portabilite de l’application, on a interet a lire les colonnes dans l’ordrecroissant des indices de colonne.�� ��boolean wasNull() a faire juste apres le getXXX() quand la derniere colonne lue pargetXXX() est indefinie (is null).�� ��ResultSetMetaData getMetaData() Pour connaıtre le schema des lignes.�� ��void close()
Si le result set est TYPE_SCROLL_[IN]SENSITIVE il dispose aussi des methodes de repositionne-ment suivantes du curseur :�� ��boolean previous()�� ��boolean first()
�� ��boolean last()�� ��boolean absolute(int row)�� ��boolean relative(int rows)
Il est aussi possible de modifier la base via un ResultSet a condition que celui-ci ait ete obtenupar un Statement cree avec le type CONCUR_UPDATABLE comme dans l’exemple suivant :
java.sql.Statement stmt =
con.createStatement(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_UPDATABLE) ;
��
��
��
��
! ! ! Attention ! ! !, pour Oracle, la requete qui fabrique le result set doitnommer chaque colonne du resultat, ce ne doit pas etre un select * ...,sinon on ne pourra pas modifier la base via le result set obtenu !
Les modifications possibles sont : l’insertion d’une ligne en utilisant la ligne d’insertion duResultSet, la modification d’une ligne et la suppression d’une ligne, voir la figure 17.5.�� ��void deleteRow(), void updateRow()
le curseur doit se trouver sur la ligne courante du result set qui doit etre celle qu’on veutmodifier. La mise a jour est rendue effective dans la base et dans le result set,�� ��void updateXXX (int/String, XXX)
mise a jour de la colonne de la ligne courante ou de la ligne d’insertion sans modifier labase de donnees. XXX peut etre : Boolean, Date, Float, Int, String . . .et meme Object Ily a aussi void updateNull (int/String) qui rend indefinie la colonne mentionnee.�� ��void moveToInsertRow()
deplace le curseur sur l’insert row, ligne speciale permettant de construire les nouvelleslignes a inserer,�� ��void insertRow()
le curseur doit se trouver sur l’insert row qui est inseree dans la base et dans le result set,�� ��void moveToCurrentRow()
retour a la ligne courante.
En cas d’erreur, la semantique de void deleteRow(), void updateRow() et void insertRow()
n’est pas claire.
194 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
Nuplet courant
Nuplet d’insertion
moveToCurrentRow()moveToInsertRow()
ResultSet.CONCUR_UPDATABLE
deleteRow() updateRow()
updateXXX()
updateXXX()
insertRow()
Fig. 17.5 – Un updatable result set avec sa ligne d’insertion. Les methodes soulignees mettent a jourla base de donnees.
ResultSetMetaData objet decrivant la constitution d’une ligne du result set qui l’a produit.�� ��int getColumnCount()�� ��String getColumnClassName(int column)
column a partir de 1.�� ��int getColumnDisplaySize(int column)�� ��String getColumnName(int column)
PreparedStatement Un objet representant une instruction SQL eventuellement precompilee (⇒efficacite). Un PreparedStatement peut etre parametre en in et execute autant de fois que l’onveut tant que la connexion qui l’a produit n’est pas fermee. C’est donc plus efficace de le creerune fois pour toutes quand on a besoin d’executer frequemment un meme ordre SQL, meme sicette instuction n’a pas de parametres et meme si chaque execution differe du fait qu’on modifieles valeurs des parametres. Si le SGBD est capable de precompiler (et d’optimiser) lui-meme larequete, alors cette instruction pourra etre executee efficacement de nombreuses fois.
Exemple de Sun
PreparedStatement pstmt =
con.prepareStatement("update Employe set salaire = ? where id = ?") ;
pstmt.setBigDecimal (1, 153833.00) ; // c’est en euros ???
pstmt.setInt (2, 110592) ; // c’est surement le boss...
pstmt.executeUpdate () ;
L’instruction peut comporter des parametres indiques par le caractere ?.�� ��void setXXX (int parameterIndex, XXX x) fixe la valeur d’un parametre avant l’execution.Les parametres sont indexes a partir de 1.�� ��ResultSet executeQuery() pour select,
�� ��int executeUpdate() pour un insert, up-date ou delete.
CallableStatement Pour executer un sous-programmes stocke. Un CallableStatement peut etreparametre en in, out et in out et execute autant de fois que l’on veut tant que la connexionqui l’a produit n’est pas fermee. C’est donc plus efficace de le creer une fois pour toutes quandon a besoin de l’executer frequemment, meme si chaque execution differe du fait qu’on modifieles valeurs des parametres in.
Voici deux exemples de chaınes exprimant un appel de sous-programme parametre :
17.3. LES API ABSTRAITES 195
Type de sous-programme Le CallableStatement
Appel de procedure : connexion.prepareCall ("{call emprunter (?,?)}")
Appel de fonction : connexion.prepareCall ("{?=call factorielle (?)}")
Des que le CallableStatement est cree, il faut specifier, une fois pour toutes, les types desparametres out ou in out ou du resultat de la fonction avec registerOutParameter().
Avant chaque execution on fixe les valeurs des parametres in et in out avec les methodes setXxx(int index, Xxx valeur) ou setXxx (String nomParametreFormel, Xxx valeur) qui uti-lise le nom du parametre formel du sous-programme, mais il faut que le pilote soit capable de lefaire.
Apres une execution on retrouve la valeur de la fonction ou celles des parametres out et in outavec les methodes getXxx (index/String).
SQLData une interface pour faire la correspondance entre les objets SQL definis par l’utilisateur(UDT) et leurs equivalents en Java.
Un objet SQLData et le nom du type SQL correspondant doivent etre fournis a la table decorrespondance de la Connection concernee. Il faudra utiliser la methode ResultSet.getObjectet dans l’autre sens : PreparedStatement.setObject.�� ��String getSQLTypeName() throws SQLException
�� ��void readSQL(SQLInput stream, String typeName) throws SQLException
Il faut lire les attributs dans leur ordre de definition dans le type objet,�� ��writeSQL(SQLOutput stream) throws SQLException
Il faut ecrire les attributs dans leur ordre de definition dans le type objet.
17.3.5 Un exemple d’objet persistant : l’Employe
Cet exemple illustre l’usage d’un PreparedStatement et d’un CallableStatement en proposant unobjet persistant, c’est a dire que son etat en memoire centrale est coherent avec son etat dans la basede donnees.
Le chargement individuel de chaque employe n’est pas une approche tres efficace, il vaudrait surementmieux charger d’un seul coup tous les employes dont on a besoin.
Voici la table et la procedure stockee abritees par le serveur :
create table Employe (id Number (5) primary key, nom Varchar2(20), salaire Number(10));
create or replace procedure Augmenter
(id in Employe.id%type, pourcentage in Natural, nouveau_sal out Employe.salaire%type) is
begin
set transaction isolation level read committed ;
update Employe
set salaire = (salaire * (100 + pourcentage))/100
where id = Augmenter.id
RETURNING salaire INTO nouveau_sal ; -- range le nouveau salaire dans ce parametre
if SQL%rowcount = 0 then
rollback; raise_application_error(-20111, ’Employe inexistant : ’||to_char(id));
end if ;
commit ;
end Augmenter ;
Un code client utilisant ces services et rendant persistantes les modifications faites aux objets Employe :
class Employe { // PARTIE STATIQUE
private static PreparedStatement charger ;
196 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
private static CallableStatement augmenter ;
public static void initialiser (Connection cnx) throws SQLException {
charger = cnx.prepareStatement ("select id, nom, salaire from Employe where id = ?") ;
augmenter = cnx.prepareCall ("{call Augmenter (?, ?, ?)}") ;
augmenter.registerOutParameter ("nouveau_sal", Types.INTEGER) ;
}
public static void fermer() throws SQLException{charger.close (); augmenter.close ();}
// PARTIE INSTANCE
private int id ; private String nom ; private int salaire ;
public Employe (final int id) throws SQLException {
Employe.charger.setInt (1, id) ;
ResultSet r = Employe.charger.executeQuery () ;
if (r.next ()) {
this.id = r.getInt("id"); nom = r.getString("nom"); salaire = r.getInt("salaire");
Employe.charger.getConnection ().commit () ;
} else {
Employe.charger.getConnection ().rollback () ;
throw new SQLException ("Employe " + id + " inexistant") ;
}
}
public void augmenter (final int pourcentage) throws SQLException {
Employe.augmenter.setInt ("id", id) ;
Employe.augmenter.setInt ("pourcentage", pourcentage) ;
Employe.augmenter.execute () ;
salaire = Employe.augmenter.getInt ("nouveau_sal") ;
}
public String toString () { return id + " " + nom + " " + salaire ; }
}
Remarquer que les variables statiques charger et augmenter n’ont besoin d’etre initialisees qu’uneseule fois. Un petit exemple d’utilisation :
Employe.initialiser (connexion) ;
{ Employe e1 = new Employe (1) ; Employe e2 = new Employe (2) ;
e1.augmenter (10) ; e2.augmenter (20) ;
}
Employe.fermer () ; connexion.close () ;
17.3.6 Un exemple JDBC : bibliotheque
Voici le code d’une application JDBC qui imprime les titres des livres empruntes et leurs emprunteurs,puis le nombre total de livres de la bibliotheque (voir l’exemple SQLJ section 17.4.8 page 208). Cecode est range dans le fichier Emprunts.java :
public class Emprunts {
private static void requete (java.sql.Statement stmt, String R) throws SQLException {
ResultSet resultat = stmt.executeQuery (R) ;
final int N_COL = resultat.getMetaData ().getColumnCount () ;
try {
while (resultat.next ()) {
for (int i = 1 ; i <= N_COL ; i++) {
Object o = resultat.getObject (i) ;
System.out.print (resultat.wasNull () ? "*null*" : o.toString ()) ;
}
System.out.println () ;
}
17.4. SQL EMBARQUE (INTEGRE) 197
} finally { resultat.close() ; }
}
private static void livreEmprunteur (Statement stmt) throws SQLException {
requete (stmt,"select titre, nom as emprunteur" +
" from Livre inner join Personne on emprunteur=p_ref") ;
}
private static void nbLivres (java.sql.Statement stmt) throws SQLException {
requete (stmt, "select count (*) from Livre") ;
}
public static void main(String args[]) throws SQLException {
Class.forName("oracle.jdbc.driver.OracleDriver") ;
final Connection connect = DriverManager.getConnection
("jdbc:oracle:thin:@//<machine>.<domaine>:<port>/filora10", "toto", "psswrd") ;
try {
connect.setAutoCommit (false) ;
Statement stmt = connect.createStatement() ;
try { stmt.execute ("set transaction read only") ;
livreEmprunteur (stmt) ;
nbLivres (stmt) ;
} finally { stmt.close() ; }
} finally { connect.close () ; }
}
}
Remarquez que la methode requete() est applicable a toute requete, meme si son affichage n’est pastres sophistique.
17.3.7 Recuperation des valeurs produites pas le SGBD (DML returning)
Voir 7.20.
Lorsque l’application client permet de creer un nouveau livre (insert) et que la clef primaire de cenouveau livre est generee par le SGBD, par exemple grace a une sequence Oracle, il serait interessantque l’application puisse connaıtre cette clef sans avoir a effectuer ensuite une requete qui risqueraitd’ailleurs d’etre plutot douteuse.
C’est ce que permet le DML returning lors d’une instruction DML insert ou update. Il est disponibleavec les pilotes Oracle sauf le pilote interne cote serveur mais ces fonctionnalites ne sont pas standard.
17.4 SQL Embarque (integre)
Il s’agit d’etendre la syntaxe et la semantique d’un langage classique (Cobol, C, Ada, Java, . . .) pourpermettre d’y integrer directement du SQL (un peu comme en PL/SQL).
17.4.1 Avantages
– Le code est plus concis et de plus haut niveau qu’avec une API, en particulier au niveau de la liaisondes variables du langage avec les instructions SQL (utilisation du
�� ��: ). (En JDBC, il faut utiliserpeniblement les methodes getXXX() et updateXXX())
– Verification de la syntaxe et des types SQL des la compilation– un source SQLJ peut parfaitement utiliser directement l’API JDBC
17.4.2 La mise en œuvre : voir figure 17.6
Precompilation, compilation puis edition de lien avec une bibliotheque.
198 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
Sources langage hote + appels à la bibliothèque
Sources langage étendu (SQL embarqué)
Fichiers objetsBibliothèque
Exécutable
Précompilateur
Compilateur standard
Editeur de liens
Fig. 17.6 – SQL embarque et API concrete : au plus haut niveau on utilise un sur-langagedu langage hote qui autorise l’ecriture d’ordres SQL, au niveau intermediaire le programme effectueexplicitement des appels aux primitives d’acces au SGBD proposees par une API concrete ou abstraite.Par exemple le preprocesseur SQL C de Postgres utilise l’API concrete libecpg, et le preprocesseurSQLJ utilise l’API abstraite JDBC.
17.4.3 SQL embarque dans du C
Le SQL embarque est defini par le standard SQL et supporte par beaucoup d’editeur de SGBD.
Avantage : facilite du portage d’un SGBD vers un autre.
Chaque ordre embarque commence par la chaıne�� ��EXEC SQL et se termine par
�� ��; . En gros il y a troisgrandes categories d’ordre embarque :
Declaration des variables de liaison , exemple si le langage hote est le C :
EXEC SQL BEGIN DECLARE SECTION ;
char user [26] ;
VARCHAR nom [20] ;
int nbAnciens ;
EXEC SQL END DECLARE SECTION ;
Ce sont des variables du langage qui seront utilisees pour transferer de l’information vers/depuisla base de donnees.
VARCHAR nom [20] sera remplace (Oracle et Postgres) par le precompilateur par :
struct {
unsigned short len ;
unsigned char arr [20] ;
} nom ;
Attention, le tableau arr ne se termine pas forcement par un ’\0’ si le tableau est plein.
Declarations d’intention , exemple si le langage hote est le C :
EXEC SQL WHENEVER SQLERROR DO erreur_sgbd () ;
Cet ordre n’a aucun effet immediat, il a par contre un effet sur la maniere dont les erreurs pro-voquees par les ordres SQL ulterieurs dans le source du programme seront prises en compte (en
17.4. SQL EMBARQUE (INTEGRE) 199
l’occurrence, en cas d’erreur, on appellera la fonction erreur_sgbd ()).
Voici deux autres exemples :
EXEC SQL WHENEVER SQLERROR continue ;
/* en cas d’erreur, on continue l’execution du programme */
EXEC SQL WHENEVER NOT FOUND DO break;
/* si le dernier FETCH n’a rien trouve : terminer la boucle */
Bien entendu, c’est la derniere declaration d’intention rencontree qui est effective.
Les instructions a proprement parler dont voici quelques exemples :
EXEC SQL CONNECT TO [email protected]:5432
AS Ma_Connexion USER :user IDENTIFIED BY :pw_user ;
EXEC SQL AT Ma_Connexion SELECT count(*) INTO :nbAnciens FROM Vue_Ancien ;
EXEC SQL CREATE TABLE Livre (ref int primary key, titre char (50)) ;
EXEC SQL Insert into Livre values (1, ’Retour a Brooklyn’) ;
EXEC SQL DELETE FROM emp WHERE deptno = :dept_number ;
EXEC SQL ROLLBACK ;
On voit que la mention des variables de liaison doit etre precedee de�� ��: .
Le�� ��AT Ma_Connexion , qui est optionnel, permet au programme de travailler simultanement
avec plusieurs connexions.
Les curseurs : la declaration :
EXEC SQL DECLARE Curseur_Tous CURSOR FOR
SELECT a.nom, a.nom_marital
FROM Vue_Ancien a ;
Si on a explore tout le resultat de la requete, on veut sortir de la boucle d’exploration, il s’agitd’une declaration d’intention :
EXEC SQL WHENEVER NOT FOUND DO break ; /* c’est le break du C */
Les operations sur le curseur : ouvrir le curseur :
EXEC SQL OPEN Curseur_Tous ;
Obtenir la prochaine ligne de la requete (ou sortir de la boucle si not found) :
EXEC SQL FETCH Curseur_Tous
INTO :nom, :nom_marital INDICATOR :nom_marital_ind ;
L’INDICATOR nom_marital_ind permettra de savoir si la colonne nom_marital est definie ounon (-1 si indefinie, is null).Fermer le curseur :
EXEC SQL CLOSE Curseur_Tous ;
200 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
17.4.4 Quelques produits
– Oracle : Pro*C/C++, Pro*COBOL, SQLJ,– le projet GNADE : SQL embarque dans du Ada 95, avec des API ODBC, PostgreSQL et MySQL– Postgres : ECPG (Embedded SQL in C ou C++) qui ressemble pas mal au Pro*C/C++ d’Oracle
(c’est un peu normal pour un standard !).
17.4.5 Exemple Postgres : bibliotheque et ECPG (exemple de 17.3.6)
Il s’agit du meme exemple que celui traite en JDBC section 17.3.6 page 196.
/* POSTGRES : fichier biblio.pgc, precompilateur : ecpg */
#include <stdio.h>
#include <string.h>
#define SECURE_COPY(DEST, SOURCE) \
if (sizeof (DEST) <= strlen (SOURCE)) { \
fprintf (stderr, "Chaıne trop longue : \"%s\"\n", SOURCE) ; \
exit (1) ; \
} \
strcpy (DEST, SOURCE) ;
/*
* Ecrit la chaıne "s" en s’arretant apres "lg" caracteres ou
* des la rencontre du fameux ’\0’.
*/
void put (const int lg, const char * const s) {
int i = 0 ;
for ( ; i < lg && s [i] != ’\0’ ; i++) printf ("%c", s [i]) ;
}
void erreur_postgres () {
fprintf (stderr, "Erreur: %s\n", sqlca.sqlerrm.sqlerrmc) ;
/* Pour eviter de boucler en cas d’erreur de deconnexion */
EXEC SQL WHENEVER SQLERROR continue ; /* continuer l’execution */
EXEC SQL DISCONNECT Ma_Connexion ; /* se deconnecter */
exit (1) ; /* quitter */
}
/* prise en compte des erreurs par erreur_postgres () */
EXEC SQL WHENEVER SQLERROR DO erreur_postgres () ;
int main (const int argc, const char * const argv []) {
int nbEmprunts = 0 ; /* pas une variable de liaison */
EXEC SQL BEGIN DECLARE SECTION ; /* variables de liaison */
char user [26] ; /* variable en entree */
char pw_user [26] ; /* variable en entree */
int nbLivres ; /* variable en sortie */
VARCHAR nom [5] ; /* variable en sortie, si chaıne trop grande
* tronque et pas de ’\0’ en fin */
char prenom [50] ; /* variable en sortie, si chaıne trop grande
* tronque et pas de ’\0’ en fin */
int prenom_ind ; /* indicateur de non valeur (-1 si is null) */
VARCHAR titre [10] ;/* variable en sortie */
17.4. SQL EMBARQUE (INTEGRE) 201
EXEC SQL END DECLARE SECTION ;
SECURE_COPY (user, argv[1]) ; SECURE_COPY (pw_user, argv[2]) ;
EXEC SQL CONNECT TO [email protected]:5432
AS Ma_Connexion
USER :user IDENTIFIED BY :pw_user ;
EXEC SQL DECLARE Les_Emprunts CURSOR FOR
select l.titre, p.nom, p.prenom
from Livre l inner join Personne p on l.emprunteur = p.p_ref ;
EXEC SQL OPEN Les_Emprunts ;
EXEC SQL WHENEVER NOT FOUND DO break;
while (1) {
EXEC SQL FETCH Les_Emprunts INTO :titre, :nom, :prenom INDICATOR :prenom_ind ;
put (titre.len, titre.arr) ; printf (" emprunte par ") ;
put (nom.len, nom.arr) ; printf (" ") ;
if (prenom_ind == -1) printf ("*null*") ; else put (sizeof (prenom), prenom) ;
printf ("\n") ;
}
EXEC SQL CLOSE Les_Emprunts ;
EXEC SQL AT Ma_Connexion SELECT count(*) INTO :nbLivres FROM Livre ;
/* AT Ma_Connexion : utile si on a plusieurs connexions ouvertes. */
printf ("Nombre de livres = %d\n", nbLivres) ;
EXEC SQL DISCONNECT Ma_Connexion ;
exit (0) ;
}
Les commandes sont ensuite :
1. precompilation
ecpg biblio.pgc
2. compilation et edition des liens
gcc -o biblio biblio.c -lecpg -I‘pg_config --includedir‘
3. execution
./biblio <utilisateur> <mot-de-passe>
17.4.6 Oracle : Pro*C, quelques specificites
Instructions
– sous Oracle, on peut inclure du code PL/SQL :
EXEC SQL EXECUTE ... END-EXEC ;
– on peut faire du SQL dynamiqueConnexion sur une base indiquee autre que celle par defaut :
EXEC SQL DECLARE BD_DU_FIL DATABASE ;
EXEC SQL CONNECT :username IDENTIFIED BY :password
AT BD_DU_FIL USING :db_string ;
202 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
db_string chaıne de caractere en syntaxe Net8 (reseau, machine et base de connees) pour se connectera une base de donnee distante. Les instructions SQL utilisant la clause AT BD_DU_FIL seront alorsdirigees vers cette base de donnees.
Gestion des erreurs
EXEC SQL INCLUDE sqlca ;
SQLCA : SQL Communication Area, pour recuperer les codes et messages d’erreurs qui proviennentdu serveur.
EXEC SQL WHENEVER <condition> <action>;
<condition> ::= SQLERROR | SQLWARNING | NOTFOUND
<action> ::= DO <appel de fonction>
| CONTINUE (ignore l’erreur et continue la sequence)
| GOTO label
| STOP (arret du prgr et rollback)
EXEC SQL WHENEVER <condition> est une declaration d’intention, elle definit comment les ordres SQLqui la suivent prendront en charge les erreurs correspondant a la condition indiquee, et ce, jusqu’auprochain EXEC SQL WHENEVER portant sur la meme condition.
17.4.7 SQLJ : SQL embarque dans du Java
Agree par plusieurs compagnies dont Oracle et Sun, permet le SQL statique et des verificationssemantiques par rapport au schema de base de donnees des le pretraitement. Sont necessaires :
– le precompilateur sqlj (translator) produit un .java a partir d’un .sqlj ainsi qu’un ou plusieursprofils pour la generation de code standard ISO SQLJ. L’option par defaut -codegen=oracle negenere pas de profils on peut avoir les profils avec -codegen=iso, les deux necessitent un piloteJDBC.sqlj invoque ensuite le compilateur java pour produire les .class.
– une librairie runtime SQLJ (SQLJ run time”)– un pilote JDBC��
��
SQLJ est capable de verifier, des la traduction du source SQLJ en source pur Java, la semantique duSQL embarque par rapport au schema de la base de donnees, pour cela il a bien entendu besoin depouvoir se connecter au SGBD.
Chaque instruction #sql {<instruction SQL>} peut etre prefixee par un contexte de connexion et/ouun contexte d’execution :
Contexte de connexion : permet a un meme programme de travailler avec plusieurs connexions aune meme base de donnees ou a plusieurs bases de donnees. S’il n’est pas mentionne, l’instructionfonctionne sur le contexte de connexion par defaut. Un contexte de connexion dispose d’uncontexte d’execution par defaut. Chaque thread utilisant le meme contexte de connexion doitdisposer de son propre contexte d’execution.
Contexte d’execution : toute instruction embarquee est executee par rapport a un contexte d’execution,s’il n’est pas mentionne il s’agit du contexte d’execution par defaut.
Les contextes de connexion
Creation de la connexion par defaut :
– la methode directe :
17.4. SQL EMBARQUE (INTEGRE) 203
oracle.sqlj.runtime.Oracle.connect
("jdbc:oracle:thin:@localhost:1521:orcl", "dupond", "passe-tigre") ;
– la methode avec la classe Mon_Application_SQLJ qui contient la methode statique main() qui serale programme principal a executer, et le fichier connexion_a_la_BDD.infos qui contient l’URL, lenom et le mot de passe de l’utilisateur :
oracle.sqlj.runtime.Oracle.connect
(Mon_Application_SQLJ.class, "connexion_a_la_BDD.infos") ;
Creation explicite d’un contexte de connexion :
final sqlj.runtime.ref.DefaultContext
ma_connexion = oracle.sqlj.runtime.Oracle.getConnection(
"jdbc:oracle:thin:@localhost:1521:orcl",
"nom-d-utilisateur",
"mot-de-passe"
) ;
Chaque connexion correspond a une session sur le SGBD.
Deux methodes statiques de DefaultContext :
setDefaultContext() positionne le contexte connexion par defaut avec le parametre
getDefaultContext() renvoie le contexte connexion par defaut.
Fermeture d’une connexion explicite et de la connexion par defaut :
ma_connexion.close () ;
oracle.sqlj.runtime.Oracle.close () ;
Bien entendu, en travaillant avec plusieurs connexions sur la meme base de donnees, depuis la connexionC2 on ne verra pas les modifications faites par C1 tant que C1 ne les aura pas validees (commit).En SQLJ, le auto-commit est a faux par defaut (contrairement a JDBC).
Les contextes d’execution
sqlj.runtime.ExecutionContext
Methode getExecutionContext() definie sur les contextes de connexion.Plusieurs methodes permettent d’obtenir de l’information sur la derniere instruction executee dans uncontexte d’execution :
getWarnings() renvoie un java.sql.SQLWarning contenant le premier avertissement generepar la derniere instruction executee dans ce contexte d’execution
getUpdateCount() nombre de lignes modifiees
getQueryTimeout()
setMaxRows(int)
getMaxRows()
Creation d’un contexte d’execution : new ExecutionContext(), ce nouveau contexte n’a pas besoind’etre lie a un contexte de connexion, il peut etre utilise avec differents contextes de connexion.
Chaque instruction s’executant dans un contexte d’execution ecrase les informations d’etat des ins-tructions precedentes.
En cas d’une application multi-taches, chaque tache (thread) doit utiliser un contexte d’executiondifferent.
204 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
Specifications des contextes a utiliser
Chaque instruction embarquee peut etre prefixee par un contexte de connexion et/ou un contexted’execution :
#sql { instruction SQL }; // connexion et execution par defaut
#sql [<contexte_connexion>] { instruction SQL }; // execution par defaut
#sql [<contexte_execution>] { instruction SQL }; // connexion par defaut
#sql [<contexte_connexion>, <contexte_execution>] { instruction SQL };
Les valeurs SQL indefinies
Les valeurs SQL indefinies (is null) sont recuperees comme la valeur null de Java. Mais tenter derecuperer une valeur SQL indefinie dans une variable Java de type primitif declenche l’exceptionsqlj.runtime.SQLNullException. La solution consiste a recuperer la valeur SQL dans un wrappercomme java.lang.Integer pour une valeur entiere.
Quelques instructions embarquees executees immediatement
Les instructions SQL embarquees en SQLJ sont toujours statiques, ainsi, si le pilote (driver) le permet,elles pourront s’executer plus efficacement que des instruction generees dynamiquement (par exempleles chaınes de caracteres fournies a JDBC).
#sql {
create table Employe (
id Number (5) primary key,
nom Varchar2 (20),
salaire Number (10, 2) check (salaire >= 0)
)
} ;
#sql { insert into Employe values (1, ’toto’, 1000.00) } ;
#sql { insert into Employe (id, nom) values (2, ’titi’) } ;
Une requete devant avoir exactement un resultat :
final int ID = 67890 ;
String nom ;
#sql {
select nom into :nom
from Employe
where id = :ID } ;
System.out.println ("Nom de " + ID + " : " + (nom==null ? "anonyme" : nom)) ;
Si la colonne nom est indefinie, alors la variable de liaison nom recoit la valeur null (on a une exceptionsi la variable de liaison est d’un type primitif).
Comme on le voit, le nom d’une variable du programme figurant dans une instruction #sql {}
doit etre prefixe par le caractere :. En fait le : peut prefixer une expression Java, par exemple :where nom = :(nom.toUpper ()) ou encore, en precisant par in que le mode de passage de l’expres-sion est en entree :where nom = :in(nom.toUpper ())
On peut bien sur utiliser aussi les autres instructions DML, par exemple :
17.4. SQL EMBARQUE (INTEGRE) 205
void augmenter_salaire (final int id, final int augmentation)
throws java.sql.SQLException
{
final sqlj.runtime.ExecutionContext ctx_execution =
sqlj.runtime.ref.DefaultContext.getDefaultContext ().getExecutionContext () ;
#sql [ctx_execution] {
update Employe
set salaire = salaire + :augmentation
where id = :id } ;
if (ctx_execution.getUpdateCount () == 0) {
#sql { rollback } ;
throw new Error ("Pas d’employe d’id = " + id) ;
} else {
#sql { commit } ;
}
}
Remarquer qu’ici tous les ordres SQL embarques sont executes dans le meme contexte d’execution.
Les iterateurs
Definition de la classe iterateur NomNumero avec les noms et types des colonnes :
#sql iterator NomId (String nom, int id) ;
Puisque NomId est une classe, sa declaration peut se faire de facon autonome en dehors de celle d’uneautre classe. Une classe iterateur declaree dans une autre classe doit etre public static :
class X {
#sql public static iterator NomId (String nom, int id) ;
...
}
Une instance de NomId disposera alors de deux methodes de type accesseur : String nom () etint id () :
{
// Declaration d’une variable iterateur
NomId monIterateur ;
// Initialisation de la variable iterateur
#sql monIterateur = {Select nom, id from Employe} ;
// Exploration de la requete
try {
while (monIterateur.next()) {
int id = monIterateur.id () ;
String nom = monIterateur.nom () ;
if (nom == null) {// c’est que : << Employe.nom is null >>
...
}
} finally {
// Fermeture * garantie * de l’iterateur
monIterateur.close() ;
206 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
}
}
Le nom et le type d’une des colonnes d’un iterateur doit correspondre a la colonne de meme nom (a lacasse pres) du select et doit etre d’un type compatible. En revanche l’ordre des colonnes de l’iterateurpeut etre different de celui des colonnes homonymes dans le select.
Types compatibles
Un petit echantillon des types compatibles :
type primitif Java type Oracle
int NUMBER
long NUMBER
float NUMBER
type reference Java type Oracle
Integer INTEGER ou NUMBER
Float NUMBER
Double NUMBER
java.math.BigDecimal NUMBER
String VarChar2
java.sql.Date DATE
Appels de sous-programmes stockes
Appel d’une procedure stockee avec un parametre en entree-sortie, un en sortie et un en entree :
int x = 5, ancien_x, delta = 3 ;
#sql { call Augmenter (:inout x, :out ancien_x, :in delta) ;
// Par defaut le mode est in, on peut donc aussi ecrire :
#sql { call Augmenter (:inout x, :out ancien_x, delta) ;
Appel de la fonction stockee sans parametre Plus_Grand_Salaire :
java.lang.Number salaireMax ;
// ou oracle.sql.NUMBER salaireMax ;
...
#sql salaireMax = { VALUES (Plus_Grand_Salaire) } ;
Embarquement de bloc PL/SQL
Peut permettre de faire un maximum de traitements sur le serveur et limiter les communicationsreseau.
#sql {
declare
...
begin
...
end } ;
17.4. SQL EMBARQUE (INTEGRE) 207
Prise en compte des exceptions
Rappel : une exception est une condition a traitement delocalise, c’est a dire qu’elle ne peut pas etretraitee a l’endroit ou elle a ete detectee : la structure de controle if then else ne convient donc paspour prendre en compte ce genre de condition.
Ici on intercepte l’exception puis on la redeclenche car on ne resout pas la condition a laquelle ellecorrespond :
try {
#sql {
select bureau into :bureau
from Employe
where id = :id } ;
System.out.println ("Bureau de " + id + " : " + bureau) ;
} catch (java.sql.SQLException excp) {
switch (excp.getErrorCode ()) {
case 2000:
System.err.println ("Erreur sur le select : aucune ligne selectionnee") ;
break ;
case 21000:
System.err.println ("Erreur sur le select : plus d’une ligne selectionnee") ;
break ;
default:
System.err.println (excp.getMessage ()) ;
}
throw excp ;
}
Les applications SQLJ peuvent etre stockees et executees sur le serveur.
Architecture
Pour JDK 1.4 et generation de code specifique Oracle :
Positionner la variable d’environnement ORACLE_HOME sur le repertoire contenant les outils JDBCet SQLJ.
Ajouter a la variable d’environnement CLASSPATH le pilote JDBC, le traducteur et le runtimeappropries :
$ORACLE_HOME/jdbc/lib/classes12.zip !! la version 9.0.1 pas la 10.2.0.1
$ORACLE_HOME/sqlj/lib/translator.jar
$ORACLE_HOME/sqlj/lib/runtime12.jar
Ligne de commande SQLJ
Les sources ont l’extension .sqlj.
sqlj <options-java> fichiers.sqlj
Verifications semantiques :
online grace a l’option -props qui indique au precompilateur comment se connecter au SGBDafin de verifier l’adequation de la semantique du programme SQLJ avec celle du schema de labase de donnees.
offline sinon, les erreurs eventuelles ne seront vues qu’a l’execution de l’application.
208 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
Generation d’un fichier .java et de fichiers serialises de profil .ser ou .class. Un profil contient desinformations a propos des instructions SQL embarquees.Tous ces fichiers (.java et .ser) sont ensuite compiles par Java pour obtenir les .class.
17.4.8 Un exemple SQLJ : bibliotheque (exemple de 17.3.6)
Il s’agit du meme exemple que celui traite en JDBC section 17.3.6 page 196 et en ECPG section 17.4.5page 200.Cette application SQLJ imprime les titres des livres empruntes et leurs emprunteurs, puis le nombretotal de livres de la bibliotheque. Ce code est range dans le fichier Emprunts.sqlj :
#sql iterator LivreEmprunteur (String titre, String emprunteur) ;
public class Emprunts {
private static void impLivreEmprunteur () throws java.sql.SQLException {
LivreEmprunteur iter ;
#sql iter = { select titre, nom as emprunteur
-- Erreur qui peut etre detectee des la compilation :
-- from Livre inner join Personne on emprunteur = p_reff
from Livre inner join Personne on emprunteur = p_ref } ;
while (iter.next()) {
System.out.println (iter.titre () + ", " + iter.emprunteur ()) ;
}
iter.close() ;
}
private static void nbLivres () throws java.sql.SQLException {
int nbLivres ;
#sql {select count (*) into :nbLivres from Livre} ;
System.out.println ("Nombre de livres " + nbLivres) ;
}
public static void main (String[] args) throws java.sql.SQLException {
oracle.sqlj.runtime.Oracle.connect (Emprunts.class, "Emprunts.properties") ;
try {
#sql {set transaction read only} ;
impLivreEmprunteur () ;
nbLivres () ;
} finally {
#sql { commit } ;
oracle.sqlj.runtime.Oracle.close() ;
}
}
}
Q. 251 Donner une implantation de la classe produite par le type iterateur LivreEmprunteur. Quelleest la chose qu’on a du mal a garantir dans cette implantation ?
On peut apprecier la brievete du code, cependant, SQLJ etant concu par Oracle il est tres lie a ceSGBD.
Lors du pretraitement, SQLJ peut verifier que la semantique du programme correspond bien a cellede la base de donnees (existance des tables, des colonnes, typage coherent des variables Java et desexpressions SQL, . . .). Pour cela il faut indiquer a SQLJ comment il peut se connecter a la base graceau fichier Emprunts-sqlj.properties :
# Informations pour que SQLJ puisse faire des
# verifications semantiques vis a vis du shema de
# la base de donnees des le pretraitement.
17.4. SQL EMBARQUE (INTEGRE) 209
#
# option SQLJ : -props=Emprunts-sqlj.properties
# ou bien les options -user, -password, -url de la commande SQLJ
sqlj.url=jdbc:oracle:thin:@<machine>.<domaine>.fr:1521:<service-de-test>
sqlj.user=test
sqlj.password=<mot-de-passe-de-test>
# Rend compte des problemes de portabilite vis a vis des extensions
# de SQLJ specifiques a Oracle
sqlj.warn=portable
# Activation des messages d’information
sqlj.warn=verbose
et fournir ce fichier en argument de la commande de pretraitement sqlj :
sqlj -props=Emprunts-sqlj.properties Emprunts.sqlj
L’erreur sur l’orthographe de la colonne p_ref incorrectement ecrite p_reff sera detectee des lepretraitement !
Lors de l’execution le programme utilise le fichier Emprunts.properties pour se connecter a la base,voici le contenu de Emprunts.properties :
# Informations pour l’execution
sqlj.url=jdbc:oracle:thin:@<machine>.<domaine>.fr:1521:<service>
sqlj.user=biblio
sqlj.password=<mot-de-passe-de-biblio>
Executer l’application :
java Emprunts
On remarque que la base utilisee pour le pretraitement et celle utilisee pour l’execution peuvent nepas etre les memes.
17.4.9 Le meme exemple en explicitant le contexte d’execution
import java.sql.SQLException ;
#sql context EmpruntsCtx ;
#sql iterator LivreEmprunteur (String titre, String emprunteur) ;
public class Emprunts {
private static void impLivreEmprunteur (EmpruntsCtx ctx)
throws SQLException
{
LivreEmprunteur iter ;
#sql [ctx] iter = {
select titre, nom as emprunteur
from Livre inner join Personne on emprunteur = p_ref
} ;
while (iter.next()) {
System.out.println (iter.titre () + ", " + iter.emprunteur ()) ;
}
210 CHAPITRE 17. DEVELOPPER UNE APPLICATION BD
iter.close();
}
private static void nbLivres (EmpruntsCtx ctx) throws SQLException {
int nbLivres ;
#sql [ctx] {select count (*) into :nbLivres from Livre} ;
System.out.println ("Nombre de livres " + nbLivres) ;
}
public static void main(String[] args) throws SQLException {
oracle.sqlj.runtime.Oracle.connect(Emprunts.class, "Emprunts.properties") ;
try {
EmpruntsCtx ctx = new EmpruntsCtx
(sqlj.runtime.ref.DefaultContext
.getDefaultContext().getConnection()) ;
#sql [ctx] {set transaction read only} ;
impLivreEmprunteur (ctx) ;
nbLivres (ctx) ;
ctx.close(ctx.KEEP_CONNECTION) ;
} finally {
#sql [ctx] { commit } ;
oracle.sqlj.runtime.Oracle.close() ;
}
}
}
17.4.10 JPublisher
Un outil fourni par Oracle qui exploite la definition des types objet SQL (voir le chapitre sur lerelationnel-objet de Oracle) pour en donner un equivalent en objets Java ou en structures C. Celapermet ensuite d’ecrire en Java des applications clientes qui utilisent ces objets.
17.5 Outils de developpement : AGL
– Oracle : JDeveloper– MicroSoft : VBA, Access– Postgres : PGaccess– Hibernate sur http://www.hibernate.org/
Chapitre 18
Introduction a Hibernate
Cette presentation d’Hibernate essaie d’etre une introduction pas trop compliquee pour faire com-prendre quelques elements de base de cet environnement. Elle ne pretend certainement pas faire dulecteur un specialiste. Elle se limite a une application simple de type client/serveur, alors qu’Hibernateest fait pour du developpement WEB.
Probablement qu’un des objectifs principaux d’Hibernate est de faciliter la persistance, dans une basede donnees, des objets manipules par le programmme.
Hibernate propose beaucoup d’outils qu’il fournit ou qu’il emprunte a d’autres editeurs (Apache, Sun,. . .) chacun synthetisant des besoins dans le domaine du genie logiciel.
Ce qui est aborde d’Hibernate :
– la persistance relativement transparente offerte par Hibernate aux objets Java.– la gestion de transactions de tres longue duree car elles implique des decisions d’un etre humain. Ces
tres longues transactions ne sont pas implantees par celles du SGBD : cela serait trop couteux. C’estHibernate qui propose de garantir la coherence des donnees manipulees par ce genre de transactionen affectant un numero (ou une date) de derniere version a certaines de ces donnees : une mise ajour d’une telle donnee ne sera acceptee par Hibernate que si la nouvelle valeur a ete produite apartir de la version la plus recente presente en base de donnees.
– lors de chargement d’objets persitants depuis la base de donnees, Hibernate peut etre amene acreer plusieurs objets persistants correspondant a une meme ligne de table. Si ces chargements sefont pendant la meme transaction SGBD (ou session Hibernate), Hibernate garantit l’unicite del’objet persistant correspondant a un chargement multiple d’une meme ligne.
18.1 Architecture d’utilisation
A priori Hibernate est une couche logicielle d’assez bas niveau puisque son role est principalement degerer la persistance d’objets Java. On peut penser qu’Hibernate sera principalement localise a cote duou des SGBD avec lesquels il travaille, autrement dit cote serveur, mais pas forcement sur la mememachine puisqu’Hibernate peut utiliser JDBC.
18.2 Quelques principes generaux
Une application Hibernate est parametrable par des proprietes, par exemple sur le choix du SGBDutilise, sans que le changement de ces parametres necessite une modification ou une recompilation dusource Java. En fait ces parametres sont utilises uniquement a l’execution de l’application ce qui faitqu’une erreur dans leur syntaxe ou semantique ne sera detectee qu’a l’execution. Ces parametres sontprincipalement indiques dans les deux fichiers hibernate.properties et hibernate.cfg.xml (voirsection 18.10 page 221).
211
212 CHAPITRE 18. INTRODUCTION A HIBERNATE
Un objet Java ne peut etre persistant, c’est a dire avoir sa place dans une table de la base dedonnees geree de facon plus ou moins transparente par Hibernate, que s’il est instance d’une classeJava mappee. La map d’une classe permet, entre autre, de savoir comment la table correspondantedevra etre implantee dans le SGBD d’accueil.
Techniquement, une classe peut etre mappee de deux manieres :
– en creant un fichier XML contenant la map de la classe, ainsi la map de personnel/Responsable.javasera dans le fichier personnel/Responsable.hbm.xml, (voir section 18.9.2 page 219)
– en ajoutant des annotations directement dans le source de la classe Java. Les annotations sont unenouveaute de Java5.
Hibernate donne un aspect partiellement declaratif a la persistance des informations de la base dedonnees qui sont gerees en memoire centrale (il reste cependant un peu de travail explicite a faire pourgarantir la persistance).
Hibernate s’adapte de facon transparente a environ 16 SGBD pour ce qui est des specificites syn-taxiques et semantiques : les ordres de creation du schema, les requetes et les ordres DML (insert (ycompris avec l’utilisation d’une sequence Oracle pour produire la valeur de la clef primaire), updateet delete) sont fabriques par Hibernate en fonction des modifications faites par le programme enmemoire centrale et du dialecte du SGBD sous-jacent.
Sans avoir a modifier l’application, on peut obtenir des services differents simplement en modifiantdes fichiers de proprietes et configuration. Par exemple :
– gerer la richesse des messages de trace imprimes dans la console de lancement (log4j)– demander la suppression de la base de donnees puis sa creation lorsque l’application demarre, optioncreate de l’application hbm2ddl.
– obtenir le source des ordres DDL creant la base de donnees– obtenir l’impression des ordres SQL produits et executes par Hibernate via JDBC– pouvoir changer de SGBD en modifiant simplement le fichier de proprietes Hibernate et eventuellement
certains fichiers mappant des classes (fichier <nom-classe>.hbm.xml).
18.3 Notion de session
Sous Hibernate, le chargement d’objet depuis la base de donnees ou la mise a jour de celle-ci avec lenouvel etat d’objets mappes ne peut se faire que via une session active.
L’activite d’une session est delimitee par :
– org.hibernate.Session session = sessionFactory.openSession ()
ou sessionFactory est un objet cree assez tot par l’application et qui couvre probablement laconnexion JDBC. Les informations de configuration sont dans le fichier hibernate.properties oudans hibernate.cfg.xml. Un de ces deux fichiers doit se trouver dans la racine des sources Java.
– session.close ()
18.4 Etats des objets mappes de l’application Java
Ces trois etats ne concerne que les instances de classes mappees.
persistent (persistant) : cet etat n’est possible que quand une session est ouverte, il correspond aufait qu’Hibernate s’occupe completement de la persistance de l’objet. L’etat de l’objet devientdetache des que la session est fermee.
detached (detache) : precedemment persistant, mais actuellement non associe a une Session, car lasession a ete fermee (close()).
transient (ephemere) : associe avec aucune Session, c’est l’etat d’un objet qui vient juste d’etre creepar new, que ce soit en dehors ou dans une session.
Seul un objet dans l’etat persistant peut faire l’objet d’une mise a jour automatique dans la base dedonnee lors d’un flush sur la session.
18.5. CLASSE MAPPEE ET FICHIER XML (POJO : PLAIN OLD JAVA OBJECTS) 213
Un objet ne peut etre dans l’etat persistant que pendant qu’une session est ouverte. Des que la sessionsera fermee, cet objet passera dans l’etat detache.
Les changements d’etat d’une instance de classe mappee se font aussi au sein d’une session :
– un objet instancie directement avec new est transient : il n’est associe a aucune session. Pendant unesession il devient persistent suite aux operations : session.save(obj), session.persist(obj) ousession.saveOrUpdate(obj).
– un nouvel objet obtenu, pendant une session, depuis la base de donnees par session.get() ousession.load() est persistent. Il devient transient avec session.delete(obj). Il devient detachedlors de la fermeture de la session (session.close())
– Un ancien objet persistent pendant une session precedente, est initialement detached lors d’unenouvelle session. Il devient persistent avec session.update(obj), session.saveOrUpdate(obj),session.lock(obj) ou comme nouvelle instance persitante avec session.merge(obj).
Fig. 18.1 – Cette figure resume une partie des transitions d’etats possibles pendant une session. Lesdeux seuls etats initiaux possibles (transient et persistant) sont indiques par ⇑. Remarquez qu’il n’ya que des methodes de Session, la session devant etre ouverte.
Détaché
session.delete(o) session.close()
session.update(o)session.saveOrUpdate(o)
session.save(o)session.saveOrUpdate(o)
session.persist(o)
Transient Persistant
new session.get()session.load()
Les objets persistants modifies sont detectes lors d’un flush() de la Session et des ordres SQL(insert, update ou delete) sont alors executes pour garantir leur persistance.
18.5 Classe mappee et fichier XML (POJO : Plain Old Java Ob-
jects)
Une classe X est dite mappee si un fichier X.hbm.xml ou X est le nom de la classe lui est associe. Cefichier decrit, en XML, l’aspect relationnel des objets de cette classe ainsi que des associations qu’ilsentretiennent avec d’autres classes mappees (clef primaire, clef etrangere, . . .).
Le code Java d’une classe mappee X ressemble a un BEAN, c’est a dire que la classe doit disposerde methodes getXxx() et setXxx() ou xxx est une variable d’instance. Par ailleurs, X doit proposerun constructeur sans parametres qui doit etre visible dans le paquetage (ni public ni protected niprivate).
Clef primaire composee de plus d’une colonne : le programmeur decrit cette clef par une nouvelleclasse. Recommandation : utiliser un type reference pour le type de la clef car alors on dispose du nullde Java pour representer l’absence de valeur.
Definir equals() et hashCode() pour ces classes peut-etre utile dans certains cas.
214 CHAPITRE 18. INTRODUCTION A HIBERNATE
18.6 La notion de proxy : procuration, delegation de pouvoir
Un proxy est une politique consistant a differer une action tant qu’on n’a pas besoin de son resultat,on parle de politique paresseuse (lazy). Les proxies hibernate concernent, entre autre, le chargementdes objets depuis la base de donnees.Par exemple, le chargement d’un objet mappe lors d’une requete ne charge a priori pas les autresobjets qu’il peut referencer par ses clefs etrangeres. Cependant ces objets references seront charges deslors qu’on tentera d’y acceder via l’objet qui les reference, mais ceci a condition de le faire au coursd’une Session active.On obtient ce comportement paresseux avec la propriete lazy="true" qui peut etre specifiee adifferents niveaux de precision de la configuration. Par exemple pour forcer le chargement de l’objetreference par une clef etrangere lors du chargement de l’objet referencant on peut ajouter le parametrelazy="false" a la colonne many-to-one du fichier map de la classe referencante, voir section 18.9.3page 221.Ici la paresse n’est pas un defaut : elle reconnaıt que le chargement d’objets peut etre differe tantqu’on n’en a pas besoin et permet donc de gagner du temps.
18.7 Architecture logicielle
Fig. 18.2 – Une (*) signifie qu’on peut avoir plusieurs instances simultanees, le (1) de Transaction
signifie qu’on a a un moment donnee au plus une transaction produite par une meme Session.
Un fichier <NomClasse>.hbm.xml
que le source .java correspondant.peut etre rangé dans le meme répertoire.
<code>
SessionFactory(*)
Session(*)
Transaction(1)
/hibernate.cfg.xml
/hibernate.properties et d’autres (i.e. log4j.properties)
Classes Java Fichiers de configuration
Les <NomClasse>.hbm.xml des classes mappées
IDEE :
Les fichiers de configuration :– La connexion a laquelle correspond une Session est par defaut une connexion JDBC, d’ou pour
configurer l’application la necessite d’indiquer de quel SGBD il s’agit.Les fichiers Java de org.hibernate :– SessionFactory memorise de facon immuable les parametres de la configuration de l’application.
En general une application ne dispose que d’une instance de SessionFactory.– Comme le dit la documentation : Session est l’interface centrale de l’abstraction de la persistance.
Sa duree de vie est determinee par le debut et la fin de sa transaction logique.– Le <code> execute pendant une Transaction utilise les methodes (entre autres) de la Session
ayant fourni cette Transaction. Ces methodes permettent principalement de gerer la persistancedes objets qu’elles manipulent. Par exemple, les objets persistants modifies ou crees ou detruits,grace aux methodes de Session, seront l’objet d’update, d’insert ou de delete lors d’un flush()
sur la session courante ou lors du commit() de la transaction.
18.8 Deux classes et beaucoup d’interfaces
Le nombre important d’interfaces a la meme signification qu’en JDBC : les interfaces fixent les fonction-nalites que le programmeur poura utiliser, en revanche chaque interface est probablement implantee
18.8. DEUX CLASSES ET BEAUCOUP D’INTERFACES 215
par autant de classes qu’il y a de SGBD auquels Hibernate est capable de s’adresser.
Les classes d’implantation seront choisies lorsque les fichiers de proprietes et de configuration aurontete charges par le programme.
Classe org.hibernate.HibernateException
C’est une java.lang.RuntimeException : pas besoin de la documenter avec une clause throws. Qua-siment toutes les methodes Hibernate sont susceptibles de declencher cette exception.
Classe org.hibernate.cfg.Configuration
Le constructeur de Configuration utilise le fichier hibernate.properties ou plutot hibernate.cfg.xml.– Configuration configure ()
lit les mapping et les proprietes dans hibernate.cfg.xml– SessionFactory buildSessionFactory ()
cree une SessionFactory correspondant a la configuration.
18.8.1 Interface org.hibernate.SessionFactory
Le tout premier objet a creer, il est ensuite immuable. Il est obtenu par :
new org.hibernate.cfg.Configuration ().configure ().buildSessionFactory () ;
qui entre autre lit les fichiers <nom-classe>.hbm.xml pour mettre en place le cadre de persistance.– Session openSession()
cree une connexion et ouvre une Session sur celle-ci.– void close()
ferme cette SessionFactory en relachant toutes les ressources : les caches, le jeu de connexions,. . .. Toutes les Session doivent avoir ete fermees au prealable.
18.8.2 Interface org.hibernate.Session
La classe centrale offrant la notion de persistance ! Le cycle de vie d’une Session est borne par le debutet la fin d’une transaction logique (une longue transaction logique peut etre realisee par plusieurstransactions du SGBD).C’est seulement lorsqu’une Session est ouverte, ainsi qu’une transaction que les objets des classespersistantes peuvent profiter de cette persistance, en general la mise a jour de la base de donnees sefait de facon optimisee lors de la validation de la transaction logique (methode commit())
Methodes pour gerer une Session
Le terme anglais flush signifiera ici synchroniser l’etat de la base de donnees avec celui de la memoirecentrale, c’est a dire que lors d’un flush, Hibernate prendra en compte toutes les modifications effectueessur les objets persistants pour les traduire en ordres SQL qu’il fait executer pas le SGBD. Ces ordresSQL pourront etre des insert, update ou delete.– void setFlushMode (org.hibernate.FlushMode flushMode)
par exemple
org.hibernate.FlushMode.COMMIT
Le flush() de la Session aura lieu quand Transaction.commit() sera execute.
org.hibernate.FlushMode.MANUAL
Il faudra appeler explicitement la methode flush() poru synchroniser l’etat de la base dedonnees avec celui de la memoire centrale (flush).
– void flush ()
Le flush1 consiste a executer les ordres SQL permettant de synchroniser l’etat de la base de donneesavec celui de la memoire centrale.1flush = faire jaillir, nettoyer a grande eau, to flush the lavatory = tirer la chasse d’eau.
216 CHAPITRE 18. INTRODUCTION A HIBERNATE
– void clear ()
Pour vider le cache (gestion des ressources).– org.hibernate.Session close ()
fin de la session
Par defaut un flush est effectue aux instants suivants :
– avant l’evaluation d’une requete,– lors du commit() de la Transaction
– lors d’un appel explicite a flush() (ouf !)
Q. 252 Pourquoi la documentation dit-elle qu’un flush doit etre execute avant l’evaluation d’unerequete ?
Voici deux possibilites pour eviter la saturation du cache de second niveau :
– Desactiver le cache de second niveau :
hibernate.cache.use_second_level_cache false
hibernate.jdbc.batch_size 20
taille du paquet JDBC.
– ou bien en appelant successivement, eventuellement plusieurs fois dans une meme transaction lo-gique, les deux methodes suivantes :
session.flush () ; // Effectue toutes les modification en memoire centrale dans la BD
session.clear () ; // Detruit toutes les instances mappees ainsi que save update delete
Demarrer une Transaction pour cette Session
– org.hibernate.Transaction t = session.beginTransaction()
Methodes de Session gerant les objets persistants
– void delete (Object object)
Supprime l’object persistant, suppression en cascades si cascade="delete" dans le mapping.– Object get (Class classeMappee, Serializable id)
classeMappee est une classe mappee, c’est a dire que dans la base de donnees lui correspond unetable. Renvoie soit :– une nouvelle instance de la classe classeMappee initialisee avec les informations trouvees dans la
base de donnees pour la ligne identifiee par la valeur de id. Cette instance est creee persistante.– null si aucune ligne de la table de la base de donnees n’a comme clef la valeur de id
Un exemple relatif a celui des sections 18.9.1 page 218 et 18.9.2 page 219 :
Responsable leader = session.get (personnel.Responsable.class, new Long (51)) ;
– Object load (Class theClass, Serializable id)
Comme get(), mais l’objet doit exister dans la base de donnees sinon c’est une erreur, exceptionHibernateException. Dans la mesure ou l’entite est censee exister dans la base de donnees, il estaussi possible de charger dans une instance existante qui doit etre transient :
personnel.Responsable r = new personnel.Responsable () ;
session.load (r, new Long (55)) ;
– void lock(Object object, org.hibernate.LockMode lockMode)
La classe LockMode definit des constantes, par exemple :– LockMode.NONE qui ne demande pas de verrou, sauf eventuellement un verrouillage en lecture
(Oracle ne verrouille pas les lignes en lecture). Permet de reassocier object avec la session.– LockMode.UPGRADE verrouillage de mise a jour materialise par un select ... for updateL’option cascade="lock" dans les fichiers de mapping pour appliquer le verrouillage aussi auxentites referencees par des clefs etrangeres.
18.8. DEUX CLASSES ET BEAUCOUP D’INTERFACES 217
– Object merge (Object object)
Copie l’etat de l’objet donne sur l’objet persistant ayant le meme identifiant, avec eventuellementchargement de ce dernier. Renvoie l’instance mise a jour et persistante.cascade="merge" pour appliquer la fusion aussi aux entites associees par des references de clefetrangeres.
– void persist (Object object)
L’objet transient devient persistant. L’objet n’est pas forcement identifie immediatement, mais ilsera sauve dans la base au moins lors du flush() de la session. Si persist() est appele alors qu’iln’y a pas de transaction en cours alors il est garanti qu’il ne fera pas de insert dans la BD.cascade="persist" pour appliquer la persistance aussi aux entites associees par des references declef etrangeres.
– Serializable save (Object object)
L’objet transient devient persistant. L’identifiant de object est genere automatiquement si la mapde la classe declare un generateur pour cette clef primaire. On peut considerer l’appel a cettemethode comme un prealable a une instruction insert de SQL. Renvoie l’identifiant (i.e. la valeurJava de la clef primaire).cascade="save-update" pour appliquer la meme operation aux entites associees par des referencesde clef etrangeres.
– void saveOrUpdate (Object object)
Fait, suivant l’etat de object :– un save() si object est transient,– un update() si object est detache.object passe donc dans l’etat persistant.cascade="save-update" pour appliquer la meme operation aux entites associees par des referencesde clef etrangeres.
– void update (Object object)
L’instance detachee object devient persistante.cascade="save-update" pour appliquer la meme operation aux entites associees par des referencesde clef etrangeres.
Methodes fabricant une requete de la Session
– org.hibernate.Query createQuery (String queryString)
cree une requete avec queryString en syntaxe HQL
Interface org.hibernate.Query
Representation objet d’une requete Hibernate. L’ordre peut comporter des parametres nommes, parexemple :nom Un meme parametre peut apparaıtre plusieurs fois dans la requete. On peut aussi utiliserle ? pour un parametre comme en JDBC, attention ils sont numerotes a partir de 0 contrairement aJDBC. On ne peut pas melanger les deux notations de parametre. La duree de vie d’une requete estlimitee a celle de la Session qui l’a cree.
– executeUpdate()
execute l’instruction update ou delete– List list()
renvoie le resultat d’une requete comme une liste, si plusieurs entites par ligne l’element de liste estun Object[]
– Object uniqueResult() pour recuperer l’unique resultat d’une requete ; null si aucun resultat etexception NonUniqueResultException si plus d’un resultat.
– Query setInteger(int position, int val)
– Query setInteger(String name, int val)
– Query setString(int position, String val)
– Query setString(String name, String val)
218 CHAPITRE 18. INTRODUCTION A HIBERNATE
Interface org.hibernate.SQLQuery
Interface org.hibernate.Transaction
Il s’agit ici de transactions dites logiques qui ne correspondent pas directement aux transactions duSGBD sous-jacent. Une transaction logique correspond a un dialogue avec l’utilisateur qui peut etretres long par rapport a la duree souhaitable d’une transaction SGBD. C’est ce qui explique (entreautres) qu’en general une transaction logique recouvre plusieurs transactions du SGBD.
La creation des transactions est liee a la propriete hibernate.transaction.factory_class qui in-dique la fabrique (factory) a utiliser. Par defaut cette fabrique est JDBCTransactionFactory qui four-nit des JDBCTransaction, ces deux classes se trouvant dans le paquetage org.hibernate.transaction.
On decrit ici le fonctionnement des methodes de ces JDBCTransaction :
void commit() Fait le flush() de la Session associee. La transaction correspondante du SGBDest elle aussi validee (commit) mais seulement si elle a ete demarree par cette transactionlogique.
void rollback() Force la transaction correspondante du SGBD a faire un rollback.
18.9 Correspondance entre les classes persistantes et les entites (tables)
correspondantes de la base de donnees
Pour qu’une instance soit potentiellement persistante, il est obligatoire que sa classe soit mappee (cemot existe dans le Petit Robert).
Le mapping d’une classe dit comment une instance de la classe doit etre stockee dans une table rela-tionnelle du SGBD.
Le mapping d’une classe s’effectue avec un fichier xml separe du fichier Java decrivant la classe. Avecla couche annotation introduite par Java5, ce mapping peut aussi etre specifie directement dans lesource Java grace a des annotations, mais nous ne verrons pas cette possibilite.
Un objet ayant une clef etrangere sur un autre, est declare en Java comme designant cet objet alorsque dans le mapping on specifiera cet attribut comme une clef etrangere.
Dans l’exemple suivant un rayon de magasin a au plus un responsable et un responsable peut l’etrede plusieurs rayons.
18.9.1 Les classes qui seront mappees
package personnel ;
public class Responsable {
private Long id ; // type reference preferable car pointeur null
private String nom ;
private int nbRayons ;
private Responsable () { id = null ; nom = null ; nbRayons = 0 ; }
public Responsable (String nom) {
this.id = null ; // affecte par Hibernate, voir Responsable.hbm.xml
this.nom = nom ; this.nbRayons = 0 ;
}
void setId (Long id) { this.id = id ;}
public Long getId () {return id ;}
public void setNom (String nom) { this.nom = nom ;}
public String getNom () { return nom ;}
18.9. CORRESPONDANCE ENTRE LES CLASSES PERSISTANTES ET LES ENTITES (TABLES) CORRESPOND
public void setNbRayons (int nbRayons) { this.nbRayons = nbRayons ;}
public int getNbRayons () { return nbRayons ;}
public void addNbRayons (int n) { this.nbRayons += n ;}
}
Le rayon Java n’a pas a stocker la valeur de clef etrangere de son responsable : un rayon connaıtdirectement son responsable !, mais attention, par defaut le mode de chargement des entitesassociees est paresseux et le responsable ne sera par charge, voir 18.9.3 page 221 :
package magasin ;
public class Rayon {
private Long id ; private String nom ; private personnel.Responsable responsable ;
private Rayon () { id = null ; nom = null ; responsable = null ; }
public Rayon (String nom, personnel.Responsable responsable) {
this.id = null ; this.nom = nom ; this.responsable = responsable ;
}
public setResponsable (personnel.Responsable resp) {
if (responsable != null) { responsable.addNbRayons (-1) ;}
responsable = resp ;
if (responsable != null) { responsable.addNbRayons (+1) ;}
} ...
}
Remarquer que les constructeurs sans parametres peuvent etre prives : ainsi l’application ne pourrapas fabriquer d’objet non ou mal initialises.
18.9.2 Les map de ces deux classes Responsable et Rayon
Les fichiers de mapping sont alors :– Le mapping de la classe personnel.Responsable : personnel/Responsable.hbm.xml<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="personnel.Responsable" table="Responsable">
<id name="id" type="long" column="id">
<generator class="sequence">
<param name="sequence">Seq_Responsable</param>
</generator>
</id>
<property name="nom" column="nom" length="20"/>
</class>
</hibernate-mapping>
<class> indique qu’a la classe Java personnel.Responsable correspondra la table Responsable
dans la base de donnees.
<id> permet de decrire la clef primaire de cette entite. Le <generator> de <id> permet de dire queles clefs primaires seront generees automatiquement par le SGBD lors du save() d’une nouvelleinstance. Ici le generateur de clef est une sequence qui convient a Oracle et Postgres dans la mesure
220 CHAPITRE 18. INTRODUCTION A HIBERNATE
ou la clef primaire est un entier. Cet objet sequence s’appelle Seq_Responsable dans le schema dela base de donnees.
<property> introduit une colonne normale pour le nom du responsable avec une longueur maximalede 20 caracteres.
Dans chaque paragraphe decrivant un attribut, le parametre name donne le nom de l’attribut dansla classe Java et column sera le nom de la colonne correspondante dans la table.
Enfin on voit que l’attribut nbRayons de la classe Responsable n’est pas persistant : on nelui fait correspondre aucune colonne de table dans le mapping.
Question : comment faire pour que l’attribut nbRayons ait toujours une valeur coherente avec lenombre de rayons effectivement sous sa responsabilite ? Il faudrait qu’Hibernate, pour une ligned’une table ne fabrique pas plus d’une instance (cela est possible au sein d’une session) et sache laretrouver quand c’est necessaire.
– Le mapping de la classe magasin.Rayon : magasin/Rayon.hbm.xml<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="magasin.Rayon" table="Rayon">
<id name="id" type="long" column="id">
<generator class="sequence">
<param name="sequence">Seq_Rayon</param>
</generator>
</id>
<property name="nom" column="nom" length="50"/>
<many-to-one lazy="true" name="responsable" column="responsable"
foreign-key="Rayon_Responsable_FK" class="personnel.Responsable"
not-null="false" cascade="none" />
</class>
</hibernate-mapping>
<class> indique qu’a la classe Java magasin.Rayon correspondra la table Rayon dans la base dedonnees.
<id> permet de decrire la clef primaire de cette entite. Le <generator> de <id> permet de dire que lesclefs primaires seront generees automatiquement par le SGBD lors du save() d’une nouvelle instance.Ici le generateur de clef est une sequence qui convient a Oracle et Postgres dans la mesure ou la clefprimaire est un entier. Cet objet sequence s’appelle Seq_Rayon dans le schema de la base de donnees.
<property> introduit une colonne normale pour le nom du rayon avec une longueur maximale de 50caracteres.
Dans chaque paragraphe decrivant un attribut, le parametre name donne le nom de l’attribut dans laclasse Java et column sera le nom de la colonne correspondante dans la table.
<many-to-one> peut etre interprete comme suit : plusieurs rayons peuvent etre diriges par un memeresponsable, autement dit plusieurs rayons peuvent referencer le meme responsable. Cela correspond
18.10. LES DEUX FICHIERS PRINCIPAUX DE CONFIGURATION 221
a une clef etrangere dans l’entite Rayon.
ResponsableRayon dirige
notation anglaise
n 1
tomany one
ResponsableRayon dirige
notation francaise
n1
Le parametre cascade peut avoir la simple valeur "all" pour dire que toutes les operations doiventetre cascadees sur l’objet associe ou "none", qui est la valeur par defaut, pour dire qu’aucune ne doitl’etre. La notion de cascade n’a en general pas grand sens pour les associations <many-to-one> et<many-to-many>, mais peut en avoir pour <one-to-one> et <one-to-many>
18.9.3 Chargement paresseux par defaut de l’objet designe par une clef etrangere
Lors du chargement d’un rayon, le mode de chargement du responsable de ce rayon est paresseux pardefaut (lazy) : il ne se fera que quand ce sera necessaire. C’est a dire que lors du get() ou load()
d’un rayon, son attribut responsable sera initialise sur un proxy non initialise avec les informations duresponsable.
Ce responsable (s’il est defini) sera charge automatiquement (mais seulement pendant une session)quand on tentera d’y acceder, par exemple en lui demandant son nom : (rayon.getResponsable().getNom()).Mais ceci ne pourra se faire que lorsqu’une Session et peut-etre aussi une Transaction sont ouvertes.Si ce n’est pas le cas, on aura une erreur d’execution.
Cette politique paresseuse par defaut a le merite de ne faire le travail que quand il est necessaire etdonc d’ameliorer les performances du programme.
Une autre approche, qui peut s’averer plus couteuse, consiste a dire que lors du chargement d’un rayonon veut que son responsable soit lui aussi systematiquement charge. Pour cela on peut mettre a fauxla politique paresseuse dans le paragraphe <many-to-one .../> de magasin/Rayon.hbm.xml avec leparametre lazy="false" :
<hibernate-mapping>
<class name="magasin.Rayon" table="Rayon">
...
<many-to-one ... lazy="false" .../>
...
</class>
</hibernate-mapping>
Une autre solution est d’utiliser la methode suivante de org.hibernate.Hibernate :– public static boolean isInitialized (Object proxy)
Dit si le proxy est initialise.– public static void initialize (Object proxy)
Initialise le proxy.
18.10 Les deux fichiers principaux de configuration
Il s’agit des deux fichiers hibernate.properties et hibernate.cfg.xml qu’on peut maintenir dansle repertoire racine des sources Java et qu’il faut copier dans le repertoire racine des classes car c’estla qu’Hibernate les cherchera.
– src/hibernate.properties
C’est l’ancien fichier de configuration d’Hibernate dans un format texte classique, par exemple :
222 CHAPITRE 18. INTRODUCTION A HIBERNATE
## dialecte a utiliser
hibernate.dialect org.hibernate.dialect.Oracle9Dialect
La documentation API de org.hibernate.cfg.Environment fournit des informations sur ce fichier.Voici un petit sous-ensemble des proprietes du fichier src/hibernate.properties :
## SGBD Oracle 10
## dialecte a utiliser
hibernate.dialect org.hibernate.dialect.Oracle9Dialect
## driver JDBC, URL de connexion
hibernate.connection.driver_class oracle.jdbc.driver.OracleDriver
hibernate.connection.url jdbc:oracle:thin:@//vlaskop.fil.univ-lille1.fr:1521/filora10
hibernate.connection.username <un-nom>
hibernate.connection.password <un-mot-de-passe>
## nombre minimum de connexions dans le pool (fond commun)
hibernate.c3p0.min_size 5
## nombre maximum de connexions dans le pool
hibernate.c3p0.max_size 20
...
## voir le source SQL genere et execute
hibernate.show_sql true
## ce source est proprement formate !
hibernate.format_sql true
## hbm2ddl signifie Hibernate mapping vers DDL
## l’option create : detruit la base puis la recree chaque fois
## que l’application est demarree
hibernate.hbm2ddl.auto create
## Specifier le niveau d’isolation JDBC defini par une
## des constantes de l’interface java.sql.Connection :
## public static final int TRANSACTION_NONE 0
## public static final int TRANSACTION_READ_COMMITTED 2
## public static final int TRANSACTION_READ_UNCOMMITTED 1
## public static final int TRANSACTION_REPEATABLE_READ 4
## public static final int TRANSACTION_SERIALIZABLE 8
## On choisit Read Committed :
hibernate.connection.isolation 2
– src/hibernate.cfg.xml
est le nouveau fichier de configuration d’Hibernate ecrit en XML, on y retrouve les memes parametesque dans hibernate.properties :<?xml version=’1.0’ encoding=’utf-8’?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- SQL dialect org.hibernate.dialect.Oracle10gDialect -->
<property name="dialect">org.hibernate.dialect.Oracle10gDialect</property>
18.10. LES DEUX FICHIERS PRINCIPAUX DE CONFIGURATION 223
<property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<property name="connection.url"> URL d’acces au SGBD </property>
<property name="connection.username">toto</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- Enable Hibernate’s automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Supprime puis recree la base de donnees a chaque demarrage de l’application
"hbm2ddl" signifie Hibernate mapping vers DDL, autrement dit Hibernate fabrique
les ordres SQL de creation de la base de donnees (les tables avec leurs contraintes,
les generateurs d’entiers, ...) a partir des fichiers de mapping, par exemple
"modele/donnee/Livre.hbm.xml". C’est ce qu’on appelle de la conception descendante,
on part de la specification de haut niveau pour creer une implantation.
1) construire le schema SQL en fonction du dialecte, ... et des fichiers
de mapping des classes persistantes
2) supprimer, dans le le SGBD, toutes les tables du schema et autres
objets comme les sequences destinees a produire des valeurs de clef,
3) re-cree dans le SGBD, toutes les tables et autres objets, lorsque la
SessionFactory est construite, autrement dit repartir de zero a chaque execution.
Mettre en commentaire si vous ne voulez pas. -->
<property name="hbm2ddl.auto">create</property>
<!-- C3P0 Connection Pool : nombre minimum de connexions dans le pool -->
<property name="c3p0.min_size">2</property>
<!-- nombre maximum de connexions dans le pool -->
<property name="c3p0.max_size">3</property>
<property name="c3p0.timeout">300</property>
<property name="c3p0.max_statements">50</property>
<property name="c3p0.idle_test_period">3000</property>
<!-- Specifier le niveau d’isolation JDBC defini par une
des constantes de l’interface java.sql.Connection :
TRANSACTION_NONE 0 | TRANSACTION_READ_UNCOMMITTED 1
TRANSACTION_READ_COMMITTED 2 | TRANSACTION_REPEATABLE_READ 4
TRANSACTION_SERIALIZABLE 8 --> <!-- Read committed : -->
<property name="connection.isolation">2</property>
<!-- Fonctionnement par defaut de JDBC : autocommit de chaque
instruction par defaut (deconseille). -->
<property name="connection.autocommit">false</property>
<!-- Relachement des connexions
auto (valeur par defaut conseillee) | on_close (deconseille)
after_transaction (JDBC) | after_statement (JTA) -->
<property name="connection.release_mode">auto</property>
<!-- Voir le SQL envoye au SGBD--> <property name="show_sql">true</property>
224 CHAPITRE 18. INTRODUCTION A HIBERNATE
<!-- Et bien formate --> <property name="format_sql">true</property>
<!-- Avec des commentaires --> <property name="use_sql_comments">true</property>
<!-- Liste des fichiers mappant les classes dont les instances peuvent persister -->
<mapping resource="personnel/Responsable.hbm.xml"/>
<mapping resource="magasin/Rayon.hbm.xml"/>
</session-factory>
</hibernate-configuration>
On peut ou non prefixer les noms des attributs (name) avec�� ��hibernate. .
Lors de la compilation ces deux fichiers ainsi que les maps de classes doivent etre copies dans lerepertoire classes.
Attention : toute erreur dans ces fichiers de configuration ne sera pas detectee lors de la compilationmais lors de l’execution du programme.
18.11 Creation de la SessionFactory
Cette creation doit se faire au tout debut de l’execution de l’application, par exemple de cette maniere :
package persistance ;
import org.hibernate.cfg.Configuration ;
public class HibernateUtil {
private static final org.hibernate.SessionFactory sessionFactory ;
static {
try {
sessionFactory = new Configuration().configure().buildSessionFactory() ;
} catch (Throwable ex) {
System.err.println("SessionFactory non creee. " + ex.getMessage ()) ;
throw new ExceptionInInitializerError (ex) ;
}
}
public static org.hibernate.SessionFactory getSessionFactory () {
return sessionFactory ;
}
public static void shutdown () { // fin de l’application
sessionFactory.close () ;
}
}
L’initialisation de la SessionFactory faite par du code static est faite lors du chargement en memoirede la classe HibernateUtil. Cette initialisation prend en compte les fichiers de configuration dansl’ordre suivant :
1. le contenu de classes/hibernate.properties
2. le contenu de classes/hibernate.cfg.xml
3. les contenus de tous les mapping mentionne dans classes/hibernate.cfg.xml dans notreexemple, il s’agit de personnel/Responsable.hbm.xml et magasin/Rayon.hbm.xml
18.12 Un outil pour assurer plus simplement la persistance
Une instance d’une classe mappee n’est pas forcement persistante. Elle ne peut l’etre que pendantqu’une Session et une Transaction sont ouvertes et si elle a ete passee en parametre a une desmethodes de la Session.
Un exemple typique de code rendant persistantes les modifications faites a un objet :
18.12. UN OUTIL POUR ASSURER PLUS SIMPLEMENT LA PERSISTANCE 225
void renommer (Responsable leader, String nouveauNom) throws Exception {
leader.setNom (nouveauNom) ;
final org.hibernate.Session session =
persistance.HibernateUtil.getSessionFactory ().openSession () ;
session.setFlushMode (org.hibernate.FlushMode.COMMIT) ;
org.hibernate.Transaction tx = null ;
try {
tx = session.beginTransaction () ;
//
// (1) leader est suppose detache (autrement dit il a deja ete persistant)
//
session.update (leader) ;
//
// (2) leader est persistant
//
tx.commit () ;
} catch (Exception e) {
if (tx != null) tx.rollback () ;
throw e ;
} finally {
session.close () ; // fermeture garantie de la session
}
//
// Ici, leader est detache
//
}
C’est lors de tx.commit () que Hibernate se rendra compte que l’objet designe par leader est devenupersistant (grace a l’execution de la methode update()) et qu’il a ete modifie, il fabriquera et executeradonc l’ordre SQL adapte au dialecte du SGBD qui rendra persistante cette modification.
Q. 253 Que se passerait-il si au point (1) de l’exemple ci-dessus l’objet designe par leader etait enfait dans l’etat transient ? voir la figure 18.1 page 213.
Q. 254 Comment peut-on resoudre facilement le probleme de la question precedente ?
Une autre solution pour unifier le code :
package persistance ;
public abstract class TraitementPersistant {
public final void executionPersistante () throws Throwable {
final org.hibernate.Session session =
persistance.HibernateUtil.getSessionFactory ().openSession () ;
org.hibernate.Transaction tx = null ;
try {
tx = session.beginTransaction () ;
faire (session) ;
tx.commit () ;
} catch (org.hibernate.HibernateException exp) {
if (tx != null) tx.rollback () ;
if (exp.getThrowableCount () == 1) throw exp ;
else throw exp.getCause () ;
} finally {
session.close () ;
}
}
226 CHAPITRE 18. INTRODUCTION A HIBERNATE
/** Methode forcement executee avec une session active passee en parametre. */
protected abstract void faire (org.hibernate.Session session) throws Exception ;
}
Il suffit d’heriter de cette classe pour implanter la methode faire() puis demander son execution avecexecutionPersistante().
18.12.1 Un exemple d’utilisation de l’outil TraitementPersistant
Par exemple le renommage d’un responsable donne en 18.12 peut se faire comme suit avec une classeinterne et statique :
class Traitement { ...
private static class Renommer extends persistance.TraitementPersistant {
private Responsable leader ;
private String nouveauNom ;
public void set (Responsable leader, String nouveauNom) {
this.leader = leader ; this.nouveauNom = nouveauNom ;
}
protected void faire (org.hibernate.Session session) throws Exception {
leader.setNom (nouveauNom) ; session.update (leader) ;
}
}
private static final Renommer renommer = new Renommer () ;
void renommer (Responsable leader, String nouveauNom) throws Throwable {
renommer.set (leader, nouveauNom) ; renommer.executionPersistante () ;
}
}
Q. 255 Ecrire la classe static qui supprime un Responsable et la methode qui l’utilise.
18.13 Le langage HQL
Permet principalement d’ecrire des requetes pour createQuery().
18.14 Les transactions
Si Hibernate fonctionne au dessus de JDBC, alors il necessite que le auto commit soit a faux.
La duree de vie d’une session correspond a exactement une transaction.
Une idee est que pendant une transaction (et donc sa session) aucun dialogue interactif avec l’utili-sateur ne doit avoir lieu. Si ce n’est pas le cas, la duree de la transaction risque d’etre tres longue etde degrader les performances transactionnelles, par exemple si un verrou est pose en debut de tran-saction sur une table pendant une heure parce que l’utilisateur a du discuter avec ses collaborateurspour prendre une decision.
L’idee consiste alors a distribuer sur plusieurs transactions un traitement necessitant un dialogue avecl’utilisateur, par exemple :
1. une premiere transaction SGBD charge les informations necessaires au dialogue puis elle setermine,
2. le dialogue a lieu en dehors de toute transaction SGBD : l’utilisateur consulte et modifie locale-ment les donnees recuperees par la premiere transaction,
18.14. LES TRANSACTIONS 227
3. une seconde et derniere transaction SGBD a lieu pour rendre persistantes les modificationsdemandees par l’utilisateur ou bien elle devrait echouer si le nouvel etat de la base de donneesn’est plus coherent avec ce que demande l’utilisateur.
Pour mettre cela en place Hibernate propose :
– une gestion automatique de versions qui permet de savoir si une modification concurrente a etefaite pendant la reflexion de l’utilisateur. Cette verification se fait generalement en fin de dialogue.Utiliser le tag <version> pour qualifier l’attribut Java et la colonne de la table contenant le numerode version le plus recent dans la map de la classe.
Par exemple, on veut pouvoir editer des messages en garantissant qu’une mise a jour ne sera enregistreedans la base que si elle a ete faite a partir de la version la plus recente du message :
<hibernate-mapping>
<class name="modele.Message" table="Message">
<id name="id" type="long" column="id">
<generator class="sequence">
<param name="sequence">Seq_Message</param>
</generator>
</id>
<version name="num_version" column="num_version"
type="long" generated="never" insert="true"/>
<property name="contenu" column="contenu" length="20"/>
</class>
</hibernate-mapping>
Pour gerer les versions on doit ajouter un attribut/colonne qui s’appelle ici num_version et est unentier. Lors de la creation d’un nouveau message (save()), cette num_version est initialisee a 0 parHibernate, puis a chaque mise a jour valide du message num_version est incrementee. Une mise ajour n’est valide que si le num_version stocke dans l’objet Java est egal au num_version stocke dansla base de donnees, sinon une erreur Hibernate arretera cette mise a jour. En effet si le num_version
stocke dans la base de donnees est different de celui de l’objet Java c’est que quelqu’un d’autre aentre-temps modifie cette ligne de la base de donnees.
– <generated="never"> signifie que la valeur de num_version n’est pas geree par la base de donneeset donc l’est certainement par Hibernate.
– <insert="true"> dit que lorsque le save() est valide, la colonne num_version apparaıtra dansl’ordre insert correspondant (sinon elle n’apparaıt pas et alors la base de donnees doit garantir unevaleur par defaut pour cette colonne num_version).
Quand une application tente de mettre a jour un message, Hibernate charge la version de ce messagedepuis la base de donnees, si cette version est plus recente que celle de l’objet mappe de l’application,une erreur est declenchee.
Ceci doit permettre d’empecher un utilisateur de sauver une modification faite a partir d’un etat ob-solete car il a deja ete modifie par un autre utilisateur, autrement dit de garantir la serialisabilite deces modifications. Le rollback() a faire apres une telle erreur devrait garantir que les autres mises ajour sont elles aussi annulees.
Voici un exemple :
228 CHAPITRE 18. INTRODUCTION A HIBERNATE
Utilisateur Fatigue Utilisateur EnForme
Charge le message d’id 57 (version 3)Charge le message d’id 57 (version 3)
Fatigue et EnForme ont chacun un objet Java de type Message contenantexactement les memes informations.
Reflechit sur le contenu Reflechit sur le contenu. . . . . .s’endort ? . . .. . . modifie le contenu
. . .
Sauve dans la BD le message d’id 57 avec sonnouveau contenu. Cela a pour effet de faire pas-ser la version a 4 dans la BD et dans l’objet Javade EnForme.Hibernate autorise la persistance de cette nou-velle version car elle a bien ete fabriquee a par-tir de la version immediatement precedente. Enfait, lors de cette modification, Hibernate com-mence par lire le numero de version actuellementdans la BD.
Se reveille ! goodbyeModifie le contenu de son objet Java puis tentede sauver dans la BD. Hibernate se rend compteque la BD contient la version 4 alors que Fa-tigue tente de faire persister une nouvelle va-leur fabriquee a partir de la version 3. Hiber-nate declenche donc une erreur pour demandera annuler cette tentative.
18.15 Unicite des objets Java mappes charges lors d’une meme ses-
sion/transaction
Hibernate gere automatiquement l’unicite de representation en memoire centrale. C’est a dire quesi pendant une transaction on charge plusieurs fois la meme ligne d’une table ou a cause d’un<lazy="false"> sur une clef etrangere, on obtiendra pour cette ligne un seul objet persistant enmemoire centrale.Mais attention cette unicite n’est assuree que pour les chargements multiples de la meme ligne quiont lieu pendant la meme session/transaction.
18.16 Exceptions Hibernate non recuperables
Par definition les exceptions Hibernate ne sont pas recuperables : on ne peut pas reparer le problemequ’elles signalent. Suite a une telle exception il faut alors absolument faire un rollback() de latransaction puis un close() de la session et enfin redeclencher cette exception.
18.17 Le verrouillage pessimiste
Hibernate utilise le systeme de verrouillage du SGBD.
18.18 Un outil pour assurer plus simplement la persistance
Il s’agit d’une classe abstraite dont la methode executionPersistante() met en place la session etla transaction necessaires a une execution persistante :
package persistance ;
18.18. UN OUTIL POUR ASSURER PLUS SIMPLEMENT LA PERSISTANCE 229
public abstract class TraitementDeSession {
public final void executionPersistante () throws Throwable {
final org.hibernate.Session session =
persistance.HibernateUtil.getSessionFactory ().openSession () ;
org.hibernate.Transaction tx = null ;
try {
tx = session.beginTransaction () ;
faire (session) ;
tx.commit () ;
} catch (org.hibernate.HibernateException exp) {
if (tx != null) tx.rollback () ;
if (exp.getThrowableCount () == 1) {
throw exp ;
} else {
throw exp.getCause () ;
}
} finally {
session.close () ;
}
}
/** Methode executee avec une session active passee en parametre. */
protected abstract void faire (org.hibernate.Session session) throws Exception ;
}
Septieme partie
Bases de donnees objet et compromisdu relationnel-objet
230
Chapitre 19
Le modele objet
Pourquoi un modele objet
Applications qui necessitent des SGBDONouvelles applications des BD faisant intervenir des informations a structure complexe.
R.G.G. Cattell. Object Data Management
– Ateliers de Genie Logiciel (AGL) : conception, specification, implementation, analyse, debogage,maintenance et evolution de programmes et de documents.
– Conception Mecanique Assistee par Ordinateur (MCAD) : vehicules spaciaux, batiments ...– Conception Electronique Assistee par Ordinateur (ECAD) : conception logique et physique.– Fabrication Assistee par Ordinateur (FAO) : voitures sur une chaine de montage, synthese chimique
...– Bureautique : gestion de l’information d’une entreprise (mail, documentation, ...)– Publication assistee par ordinateur (PAO) et Hypertextes : manip de documents complexes, docu-
ments a comportement dynamique.– Graphiques : representations graphiques d’objets complexes (souvent en lien avec la CAO et PAO)– Applications Scientifiques et Medicales : manipulation et analyse de representations chimiques, bio-
logiques, physiques.– Services systemes– Fabrication et controle temps reel– Les bases de connaissances
Les avantages generaux des objets : union de donnee et de code, implementation cachee.
Disposer d’une bonne integration entre langage declaratif (type SQL ou L4G) et langage imperatif(L3G). Un peu comme le fait PL/SQL dans Oracle.
19.1 Navigation : le retour
��
��
Dans le modele relationnel, une instance d’entite (une ligne) est identifiee par son contenu (par exemplesa clef primaire). Pour retrouver une ou des instances particulieres on est alors amene a effectuer unerecherche associative (i.e. par le contenu).
Par exemple, en supposant qu’une voiture possede exactement un proprietaire, on peut retrouver lescouples voiture/proprietaire par l’equi-jointure :
select v.numero, p.nom
from Voiture v inner join Personne p on v.proprietaire = p.id ;
232 CHAPITRE 19. LE MODELE OBJET
'
&
$
%
Dans le modele objet, une instance d’entite (un objet) peut aussi etre identifiee par son contenu et ilest donc possible de faire des recherches associatives, mais un objet est de toute facon identifie parson identifiant unique, ou OID (Object IDentifier). Cet identifiant permet de localiser plus ou moinsdirectement l’objet et il est alors possible de remplacer les operations d’equijointure sur cle etrangerepar des acces direct a l’objet qui sont a priori plus efficaces. Cet acces direct a un objet grace a sonidentifiant s’appelle la navigation.
Par exemple, en supposant qu’une voiture conserve non pas la clef de son proprietaire mais son OID, onpourra alors utiliser la navigation et ainsi simplifier la requete et eviter une equijointure. La navigations’exprime comme en Java ou en Ada par une notation pointee :
select v.numero, v.proprietaire.nom
from v in Voiture ;
19.1.1 SGBD et OID
– Oracle : Chaque objet d’une table objet se voit attribue un OID systeme de 16 octets unique dansla table.Il est aussi possible d’utiliser un OID base sur la clef primaire de l’objet : ceci evite de consommerles 16 octets de l’OID systeme et l’index qui va avec, et les chargements de table seront plus rapides.Les references d’objet (REF), qui permettent la navigation, peuvent n’etre basees que sur cet OID(systeme ou clef primaire) dans le cas de references a portee limitee a une table (scoped REF).Elles peuvent aussi etre capables de designer un objet sans qu’on connaisse a priori la table qui lecontient. Bien entendu, ces dernieres references occupent plus de place.
– Postgres : la colonne systeme oid pas forcement unique car seulement 4 octets mais on dispose ausside la colonne implicite tableoid, et ctid (couple numero de block, indice du tuple dans le bloc)pour Postgres).Mais pratiquement, que peut-on faire de ces informations ? ? ?
19.1.2 Problematique des OID
– les OID doivent rester constants lors des mises a jour de tuple, en effet lors d’une mise a jour, untuple peut carrement etre deplace sur le disque. Ainsi le ctid de Postgres qui est l’adresse physiquedu tuple dans sa table ne peut servir d’OID.
– Pour manipuler un objet persistant, il faut d’abord le charger en memoire centrale depuis le disque.L’OID doit permettre de designer l’objet qu’il soit ou non charge en memoire.
Par contre, une fois charge, pour que l’acces soit efficace, il faut utiliser le pointeur memoire. Lorsde differentes executions, l’objet sera charge a des adresses differentes, on ne peut donc pas utiliserson adresse memoire comme OID. Voir la figure 19.1.
– Plusieurs transactions peuvent tenter d’acceder au meme objet, . . .– Qu’advient-il des OID des objets detruits ? (predicat IS DANGLING en Oracle).
33 AAA 59 11 VVV 75
Dupont 11 VVV 75
33 AAA 59 Transaction 1
Transaction 2
Fig. 19.1 – Quelques objets charges en memoire centrale
19.2. L’ORIENTE OBJET : LES DEUX APPROCHES 233
19.2 L’oriente Objet : les deux approches
– puriste : on fait de l’objet pur (SGBDO)– pragmatique : on introduit la notion d’objet au-dessus du relationnel (SGBDRO)
19.3 SGBDO
A partir de 1988 sont apparus les premiers SGBDO comme O2 (INRIA), ObjectStore,
En septembre 1991, creation de l’ODMG (Object Database Management Group), groupe de reflexionpour l’elaboration d’un standard de SGBD0.
3 langages (derniere version en 1997) :
ODL (Equivalent de DDL)
OQL (En gros le SELECT a la mode objet)
OML langages de manipulation destine a etre integre dans C++, Smalltalk et Java.
19.3.1 Bourse ODL
La figure 19.2 donne un diagramme UML modelisant un systeme d’informations de la bourse.
NegociateurValeur
nb_titres
Offre Demande
porte_feuilles**
1
*
1
*
Ordre
Fig. 19.2 – Diagramme de classes UML du SI de la bourse
On definit des types d’objet par des interfaces, et des classes qui implementent des interfaces (tressimilaire a Java : heritage simple, implementation de plus d’une interface possible)
Une classe peut aussi indiquer, grace au mot clef�� ��extent , le nom de la collection (ou des collections)
destinee a heberger ses instances).
class Valeur (extent Les_Valeurs) {
attribute String nom ;
attribute Float cours ;
}
// Pour le porte-feuilles d’un negociateur, c’est a
// dire l’association N:N attribuee par le nombre de
// titres entre un Negociateur et une valeur,
// on a besoin d’introduire une entite pour cet attribut
// Remarque : on fait quelque chose de tres similaire en relationnel
// en creant une relation pour chaque association N:N
234 CHAPITRE 19. LE MODELE OBJET
class Element_De_Porte_Feuilles (extent Les_Elements) {
attribute Int nb_titres ;
attribute Valeur la_valeur ;
relationship Negociateur le_negociateur
inverse Negociateur::porte_feuilles ;
}
-- La clef ’id’ permettra un acces associatif
class Negociateur (extent Les_Negociateurs key id) {
//
// Declaration des attributs (variables d’instance)
//
attribute Short id ;
attribute String nom ;
attribute Short solde ;
//
// Declaration des relations ou associations
//
relationship list<Ordre> les_ordres
inverse Ordre::le_negociateur ;
relationship set<Element_De_Porte_Feuilles> porte_feuilles
inverse Element_De_Porte_Feuilles::le_negociateur ;
//
// Declaration des methodes
//
short capital () ;
}
-- La relation ’la_valeur’ permettra un acces navigationnel
class Ordre (extent Les_Ordres) {
attribute Short nb_titres ;
attribute Date la_date ;
relationship Valeur la_valeur ;
relationship Negociateur le_negociateur
inverse Negociateur::les_ordres ;
}
class Offre extends Ordre {
attribute Short prix_min ;
}
class Demande extends Ordre {
attribute Short prix_max ;
}
La collection Les_Ordres va contenir a la fois les Offre et les Demande.
19.3.2 A quoi sert ODL ?
Toutes les declarations ODL sont abstraites :– les types de base comme Short n’ont pas d’implantation definie,– les methodes ne sont que declarees.Le schema objet decrit en ODL sert a deux choses :
19.3. SGBDO 235
– guider l’implementation (en terme de representation des types abstraits d’ODL, et de codage desmethodes) qui sera faite grace a OML qui decrit l’integration de ces objets dans au moins troislangages (C++, Smalltalk et Java).
– permettre l’ecriture de requetes en langage OQL sans avoir a connaıtre l’implementation.
19.3.3 Bourse OQL
Les noms des negociateurs qui ont un capital d’au moins 5000 :
select n.nom
from n in Les_Negociateurs
where n.capital() >= 5000 ;
A noter l’utilisation de la methode d’instance capital().
Les noms des negociateurs qui ont un capital d’au moins 5000 euros pour au moins une des valeursdont ils ont des titres en porte-feuilles :
select n.nom
from n in Les_Negociateurs, e in n.porte_feuilles
where e.la_valeur.cours * e.nb_titres >= 5000 ;
A noter :– l’iteration cachee : pour chaque negociateur, on teste chacun de ses elements de porte-feuille avecc
la clause where,– la navigation e.la_valeur.cours qui permet d’eviter une jointure.
Chapitre 20
Le relationnel-objet de Oracle
Avec le relationnel-objet Oracle permet la definition de nouveaux types de donnees. Parmi ceux-cinous verrons les types objets, les types references et les types tables emboıtables. Nous ne verrons pasles types varray.
20.1 Peliminaire
Oracle introduit deux niveaux dans son approche du relationnel-objet :
le niveau conceptuel des types introduits par l’instruction�� ��create type :
– types objet (attributs + methodes)– types tables emboıtablesA ce niveau on decrit des objets sans pouvoir exprimer aucune contrainte d’integrite et on nefait (ou ne devrait faire) aucune hypothese sur la maniere dont seront stockes physiquement lesobjets. Exemple :
create type Adresse as object (
numero Number (5), rue VARCHAR2 (20), ville VARCHAR2 (20)
) ;
le niveau stockage des tables introduites par l’instruction�� ��create table : ce sont les structures
d’accueil — ou de stockage — des valeurs des objets et des tables emboıtables. C’est seulementa ce niveau qu’on peut exprimer des contraintes d’integrite et des triggers.
On a maintenant deux sortes de tables :
les tables objets formees d’une seule colonne de type objet declarees comme suit :
create table Les_Adresses of Adresse (
constraint Les_Adresses_PK primary key (numero, rue, ville),
constraint Les_Adresses_Prix_Positif check (1 <= numero)
) ;
create table Les_Adresses_2 of Adresse (
constraint Les_Adresses_2_PK primary key (numero, rue, ville)
) ;
On ne peut pas leur adjoindre d’autres colonnes.
les tables relationnelles comme d’habitude dont certaines colonnes peuvent etre d’un typeobjet :
create table Relationnelle (
a Adresse,
loyer Number (5, 2),
constraint Relationnelle_PK primary key (a.numero),
constraint Relationnelle_Prix_Positif check (1 <= a.numero)
) ;
236
20.2. TYPES OBJET ET METHODES 237
Remarquer la notation pointee a.numero pour exprimer une contrainte portant sur unattribut d’une colonne objet.
20.2 Types objet et methodes
La definition d’un type objet se fait comme suit :
create type Personne as object (
nom VARCHAR2 (20),
naissance Date,
member function Age(AujourDhui Date) return Natural -- methode d’instance
) not final ; -- est heritable
member indique que la fonction Age est une methode d’instance.
not final est en rapport avec la possibilite de declarer des sous-types qui heriteront de Personne. Pardefaut un type est final.
L’implantation des methodes se fait dans le body :
create or replace type body Personne as
member function Age (AujourDhui Date) return Natural is
begin
return floor (months_between (AujourDhui, self.naissance) / 12) ;
end Age ;
end ;
self est le this de Java et comme en Java, il n’est obligatoire qu’en cas d’ambiguıte.
20.2.1 Appel d’une methode
Cela se fait en prefixant la methode en prefixant la methode par l’objet sur lequel on veut l’executer :
create or replace function Majeur (P in Personne) return Varchar2 is
begin
if P.Age (sysdate) < 18 then
return ’mineur’ ;
else
return ’majeur’ ;
end if ;
end Majeur ;
20.2.2 Creer un nouveau type par composition
La composition peut servir a definir :
– un autre type (element d’une collection, attribut, d’un type objet ...),– le type d’une colonne de table (table relationnelle),– le type des lignes d’une table objet.
Le type Etudiant va etre compose de deux attributs :
create type Etudiant as object (
p Personne,
a Adresse,
member function Age (AujourDhui Date) return Natural
) ;
Q. 256 Ecrire le body de Etudiant.
Pour l’appel d’une methode on remarque la notation prefixee par l’objet sur lequel la methode doits’appliquer (comme en Smalltalk, Java, C++, Ada 2005).
238 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
20.2.3 Surcharge possible des methodes (overloading)
Plusieurs methodes de meme nom peuvent coexister dans le meme type si elles n’ont pas la memesignature. La signature d’une methode comporte son nom, le nombre, les types et l’ordre de sesparametres y compris le parametre implicite self.
20.3 Oracle 8 ne propose pas l’heritage
20.4 Oracle 10 propose l’heritage simple
create type <nom-du-sous-type> under <super-type>
([overriding] member ..., ...) [[not] final]; -- final par defaut
Par defaut un type objet est final : il ne peut avoir de sous-types.Un sous-type :– herite des attributs et methodes de son super-type,– peut definir de nouveaux attributs et de nouvelles methodes,– peut redefinir des methodes heritees (grace au qualificatif overriding).En principe un sous-type doit definir au moins un nouvel attribut ou un nouveau sous-programmemembre ou une redefinition d’une methode heritee.
Contrairement a Java et SmallTalk qui ne disposent que d’une seule hierarchie d’heritage, et commeAda et C++, en Oracle on peut avoir plusieurs hierarchies d’heritage. Par exemple le type Personne
n’herite d’aucun autre type et pourrait etre la racine d’une hierarchie d’heritage.
20.4.1 Creer un nouveau type par heritage
create type Employe under Personne (salaire Number (10, 2)) not final ;
create type Stresse under Employe (stress Number (1)) not final ;
create type Programmeur under Stresse (email Varchar2 (30)) final ;
create type Secretaire under Stresse (tel Varchar2 (20)) ;
Les attributs nom et naissance et la methode Age sont herites par Employe. Seuls les nouveauxattributs sont declares, par exemple le salaire de Employe.Les types Programmeur et Secretaire ne pourront pas avoir de sous-types car il sont final explicite-ment ou par defaut.
20.4.2 Redefinition de methode : overriding
La redefinition (override) n’a rien a voir avec la surcharge (overloading voir section 20.2.3 page 238).Une redefinition de methode a necessairement la meme signature que la methode heritee redefinie.Une redefinition doit fournir les memes eventuelles valeurs par defaut a ses parametres que la methoderedefinie.
On peut cacher une methode membre heritee en la redefinissant par une methode statique ( !) et cecisans le mot clef overriding ( ! ! !).En PL/SQL, une redefinition ne peut appeler la methode redefinie originale (comme on le fait en Javaavec super). On peut quand meme factoriser le code en utilisant des methodes statiques mais c’estun peu scabreux !A cause de la possibilite de redefinition, l’appel de methode donne lieu a une liaison dynamique quichoisit la bonne methode a executer en fonction du type precis de self et non pas bien sur en fonctiondu type statique de l’expression qui calcule self (c’est exactement la meme chose qu’en Java).
20.4.3 Compatibilite d’un type avec ses super types (substitutable)
La ou on peut mettre un objet d’un type T on peut aussi mettre tout objet d’un sous-type de T ,meme si le sous-type de T a ete cree apres la structure d’accueil.
20.4. ORACLE 10 PROPOSE L’HERITAGE SIMPLE 239
create table Les_Personnes of Personne (
constraint PK_Les_Personnes primary key (nom)
) ;
insert into Les_Personnes values (Personne (’toto’, null)) ;
create type Internaute under Personne (email Varchar2 (30)) not final ;
insert into Les_Personnes values (Internaute (’Dufour’, null, ’[email protected]’)) ;
Cela est vrai pour les references, les attributs objets d’un objet, les colonnes objet de tables relation-nelles, les tables objets et les collections.
Cette compatibilite peut cependant etre desactivee au niveau stockage pour des tables ou des colonnesspecifiques. Voir l’option not substitutable de create table.
20.4.4 Valeur litterale d’objet : les constructeurs de valeur
Tout type objet definit implicitement un constructeur permettant d’exprimer litteralement la valeurd’un objet. Le nom d’un constructeur est le nom du type objet et il possede exactement autant deparametres que l’objet a d’attribut. Si on ne veut pas renseigner certains attributs, il faudra doncecrire explicitement null.
Exemples d’objets litteraux exprimes grace aux constructeurs :
Personne (’Dupont’, ’21/12/1975’)
Employe (’Truc’, ’12/6/1986’, 2000.0)
Programmeur (’Stresse’, ’02/6/1980’, 2000.0, 9, ’aa@fr’)
Adresse (null, null, ’Lille’)
Etudiant (Personne (’Dupont’, null),
Adresse (12, ’Charcot’, ’Lille’))
Tester la methode Age :
select Personne(’Boyle’, to_date (’23/9/1989’,’dd/mm/yyyy’)).Age(sysdate)
from dual ;
20.4.5 Creer une table objet : structure de stockage
Une table objet est constituee d’une seule colonne du type de l’objet. Les contraintes classiques peuventetre exprimees sur les attributs de l’objet constituant la table, par exemple ici la clef primaire est lenom de l’etudiant (remarquez la notation
�� ��p.nom ).
create table Les_Etudiants of Etudiant (
constraint PK_Les_Etudiants primary key (p.nom)
) ;
Pour retrouver les etudiants de la table Les_Etudiants en tant qu’objets de type Etudiant il faututiliser la fonction Value, sinon on verra simplement une ligne d’une table relationnelle dont lescolonnes sont les attributs de l’objet (voir section 20.4.13 page 242).
La fonction Treat (voir section 20.4.18 page 244) et le predicat is [not] of type permettront toujoursde prendre en compte le type precis de l’objet (voir section 20.4.17 page 244).
20.4.6 Garnir une table objet
Il est alors possible d’exprimer de nouvelles valeurs d’objet lors de l’insertion ou du update :
insert into Les_Etudiants values
(Etudiant (Personne (’Dupont’, ’21/12/1975’),
Adresse (12, ’Charcot’, ’Lille’))) ;
240 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
insert into Les_Etudiants values
(Etudiant (Personne (’Boyle’, null), Adresse (null, null, null))) ;
On peut mettre a jour l’adresse ou la ville :update Les_Etudiants e
set e.a = Adresse (12, ’Faidherbe’, ’Lille’)
where e.p.nom = ’Boyle’ ;
update Les_Etudiants e
set e.a.ville = ’Paris’
where e.p.nom = ’Boyle’ ;
20.4.7 Une table objet ne peut contenir d’objet indefini
Un objet indefini (is null) n’existe pas alors qu’un objet defini existe meme si tous ses attributs sontindefinis (is null).Une table objet ne peut pas contenir d’objet indefini, cela reviendrait a admettre qu’une table puisseavoir des lignes qui n’existent pas. Autrement dit le insert dans une table objet cree necessairementun objet, meme si tous ses attributs sont indefinis et l’update qui suit donne lieu a une erreur si latable est initialement non vide :
insert into Les_Etudiants values (Etudiant (null, null)) ; -- OK
insert into Les_Etudiants values (null) ; -- erreur ORA-22805 :
-- impossible d’inserer un objet NULL dans des tables objet ou emboıtees
Pour la meme raison on a :
update Les_Etudiants e
set e = null ; -- erreur ORA-22805 :
-- impossible d’inserer un objet NULL dans des tables objet ou emboıtees
En revanche, une table relationnelle peut parfaitement avoir des objets indefinis dans ses colonnes detype objets, car cela ne remet pas en cause l’existance des lignes contenant ces objets indefinis.
20.4.8 Acces aux composants par notation pointee�
�Pour les tables objet il faut toujours declarer un alias de table et l’utiliser pour prefixer les
attributs de l’objet. Ceci est valable pour toutes les instructions du DML.
Dans l’exemple suivant l’alias est l_etudiant :
select e.p.nom as nom, e.Age (sysdate) as age
from Les_Etudiants e ;
Bien que cela ne soit pas souhaitable, on peut aussi acceder directement a la methode Age de l’objetPersonne :
select e.p.nom as nom, e.p.Age (sysdate) as Age
from Les_Etudiants e ;
20.4.9 Modifier l’implementation d’un type objet
A l’instar des paquetages, quelles que soient les conditions, il est toujours possible de modifier etrecompiler le body d’un type :
�� ��create or replace type body .
Cette propriete particulierement agreable resulte de la separation claire entre specification (la definitiondu type) et implantation (le body du type).
20.4.10 Modifier la definition d’un type objet : alter type
Pour la definition d’un type objet, on ne peut qu’ajouter de nouvelles methodes membre, on ne peuten aucun cas modifier ou ajouter d’attributs.
Par exemple, bien que la table Les_Etudiants utilise le type Etudiant, il est possible d’ajouter lamethode Statut grace a la commande
�� ��alter type :
20.4. ORACLE 10 PROPOSE L’HERITAGE SIMPLE 241
alter type Etudiant replace as object (
p Personne,
a Adresse,
member function Age (AujourDhui Date) return Natural,
member function Statut (AujourDhui Date) return Varchar2,
member function Nom return Varchar2
) ;
create or replace type body Etudiant as ...
member function Age (AujourDhui Date) return Natural is
begin
return p.Age (AujourDhui) ;
end Age ;
...
end ;
Q. 257 Ecrire la fonction membre Statut qui renvoie ’majeur’ ou ’mineur’.
20.4.11 Sous-programmes membres sans parametres
Une fonction membre sans parametres est declaree comme en Ada, c’est a dire sans les parenthesesqui delimitent les parametres formels.
En revanche lors de l’appel d’une fonction membre sans parametres, il faut quand meme mettre lesparentheses (comme en Java ou en C).
select e.Nom () as nom from Les_Etudiants e ;
20.4.12 Le probleme de la persistance des modifications
Les SGBDOO (purement objet) gerent automatiquement la persistance des objets : si on modifieun attribut d’un objet persistant par une simple affectation et que cet objet provient de la base dedonnees, le SGBDOO garantit la persistance de cette modification. Autrement dit, le programmeurmanipule ses objets exactement comme il le ferait dans un langage objet, sans avoir a se preoccuperde savoir s’ils sont persistants ou non.
Malheureusement, Oracle n’est que relationnel-objet (SGBDRO) et ne gere pas la persistance des ob-jets : c’est au programmeur de coder, si necessaire, l’ordre update qui garantira la persistance d’unemodification d’un objet.
Prenons l’exemple d’une methode permettant de modifier le numero dans la partie adresse d’unetudiant.
alter type Etudiant replace as object (
...,
member procedure Changer_Numero (nouveau_numero in Positive)
) ;
Si cette modification n’est pas destinee a etre persistante il suffit de modifier l’objet en memoirecentrale :
create or replace type body Etudiant as
...
member procedure Changer_Numero (nouveau_numero in Positive) is
begin
242 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
self.a.numero := nouveau_numero ;
end Changer_Numero ;
end ;
En revanche si cette modification doit etre persistante, on peut etre tente d’emettre un ordre de misea jour dans la methode :
create or replace type body Etudiant as
...
member procedure Changer_Numero (nouveau_numero in Positive) is
begin
self.a.numero := nouveau_numero ;
update Les_Etudiants
set a.numero = nouveau_numero
where p.nom = self.nom ;
end Changer_Numero ;
end ;
Plusieurs inconvenients :
– choix precoce sur le fait que la methode a un effet persistant ou non – on pourrait par exemplefournir systematiquement deux versions de chaque procedure, une persistante et l’autre non. cechoix n’est pas a faire avec un SGBDOO
– en cas de persistance il faut connaıtre les structures de stockage (les tables). Un meme type d’objetpeut etre stocke dans plusieurs tables : comment choisir la bonne table pour garantir la persistance ?Cela pose aussi des problemes de maintenance si on choisit de modifier les noms des tables destockage.
Une solution : gerer la persitance a l’exterieur des methodes.
20.4.13 Value pour recuperer la ligne en tant qu’objet
La fonction value permet de recuperer le tuple selectionne en tant qu’objet, elle n’est donc applicablequ’aux tuples d’une table ou une vue objet.
La fonction value prend en parametre un alias d’une table ou d’une vue objet et renvoie une instancedu type d’objet declare statiquement pour les lignes de cette table (meme si l’objet de la ligne estd’un sous-type de ce type).
select Value (e) from Les_Etudiants e ;
En revanche, pour un update :
update Les_Etudiants e
set e = Etudiant (...)
where ... ;
Elle est par exemple utile en PL/SQL pour recuperer une ligne comme un objet :
declare
Toto Etudiant ;
begin
select Value (e) into Toto
from Les_Etudiants e where e.p.nom = ’Toto’ ;
Toto.Changer_Numero (20) ;
update Les_Etudiants e
set e = Toto where e.p.nom = ’Toto’ ;
end ;
20.4. ORACLE 10 PROPOSE L’HERITAGE SIMPLE 243
20.4.14 Definir un ordre sur un type : methode order
Les clauses order by, distinct, group by (entre autres) de SQL ont besoin d’une relation d’ordre surles valeurs qu’elles manipulent. Ces valeurs pouvant etre des objets, le programmeur relationnel-objetdoit pouvoir definir des ordres sur ses types objets.
Une methode d’instance qualifiee de order definit l’ordre des valeurs du type :
alter type Etudiant replace as object (
...,
order member function Compare (Avec in Etudiant) return Number
) ;
Une fonction d’ordre doit renvoyer (comme en C et en Java) :– un entier negatif pour signifier que self est strictement plus petit que Avec.– zero pour signifier que self est egal a Avec.– un entier positif pour signifier que self est strictement plus grand que Avec.
Ici on decide d’ordonner les etudiants par dates de naissance croissantes :
create or replace type body Etudiant as
...
order member function Compare (Avec in Etudiant) return Number is
begin
return self.p.Naissance - Avec.p.Naissance ;
end Compare ;
end ;
Cette fonction sera par exemple utilisee lors d’un order by, a condition d’utiliser la fonction Value()
comme ici :
select e.p.nom from Les_Etudiants e order by Value (e) ;
ou lors d’un group by.Attention : seul un type racine d’heritage peut definir une fonction order.
Q. 258 Le fait que seul le type racine puisse definir un ordre peut-il s’expliquer ?
20.4.15 Passage par adresse : nocopy
Par defaut, Oracle passe les objets par copie (quel que soit le mode in, out ou in out). Le qualificatifnocopy demande un passage par adresse.
Dans les fonctions membre self est passe implicitement et par defaut en in.
Dans les procedures membre self est passe implicitement et par defaut en in out.Ce qui suit peut aussi se faire pour le parametre implicite self des fonctions member qui est en modein out.
order member function Compare
(self in nocopy Etudiant, Avec in nocopy Etudiant) return Number
Cela peut accelerer les choses en cas de gros parametre.
20.4.16 Methode de classe : static
Ces methodes ne recoivent pas de parametre self, elles s’executent independamment de toute instancedu type (comme en Java).
alter type Etudiant replace as object (
...,
static function constructeur (...) return Etudiant
) ;
create or replace type body Etudiant as
244 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
...
static function constructeur (...) return Etudiant is...
end ;
Le nom du type est utilise comme prefixe lors de l’appel d’un sous-programme statique.
20.4.17 Predicat sur le type precis d’un objet : is of type
<expr-objet> is [not] of type ( [only] <type> {, [only] <type>} )
Ce predicat est vrai si le type dynamique de <expr-objet> est un sous-type d’un des <type> de laliste. Ce doit etre exactement le meme type si only.Par exemple on ne veut voir que les employes programmeurs ou secretaires :
create table Les_Employes of Employe ;
select e.nom, e.salaire
from Les_Employes e
where Value (e) is of type (only Programmeur, Secretaire) ;
Q. 259 Dans cet exemple, peut-on se passer de only ? pourquoi ? (voir 20.4.1 page 238)
Q. 260 Quel est le type statique de e ? la clause select peut-elle consulter e.stress ?
Q. 261 Donner deux is of type qui seront toujours vrais sur Les Employes.
20.4.18 Projeter un objet sur un de ses super-types : Treat
Treat (<expr-objet> as [ref] <type>)
<type> peut etre un super ou un sous-type du type statique de <expr-objet>. Si pour une ligne, letype dynamique de <expr-objet> est sous-type de <type> alors la projection de <expr-objet> sur<type> est renvoyee, sinon l’objet indefini (is null) est renvoye.
Treat nous permet de voir les Programmeur et les Secretaire comme des Stresse :
select e.nom, e.salaire, Treat (Value (e) as Stresse).stress
from Les_Employes e
where Value (e) is of type (only Programmeur, Secretaire) ;
Q. 262 Simplifier la requete pour qu’on puisse voir tous les stresses quel que soit leur type precis.
L’expression type statique correspond au type d’objet le plus precis qu’on puisse associer a l’expressiondes sa compilation. Par exemple le type statique de l’expression Value (p) dans :
select Value (p) from Les_Personnes p ;
est Personne alors que dans :
select Value (e) from Les_Employes e ;
le type statique de Value (e) est Employe.
Cependant on sait que les objets de la table Les_Employes peuvent etre du type Employe ou de n’im-porte lequel de ses sous-types, Programmeur par exemple. Donc, a l’execution, la valeur de Value (e)
pourra etre d’un type plus specifique que Employe.
Par exemple, si on ne veut voir que les programmeurs avec tous leurs attributs de programmeur :
create view Vue_Programmeurs of Programmeur as
select Treat (Value (e) as Programmeur)
from Les_Employes e
where Value (e) is of type (only Programmeur) ;
20.5. LES REFERENCES : REF 245
20.4.19 Supprimer des types : drop type
Il faut evidemment le faire dans le bon ordre.
drop table Les_Etudiants ;
drop type Etudiant ;
drop type Personne ;
drop type Adresse ;
20.4.20 Limitation des types objet
Lors de la declaration d’un type objet, on ne peut faire figurer aucune contrainte (comme check) surles attributs.
On pourra bien sur le faire a la declaration d’une table objet.
20.4.21 Exercices
Q. 263 Introduire le type UE (Unite d’Enseignement) qui a comme attributs un nom (unique pourtoutes les UE) un certain nombre de credits ECTS et un volume horaire.
Q. 264 Chaque etudiant peut suivre plusieurs UE et chaque UE peut etre suivie par plusieursetudiants : implementer la table des UE et les associations qu’elles entretiennent avec les etudiants.
Q. 265 Ecrire les ordres SQL qui :
1. ajoute une UE,
2. inscrit un etudiant a une UE.
Q. 266 Ajouter aux etudiants la fonction membre volume qui renvoie la somme des volumes horairesdes UE auxquelles est inscrit l’etudiant. Quel defaut y a-t-il dans l’implantation de volume ?
Q. 267 Lister les etudiants qui sont inscrits a moins de 200h.
Q. 268 Comment empecher qu’un etudiant s’inscrive pour un volume de plus de 300 heures ?
Q. 269 Requetes calculant :
1. le nombre d’UE par etudiant,
2. le nombre d’etudiants par UE
Q. 270 Comment faire pour qu’on puisse lister les etudiants par ordre croissant ou decroissant deleurs volumes horaire ? le faire.
20.5 Les references : ref
Une reference (ref) est un pointeur logique vers un objet d’une table objet (une reference contientl’OID de l’objet sur 16 octets, l’OID de la table ou de la vue sur 16 octets et le rowid hint sur 10octets).Comme pour les clefs etrangeres, ce mecanisme repose sur l’utilisation d’index.
Par exemple, soit une association 1 vers n de l’objet Voiture vers l’objet Etudiant :
create type Voiture as object (
immatriculation VARCHAR2 (10),
proprietaire ref Etudiant
) ;
246 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
create table Les_Voitures of Voiture (
constraint PK_Les_Voitures primary key (immatriculation)
) ;
Dans cette implantation les Etudiant peuvent parfaitement se trouver dans plusieurs tables : avecune reference comment savoir dans quelle table se trouve l’etudiant ?
Il vaudrait surement mieux imposer que tous les Etudiant references soient dans une seule table :
create table Les_Voitures of Voiture (
constraint PK_Les_Voitures primary key (immatriculation),
scope for (proprietaire) is Les_Etudiants
) ;
Ce qui fait aussi que les references prennent moins de place puisqu’on sait dans quelle table se trouventles objets references.
20.5.1 Obtenir la reference d’un objet : la fonction ref (objet)
Seule une table objet peut fournir des references sur ses objets (c’est a dire sur ses lignes), c’est le casde la table Les_Etudiants :
insert into Les_Voitures values (
Voiture (’34 WWW 59’,
(select ref (e) from Les_Etudiants e where e.p.nom=’Dupont’))) ;
insert into Les_Voitures values (
Voiture (’22 XYZ 62’,
(select ref (e) from Les_Etudiants e where e.p.nom = ’Dupont’))) ;
insert into Les_Voitures values (
Voiture (’55 ABC 59’,
(select ref (e) from Les_Etudiants e where e.p.nom = ’Boyle’))) ;
Donc Dupont possede deux voitures.
Voici une representation graphique de l’exemple precedent :
immatriculation proprietaire
34 WWW 59
55 ABC 59
22 XYZ 62
p.nom p.naissance a.numéro a.rue
Dupont 21/12/1975 12 Charcot
Durif
a.ville
Lille
Lille
Les_Voitures Les_Etudiants
20.5.2 Obtenir l’objet reference : la fonction deref (reference)
Si la reference est pendante (l’objet designe n’existe plus), la valeur de deref est indefinie.
20.5.3 Naviguer sur les references : plus de jointure !
Le grand interet des references est qu’on peut les utiliser pour faire de la navigation : bon nombre dejointures qu’il faut ecrire explicitement dans le modele relationnel seront prises en compte implicite-ment par Oracle, et meme, parfois, Oracle pourra se passer de jointure. Donc, a priori, la navigationa au moins deux avantages :
– Simplicite d’ecriture des ordres SQL.– Efficacite de leur execution.
20.5. LES REFERENCES : REF 247
Attention, la navigation sur les�� ��ref n’est possible que dans le monde SQL. Ainsi en PL/SQL seul
les ordres SQL embarques pourront utiliser la navigation.
Par exemple les couples immatriculation, nom du proprietaire :
select v.immatriculation, v.proprietaire.p.nom
from Les_Voitures v ;
La jointure n’est pas explicite : on accede directement a l’objet Etudiant en navigant sur la referencev.proprietaire.
Q. 271 Que calcule la requete suivante ?
select v.proprietaire.Age (to_date (’1/6/2009’,’dd/mm/yyyy’)) as Age,
count (*) / count (distinct v.proprietaire) as Nb_Voitures
from Les_Voitures v
group by v.proprietaire.Age (to_date (’1/6/2009’,’dd/mm/yyyy’)) ;
Q. 272 Comment faire pour prendre aussi en compte les etudiants ne possedant pas de voiture ?comme quoi la navigation n’est pas une baguette magique.
Un autre exemple de navigation sur la base de donnees suivante :
SousSection
titre
Share
Exclusive
Attribut
titre
Multi−versions
Verrous
Problématique
Types
Constructeurs
Section Chapitre
titre
Les transactions
Relationnel−objet
PL/SQL
ma_section mon_chapitre
select s.titre, s.ma_section.titre, s.ma_section.mon_chapitre.titre ;
from SousSection s ;
Q. 273 Implanter et garnir l’exemple precedent, est-il possible d’utiliser le meme type objet pourdefinir les tables Section et SousSection?
Q. 274 Requete qui donne les nombres minimum, maximum et moyen de sous-sections par chapitre.
20.5.4 Limiter la portee des references : scope for
A priori, une ref peut referencer un objet se trouvant dans n’importe quelle table objet du bon type.De telles references sont couteuses en espace memoire et en temps d’acces. De plus on peut imaginerqu’une telle souplesse puisse etre a l’origine de la question : “mais dans quelle table se trouve l’objetque je reference ?”.
L’implementation de la figure 20.1 est satisfaisante tant qu’on cherche le logement d’un equipement.
Q. 275 Requete listant les equipements des maisons ?
Pour resoudre ces deux problemes Oracle propose la contrainte obligeant a ce que seuls les objetsd’une table particuliere puissent etre references : en scoped ref les references seront moins couteuses enespace (pas plus de 16 octets) et on saura toujours dans quelle table se trouvent les objets references.
alter table Les_Voitures
add (scope for (proprietaire) is Les_Etudiants) ;
Ou bien :
248 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
Lille
Robinet
Baignoire
Robinet
adresse
Lille
Lyon
Les_AppartementsEquipement
logementlibellé adresse
Les_Maisons
Fig. 20.1 – Les Maisons et Les Appartements sont des tables objet de type Logement. Est-ce vraimentune bonne idee ?
drop table Les_Voitures ;
create table Les_Voitures of Voiture (
constraint PK_Les_Voitures primary key (immatriculation),
scope for (proprietaire) is Les_Etudiants
) ;
Les references a portee limitee peuvent aussi etre exploitees par l’optimiseur.
Conclusion : la clause scoped ref ne peut etre que fortement recommandee pour des raisons d’efficaciteet de lisibilite de la base de donnees.
20.5.5 Integrite referentielle des references ref ⇒ scope for
Depuis Oracle 10, on peut utiliser une syntaxe tres proche de celle des clefs etrangeres pour maintenirl’integrite des references ref :
constraint <nom> foreign key (<colonne-referencante>)
references <table> [on delete cascade | set null ]
autrement dit, lors de la suppression d’un objet reference on peut demander a ce que les lignes quile referencent soient elles aussi supprimees ou bien que les ref soient rendues indefinies. Rappel : latable referencee est forcement une table objet.
Cette contrainte ajoute automatiquement une contrainte de portee (scoped ref). Par exemple on veutqu’un equipement disparaisse quand son logement est detruit :
create type Logement as object (
adresse Varchar2 (20)
) not final ;
create table Les_Maisons of Logement ;
create table Les_Appartements of Logement ;
create table Equipement (
libelle Varchar (20),
logement ref Logement,
constraint logement_OK foreign key (logement) references Les_Maisons
on delete cascade
) ;
Les logements references seront forcement dans la table Les_Maisons.
Cette contrainte n’est pas possible pour les references se trouvant dans des tables emboıtees.
Q. 276 Reprendre les exercices de la section 20.4.21 a la page 245 en remplacant toutes les clefsetrangeres par des references.
20.6. LES TABLES EMBOITEES 249
20.5.6 Tester les references pendantes : is [not] dangling
Une reference est pendante si l’objet qu’elle reference n’existe plus. Si on ne garantit pas l’integritereferentielle des references, il est possible que l’objet reference ait ete detruit entre temps, on dit alorsque la reference est pendante.
Le predicat is dangling permet de savoir si une reference est pendante.
delete from Les_Etudiants e where e.p.nom = ’Dupont’ ;
update Les_Voitures
set proprietaire = null
where proprietaire is dangling ;
20.5.7 Limitation des references
Une ref ne peut faire l’objet d’une contrainte d’unicite ou de clef primaire.
Dans les expressions de contrainte (unique, check, . . .) on ne peut pas naviguer sur les ref.
20.6 Les tables emboıtees
Une colonne de table va maintenant pouvoir contenir un nombre (presque) quelconque de valeurs.
Pour cela on peut definir des tables de taille maximale fixee (les Varray) ou bien des tables emboıteessans limite de taille.
Varray est plus efficace que table emboıtee.
Dans la suite on ne verra que les tables emboıtees qui sont fonctionnellement plus riches.
20.6.1 Declarer un type de table emboıtable
On reimplemente l’association proprietaire en affectant a chaque etudiant la table (emboıtee) de sesvoitures :
create type Voiture as object (immatriculation VARCHAR2 (10)) ;
create type Des_Voitures as table of Voiture ;
20.6.2 Utiliser le type table emboıtable pour typer une colonne
create table Les_Conducteurs_Relationnelle (
p Personne,
a Adresse,
v Des_Voitures,
constraint PK_Les_Conducteurs_Relationnelle primary key (p.nom)
) nested table v store as Tab_Voitures ;
Attention : Les_Conducteurs_Relationnelle n’est pas une table objet.
Grace a la clause nested table v store as Tab_Voitures, le contenu des tables emboıtees de cha-cun des conducteurs sera stocke dans l’unique table Tab_Voitures. Pour memoriser l’appartenanced’une voiture de la table Tab_Voitures a la table emboıtee v d’un conducteur particulier, Oracle ajouteune colonne dans chacune des deux tables (16 octets). La table Tab_Voitures n’est pas manipulabledirectement.
250 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
20.6.3 Utiliser le type table emboıtable pour typer un attribut d’objet
create type Conducteur under Personne (a Adresse, v Des_Voitures) ;
create table Les_Conducteurs of Conducteur (
constraint PK_Les_Conducteurs primary key (nom)
) nested table v store as Tab_Conducteurs ;
Q. 277 Pourquoi n’est-il plus necessaire qu’une voiture connaisse son proprietaire ?
C’est cette deuxieme version que nous utiliserons.
20.6.4 Constructeurs de valeur de table emboıtee
On peut exprimer la valeur d’une table emboıtable grace a son constructeur qui porte le nom du typetable :
– exprimer une table vide : Des_Voitures ()
– exprimer une table avec un contenu de depart :Des_Voitures (Voiture (’34 WWW 59’), Voiture (’22 XYZ 62’))
20.6.5 Insertions
insert into Les_Conducteurs values
(Conducteur(’Dupont’, ’21/12/1975’,
Adresse (12, ’Charcot’, ’Lille’),
Des_Voitures (Voiture(’34 WWW 59’), Voiture(’22 XYZ 62’)))) ;
insert into Les_Conducteurs values
(Conducteur (’Boyle’, null,
Adresse (null, null, ’Lille’),
null)) ;
insert into Les_Conducteurs values
(Conducteur (’Selby’, null, null, null)) ;
Les attributs v de Boyle et Selby sont indefinis, ce qui est different d’une table vide.
L’update suivant permet de fixer une table vide pour Boyle :
update Les_Conducteurs c
set v = Des_Voitures ()
where c.nom = ’Boyle’ ;
A ce stade, voici une representation conceptuelle graphique du contenu de Les_Conducteurs :
34 WWW 59
22 XYZ 62
Boyle Lille immatriculation
Selby
a.numéro a.rue
21/12/1975 12 Charcot
a.ville
Lille
v
Dupont
nom naissance
Les_Conducteurs
immatriculation
20.6. LES TABLES EMBOITEES 251
On remarque que les attributs a et v de Selby sont indefinis, alors que ceux de Boyle sont definis : latable emboıtee de Boyle est simplement vide.Et une representation qui tend plus vers l’implantation physique :
xxx : 1
LilleBoyle xxx : 2
a.numéro a.rue
21/12/1975 12 Charcot
a.ville
LilleDupont
nom naissance
Les_Conducteurs
v
Selby
xxx : 3Céline
Tab_Voitures
immatriculation
34 WWW 59
22 XYZ 62
xxx
1
1
11 CVS 75
41 SVN 94
3
3
On a ajoute le conducteur Celine pour mieux montrer que Tab_Voitures contient toutes les voitures.
Q. 278 Quel probleme se pose si on veut qu’une voiture puisse avoir plusieurs conducteurs ? Commentle resoudre en conservant les tables emboıtees ?
20.6.6 Mises a jour de la table emboıtee : table ( requete )
Pour cela, il faut faire travailler l’ordre de mise a jour sur une requete qui renvoie la table emboıtee.Le resultat de la requete doit etre qualifie par le mot
�� ��table (the dans les anciennes versions Oracle).Supprimons une voiture a Dupont :
delete from
table (select c.v from Les_Conducteurs c where c.nom = ’Dupont’) v
where v.immatriculation = ’34 WWW 59’ ;�
�
�
�Attention : le select de la fonction table doit produire au plus une ligne :– s’il produit plus d’une ligne, une erreur Oracle est declenchee,– s’il ne produit aucune ligne ou bien que la table emboıtee n’est pas definie (is null) alors un select
considerera qu’il s’agit d’une table vide, alors qu’une mise a jour provoquera une erreur Oracle.
Ajouter une voiture a Boyle :
insert into
table (select c.v from Les_Conducteurs c where c.nom = ’Boyle’)
values (Voiture (’55 ABC 59’)) ;
20.6.7 Consultation de tables emboıtees
Les voitures de Boyle :
select v.immatriculation -- voitures de Boyle
from table (select c.v from Les_Conducteurs c where c.nom = ’Boyle’) v ;
Et pour voir les couples nom du conducteur, voiture, on ecrit tout simplement :
select c.nom, v.immatriculation -- couples (conducteur, voiture)
from Les_Conducteurs c, table (c.v) v ;
Ici chaque conducteur n’est joint qu’avec les tuples de sa table emboıtee.
Donc, si un conducteur ne conduit aucune voiture, il n’apparaıtra pas dans la liste. Si on veut qu’ilapparaisse quand meme, on peut utiliser une jointure externe ((+) apres la table emboıtee) :
select c.nom, NVL (v.immatriculation, ’Pas de voiture’)
from Les_Conducteurs c, table (c.v) (+) v ;
252 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
20.6.8 Methodes PL/SQL de manipulation des tables emboıtees
Ces methodes sont utilisables uniquement en PL/SQL.
En PL/SQL, l’acces a une table emboıtee se fait en l’indicant a partir de 1.Voici quelques-unes des methodes applicables aux tables emboıtees :
EXISTS (i) Dit si le i ieme element existe, car il peut y avoir des trous dus aux suppressions.
COUNT Nombre d’elements effectifs de la table emboıtee (les trous ne sont pas comptes)
FIRST et LAST renvoient le plus petit (plus grand) indice d’un element de la table emboıtee. Ilpeut y avoir des ’trous’ entre FIRST et LAST (dus a des suppressions dans la table emboıtee).En general on a donc COUNT <= LAST - FIRST + 1. Les trous peuvent etre detectes avec lamethode EXISTS (i). Attention FIRST et LAST sont indefinis si la table est indefinie ou vide.
PRIOR (i) et NEXT (i) renvoient respectivement l’indice du non-trou precedant ou suivant i. Leresultat est indefini s’il n’y a pas de non-trou.
DELETE (i) supprime le ieme element et cree donc un trou a l’indice i.
Le nombre de voitures par conducteur ne peut pas s’ecrire comme suit car la methode count n’estutilisable qu’en PL/SQL :
select c.nom, c.v.count -- Instruction erronee
from Les_Conducteurs c ;
on ecrira plutot :
select c.nom, count (v.immatriculation)
from Les_Conducteurs c, table (c.v) (+) v
group by c.nom ;
Pour illustrer l’utilisation d’une table, voici quelques manieres de calculer le nombre de voitures duconducteur dont le nom est passe en parametre (il s’agit de versions inutilement compliquees) :
1. Le plus simple en utilisant la methode Count :
create or replace function Nb_Voitures (
nom in Les_Conducteurs.nom%type
) return Natural is
v Des_Voitures ;
begin
select c.v into v
from Les_Conducteurs c
where c.nom = Nb_Voitures.nom ;
return case when v is null then 0 else v.count end ;
end Nb_Voitures ;
2. Avec une boucle pour en utilisant First et Last :
create or replace function Nb_Voitures (
nom in Les_Conducteurs.nom%type
) return Natural is
v Des_Voitures ;
n Natural := 0 ;
begin
select c.v into v
from Les_Conducteurs c
where c.nom = Nb_Voitures.nom ;
if v is not null and v.First is not null then
for I in v.First..v.Last loop
20.6. LES TABLES EMBOITEES 253
if v.exists (I) then
n := n + 1 ;
end if ;
end loop ;
end if ;
return n ;
end Nb_Voitures ;
3. Avec une boucle tant que : First et Next
create or replace function Nb_Voitures (
nom in Les_Conducteurs.nom%type
) return Natural is
v Des_Voitures ;
n Natural := 0 ;
i Positive ;
begin
select c.v into v
from Les_Conducteurs c
where c.nom = Nb_Voitures.nom ;
if v is not null then
i := v.First ;
while i is not null loop
n := n + 1 ;
i := v.Next (i) ;
end loop ;
end if ;
return n ;
end Nb_Voitures ;
Q. 279 Ecrire une autre version de Nb Voitures avec next.
20.6.9 Emboıtement des tables emboıtees
Depuis Oracle 10 (ou 9 ?) on peut emboıter des tables sur un nombre quelconque de niveaux (en Oracle8 on ne pouvait avoir qu’un seul niveau d’emboıtement).
create type Rangee as table of Voiture ;
create type Etage as table of Rangee ;
create type Parking as table of Etage ;
create table Les_Parkings (
id Number (5),
p Parking
) nested table p store as Tab_Etages
(nested table Column_Value store as Tab_Rangees
(nested table Column_Value store as Tab_Voitures) ) ;
Column_Value est le nom par defaut de l’unique colonne anonyme d’une table.
On ne peut decrire aucune contrainte lors de la definition d’un type de table emboıtable.
Si elle n’est pas nommee explicitement, la colonne d’une table emboıtee s’appelle Column_Value.
254 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
Q. 280 Requete donnant le nombre de voitures par parking.
Q. 281 Inserer un parking disposant d’un etage compose d’une rangee vide.
Q. 282 A ce parking, ajouter un etage compose d’une rangee accueillant deux voitures.
Q. 283 Comment introduire un nombre maximal de voitures pouvant etre garees dans une rangee ?
Q. 284 Representer la meme information de facon purement relationnelle, puis ecrire la meme requeteet initialiser la table de la meme maniere.
20.6.10 Exercices basiques sur Les Conducteurs, section 20.6.3
Q. 285 Requete calculant les noms des conducteurs conduisant au moins 2 voitures.
Q. 286 Requete calculant les noms des conducteurs possedant au moins une voiture immatriculeedans le Pas-de-Calais (62).
Q. 287 Requete donnant les immatriculations des voitures du Pas-de-Calais.
Q. 288 Requete donnant le nombre de voitures par departement, fonction Substr(chaıne, dep,
lg), par exemple Substr(’abcdefg’, 6, 2) = ’fg’ et Substr(’abcdefg’, -3, 2) = ’ef’, le -3
part de la fin.
20.6.11 Exercices de synthese
Reprendre les exercices de la section 20.4.21 a la page 245 en procedant de la maniere suivante : chaqueetudiant dispose d’une table emboıtee contenant les references de ses UE. En revanche, on ne changerien a la representation des UE.
20.7 Tables emboıtees et REF
Supposons maintenant qu’un conducteur puisse conduire un nombre quelconque de voitures et qu’unevoiture puisse etre conduite par un nombre quelconque de conducteurs. On va donner autonomie auxvoitures en ne les mettant plus dans une table emboıtee.
Un conducteur dispose d’une table emboıtee contenant les references des voitures conduites et unevoiture d’une table emboıtee contenant les references de ses conducteurs. L’association doit restersymetrique contrairement a la solution suggeree pour l’exercice 20.6.11 page 254.
Cette symetrie a deux consequences :
– elle provoque une dependance circulaire : on verra qu’il est possible de donner des definitions detype incompletes permettant de declarer les references et qui seront completees par la suite.
– Oracle nous permet la symetrie de structure, par contre on ne peut lui demander de garantir lasymetrie de contenu — c’est a dire le fait qu’un conducteur conduit une voiture si et seulement sicette voiture est conduite par ce conducteur. C’est donc au programmeur qu’il incombe de garantircette symetrie de contenu.
20.7.1 Definitions incompletes
create type Voiture ;
create type Conducteur ;
20.7. TABLES EMBOITEES ET REF 255
20.7.2 Definitions des tables emboıtees
On a d’abord besoin d’introduire un type intermediaire qui permettra de typer l’unique colonne destables emboıtables.
create type Ref_Voiture as object (r ref Voiture) ;
create type Ref_Conducteur as object (r ref Conducteur) ;
create type Ens_Voitures as table of Ref_Voiture ;
create type Ens_Conducteurs as table of Ref_Conducteur ;
Une autre solution pas tout a fait equivalente permet de se passer du type intermediaire :
create type Ens_Voitures as table of ref Voiture ;
create type Ens_Conducteurs as table of ref Conducteur ;
mais alors la table Ens_Voitures contient une unique colonne anonyme et il faudra utiliser le pseudo-identificateur Column_Value pour designer cette colonne. Par exemple, si la_voiture designe unevaleur de Ens_Voitures, avec la premiere solution on ecrirait :
la_voiture.r.immatriculation
et avec la seconde il faudrait ecrire :
la_voiture.Column_Value.immatriculation
La suite de l’exemple s’appuie sur la premiere solution.
20.7.3 Definitions completes de types
create type Voiture as object (
immatriculation VARCHAR2 (10),
conducteurs Ens_Conducteurs
) ;
create type Conducteur under Personne (
voitures Ens_Voitures
) ;
On peut verifier que chacun des huits types precedents depend indirectement de lui-meme.
20.7.4 Declarations des tables objets
create table Les_Voitures of Voiture (
constraint PK_Les_Voitures primary key (immatriculation)
) nested table conducteurs store as Tab_Ref_Conducteurs ;
create table Les_Conducteurs of Conducteur (
constraint PK_Les_Conducteurs primary key (nom)
) nested table voitures store as Tab_Ref_Voitures;
Il semble qu’il ne soit pas possible de limiter la portee des references contenues dans une table emboıtee(aucun moyen d’utiliser la clause Scope For).
256 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
20.7.5 Maintenir la symetrie de l’association conducteur/voiture
Pour creer une association entre un conducteur et une voiture, on a tout interet a utiliser une procedurestockee.
insert into Les_Voitures values (Voiture (’33 ABC 59’, Ens_Conducteurs ())) ;
insert into Les_Conducteurs values
(Conducteur (’Toto’, ’1/1/80’, Ens_Voitures ())) ;
Q. 289 Donner deux requetes permettant de voir que l’assocition est bien symetrique.
Q. 290 Ecrire la procedure Conduire qui associe un conducteur et une voiture.
Les deux requetes suivantes devraient toujours donner le meme resultat :
select c.nom, v.r.immatriculation
from Les_Conducteurs c, table (c.voitures) v ;
select c.r.nom, v.immatriculation
from Les_Voitures v, table (v.conducteurs) c ;
Et voici la procedure Conduire :
create or replace procedure Conduire (
nom in Les_Conducteurs.nom%type,
i in Les_Voitures.immatriculation%type) is
n natural ;
Pas_De_Table_Emboitee exception ;
pragma Exception_init (Pas_De_Table_Emboitee, -22908) ;
begin
-- tester si l’association existe deja
select count (*) into n
from Les_Conducteurs c, table (c.voitures) v
where c.nom = Conduire.nom
and v.r.immatriculation = i ;
if n = 1 then
raise_application_error (-20111, ’association existe deja’) ;
end if ;
-- mettre les deux a jour
savepoint debut ;
insert into
table (select c.voitures from Les_Conducteurs c where c.nom = Conduire.nom)
values ((select ref (v) from Les_Voitures v where v.immatriculation = i)) ;
insert into
table (select v.conducteurs from Les_Voitures v where v.immatriculation = i)
values ((select ref (c) from Les_Conducteurs c where c.nom = Conduire.nom)) ;
exception
when Pas_De_Table_Emboitee then
rollback work to savepoint debut ;
raise_application_error (-20111, ’conducteur ou voiture inexistant’) ;
end Conduire ;
Attention cette procedure PL/SQL n’est valide qu’a partir de Oracle10, en Oracle 8 il aurait falluecrire :
-- tester si l’association existe deja
select count (*) into n
from Les_Conducteurs c,
20.8. NIVEAUX DE PURETE DES METHODES D’OBJET 257
table (select c2.voitures from Les_Conducteurs c2 where c2.p.nom = c.nom) v
where c.nom = Conduire.nom
and v.r.immatriculation = i ;
20.7.6 Suppression en cas de dependance circulaire : drop ... force
En regle generale, Oracle interdit de detruire un objet si d’autres objets en dependent. Par exemple iln’est pas possible de detruire une table referencee par des clefs etrangeres d’autres tables.
En cas de dependance circulaire, on est alors tres embete ! heureusement Oracle fournit la clause forcequi permet de forcer la suppression d’un type, par exemple :
drop type Voiture force ;
20.7.7 Exercices
Reprendre l’exercice de la section 20.6.11 a la page 254 en proposant cette fois une implantationsymetrique de l’association entre les etudiants et les UE.
20.8 Niveaux de purete des methodes d’objet
Lors de la declaration d’un type objet, il est possible de specifier si une methode peut ou non lire ouavoir des effets de bord sur l’environnement.
Ceci a un interet en termes de genie logiciel.
PRAGMA RESTRICT_REFERENCES (
<nom_methode> | default,
{WNDS | WNPS | RNDS | RNPS | TRUST}+
)
W/R Write/ReadN No
D/P Database/PackageS State
DEFAULT le pragma est applique a toutes les methodes qui n’ont pas un pragma explicite.
WNDS aucune ecriture dans la base de donnees.
WNPS aucune ecriture dans un paquetage (modification de variables globales).
RNDS aucune lecture dans la base de donnees.
RNPS aucune lecture ou consultation d’une variable globale de paquetage.
TRUST aucune verification des restrictions precedentes ne sera faite : on fait confiance au code.
create type Personne as object (
...,
member function Age (AujourDhui Date) return Natural,
member function Heures_Supplementaires () return Natural,
pragma restrict_references (Age, RNDS, RNPS, WNPS, WNDS),
pragma restrict_references (Heures_Supplementaires, RNPS, WNPS, WNDS)
) ;
Q. 291 En provocant sciemment des erreurs, verifier que ces niveaux de purete sont bien un garde-fou.
20.9 Conception d’un schema relationnel-objet
Comme on vient de le voir, a un MCD donne, il est possible de donner un grand nombre d’implanta-tions relationnelle-objet. Cette diversite de solutions pose un probleme si on n’est pas capable de les
258 CHAPITRE 20. LE RELATIONNEL-OBJET DE ORACLE
decrire simplement et ainsi de les comparer.
Le but ici est de fournir une notation graphique permettant d’exprimer clairement et sans la lourdeurde la syntaxe Oracle les choix fait pour l’implantation. Cette notation s’appelle le schema navigationnel.
Ce schema navigationnel peut aussi permettre de se faire une idee a priori de l’adequation de l’im-plantation qu’il decrit avec les operations ou requetes qu’il devra supporter (par exemple : ce schemanavigationnel permet-il une execution efficace de telle requete tres frequente ?).
20.9.1 Notations graphiques
Une fleche simple correspond a exactement une reference.
Une fleche double correspond a plusieurs references (eventuellement zero). Cet ensemble de referencespeut-etre implante de diverses manieres : table emboıtee de references, table (clef de l’objet referencant,reference).
Si les objets references par une fleche double ne sont pas partageables on peut aussi se passer dereferences en les stockant dans une table emboıtee dans l’objet referencant.
20.9.2 Association 1-N
VoitureEtudiant
Voiture
Etudiant
v
VoitureEtudiant
Voiture
Chaque voiture a une référence sur son propriétaire
Chaque étudiant possède la table emboitée de ses voitures
Chaque étudiant possède une table emboitée de références sur ses voitures
qui est équivalent à
Etudiant
ref Voiturev
Dans le premier cas on a simplement remplace une clef etrangere par une reference. Sauf si on a poseune contrainte d’integrite referentielle sur la colonne referencante, Oracle autorise la suppression d’unobjet reference.
Dans le second cas les voitures n’existent pas de facon autonome : une voiture ne peut etre memoriseeque si elle appartient a un etudiant. Une contrainte d’unicite sur les voitures d’un etudiant ne peutetre exprimee simplement : il faut la programmer.
20.9. CONCEPTION D’UN SCHEMA RELATIONNEL-OBJET 259
Dans le troisieme cas il n’est pas possible de garantir que les references de voiture sont forcement prisedans la table des voitures (pas de clause scope for possible).
20.9.3 Association N-N
Cela risque de donner encore plus de possibilites differentes, bonjour la maintenance, a moins peut-etrede travailler avec un outil de haut niveau qui cache cette complexite.
Le relationnel pur a toujours l’enorme avantage de la simplicite !
Bibliographie
[1] PostgreSQL 8.2.1 Documentation. 2006. Documentation plutot bien lisible du SGBD PostgreSQL(on y apprend des choses), le site : http://www.postgresql.org.
[2] S. Sudarshan Abraham Silberschatz, Henry F. Korth. Database System Concepts. Mc Graw Hill,1997. Fondamental. Un classique et assez gros bouquin general, qui parle de quasiment tous lesaspects. Un de ceux que je prefere.
[3] ACSIOME. Modelisation dans la conception des systemes d’information. Masson, 1990. Un bonbouquin sur la modelisation avec plein d’exemples et d’exercices tres complets.
[4] Nacer Boudjlida. Bases de donnees et systemes d’informations. Dunod, ISBN 2-10-004309-9,1999. Assez proche des objectifs de ce poly. Les generalites sont exposees clairement, en revanche,techniquement il y a relativement peu d’informations sur un SGBD particulier, les SGBD donnesen exemple sont Sybase et Oracle.
[5] Chris J. Date. Introduction aux bases de donnees, 8ieme edition. Vuibert, ISBN 2-7117-4838-3,2004. Fondamental. Un (tres bon, le meilleur de ce que j’ai pu lire) classique. Une introductionintuitive aux fondements des BDD relationnelles.
[6] Steven Feuerstein. Oracle PL/SQL, Guide du programmeur, 3ieme edition. O’Reilly, ISBN 2-84177-238-1, 2002. Technique. Un gros bouquin specialise, pour se perfectionner en PL/SQL. Lechapitre sur les dates est particulierement limpide. Malheureusement cet ouvrage ne donne aucunretour d’experience pour la mise en place de transactions (voir mon poly :-).
[7] A Sayah G. Padiou. Techniques de synchronisation pour les applications paralleles. CepaduesEditions, 1990. Un livre pas tres epais qui introduit clairement les problemes des applicationsparalleles, leur comprehension, et les techniques permettant de les resoudre.
[8] Georges Gardarin. Bases de Donnees, objet et relationnel. Eyrolles, 1999. Fondamental. Un grosbouquin general, qui parle de quasiment tous les aspects. Enormement de references bibliogra-phiques, mais, dans le genre, je prefere [2] et surtout [5].
[9] Jennifer Widom Hector Garcia-Molina, Jeffrey D. Ullman. Database Systems, the complete book.Prentice Hall, 2002. Un super livre aussi.
[10] Christian Maree / Guy Ledant. SQL2 Initiation Programmation. Armand Colin, 1994. Technique.Tres pratique sur SQL2.
[11] Philippe Mathieu. Des bases de donnees a l’internet. Vuibert, 2000. Un bon bouquin assezgeneral sur les bases de donnees et qui aborde la construction d’applications Web.
[12] Jason Price. Java Programming with Oracle SQLJ. O’Reilly, 2001. Je n’en ai lu qu’un chapitredont je deduis que cet ouvrage sur SQLJ a l’air tres pedagogique. Le chapitre est celui qu’on peuttrouver a partir de la page http://www.oreilly.com/catalog/orasqlj.
[13] Raghu Ramakrishnan and Johannes Gehrke. Database Management Systems. McGraw-Hill, ISBN0072465638, 2002. Je ne l’ai pas lu (c’est Cedric qui me l’a indique : C’est en anglais, il est trescher, mais je le trouve tres bien et tres clair).
[14] Gunther Sturner. Oracle7. Thomson Computer Press, 1995. Technique. Genial pour savoircomment marche Oracle.
260