coursbdd_théorie

260
Chapitre 1 Base de donn´ ees et Syst` eme de gestion de base de donn´ ees 1.1 Qu’est-ce qu’une base de donn´ ees (BD) Une base de donn´ ees peut ˆ etre vue comme le besoin de m´ emoriser de fa¸ con durable des donn´ ees et de pouvoir exprimer le plus pr´ ecis´ ement possible les relations qu’entretiennent ces donn´ ees. Une fois cette repr´ esentation faite il est n´ ecessaire d’associer des fonctionnalit´ es (programmes et des requˆ etes) ` a cette base de donn´ ees afin de pouvoir l’exploiter le plus facilement possible. Toutes les personnes exploitant la mˆ eme base de donn´ ees n’ont pas la mˆ eme fonction et n’ont donc pas forc´ ement besoin de voir les mˆ emes informations ou d’appliquer les mˆ emes actions ` a la base de donn´ ees. Les syst` emes des privil` eges, des vues et des programmes stock´ es permettent de d´ elimiter rigoureusement ces diff´ erentes visions d’une mˆ eme base de donn´ ees (chaque vision est nomm´ ee sch´ ema externe). Enfin, plusieurs utilisateurs peuvent appliquer simultan´ ement des modifications ` a la mˆ eme base de donn´ ees, il est alors n´ ecessaire d’utiliser des techniques d’isolation et de synchronisation afin de garantir la coh´ erence de ces modifications. 1.2 Qu’est-ce qu’un syst` eme de gestion de base de donn´ ees (SGBD) Un SGBD est la structure d’accueil d’une ou plusieurs bases de donn´ ees : il offre les outils n´ ecessaires ` a la mise en place d’une base de donn´ ees. On pourrait comparer le SGBD au syst` eme d’exploitation et la base de donn´ ees ` a un programme d’application utilisant les services du syst` eme. Voici quelques-unes des caract´ eristiques d’un SGBD : – Capacit´ e de g´ erer des donn´ ees persistantes et structur´ ees. – Capacit´ e` a g´ erer, autant que possible, la emantique des donn´ ees et ` a garantir des propri´ et´ es (les contraintes, assertions, domaines des attributs, triggers et proc´ edures stock´ ees) – Pouvoir manipuler facilement et efficacement de tr` es grand volumes de donn´ ees. – Permettre l’ex´ ecution de transactions concurrentes par un ou plusieurs utilisateurs tout en conser- vant les propri´ et´ es de la BD. – Assurer la ecurit´ e des donn´ ees : – contrˆ oler les acc` es en fonction de droits accord´ es aux diff´ erents utilisateurs. – tol´ erer les pannes logicielles ou mat´ erielles grˆ ace ` a des proc´ edures de reprise. – Procurer l’ind´ ependance physique : le SGBD permet de manipuler les donn´ ees ind´ ependemment de leurs implantations mat´ erielles. – Procurer l’ind´ ependance logique : chaque utilisateur ne voit de la base que les donn´ ees qui lui sont n´ ecessaires (sch´ ema externe). 1

Upload: martin-brait

Post on 25-Jun-2015

415 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: coursBDD_Théorie

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

Page 2: coursBDD_Théorie

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).

Page 3: coursBDD_Théorie

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 :

Page 4: coursBDD_Théorie

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.

Page 5: coursBDD_Théorie

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.

Page 6: coursBDD_Théorie

Premiere partie

Relationnel et SQL

6

Page 7: coursBDD_Théorie

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

Page 8: coursBDD_Théorie

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.

Page 9: coursBDD_Théorie

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 ?

Page 10: coursBDD_Théorie

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.

Page 11: coursBDD_Théorie

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 ;

Page 12: coursBDD_Théorie

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.

Page 13: coursBDD_Théorie

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 ?

Page 14: coursBDD_Théorie

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 ;

Page 15: coursBDD_Théorie

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)

Page 16: coursBDD_Théorie

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) ;

Page 17: coursBDD_Théorie

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

Page 18: coursBDD_Théorie

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.

Page 19: coursBDD_Théorie

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 !’

Page 20: coursBDD_Théorie

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.

Page 21: coursBDD_Théorie

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,

Page 22: coursBDD_Théorie

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.

Page 23: coursBDD_Théorie

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.

Page 24: coursBDD_Théorie

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.

Page 25: coursBDD_Théorie

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

Page 26: coursBDD_Théorie

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 :

Page 27: coursBDD_Théorie

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

Page 28: coursBDD_Théorie

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

Page 29: coursBDD_Théorie

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

Page 30: coursBDD_Théorie

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) ;

Page 31: coursBDD_Théorie

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,

Page 32: coursBDD_Théorie

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

Page 33: coursBDD_Théorie

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.

Page 34: coursBDD_Théorie

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

Page 35: coursBDD_Théorie

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.

Page 36: coursBDD_Théorie

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.

Page 37: coursBDD_Théorie

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).

Page 38: coursBDD_Théorie

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.

Page 39: coursBDD_Théorie

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

Page 40: coursBDD_Théorie

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

Page 41: coursBDD_Théorie

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

Page 42: coursBDD_Théorie

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)

Page 43: coursBDD_Théorie

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.

Page 44: coursBDD_Théorie

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).

Page 45: coursBDD_Théorie

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 ?

Page 46: coursBDD_Théorie

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 :

Page 47: coursBDD_Théorie

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)

Page 48: coursBDD_Théorie

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

Page 49: coursBDD_Théorie

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

Page 50: coursBDD_Théorie

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 :

Page 51: coursBDD_Théorie

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.

Page 52: coursBDD_Théorie

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.

Page 53: coursBDD_Théorie

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 ;

Page 54: coursBDD_Théorie

54 CHAPITRE 5. CONTRAINTES D’INTEGRITE EN SQL

drop sequence Id_Voiture ;

Page 55: coursBDD_Théorie

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

Page 56: coursBDD_Théorie

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) :

Page 57: coursBDD_Théorie

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

Page 58: coursBDD_Théorie

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

Page 59: coursBDD_Théorie

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

Page 60: coursBDD_Théorie

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) ;

Page 61: coursBDD_Théorie

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.

Page 62: coursBDD_Théorie

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.

Page 63: coursBDD_Théorie

Deuxieme partie

Developpement serveur

63

Page 64: coursBDD_Théorie

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

Page 65: coursBDD_Théorie

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

Page 66: coursBDD_Théorie

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.

Page 67: coursBDD_Théorie

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

Page 68: coursBDD_Théorie

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 ;

Page 69: coursBDD_Théorie

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.

Page 70: coursBDD_Théorie

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.

Page 71: coursBDD_Théorie

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 ;

Page 72: coursBDD_Théorie

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 ;

Page 73: coursBDD_Théorie

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.

Page 74: coursBDD_Théorie

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 ;

Page 75: coursBDD_Théorie

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 :

Page 76: coursBDD_Théorie

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 ;

Page 77: coursBDD_Théorie

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 :

Page 78: coursBDD_Théorie

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) ;

Page 79: coursBDD_Théorie

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

Page 80: coursBDD_Théorie

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) :

Page 81: coursBDD_Théorie

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

Page 82: coursBDD_Théorie

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 ....

Page 83: coursBDD_Théorie

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,

Page 84: coursBDD_Théorie

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 ;

Page 85: coursBDD_Théorie

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 ?

Page 86: coursBDD_Théorie

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

Page 87: coursBDD_Théorie

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).

Page 88: coursBDD_Théorie

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).

Page 89: coursBDD_Théorie

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 !’) ;

Page 90: coursBDD_Théorie

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 :

Page 91: coursBDD_Théorie

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

Page 92: coursBDD_Théorie

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 ;

Page 93: coursBDD_Théorie

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)

) ;

Page 94: coursBDD_Théorie

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.

Page 95: coursBDD_Théorie

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.

Page 96: coursBDD_Théorie

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 :

Page 97: coursBDD_Théorie

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 ;

Page 98: coursBDD_Théorie

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.

Page 99: coursBDD_Théorie

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

Page 100: coursBDD_Théorie

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

Page 101: coursBDD_Théorie

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).

Page 102: coursBDD_Théorie

Troisieme partie

Schema externe

102

Page 103: coursBDD_Théorie

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.

Page 104: coursBDD_Théorie

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

Page 105: coursBDD_Théorie

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

Page 106: coursBDD_Théorie

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,

Page 107: coursBDD_Théorie

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> ;

Page 108: coursBDD_Théorie

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 :

Page 109: coursBDD_Théorie

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 ;

Page 110: coursBDD_Théorie

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.

Page 111: coursBDD_Théorie

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.

Page 112: coursBDD_Théorie

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

Page 113: coursBDD_Théorie

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.

Page 114: coursBDD_Théorie

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.

Page 115: coursBDD_Théorie

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,

Page 116: coursBDD_Théorie

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 ) ;

Page 117: coursBDD_Théorie

Quatrieme partie

Optimisations

117

Page 118: coursBDD_Théorie

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

Page 119: coursBDD_Théorie

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

Page 120: coursBDD_Théorie

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.

Page 121: coursBDD_Théorie

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

Page 122: coursBDD_Théorie

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.

Page 123: coursBDD_Théorie

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.

Page 124: coursBDD_Théorie

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 *

Page 125: coursBDD_Théorie

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} ?

Page 126: coursBDD_Théorie

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, . . .

Page 127: coursBDD_Théorie

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

Page 128: coursBDD_Théorie

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)

Page 129: coursBDD_Théorie

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.

Page 130: coursBDD_Théorie

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 :

Page 131: coursBDD_Théorie

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’ ;

Page 132: coursBDD_Théorie

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

Page 133: coursBDD_Théorie

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

Page 134: coursBDD_Théorie

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 :

Page 135: coursBDD_Théorie

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.

Page 136: coursBDD_Théorie

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

Page 137: coursBDD_Théorie

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.

Page 138: coursBDD_Théorie

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 ;

Page 139: coursBDD_Théorie

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 :

Page 140: coursBDD_Théorie

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

Page 141: coursBDD_Théorie

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.

Page 142: coursBDD_Théorie

Cinquieme partie

Les transactions

142

Page 143: coursBDD_Théorie

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

Page 144: coursBDD_Théorie

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

Page 145: coursBDD_Théorie

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.

Page 146: coursBDD_Théorie

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.

Page 147: coursBDD_Théorie

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].

Page 148: coursBDD_Théorie

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

Page 149: coursBDD_Théorie

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.

Page 150: coursBDD_Théorie

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.

Page 151: coursBDD_Théorie

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.

Page 152: coursBDD_Théorie

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

Page 153: coursBDD_Théorie

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 :

Page 154: coursBDD_Théorie

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).

Page 155: coursBDD_Théorie

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

Page 156: coursBDD_Théorie

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.

Page 157: coursBDD_Théorie

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

Page 158: coursBDD_Théorie

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 ?

Page 159: coursBDD_Théorie

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.

Page 160: coursBDD_Théorie

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.

Page 161: coursBDD_Théorie

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 :

Page 162: coursBDD_Théorie

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 :

Page 163: coursBDD_Théorie

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 :

Page 164: coursBDD_Théorie

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.

Page 165: coursBDD_Théorie

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

Page 166: coursBDD_Théorie

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 !

Page 167: coursBDD_Théorie

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 :

Page 168: coursBDD_Théorie

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.

Page 169: coursBDD_Théorie

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

Page 170: coursBDD_Théorie

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)

Page 171: coursBDD_Théorie

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

Page 172: coursBDD_Théorie

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 ?

Page 173: coursBDD_Théorie

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.

Page 174: coursBDD_Théorie

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.

Page 175: coursBDD_Théorie

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,

Page 176: coursBDD_Théorie

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)

Page 177: coursBDD_Théorie

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 ?

Page 178: coursBDD_Théorie

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

Page 179: coursBDD_Théorie

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-

Page 180: coursBDD_Théorie

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

Page 181: coursBDD_Théorie

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

Page 182: coursBDD_Théorie

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

Page 183: coursBDD_Théorie

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

Page 184: coursBDD_Théorie

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) ;

Page 185: coursBDD_Théorie

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)

Page 186: coursBDD_Théorie

Sixieme partie

Developpement client/serveur

186

Page 187: coursBDD_Théorie

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

Page 188: coursBDD_Théorie

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

Page 189: coursBDD_Théorie

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

Page 190: coursBDD_Théorie

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 ?

Page 191: coursBDD_Théorie

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

Page 192: coursBDD_Théorie

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()

Page 193: coursBDD_Théorie

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.

Page 194: coursBDD_Théorie

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 :

Page 195: coursBDD_Théorie

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 ;

Page 196: coursBDD_Théorie

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 () ;

}

Page 197: coursBDD_Théorie

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.

Page 198: coursBDD_Théorie

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

Page 199: coursBDD_Théorie

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 ;

Page 200: coursBDD_Théorie

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 */

Page 201: coursBDD_Théorie

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 ;

Page 202: coursBDD_Théorie

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 :

Page 203: coursBDD_Théorie

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.

Page 204: coursBDD_Théorie

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 :

Page 205: coursBDD_Théorie

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() ;

Page 206: coursBDD_Théorie

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 } ;

Page 207: coursBDD_Théorie

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.

Page 208: coursBDD_Théorie

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.

Page 209: coursBDD_Théorie

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 ()) ;

}

Page 210: coursBDD_Théorie

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/

Page 211: coursBDD_Théorie

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

Page 212: coursBDD_Théorie

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.

Page 213: coursBDD_Théorie

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.

Page 214: coursBDD_Théorie

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

Page 215: coursBDD_Théorie

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.

Page 216: coursBDD_Théorie

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.

Page 217: coursBDD_Théorie

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)

Page 218: coursBDD_Théorie

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 ;}

Page 219: coursBDD_Théorie

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

Page 220: coursBDD_Théorie

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

Page 221: coursBDD_Théorie

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 :

Page 222: coursBDD_Théorie

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>

Page 223: coursBDD_Théorie

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>

Page 224: coursBDD_Théorie

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 :

Page 225: coursBDD_Théorie

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 () ;

}

}

Page 226: coursBDD_Théorie

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,

Page 227: coursBDD_Théorie

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 :

Page 228: coursBDD_Théorie

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 ;

Page 229: coursBDD_Théorie

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 ;

}

Page 230: coursBDD_Théorie

Septieme partie

Bases de donnees objet et compromisdu relationnel-objet

230

Page 231: coursBDD_Théorie

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 ;

Page 232: coursBDD_Théorie

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

Page 233: coursBDD_Théorie

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

Page 234: coursBDD_Théorie

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 :

Page 235: coursBDD_Théorie

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.

Page 236: coursBDD_Théorie

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

Page 237: coursBDD_Théorie

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).

Page 238: coursBDD_Théorie

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.

Page 239: coursBDD_Théorie

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’))) ;

Page 240: coursBDD_Théorie

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 :

Page 241: coursBDD_Théorie

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

Page 242: coursBDD_Théorie

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 ;

Page 243: coursBDD_Théorie

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

Page 244: coursBDD_Théorie

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) ;

Page 245: coursBDD_Théorie

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

) ;

Page 246: coursBDD_Théorie

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.

Page 247: coursBDD_Théorie

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 :

Page 248: coursBDD_Théorie

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.

Page 249: coursBDD_Théorie

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.

Page 250: coursBDD_Théorie

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

Page 251: coursBDD_Théorie

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 ;

Page 252: coursBDD_Théorie

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

Page 253: coursBDD_Théorie

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.

Page 254: coursBDD_Théorie

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 ;

Page 255: coursBDD_Théorie

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).

Page 256: coursBDD_Théorie

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,

Page 257: coursBDD_Théorie

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

Page 258: coursBDD_Théorie

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.

Page 259: coursBDD_Théorie

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 !

Page 260: coursBDD_Théorie

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