Vous êtes sur la page 1sur 608

See discussions, stats, and author profiles for this publication at: https://www.researchgate.

net/publication/366569633

SGBD Relationnels - Tome 1: Bases de données relationnelles -


État de l'art

Book · December 2022

CITATIONS READS

0 400

1 author:

Joachim Tankoano
University of Ouagadougou
14 PUBLICATIONS 39 CITATIONS

SEE PROFILE

All content following this page was uploaded by Joachim Tankoano on 24 December 2022.

The user has requested enhancement of the downloaded file.


SGBD relationnels - Tome 1
Bases de données relationnelles
État de l’art
2ème Édition
SGBD relationnels – Tome 1
Bases de données relationnelles
État de l’art
Joachim TANKOANO

2ème Édition
Copyright © Joachim TANKOANO, 2021

« Le Code de la propriété intellectuelle et artistique n’autorisant, aux termes


des alinéas 2 et 3 de l’article L.122-5, d’une part, que les « copies ou
reproductions strictement réservées à l’usage privé du copiste et non
destinées à une utilisation collective » et, d’autre part, que les analyses et
les courtes citations dans un but d’exemple et d’illustration, « toute
représentation ou reproduction intégrale, ou partielle, faite sans le
consentement de l’auteur ou de ses ayants droit ou ayants cause, est illicite
» (alinéa 1er de l’article L. 122-4). »
« Cette représentation ou reproduction, par quelque procédé que ce soit,
constituerait donc une contrefaçon sanctionnée par les articles 425 et
suivants du Code pénal. »
ISBN du livre imprimé : 978-2-9577933-2-7
PREFACE
Après une carrière consacrée à l’informatique et aux nouvelles technologies
au Burkina Faso, Joachim TANKOANO nous offre ici un ouvrage de
référence pour l’enseignement des bases de données alliant théorie, mise en
œuvre pratique, et propositions d’évolution.

Après ses études à l’Université de Montréal couronnées par un B. sc.


Informatique et par un M. sc. Informatique, il a réalisé une thèse d’État à
Nancy, dans l’équipe de génie logiciel que je dirigeais au sein du CRIN
(Centre de Recherche en Informatique de Nancy), laboratoire qui deviendra
ensuite le LORIA (Laboratoire Lorrain de Recherche en Informatique et ses
Applications). En parallèle de sa thèse d’état, il a enseigné à l’IUT
« Charlemagne » de Nancy. Joachim choisit ensuite de rentrer au pays, en
laissant un excellent souvenir tant à ses collègues chercheurs
qu’enseignants. Rapidement, il est recruté à l’IAI (Institut inter Africain
d’Informatique) à Libreville, où il a fait preuve d’une rigueur scientifique
et de talents pédagogiques. Il a pu ainsi lier de nombreux contacts parmi
les informaticiens africains. De l’IAI, il a été rappelé en 1990 pour la création
au sein de l’Université de Ouagadougou de la 1ère École Informatique de
son pays (l’École Supérieure d’Informatique (ESI)) qui fut transférée ensuite
à Bobo-Dioulasso pour la création de l’Université Polytechnique de Bobo-
Dioulasso. Il a sollicité et obtenu de ses anciens collègues de Nancy leur
participation à ce développement, en allant y faire des cours aussi souvent
que possible. Joachim se passionne aussi pour les politiques publiques de
développement de l’informatique au Burkina Faso et intègre la DELGI
(Délégation Générale à l’Informatique), tout en continuant ses
enseignements. Il en deviendra le Délégué Général de 1995 à 2005. Il fut
aussi très actif au sein de la Conférence Africaine de Recherche en
Informatique (CARI). De 2006 à 2008 il est Ministre en charge du ministère
nouvellement créé, responsable conjointement des secteurs de
l’Informatique, des Télécommunications et des Postes du Burkina Faso. En
qualité de Délégué Général et de Ministre, il a aidé son pays à adopter une
vision holistique pour l’appropriation de l’utilisation des technologies
numériques, centrée sur une cyberstratégie nationale et des cyberstratégies
sectorielles intégrées dans les politiques nationales et sectorielles de
Joachim TANKOANO

développement et sur la mise en place d’un cadre juridique attrayant,


favorable à la mise en place d’une politique volontariste de développement
d’infrastructures de base de qualité, au développement de ressources
humaines qualifiées et à la dématérialisation des procédures. Après cette
période d’intense activité politique, il a apporté, à la demande du PNUD,
un appui à un pays africain de 2009 à 2010 pour l’élaboration de documents
de politiques de développement des technologies du numérique, avant de
revenir à ses enseignements, et en particulier sur ceux relatifs aux bases de
données.

Cet ouvrage en deux tomes est le fruit du travail de cette période de dix
années, au cours desquelles Joachim TANKOANO a dispensé un cours de
bases de données à l’Institut Burkinabè des Arts et Métiers (IBAM) de
l’Université Joseph Ki-Zerbo (ex Université de Ouagadougou), imaginé des
exemples et des exercices pratiques et constamment amélioré son contenu,
pour aboutir à cet ouvrage considérable de qualité, de complétude et de
rigueur : chacun des nombreux exemples proposés a été mis en œuvre sur
machine et validé avec ses étudiants.

Le discours est organisé en 16 chapitres de manière assez classique mais en


partant systématiquement des exigences que la technologie des bases de
données doit satisfaire pour pouvoir jouer son rôle au sein d’une entreprise,
liées à l’utilisation des données et à la préservation de leurs qualités, pour
aller vers les réalisations concrètes. Le modèle relationnel pur en premier y
est détaillé, en mettant en évidence la cohérence, la fiabilité de l’approche
grâce à l’algèbre relationnelle et à la normalisation. Puis apparaissent de
nouveaux objets à conserver (textes, images, vidéos, …) et de nouveaux
modèles, puis les multibases, les bases de données réparties, le modèle
objet-relationnel, relationnel et XML. La mise en œuvre des bases de
données est traitée et discutée en détail, que ce soit sur les connecteurs, leur
intégration dans les langages de programmation, ou sur l’exécution. En
particulier, on y trouvera comment sont traduites les requêtes SQL en
programmes exécutables, comment optimiser la répartition, etc.

Mais c’est loin d’être classique, car l’ensemble est sous-tendu tout au long
de l’ouvrage, par un souci critique d’analyser l’évolution des modèles, des
techniques et surtout des architectures en fonction de l’évolution des
vi
SGBD relationnels – Tome 1, État de l’art

besoins.

On découvrira ainsi, dans ce 2ème tome, une grande attention portée aux
architectures et à leurs évolutions. On notera, en particulier, une
proposition qui permet de garder les qualités de rigueur du modèle
relationnel pur et les avantages conceptuels de l’approche Objet et XML.
Elle s’appuie sur l’approche évolutionnaire, très en vogue dans les organes
de normalisation de SQL et chez les grands éditeurs de SGBD, pourtant
critiquée par Chris Date lui-même, à cause des impacts négatifs sur la
cohérence du modèle relationnel qui en résultent et sur sa capacité à
préserver les acquis du modèle relationnel pur. Elle permet l’évolution par
l’intégration de nouveaux concepts dans le modèle relationnel. En plus de
cette intégration de ces nouveaux concepts dans le modèle relationnel,
l’approche proposée ici s’appuie également sur une mise en
correspondance de la perception que ces concepts permettent de définir par
rapport à la perception définie par une base de données relationnelle pure
utilisée en interne pour garantir l’intégrité et l’indépendance des données.
Cette proposition, détaillée clairement et de façon complète dans cette 2ème
édition de l’ouvrage, tant sur sa logique que sur sa capacité à contribuer à
une évaluation efficace des requêtes SQL, permet de tirer le meilleur de
mondes difficiles à concilier, car visant parfois des objectifs différents, sans
sacrifier les bénéfices du modèle initial.

Cet ouvrage s’adresse en premier lieu aux étudiants en informatique, mais


aussi aux ingénieurs désirant se remémorer ou compléter leurs
connaissances, aux responsables et architectes de systèmes d’information et
à tous ceux qui cherchent un ouvrage de référence. À ne pas douter, il sera
aussi un outil précieux pour les enseignants.

C’est une contribution essentielle pour permettre de réussir la transition


numérique, en particulier au Burkina Faso et en Afrique.

Jean Claude DERNIAME


Professeur émérite de
l’Université de Lorraine

vii
TABLE DES MATIÈRES

PREFACE ............................................................................................................. V

AVANT-PROPOS ........................................................................................ XVII

REMERCIEMENTS ......................................................................................XXV

CHAPITRE 1. : INTRODUCTION ................................................................ 1

1.1. Du fichier aux bases de données 1

1.1.1. Le système d’information : un besoin fondamental pour toute entreprise . 1


1.1.2. La gestion manuelle de la mémoire d’un système d’information ................. 2
1.1.3. La technologie des fichiers.................................................................................... 3
1.1.4. La technologie des bases de données .................................................................. 8

1.2. Les principales exigences imposées à la technologie des bases de données 9

1.2.1. L’indépendance des données ............................................................................. 10


1.2.2. La cohérence des données (l’intégrité et la non-redondance)....................... 16
1.2.3. La confidentialité .................................................................................................. 18
1.2.4. La gestion des accès concurrents ........................................................................ 19
1.2.5. La sécurité .............................................................................................................. 21
1.2.6. Les performances .................................................................................................. 22
1.2.7. L’adéquation de l’interface d’accès pour la manipulation des données..... 23
1.2.8. Le langage SQL ..................................................................................................... 25

CHAPITRE 2. : CONCEPTS ET LANGAGES FORMELS DU MODELE


RELATIONNEL ................................................................................................. 27

2.1. Introduction 27

2.2. Les concepts de base du modèle relationnel 29

2.2.1. Les concepts de relation et de domaine/attribut ............................................. 29


2.2.2. Le concept de dépendance entre groupes d’attributs ..................................... 33
2.2.3. Les concepts de clé primaire, clé alternative et clé étrangère ....................... 34
2.2.4. Le pouvoir de modélisation du modèle relationnel et son importance pour
la conception ........................................................................................................................... 36
Joachim TANKOANO

2.3. Le langage algébrique relationnel 41

2.3.1. Les opérateurs algébriques relationnels ...........................................................41


2.3.2. L’utilité du langage algébrique relationnel ......................................................48
2.3.3. Exemples de calculs algébriques relationnels .................................................49
2.3.4. Exercice ....................................................................................................................58

2.4. Les langages prédicatifs relationnels 59

2.4.1. Quelques rappels sur le formalisme de la logique du 1 er ordre ...................59


2.4.2. Les requêtes des langages prédicatifs relationnels .........................................64
2.4.3. Le pouvoir d’expression des langages prédicatifs relationnels ....................68
2.4.4. L’utilité des langages prédicatifs relationnels .................................................71
2.4.5. Exemples de formulation de requêtes prédicatives relationnelles ..............72
2.4.6. Exercice ....................................................................................................................85

CHAPITRE 3. : CONCEPTION DES SCHEMAS LOGIQUES


RELATIONNELS (DEPENDANCES ET NORMALISATION) ............... 87

3.1. Introduction 87

3.2. Pourquoi normaliser une relation ? 88

3.2.1. Les anomalies possibles dans une extension légale de relation ...................88
3.2.2. Ce que permet la normalisation d’une relation ...............................................90

3.3. Les outils de la normalisation : les différentes formes de dépendances 92

3.3.1. Les dépendances fonctionnelles (DF) ................................................................93


3.3.2. Les dépendances multivaluées (DMV) ...........................................................106
3.3.3. Les dépendances de jointure (DJ) ....................................................................111

3.4. Les formes normales 114

3.4.1. La 1ère forme normale (1FN) ...............................................................................114


3.4.2. La 2ème forme normale (2FN) .............................................................................117
3.4.3. La 3ème forme normale (3FN) .............................................................................119
3.4.4. La forme normale de BOYCE-CODD (FNBC) ...............................................121
3.4.5. La 4ème forme normale (4FN) .............................................................................125
3.4.6. La 5ème forme normale (5FN) .............................................................................127

3.5. Les approches de conception d’un schéma logique relationnel 129

3.5.1. L’objectif général de ces approches .................................................................129

x
SGBD relationnels – Tome 1, État de l’art

3.5.2. Les approches relationnelles et les approches orientées sémantique ....... 131
3.5.3. Exemples de méthodes relationnelles de conception d’un schéma
relationnel.............................................................................................................................. 134

CHAPITRE 4. : PRESENTATION DU LANGAGE SQL ...................... 149

4.1. Introduction 149

4.2. Les concepts propres au langage SQL 155

4.3. Le langage de manipulation des données de SQL 156

4.3.1. L’ordre SELECT / FROM / WHERE ................................................................. 156


4.3.2. Exemples de formulation de requêtes à l’aide d’un ordre SELECT .......... 182
4.3.3. Exercice ................................................................................................................. 188
4.3.4. L’ordre INSERT INTO ....................................................................................... 189
4.3.5. L’ordre UPDATE ................................................................................................. 191
4.3.6. L’ordre DELETE .................................................................................................. 193

4.4. Le langage de définition des données de SQL 194

4.4.1. Le dictionnaire des données et l’architecture des schémas ......................... 195


4.4.2. La création, modification et suppression de la définition d’une table ..... 197
4.4.3. La création, modification, suppression et manipulation d’une vue .......... 205
4.4.4. La création, modification et suppression d’une séquence .......................... 214
4.4.5. La gestion des droits d’accès et de la confidentialité ................................... 216

4.5. L’architecture fonctionnelle du moteur SQL 224

CHAPITRE 5. : EXTENSION DE SQL EN LANGAGE COMPLET DE


PROGRAMMATION (PL/SQL) ................................................................... 229

5.1 Introduction 229

5.2. Les blocs 234

5.2.1. Les constantes et les variables .......................................................................... 236


5.2.2. Les types relatifs aux valeurs composites ...................................................... 237
5.2.3. Les variables hôtes.............................................................................................. 241
5.2.4. L’instruction d’affectation de la valeur d’une expression à une variable 241
5.2.5. Les instructions de contrôle de la logique d’enchainement des actions .. 243
5.2.6. Les instructions de manipulation d’une base de données .......................... 245
5.2.7. Les curseurs.......................................................................................................... 249

xi
Joachim TANKOANO

5.2.8. Les variables curseurs .........................................................................................258


5.2.9. Le mécanisme des exceptions ............................................................................263

5.3. Les procédures et fonctions 269

5.3.1. La déclaration et la définition des procédures et fonctions .........................272


5.3.2. Les appels de procédures et fonctions dans du code PL/SQL .....................276
5.3.3. L’appel d’un sous-programme stocké à partir d’un navigateur ..................280

5.4. Les packages 286

5.4.1. Les instructions de création d’un package ......................................................288


5.4.2. Exemple de création de packages .....................................................................290
5.4.3. Les packages offerts par Oracle ........................................................................295

5.5. Les déclencheurs (ou triggers) 296

CHAPITRE 6. : CONNECTEURS DES BASES DE DONNEES ........... 302

6.1. Introduction 302

6.2. Le connecteur ODBC 305

6.2.1. L’architecture du connecteur ODBC ................................................................305


6.2.2. Quelques exemples de fonctionnalités de l’API ODBC du langage C......307
6.2.3. Un exemple de programme................................................................................313

6.3. Le connecteur JDBC 314

6.3.1. L’architecture du connecteur JDBC ..................................................................315


6.3.2. La connexion et la déconnexion à une source de données ...........................317
6.3.3. L’exécution d’un ordre SQL non paramétré ...................................................322
6.3.4. L’exécution d’un ordre SQL paramétré ...........................................................336
6.3.5. L’exécution des procédures et fonctions stockées .........................................341

CHAPITRE 7. : GESTION DE LA MEMOIRE RELATIONNELLE .... 346

7.1. Introduction 346

7.2. La structure et le fonctionnement d’un disque magnétique 347

7.2.1. Une vue schématique de l’architecture d’un disque magnétique ..............347


7.2.2. La lecture et l’écriture sur un disque magnétique .........................................348
7.2.3. Disques magnétiques versus mémoire centrale ............................................349

xii
SGBD relationnels – Tome 1, État de l’art

7.2.4. Un exemple de caractéristiques d’un disque magnétique .......................... 350

7.3. Les principes de base pour le stockage d’une table sur un disque 351

7.3.1. Un rappel sur la structure logique d’une base de données relationnelle. 351
7.3.2. Le stockage d’une table dans des blocs de disques ...................................... 351
7.3.3. L’organisation des lignes dans un bloc .......................................................... 352
7.3.4. L’organisation des colonnes dans une ligne .................................................. 353
7.3.5. Les conséquences sur le temps d’accès aux lignes d’une table stockées en
vrac dans des blocs ............................................................................................................... 354
7.3.6. Les techniques pour réduire le temps d’accès à une ligne d’une table ..... 356
7.3.7. L’impact des disques SSD sur le temps d’accès à une ligne d’une table . 357

7.4. La réduction du temps d’accès à l’aide d’une stratégie de placement des lignes
de tables 358

7.4.1. Pourquoi gérer le placement des lignes à l’intérieur des blocs .................. 358
7.4.2. Les leçons à tirer .................................................................................................. 359

7.5. La réduction du temps d’accès à l’aide d’une zone tampon 360

7.6. La réduction du temps d’accès à l’aide d’index 361

7.6.1. L’objectif et le principe général ....................................................................... 362


7.6.2. L’exemple utilisé pour les illustrations .......................................................... 363
7.6.3. Les index non denses ......................................................................................... 364
7.6.4. Les index denses ................................................................................................. 372
7.6.5. Les arbres B+........................................................................................................ 374
7.6.6. Le hachage statique ............................................................................................ 389
7.6.7. Le hachage dynamique ...................................................................................... 393
7.6.8. Les Index bitmap ................................................................................................ 409
7.6.9. Exercices ............................................................................................................... 411

7.7. La gestion de la mémoire relationnelle par le SGBD Oracle 414

7.7.1. L’architecture physique d’un SGBD Oracle en termes de fichiers ............ 414
7.7.2. L’organisation logique des données à l’intérieur des fichiers ................... 415
7.7.3. Le langage de définition du schéma physique d’une base de données ... 420
7.7.4. L’architecture interne d’un serveur de bases de données Oracle .............. 429

7.8. Exercice 431

CHAPITRE 8. : TRANSFORMATION DES REQUETES SQL EN


CODE EXECUTABLE ..................................................................................... 433

xiii
Joachim TANKOANO

8.1. Introduction 433

8.2. Principes généraux 434

8.3. Optimisation du plan d’exécution logique d’une requête 440

8.3.1. Les règles de réécriture basées sur des équivalences algébriques .............442
8.3.2. Les règles de réécriture basées sur des équivalences logiques ...................449

8.4. Génération du plan d’exécution physique d’une requête 452

8.4.1. L’algorithme de tri fusion ..................................................................................454


8.4.2. Les algorithmes pour la sélection .....................................................................459
8.4.3. Les algorithmes pour la projection ..................................................................463
8.4.4. Les algorithmes pour la jointure ......................................................................465
8.4.5. Les algorithmes pour l’intersection, l’union, la différence et la division .471
8.4.6. Le choix des algorithmes et la génération du plan d’exécution physique 471
8.4.7. Les approches pour le déroulement d’un plan d’exécution physique.......476

8.5. L’ordre EXPLAIN PLAN d’Oracle 479

8.5.1. Le plan d’exécution physique d’Oracle ...........................................................480


8.5.2. Les itérateurs du SGBD Oracle .........................................................................482
8.5.3. La mise en œuvre de l’ordre « EXPLAIN PLAN » .........................................485
8.5.4. Exemples ...............................................................................................................485

8.6. Exercices 492

CHAPITRE 9. : GESTION DES ACCES CONCURRENTS ET DES


REPRISES EN CAS D’INCIDENT ............................................................... 496

9.1. Introduction 496

9.2. Problématique 497

9.2.1. Les anomalies liées aux accès simultanés à une base de données..............498
9.2.2. Les caractéristiques des incidents ....................................................................505
9.2.3. Les propriétés attendues de l’environnement d’exécution ..........................506

9.3. La gestion des accès concurrents 507

9.3.1. Les caractéristiques d’un plan d’exécution de transactions concurrentes 508


9.3.2. Les approches pour le contrôle de la concurrence des transactions ...........523
9.3.3. Les mécanismes de contrôle basés sur l’utilisation des verrous .................526

xiv
SGBD relationnels – Tome 1, État de l’art

9.3.4. Les mécanismes de contrôle basés sur les estampilles ................................ 541
9.3.5. Les mécanismes de contrôle basés sur la certification des transactions ... 546
9.3.6. Les quatre niveaux d’isolation de SQL ........................................................... 547

9.4. La gestion des reprises après incident 549

9.4.1. Les moyens de mise en œuvre des reprises après incident ......................... 550
9.4.2. Les différents types de reprises après incident ............................................. 555

9.5. La gestion des accès concurrents et des reprises sous Oracle 559

9.5.1. La démarcation des transactions à l’intérieur d’une session Oracle ......... 560
9.5.2. La gestion de l’exécution concurrente des transactions sous Oracle ......... 562
9.5.3. La gestion d’une reprise après un incident sous Oracle .............................. 574

9.6. Exercice 579

xv
AVANT-PROPOS
Le public visé

Cet ouvrage en deux tomes reprend le contenu du cours que j’ai appris à
enseigner à l’Institut Burkinabè des Arts et Métiers (IBAM) de l’Université
Joseph Ki-Zerbo (ex-Université de Ouagadougou) en Licence 2 et 3 et en
Master 1 de la filière MIAGE (Méthodes Informatiques Appliquées à la
Gestion), au cours des dix dernières années de ma carrière d’enseignant.

Le tome 1 est intitulé « SGBD relationnels – Tome 1, Bases de données


relationnelles – État de l’art » et le tome 2 « SGBD relationnels – Tome 2,
Vers les Bases de données Réparties, Objet, Objet-relationnelles, XML,
… ».

Cet ouvrage a été rédigé à la demande de jeunes enseignants qui souhaitent


poursuivre et améliorer les résultats de cet effort d’apprentissage. Il s’agit
donc avant tout d’un ouvrage dont la vocation est de servir de support aussi
bien à un enseignant pour la préparation de ses cours qu’à un étudiant pour
l’approfondissement de son apprentissage. Cet ouvrage peut aussi servir à
un professionnel de l’informatique désireux d’approfondir par lui-même
ses connaissances sur la matière traitée. Les personnes désireuses de se faire
une idée générale sur cette matière peuvent aussi trouver leur compte en
parcourant les parties introductives qui leurs semblent accessibles.

Cet ouvrage contient la presque totalité des exemples et des nombreux


exercices que mes étudiants ont eu à traiter au cours de leurs travaux dirigés
et de leurs travaux pratiques, en s’appuyant notamment sur le SGBD Oracle
12c. Je voudrais au passage les remercier pour leur contribution parce qu’ils
détiennent de facto une partie de la paternité des solutions présentées. Je
voudrais aussi au passage préciser que j’ai pris le soin de tester les solutions
correspondant à du code exécutable.

Les prérequis et l’objectif général de la matière traitée

Les prérequis de la matière traitée sont :


• L’initiation à l’algorithmique et à la programmation
Joachim TANKOANO

• L’initiation à la programmation orientée objet pour ce qui concerne


les chapitres 6, 11, 12, 13, 14 et 15
• L’initiation aux méthodes systémiques de conception des
applications informatiques.

L’objectif général de cette matière est d’amener l’apprenant à :


• Comprendre d’une part, la raison d’être de la technologie des bases
de données, à savoir, les difficultés rencontrées dans le
fonctionnement des entreprises et les limites de la technologie des
systèmes de gestion des fichiers qui ont conduit à son
développement et d’autre part, les garanties (sur la qualité des
données d’un système d’information et sur les possibilités offertes
pour l’utilisation de ces données dans le contexte d’une entreprise)
que cette technologie doit fournir pour pouvoir servir de support
pour le stockage de données de toute sorte
• Acquérir les connaissances requises pour une utilisation efficace et
efficiente de cette technologie comme une boîte noire, fondées sur
une compréhension fine de l’organisation et du fonctionnement
interne de cette boîte noire, notamment dans le cadre de projets
d’informatisation complexes basés sur l’intégration de technologies
modernes
• Comprendre les évolutions en cours de cette technologie, à savoir, ce
qui motive ces évolutions, les voies explorées, leurs limites et les
perspectives envisageables.

Le fil conducteur dans l’ouvrage

La technologie des bases de données n’est qu’un moyen pouvant au sein


d’une entreprise servir de support pour le stockage des données d’un
système d’information. Depuis les premières versions jusqu’à celles
proposées à ce jour sur le marché, les fonctionnalités définies et
développées dans le cadre de sa mise en œuvre n’ont eu comme seul but
que d’apporter une réponse appropriée aux exigences liées aux
préoccupations des entreprises qu’elle doit satisfaire pour pouvoir jouer ce
rôle central et névralgique. Aussi, pour aborder l’enseignement de cette

xviii
SGBD relationnels – Tome 1, État de l’art

technologie de la façon la plus pragmatique possible selon une approche


orientée finalité, nous avons choisi dans cet ouvrage de retenir comme fil
conducteur, la manière dont elle procède pour satisfaire à ces exigences,
tant sur le plan des fondations théoriques sur lesquelles elle repose que sur
les techniques qu’elle met en œuvre.

Pourquoi une 2ème édition

La 1ère édition de cet ouvrage a mis en évidence le fait que le modèle objet-
relationnel et le modèle relationnel et XML sacrifient les garanties
d’intégrité et d’indépendance des données. Les conséquences de ce constat
y ont été illustrées à l’aide d’exemples. La principale raison d’être de cette
2ème édition est d’y inclure une explication des causes de ce constat ainsi
qu’une description complète et détaillée de la solution palliative esquissée
dans la 1ère édition. Ces ajouts ont été opérés en veillant à la cohérence du
contenu des deux tomes qui composent l’ouvrage.

Ce tome 1 de l’ouvrage

Issue au début des années 70 des travaux de CODD E.F., le père fondateur
du modèle relationnel pur, la 2ème génération de la technologie des bases de
données est encore très largement utilisée par les entreprises. Cette
résistance au temps, dans un secteur caractérisé par des mutations
effrénées, s’explique en grande partie par le fait que cette 2ème
génération repose nativement sur deux éléments essentiels :
• Elle repose sur un ensemble de concepts volontairement réduit au
minimum nécessaire, non dérivés d’un paradigme de
programmation en particulier, qui induisent une perception sur les
données : (i) dénudée de toute ambiguïté susceptible d’induire des
interprétations différentes, (ii) indépendante des modèles
sémantiques et des modèles de données abstraites qui relèvent du
domaine de la programmation et pouvant servir pour leur
implémentation logique (iii) pouvant servir pour la dérivation d’une
implémentation physique sur les supports de stockage sans avoir à
procéder à une décomposition préalable des informations
modélisées en informations plus élémentaires, (iv) pouvant de ce fait
servir de base commune à toutes les applications devant avoir des

xix
Joachim TANKOANO

perceptions différentes sur ces données


• Elle repose en outre et surtout sur des fondations théoriques qui
permettent de déterminer de façon rigoureuse, en fonction des règles
de gestion et des besoins de l’entreprise, une organisation logique
possible des données ainsi qu’une répartition possible de ces
données, pouvant garantir leur intégrité.

Ce tome est consacré à la présentation des fondamentaux des SGBD


relationnels. Il décrit de façon détaillée, comment en s’appuyant sur le
modèle relationnel pur, la 2ème génération de cette technologie a réussi à
fournir toutes les garanties, liées aux principales préoccupations des
entreprises, à savoir : l’indépendance des données, l’intégrité, la
confidentialité, la gestion des accès simultanés par plusieurs utilisateurs, la
sécurité en cas d’incident, les performances et à l’adéquation du langage
d’interface permettant la manipulation des données selon l’approche
relationnelle.

Les garanties liées à l’intégrité, à la confidentialité et à l’adéquation du


langage de manipulation des données, sont abordées en premier dans les
chapitres 2, 3, 4, 5 et 6 dédiés aux aspects relevant de l’interface utilisé pour
accéder aux fonctionnalités de cette 2ème génération. Les langages et les
techniques présentés dans les chapitres 2, 3 et 4 sont accompagnés de
guides méthodologiques appuyés par de nombreux exemples qui facilitent
leur appropriation.
• Le chapitre 2 contient une présentation : (i) des concepts du modèle
relationnel pur qui servent de support pour organiser logiquement
les données, (ii) des langages abstraits de manipulation de ces
données, dérivés de ces concepts en prenant appui sur la théorie des
ensembles et sur la logique du 1er ordre.
• Le chapitre 3 contient la présentation des techniques d’ingénierie
utilisées pour la conception de l’organisation logique des données.
Ces techniques ont elles aussi été dérivées des concepts du modèle
relationnel pur. Elles sont basées sur la normalisation et conduisent
à une organisation logique des données qui permet au SGBD d’offrir
une garantie sur le respect des contraintes d’intégrité qui découlent

xx
SGBD relationnels – Tome 1, État de l’art

des dépendances qui doivent être maintenues entre les données


élémentaires du système d’information au regard des règles de
gestion.
• Le chapitre 4 introduit SQL, le langage d’interface normalisé qui
permet d’une part, la création du schéma qui définit l’organisation
logique d’une base de données relationnelle et d’autre part, la
manipulation de cette base de données selon une approche
relationnelle en faisant abstraction de son organisation physique. Ce
chapitre met en exergue les apports du modèle relationnel pur,
présenté dans les chapitres 2 et 3, qui font de SQL (i) un puissant
langage de définition des données intégrant nativement la
spécification des contraintes d’intégrité et de confidentialité des
données et (ii) un puissant langage abstrait de manipulation de ces
données.
• Le chapitre 5 traite des extensions apportées à SQL, pour en faire un
langage complet de programmation. Ce langage étendu intègre
nativement les fonctionnalités pour l’exécution des ordres SQL de
démarcation des transactions et de manipulation d’une base de
données à partir d’un programme. Une autre de ses caractéristiques
essentielles est qu’il permet d’éviter tout conflit d’impédance lié à
une incompatibilité entre les types qu’il supporte et les types du
SGBD. Ce chapitre explicite les différentes possibilités que peut offrir
un tel langage, ce qui met en évidence son utilité.
• Quant au chapitre 6, il traite des connecteurs des bases de données
dont le rôle est de permettre la manipulation d’une base de données
à l’aide de requêtes SQL à partir d’un programme, peu importe le
langage de programmation utilisé.

La matière traitée dans ces chapitres 2, 3, 4, 5 et 6 permet aux SGBD


relationnels de fournir les garanties liées à l’intégrité, à la confidentialité et
à l’adéquation de l’interface d’accès pour la manipulation des données
selon l’approche relationnelle.
La manière dont les SGBD relationnels procèdent pour fournir les garanties
relatives à l’indépendance, aux performances, à la gestion des accès

xxi
Joachim TANKOANO

simultanés par plusieurs utilisateurs et à la sécurité des données lors de la


survenue d’un incident, est abordée dans les chapitres 7, 8 et 9 à travers une
présentation des techniques mises en œuvre pour : (i) l’implémentation
physique des données sur les supports de stockage, (ii) la transformation
des requêtes SQL en code exécutable et (iii) la gestion de la concurrence
et des incidents. La présentation des techniques utilisées pour le faire est
appuyée par de nombreux exemples.
• Le chapitre 7 explique comment les données, organisées
logiquement à l’aide du modèle relationnel pur et manipulées par le
développeur à l’aide du langage abstrait qu’est SQL, sont organisées
et stockées sur les supports physiques de stockage. Ce chapitre
explique aussi comment fonctionnent les techniques et les
algorithmes utilisés pour accéder physiquement à ces données, afin
de satisfaire à l’exigence relative aux performances. Plus
précisément, ce chapitre traite des techniques d’amélioration des
performances basées notamment sur le placement des données sur
les supports physiques de stockage, sur l’utilisation d’une zone
tampon entre les applications et la base de données et sur l’utilisation
des techniques d’indexation des tables à l’aide d’arbres B+, du
hachage statique, du hachage dynamique et des index bitmap. Ce
chapitre aborde également la prise en compte de ces techniques au
niveau du langage SQL et du fonctionnement interne des SGBD
relationnels.
• Le chapitre 8 explique pour sa part, comment fonctionne en interne
les principales étapes du processus de transformation d’une requête
SQL en code exécutable. Ce processus de transformation inclut une
phase d’optimisation logique et une phase d’optimisation physique
pouvant concourir également à la satisfaction de l’exigence relative
aux performances. Ce processus s’effectue de façon dynamique, ce
qui lui permet de concourir aussi et surtout à l’indépendance
physique des données en rendant ainsi possible la manipulation
d’une base de données à l’aide d’opérateurs algébriques relationnels,
qui font abstraction de l’organisation des données sur les supports
physiques et des algorithmes mis en œuvre pour cette manipulation.

xxii
SGBD relationnels – Tome 1, État de l’art

Au cours de la phase d’optimisation logique, chaque requête est


transformée dynamiquement en une séquence optimisée
d’opérations algébriques relationnelles opérant sur des données
définies par le schéma logique de la base de données. Au cours de la
phase d’optimisation physique, chaque opérateur algébrique
relationnel de la séquence optimisée d’opérations algébriques
relationnelles est remplacé dynamiquement par une séquence
d’opérateurs physiques, qui calcule le résultat de cet opérateur
algébrique, à partir des éléments de l’implémentation des données
sur les supports physiques de stockage, en utilisant l’algorithme le
plus approprié au regard du contexte. On y trouve à cet effet, une
présentation des règles de réécriture utilisées dans la phase
d’optimisation logique. Ces règles de réécriture sont basées sur les
équivalences algébriques relationnelles et sur les équivalences
logiques découlant des propriétés des opérateurs algébriques
relationnels et des opérateurs logiques. On y trouve également
quelques exemples d’algorithmes alternatifs d’implémentation des
opérateurs algébriques, utilisés dans la phase d’optimisation
physique et une présentation des techniques utilisées pour
implémenter dynamiquement ces algorithmes à l’aide d’opérateurs
physiques prédéfinis.
• Le chapitre 9 présente de façon détaillée, comment les SGBD
procèdent pour offrir un environnement d’exécution, pouvant
apporter une solution satisfaisante aux exigences relatives aux accès
simultanés à une base de données et aux reprises en cas d’incident,
en garantissant l’ACIDité (Atomicité, Cohérence, Isolation et
Durabilité) dans cet environnement d’exécution. On y trouve une
présentation des mécanismes de contrôle des accès concurrents
basés sur l’utilisation des verrous, des estampilles et sur la
certification des transactions. On y trouve aussi une présentation des
moyens utilisés pour la gestion des reprises en cas d’incident (journal
des transactions, points de reprise ou de sauvegarde, sauvegardes
périodiques de la base de données), ainsi qu’une présentation des
différents types de reprises (reprise transaction, reprise à chaud et
reprise à froid). La mise en œuvre de ces techniques par le SGBD

xxiii
Joachim TANKOANO

Oracle est également décrite à titre d’illustration.

Le tome 2 de l’ouvrage

Ce tome traite de la prise en compte par les SGBD relationnels des besoins
émergents engendrés par la digitalisation des services que les entreprises
offrent à tous les acteurs de la société, les citoyens y compris. Ces nouveaux
besoins imposent aux SGBD des garanties sur la scalabilité et la
disponibilité sans interruptions de leur offre de services aux applications
ainsi que des garanties pour le stockage de mégadonnées abstraites et
complexes et pour leur manipulation selon des approches non-
relationnelles (orientées objet et XML par exemple). Ce tome est consacré
à la présentation des évolutions des SGBD relationnels dont l’ambition est
d’apporter une réponse satisfaisante à ces nouvelles exigences.

Au-delà de cette présentation, ce tome 2 explique pourquoi dans le cadre


de ces évolutions, les modèles de données qui résultent de l’intégration de
nouveaux concepts dans le modèle relationnel pur, comme le modèle objet-
relationnel et le modèle relationnel et XML, sacrifient les garanties
d’intégrité et d’indépendance. Pour éviter ces conséquences indésirables, ce
tome 2 propose un socle pour ces évolutions ayant la capacité de garantir
la préservation des acquis de la 2ème génération des SGBD. Ce socle
s’appuie sur une architecture des schémas basée sur l’architecture
ANSI/SPARC des SGBD. Celle-ci fait reposer ces évolutions sur une
intégration de nouveaux concepts dans le modèle relationnel pur mais aussi
sur une mise en correspondance d’une part, des perceptions induites par
ces nouveaux concepts et d’autre part, par celle induite par une base de
données relationnelle pure utilisée en interne pour garantir l’intégrité et
l’indépendance. En outre, cette mise en correspondance donne aussi la
possibilité de garantir l’efficacité de l’évaluation des requêtes SQL. Ceci a
comme avantage la préservation des résultats des investissements consentis
et du savoir-faire acquis autour des SGBD relationnels depuis son
avènement 1970.

xxiv
SGBD relationnels – Tome 1, État de l’art

La notation utilisée pour les règles syntaxiques

Les règles syntaxiques sont décrites dans l’ouvrage en s’appuyant sur la


forme de Bachus-Naur où :
• Les symboles terminaux sont notés en gras
• Les symboles non-terminaux sont notés en italique
• Les principaux méta-symboles utilisés sont :
o ::= pour expliciter un symbole non-terminal
o | pour séparer des constructions syntaxiques alternatives
o [ et ] pour délimiter une construction syntaxique optionnelle
o { et } pour délimiter une construction syntaxique non
optionnelle
o [,..n] qui se place après une construction syntaxique pour
indiquer qu’elle peut se répéter plusieurs fois en utilisant la
virgule comme séparateur
o [..n] qui se place après une construction syntaxique pour
indiquer qu’elle peut se répéter plusieurs fois en utilisant le
caractère blanc comme séparateur
o ' et ' pour encadrer les caractères |, [, ], { et } lorsque ces
caractères ne doivent pas être interprétés comme étant des
méta-symboles.

Joachim TANKOANO
tankoanoj@gmail.com

REMERCIEMENTS
Je tiens à remercier infiniment Monsieur Jean Claude DERNIAME pour
l’intérêt qu’il a accordé à cet ouvrage et pour m’avoir encouragé à le publier.

xxv
Chapitre 1. : Introduction
1.1. Du fichier aux bases de données

1.1.1. Le système d’information : un besoin fondamental pour


toute entreprise

Le rôle d’un système d’information

Pour fonctionner, toute entreprise (industrielle, commerciale, publique ou


autre) a besoin d’un système d’information. Le rôle d’un système
d’information est de fournir aux acteurs concernés les informations dont ils
ont besoin pour agir dans le cadre de leurs activités (fabriquer, commander,
vendre, facturer, contrôler, décider, etc.).

Les moyens dont a besoin un système d’information pour fonctionner

Pour jouer son rôle, le système d’information d’une entreprise utilise un


ensemble de moyens permettant de collecter les informations considérées
comme pertinentes pour l’entreprise, de traiter ces informations, de les
stocker et d’assurer leur circulation entre les acteurs concernés.

Le support de stockage des informations dans un système d’information

Dans un système d’information, les informations sont sous forme de


données élémentaires persistantes évolutives, dont les valeurs ont une
signification pour l’entreprise (exemples de données élémentaires : le nom
du produit numéro 10, le prix de ce produit, la date de naissance de
l’employé Jean).

Le support sur lequel ces données élémentaires sont stockées constitue la


mémoire de ce système d’information et par extension, la mémoire de
l’entreprise concernée. Cette mémoire est un composant essentiel du
système d’information. Elle doit être adaptée à la complexité des activités
de l’entreprise et à ses exigences en matière de performances et de sécurité.

S’il s’agit d’un petit commerçant du secteur informel, sa mémoire humaine


peut être suffisante pour stocker les informations relatives à l’état des
quelques processus qu’il met en œuvre pour la réalisation de ses activités
Joachim TANKOANO

(vente à crédit, approvisionnement à crédit, …) et aux entités que ces


processus manipulent (articles, clients, …).

En revanche, s’il s’agit d’une entreprise qui fait intervenir plusieurs acteurs,
la mémoire humaine devient insuffisante et inadaptée pour la
mémorisation des informations dont ces acteurs ont besoin pour accomplir
leurs tâches. Dans ce cas, l’entreprise doit recourir à une mémoire artificielle
gérée manuellement ou à l’aide d’un système informatique.

1.1.2. La gestion manuelle de la mémoire d’un système


d’information

Avant l’apparition des premiers ordinateurs au cours de la 2ème moitié du


20ème siècle, la mémoire des entreprises était gérée manuellement à l‘aide
de « fichiers papiers ». Aujourd’hui encore, elles sont nombreuses les
entreprises qui s’adonnent à cette pratique, particulièrement dans les pays
en développement où il s’agit de la presque totalité d’entre elles.

La gestion manuelle de la mémoire d’une entreprise à l‘aide de « fichiers


papiers » s’appuie sur des « fiches de renseignement », des « registres »,
des « documents » de tout genre, des « classeurs », des « armoires de
rangement », etc… Les informations conservées dans un « fichier papier »
sont organisées et transcrites sur des « fiches », dans des « registres » ou
sur d’autres types de « documents » papiers, éventuellement rangés dans
des « classeurs » répartis dans des « tiroirs d’armoires ». Par exemple, s’il
s’agit d’une banque, l’ouverture d’un nouveau compte client doit dans ce
type de gestion s’accompagner de l’affectation à ce compte d’une nouvelle
« fiche compte » sur laquelle la banque note les informations
d’identification du client et du compte ainsi que celles relatives aux
opérations effectuées sur ce compte : dépôts, retraits, prêts, etc.

La gestion manuelle de la mémoire du système d’information génère au


sein des entreprises des difficultés qui sont multiples et multiformes. Parmi
les plus importantes, on peut citer :
• Les difficultés d’accès et de partage : par exemple, l’accès aux
informations d’un compte nécessite de rechercher manuellement la

2
SGBD relationnels – Tome 1, État de l’art

« fiche » créée pour ce compte dans le système de rangement mis en


place par la banque. Cette recherche peut être longue et fastidieuse
si la banque a ouvert un grand nombre de comptes. En outre, cette
« fiche » n’est exploitable que par un seul acteur à la fois et en un
seul lieu.
• Les difficultés de synthétisation de l’information à des fins d’analyse
de l’activité de l’entreprise : par exemple, l’exploitation des
informations transcrites sur les « fiches comptes » afin d’élaborer
périodiquement des statistiques peut s’avérer complexe, voire
irréalisable.
• Les difficultés de recoupement des informations : par exemple, la
recherche de toutes les « fiches comptes » appartenant à un même
client peut aussi s’avérer complexe, voire hasardeuse.
• Les difficultés de sécurisation et de conservation des informations :
par exemple, les moyens mis en œuvre ne rendent pas possible la
mise en place d’un système sophistiqué pouvant faire en sorte que
certains acteurs non habilités ne puissent pas accéder à une partie
des informations contenues sur une « fiche compte ». Par ailleurs,
les multiples déplacements et manipulations des « fiches comptes »
par plusieurs acteurs peuvent à la longue entrainer des pertes ou une
détérioration du support papier utilisé. En outre, l’espace physique
nécessaire pour le stockage de ces « fiches » peut être non
négligeable, ce qui peut les rendre très encombrantes.

1.1.3. La technologie des fichiers


L’apparition des premiers ordinateurs, dotés de supports de masse (disques
durs, tambours, bandes magnétiques, etc.) a été accueillie comme une
aubaine pouvant aider à surmonter les difficultés liées à la gestion manuelle
de la mémoire d’une entreprise.

Les premières solutions mises en œuvre à cet effet n’ont toutefois conduit
qu’à une simple transformation des « fichiers papiers » en « fichiers
informatiques » gérés à l‘aide des fonctionnalités offertes par le système de
gestion des fichiers (SGF) du système d’exploitation de l’ordinateur hôte.

3
Joachim TANKOANO

Ces fichiers, stockés dans des mémoires de masse, étaient constitués


d’enregistrements (ou articles) équivalents soient à des « fiches papier »,
soient aux lignes d’un « registre papier ».

Ces premières solutions informatiques, très simplistes, se sont développées


en s’appuyant sur des méthodes dites cartésiennes de conception des
applications informatiques.

Dans ces méthodes basées sur le principe « diviser pour régner », dans la
phase d’analyse, le concepteur doit :
• Identifier les résultats que l’application doit produire (Exemple :
relevés de comptes, tableau de bord pour le suivi des opérations
journalières du jour J, états journaliers des opérations de caisse à
saisir le jour J+1, …)
• Identifier, en fonction des résultats à produire, les lots de données
que les utilisateurs doivent fournir à l’application (Exemple :
données des fiches de création de compte, des états journaliers des
opérations de caisse à saisir la nuit pour la production des états du
jour J+1, …).

Ensuite, dans la phase de conception, le concepteur doit décomposer


successivement l’application en unités de traitement de plus en plus
élémentaires, afin de faire ressortir :
• Les unités de traitement qui peuvent constituer des programmes
• Les résultats que chacun de ces programmes doit produire
• Les fichiers de données persistantes et temporaires requis en entrée
et en sortie par chacun de ces programmes, au regard des
traitements à effectuer, en précisant leurs caractéristiques
• Les règles d’enchainement de l’exécution de ces programmes
• Les procédures de reprises lorsque l’exécution d’un programme se
termine de façon anormale.

Dans la phase de réalisation, le développeur doit définir la logique


d’enchainement des traitements que chaque programme doit effectuer, en
tenant compte de l’organisation physique des fichiers utilisés en entrée et

4
SGBD relationnels – Tome 1, État de l’art

en sortie et des méthodes d’accès supportés par ces fichiers (séquentiel,


séquentiel indexé, direct). En outre, pour accéder en lecture et/ou en
écriture, à partir d’un programme, aux données contenues dans un fichier,
le développeur doit recourir aux instructions proposées par le langage
utilisé. Ces instructions doivent ensuite être traduites par le compilateur en
code d’appel à des sous-programmes, contenus dans une bibliothèque de
sous-programmes du système de gestion des fichiers du système
d’exploitation de l’ordinateur hôte. Pour permettre au compilateur de
procéder à cette traduction, chaque fichier utilisé dans un programme
doit obligatoirement être déclaré par le développeur. Le but de cette
déclaration est de fournir au compilateur toutes les informations dont il a
besoin pour la traduction. La déclaration d’un fichier doit permettre
l’identification :
• Du type de support concerné (disque, bande magnétique, …)
• Du fichier sur ce support
• De l’organisation physique du fichier (trié, non trié, …)
• Des méthodes d’accès supportés par le fichier
• De la structure des enregistrements du fichier.

Les applications conçues en s’appuyant sur la démarche préconisée par ces


méthodes de conception dites cartésiennes ne conduisent qu’à une simple
transposition des traitements manuels sur l’ordinateur. Elles amènent à
traiter par lot et en différé les données collectées par les acteurs tout au long
de leurs activités dans le but de confier à l‘ordinateur les traitements
répétitifs et fastidieux ou nécessitant des calculs complexes ou pouvant être
entachés d’erreurs lorsqu’ils sont réalisés manuellement.

Bien évidemment, la principale attente d’une application informatique ne


doit pas se limiter qu’à une simple transposition sur l’ordinateur des
traitements qui se font difficilement manuellement. Une application
informatique doit aussi et surtout viser une assistance innovante, fine et
interactive, à toutes les étapes des tâches que l’utilisateur effectue dans le
cadre de ses activités afin de simplifier son travail, d’accroître son efficacité
et son efficience, tout en lui offrant de nouvelles possibilités.

5
Joachim TANKOANO

De multiples raisons font que cette approche qui consiste à implémenter la


mémoire des systèmes d’information en s’appuyant sur la technologie des
systèmes de gestion des fichiers des systèmes d’exploitation et les méthodes
de conception cartésiennes ne peut pas faciliter la conception, l’exploitation
et la maintenance de telles applications interactives. Parmi ces raisons, les
plus importantes sont les suivantes :
1) La très forte dépendance qui existe entre les programmes et les
données : l’ajout d’un nouveau champ dans les enregistrements d’un
fichier afin de prendre en compte un nouveau besoin doit
obligatoirement s’accompagner de la modification de la déclaration
de ce fichier dans tous les programmes qui utilisent ce fichier, y
compris dans les programmes qui l’utilisent sans exploiter ce
nouveau champ. Par exemple, après avoir développé et mis en
exploitation avec succès une application pour la gestion salariale,
une entreprise peut décider d’étendre cette application à la gestion
administrative des employés. La prise en compte de ce nouveau
besoin ne peut se faire qu’en ajoutant de nouveaux champs (relatifs
par exemple aux congés) dans différents fichiers persistants
existants. Ceci a comme conséquence, la modification de tous les
programmes existants qui utilisent ces fichiers, afin d’actualiser la
description de ces fichiers, même si ces programmes n’exploiteront
pas ces nouveaux champs. L’obligation de déclarer dans chaque
programme tous les fichiers qu’il utilise crée de ce fait une très forte
dépendance des programmes et des données qui accroît le coût de la
maintenance des applications. En outre, le changement de
l’organisation physique d’un fichier pour passer par exemple d’un
fichier séquentiel à un fichier séquentiel indexé peut entrainer une
modification en profondeur de la logique des programmes qui
exploitent ce fichier. La modification de la logique d’un programme
pour ce type de raison peut entrainer des coûts de maintenance
encore plus élevés.
2) Les redondances dans les données : le fait de déterminer les champs
des enregistrements des fichiers devant contenir les données
persistantes en fonction des besoins des programmes peut avoir
comme conséquence qu’un champ associé à une information donnée

6
SGBD relationnels – Tome 1, État de l’art

peut être présent dans plusieurs de ces fichiers à la fois. Chaque fois
qu’une information est requise dans des programmes différents, ceci
peut avoir comme conséquence l’introduction de données
redondantes dans plusieurs fichiers de l’application. Par exemple,
dans une banque, le « fichier dépôts » utilisé pour la gestion des
dépôts effectués par les clients et le « fichier prêts » utilisé pour la
gestion des prêts accordés aux clients peuvent contenir tous les deux
des informations relatives aux clients concernés. De ce fait, les
informations relatives aux clients qui ont à la fois effectué des dépôts
et contracté des prêts seront présentes dans ces deux fichiers sous
forme de données redondantes.
Des copies multiples d’une donnée peuvent aussi se retrouver dans
le même fichier. Ce cas de figure peut se produire dans le « fichier
des prêts » s’il existe des clients qui ont contracté plusieurs prêts.
Dans ce cas de figure, les informations qui décrivent un client se
retrouveront dans plusieurs lignes du fichier, sous forme de données
redondantes.
Dans les deux situations, la redondance peut engendrer des
incohérences dans l’état du système d’information, dues au fait que,
pour diverses raisons, les valeurs des copies multiples d’une donnée
peuvent à un moment donné être différentes. Les contrôles requis
pour éviter ces incohérences, qui mettent à rude épreuve l’intégrité
des données, peuvent s’avérer complexes et coûteux en temps
d’exécution.
3) L’incapacité du système de gestion des fichiers des systèmes
d’exploitation à gérer le partage des données : Pour qu’une
application puisse apporter une assistance en temps réel
simultanément à plusieurs utilisateurs, il faut en particulier que les
traitements effectués pour le compte de ces utilisateurs puissent, si
cela est nécessaire, accéder simultanément en lecture et/ou en
écriture aux données contenues dans un fichier de l’application sans
altérer l’intégrité de ces données. Pour ce faire, il faut que le système
de gestion des fichiers du système d’exploitation de l’ordinateur
hôte soit en mesure de gérer convenablement les conflits d’accès aux

7
Joachim TANKOANO

données contenues dans ce fichier. Ce qui n’est pas le cas.


Par ailleurs, l’accès aux données d’un fichier par un utilisateur ne
doit pouvoir se faire qu’en respectant les autorisations d’accès
définies par le propriétaire des données. Pour ce faire, le système de
gestion des fichiers du système d’exploitation de l’ordinateur hôte
doit offrir des mécanismes pour une gestion fine de la confidentialité
pouvant porter sur une partie des données contenues dans un
fichier. Ce qui n’est pas non plus le cas.

1.1.4. La technologie des bases de données

La technologie des bases de données a été introduite dans les systèmes


informatiques dans le but de pallier les insuffisances des systèmes de
gestion des fichiers des systèmes d’exploitation. L’objectif général visé par
cette technologie est d’offrir un moyen de stockage de la mémoire d’un
système d’information qui fournit les garanties requises pouvant faciliter la
conception, l’exploitation et la maintenance d’applications informatiques
interactives, apportant une assistance fine aux acteurs de l’entreprise dans
toutes les tâches qu’ils effectuent.

Une BASE DE DONNEES (BD) est un ensemble structuré de données


persistantes cohérentes, stockées sur des supports physiques, auxquels un
ou plusieurs ordinateurs peuvent accéder afin de satisfaire simultanément
les besoins en informations d’utilisateurs ayant des vues différentes sur ces
données, sans altérer l’intégrité de ces données.

On appelle SYSTÈME DE GESTION DE BASES DE DONNEES (SGBD), le


logiciel qui offre les fonctionnalités qui permettent de créer, d’administrer
et d’accéder à cette base de données.

La technologie des bases de données s’est développée en même temps que


les approches systémiques de conception des systèmes d’information. Ces
approches ont été définies pour permettre la conception des systèmes
d’information, en s’appuyant sur la technologie des bases de données, pour
pouvoir considérer les entreprises comme étant constituées de trois
systèmes en interaction continue :

8
SGBD relationnels – Tome 1, État de l’art

• Le système opérant chargé de la production dans l’entreprise


• Le système de pilotage chargé de la conduite de l’entreprise vers
l’atteinte de ses objectifs
• Le système d’information chargé, d’une part de fournir en temps réel
aux acteurs des deux autres systèmes les informations dont ils ont
besoin et d’autre part d’assurer les échanges d’informations requis
entre l’entreprise et son environnement.

Le principal intérêt de ces approches systémiques de conception, qui


présupposent des accès simultanés aux données de l’entreprise, réside dans
le fait qu’elles amènent le concepteur à définir le cahier des charges du
système d’information en analysant les besoins en informations du système
opérant, du système de pilotage et de l’environnement de l’entreprise et
non en analysant les besoins en informations de programmes identifiés
comme étant requis. Ceci permet de s’assurer plus aisément de la
pertinence du système d’information par rapport aux besoins de
l‘entreprise, voire de lier la conception du système d’information à une
refonte globale de l’organisation et du fonctionnement de l’entreprise afin
de tirer le meilleur parti des possibilités offertes par l’outil informatique.

1.2. Les principales exigences imposées à la


technologie des bases de données
Pour servir de support pour le stockage des données d’un système
d’information, la technologie des bases de données doit répondre aux
exigences relatives aux garanties qu’elle doit fournir par rapport aux
préoccupations des entreprises, non prises en compte par la technologie des
systèmes de gestion des fichiers des systèmes d’exploitation. Chacune de
ces garanties contribue de façon déterminante à l’intérêt que les entreprises
accordent à cette technologie. Aujourd’hui, le noyau dur de ces garanties,
développé notamment autour du modèle relationnel de E.F. CODD, défini
en 1970, est fourni nativement dans la presque totalité des offres
commerciales modernes relatives à cette technologie. Il s’agit des garanties
relatives qui concernent :

9
Joachim TANKOANO

• L’indépendance des données traitée tout au long des autres chapitres


• La cohérence des données (intégrité et non-redondance des données)
traitée dans les chapitres 2, 3, 4 et 5
• La confidentialité des données traitée dans les chapitres 4 et 5
• La gestion des accès concurrents aux données traitée dans le chapitre
9
• La sécurité lors de la survenue d’un incident traitée dans le chapitre
9
• L’adéquation des performances traitée dans les chapitres 7 et 8
• L’adéquation de l’interface d’accès pour la manipulation des
données traitées dans les chapitres 2, 4, 5 et 6.

Ce qui suit est une présentation introductive de ces exigences. Pour chacune
d’elle cette présentation introductive met en exergue les attentes liées à sa
prise en charge et résume la manière dont les SGBD s’y prennent pour
l’assurer. Cette présentation introductive n’a pas pour finalité d’amener le
lecteur à comprendre tout de suite le comment de cette prise en charge. La
manière dont les SGBD s’y prennent pour prendre en charge ces exigences
notamment en s’appuyant sur le modèle relationnel de E.F. CODD, défini
en 1970, y compris les fondements théoriques des approches mises en
œuvre, est étudiée de façon plus détaillée comme indiqué ci-dessus dans
les autres chapitres de ce tome. En revenant sur cette présentation
introductive chaque fois que de besoin, le lecteur pourra se faire une idée
sur l’évolution de son cheminement vers une bonne compréhension de la
prise en charge de ces exigences par la technologie des bases de données.

Les exigences additionnelles que la technologie des bases de données doit


prendre en charge sont étudiées dans le tome 2.

1.2.1. L’indépendance des données

L’exigence liée à l’indépendance des données impose aux SGBD la capacité


de garantir la possibilité de pouvoir modifier, sous certaines conditions,
l’organisation logique globale des données telle qu’elle peut être perçue

10
SGBD relationnels – Tome 1, État de l’art

indépendamment des applications existantes, l’organisation logique des


données telle qu’elle est perçue par une application en particulier et
l’organisation physique des données sur les supports de stockage, sans que
cela n’ait de répercussion sur les applications existantes.

On distingue ainsi deux niveaux d’indépendance des donnés :


• L’indépendance logique qui permet de prendre en compte de
nouveaux besoins en enrichissant la structure logique globale des
données ou en modifiant l’organisation logique de ces données telle
qu’elle est perçue par une application, sans que cela ne nécessite une
modification des applications existantes non concernées
• L’indépendance physique qui permet de modifier l’organisation des
données sur les supports physiques afin de prendre en compte de
nouvelles contraintes ou d’améliorer les performances des
mécanismes d’accès aux données, sans que cela n’ait d’incidence sur
l’organisation logique des données et sans que cela ne nécessite une
modification des applications existantes. Ces modifications peuvent
concerner notamment le changement de la représentation interne
des données, la modification des critères de tri des données, l’ajout
ou la suppression de chemins d’accès aux données à l’aide d’index,
la modification de la répartition physique des données sur plusieurs
ordinateurs, etc...

Le principal avantage de ces possibilités est la réduction des coûts relatifs à


la maintenance. Grâce à l’indépendance des données, l’entreprise peut, par
exemple, enrichir la structure logique des données stockées dans une base
de données en y ajoutant de nouveaux types de données requis par de
nouvelles applications ou modifier l’organisation physique de ces données
afin de rechercher de meilleures performances, sans avoir à modifier les
programmes existants.

Comme cela a déjà été mis en évidence, il est impossible de garantir


l’indépendance des données telle que définie lorsque les applications
s’appuient sur la technologie des fichiers classiques principalement pour la
raison suivante : le choix des supports, la description de la structure logique
des données et de leur organisation physique sur ces supports ainsi que le

11
Joachim TANKOANO

choix des algorithmes qui en découlent pour l’accès à ces données, doivent
se faire à l’intérieur des programmes par le développeur. De ce fait, un
changement de ces choix ou une modification de ces descriptions, peut
entraîner une maintenance très lourde d’un très grand nombre de
programmes.

L’indépendance des données dans la technologie des bases de données est


garantie en grande partie par l’architecture des schémas définie en 1975 par
l’« ANSI/SPARC (American National Standards Institute / Standards
Planning And Requirements Committee) ». Cette architecture donne à ces
schémas et aux applications des garanties d’indépendance. Elle amène à
décrire une base de données, en dehors de tout programme, à l’aide de ces
schémas, en s’appuyant sur trois niveaux d’abstraction permettant de
percevoir, de décrire et de manipuler les données différemment :
• Le niveau conceptuel et logique
• Le niveau physique (ou interne)
• Le niveau externe.

Cette architecture peut se schématiser comme ci-dessous.

Au niveau conceptuel, le contenu d’une base de données est décrit à l’aide


d’un schéma conceptuel des données. Ce schéma conceptuel a pour finalité
de répondre aux questions ci-après : « Comment l’entreprise perçoit-elle le
monde réel à travers sa base de données ? Quelles sont les entités du monde réel et
quelles sont les relations inter-entités qui doivent être décrites dans la base de
données ? Avec quelles données doivent-elles être décrites et quelles sont les
contraintes d’intégrité auxquelles ces données, ces entités et ces relations doivent
être soumises pour que le contenu de la base de données soit en parfaite cohérence
avec les règles de gestion de l’entreprise ? »

En d’autres termes, le niveau conceptuel se focalise sur la sémantique des


données. Il vise la définition du « QUOI » en utilisant le vocabulaire des
acteurs de l’entreprise. Il permet de définir la vue abstraite de ces acteurs sur
les données du système d’information de l’entreprise, c’est-à-dire, de dire
comment ces données sont perçues par ces acteurs indépendamment de la
technologie utilisée pour leur stockage et pour leur manipulation.

12
SGBD relationnels – Tome 1, État de l’art

NIVEAUX
D’ABSTRACTION Vues logiques individualisées par application
NIVEAU
EXTERNE
Schéma externe …….. Schéma externe
1 . N
<--- Mapping des perceptions --->

Vue globale des acteurs de l’entreprise & vue


NIVEAU logique globale indépendante des applications
CONCEPTUEL
ET LOGIQUE Schéma conceptuel & Schéma logique global

<- Mapping des perceptions ->

NIVEAU Vue sur les supports physiques


PHYSIQUE
(OU INTERNE) Schéma physique (ou interne)

La définition du schéma conceptuel des données est une activité de


modélisation du monde réel du type de celle préconisée par les méthodes
de conception systémiques telles que MERISE. Les formalismes utilisés
pour cette modélisation sont en général graphiques et orientés sémantique,
ce qui facilite la lecture des schémas conceptuels par les utilisateurs non
informaticiens et donc le dialogue entre informaticiens et non
informaticiens lors de l’analyse des besoins. Les formalismes les plus
utilisés sont ceux proposés par le modèle « E/A (Entité / Association) » et
« UML (Unified Modeling Language) ».

Au niveau logique, le contenu d’une base de données se décrit à l’aide d’un


schéma logique global de la base de données pouvant s’obtenir par
transformation du schéma conceptuel des données. Ce schéma logique
global a pour finalité de répondre à la question suivante : « Comment est-ce
que les données décrites par le schéma conceptuel doivent-elles être organisées
logiquement, indépendamment de la perception individuelle de chaque application,
pour qu’elles deviennent manipulables dans un programme, en respectant les

13
Joachim TANKOANO

contraintes d’intégrité définies par ce schéma conceptuel ? Quelles sont les


opérations logiques abstraites qui permettent cette manipulation ? »

En d’autres termes, le niveau logique vise le choix d’un modèle de données


relevant du domaine de la programmation et la structuration logique, à
l’aide de ce modèle, des données décrites par le schéma conceptuel afin de
rendre ces données manipulables à l’aide d’un programme, sans que leurs
contraintes d’intégrité ne puissent être violées. Le schéma logique de la base
de données qui en découle, définit une vue abstraite globale des données,
dépendante du modèle de données retenu, mais qui doit idéalement être
indépendante de toute application et de l’organisation des données sur les supports
physiques.

Au niveau externe, le contenu d’une base de données se décrit à l’aide de


schémas externes (ou sous-schémas) conceptuels ou logiques. Chaque
schéma externe, défini pour une application donnée, a pour finalité de
répondre aux questions suivantes : « Quelle est la portion du schéma conceptuel
et / ou logique qui concerne les données que cette application peut voir et peut
manipuler ? Comment est-ce que ces données sont-elles perçues et manipulées par
cette application ? » La définition du sous-schéma logique d’une application
peut se définir en s’appuyant sur un modèle de données relevant du
domaine de la programmation, différent de celui sur lequel repose la
définition du schéma logique global de la base de données. Le SGBD doit
faire en sorte que cette application ne puisse voir et ne puisse manipuler la
base de données qu’à travers ce sous-schéma logique.

Pour chaque application, le niveau externe vise donc la définition d’une vue
abstraite personnalisée sur les données, qui permet à cette application de
percevoir et de manipuler les données comme si elles étaient stockées dans
la base de données virtuelle qui répond à ses besoins spécifiques.

Les schémas conceptuel et logique d’une base de données peuvent évoluer,


afin de prendre en compte de nouveaux besoins sans perturber le
fonctionnement des applications existantes si les schémas externes de
celles-ci sont préservés, ou en d’autres termes, si l’ensemble des données
que chaque application existante voit et manipule n’est pas affecté. De
même, le schéma externe d’une application peut évoluer afin de prendre en
compte de nouveaux besoins spécifiques, sans que cela n’ait une incidence

14
SGBD relationnels – Tome 1, État de l’art

sur le fonctionnement des autres applications existantes, ni sur le schéma


conceptuel et le schéma logique global.

À ce jour, les concepteurs des SGBD ont implémenté principalement quatre


modèles de données différents relevant du domaine de la programmation.
Chacun de ces modèles induit une perception sur les données, différente de
celle des autres, conduisant de ce fait à des schémas logiques globaux et à
des schémas externes logiques différents. Chacun de ces modèles a donné
naissance à une nouvelle génération de SGBD permettant la prise en
compte de liens sémantiques entre données plus ou moins complexes. Il
s’agit :
• Du modèle hiérarchique où les données présentées au développeur
par les SGBD de la 1ère version de la 1ère génération sont
interconnectées entre elles sous forme d’arbres
• Du modèle en réseau où les données présentées au développeur par
les SGBD de la 2ème version de la 1ère génération sont interconnectées
entre elles sous forme de réseaux
• Du modèle relationnel où les données sont présentées au
développeur par les SGBD de la 2ème génération sous forme de tables
qui se font référence les unes les autres et qui sont soumises à des
contraintes d’intégrité
• Du modèle orienté objet où les données sont présentées au
développeur par les SGBD de la 3ème génération sous formes de
collections d’objets persistants. Ces objets ont une structure abstraite
complexe et sont dotés d’un identifiant permanent, fixe et unique et
d’opérations (appelées méthodes) pour leur manipulation.

Le modèle hiérarchique et le modèle en réseau étant devenus obsolètes,


nous ne nous intéresserons dans le tome 1 de l’ouvrage, y compris dans
cette section, qu’au modèle relationnel qui a l’avantage de reposer sur des
fondements théoriques bien établis. Le modèle orienté objet n’est abordé
quant à lui que dans le tome 2 de l’ouvrage.

Au niveau physique ou interne, le contenu d’une base de données se décrit


à l’aide du schéma physique de la base de données. Ce schéma a pour

15
Joachim TANKOANO

finalité de répondre aux questions suivantes : « Comment est-ce que les


données décrites au niveau logique (global et externe) sont-elles organisées et
stockées sur les supports physiques comme les disques (qui sont constitués de
cylindres, constitués eux-mêmes de pistes subdivisées en secteurs) et comment est-
ce que les opérations logiques abstraites de manipulations de ces données décrites
au niveau logique (global et externe) doivent-elles être transformées en opérations
physiques de manipulation de données stockées sur les supports physiques ? »

En d’autres termes, le niveau physique définit une vue des données sur les
supports physiques de stockage. Il vise la définition du « COMMENT », c’est-
à-dire une implémentation sur les supports physiques des données décrites
par le schéma logique de la base de données. Cette implémentation est en
général définie en termes de sites de répartition des données, de fichiers, de
pages ou blocs, d’enregistrements, de chemins d’accès, de pointeurs,
d’algorithmes de recherche, etc., sans s’encombrer ou chercher à tirer
avantage des fonctionnalités des systèmes de gestion des fichiers des
systèmes d’exploitation.

Le schéma physique d’une base de données peut être modifié librement à


tout moment et autant de fois que nécessaire, afin d’améliorer par exemple
les performances des applications, sans que cela n’ait de répercussion sur
les schémas logiques de la base de données (aux niveaux global et externe)
et au niveau de la logique des applications existantes.

Le passage d’un niveau à l’autre s’appuie sur le mapping des perceptions


que les concepts de ces niveaux permettent de construire.

La technologie des bases de données garantit l’indépendance des données


en offrant les fonctionnalités qui permettent de créer et d’exploiter des bases
de données qui adhèrent à cette architecture des schémas.

1.2.2. La cohérence des données (l’intégrité et la non-


redondance)

L’exigence liée à la cohérence des données impose aux SGBD la capacité de


pouvoir garantir à tout instant la cohérence des données stockées dans une
base de données indépendamment du comportement des applications,
c’est-à-dire, peu importe comment ces applications manipulent ces

16
SGBD relationnels – Tome 1, État de l’art

données.

Pour que ces données soient considérées comme étant cohérentes, les deux
conditions ci-après doivent être satisfaites à tout instant :
1) Ces données doivent respecter les contraintes d’intégrité qui
découlent des règles de gestion de l’entreprise concernée
2) Ces données ne doivent pas contenir des redondances ou s’il en
existe ces redondances doivent être voulues et contrôlées de façon
automatique afin d’éviter l’accès à des valeurs contradictoires.

Les contraintes d’intégrité qui découlent des règles de gestion de


l’entreprise sont en général identifiées et définies au cours de la phase
d’analyse des besoins des utilisateurs. Leur définition doit faire partie du
schéma conceptuel et du schéma logique global de la base des données
concernée. Ces contraintes d’intégrité concernent principalement :
• Le domaine de définition des valeurs légales de chaque donnée
élémentaire du système d’information
• Le caractère obligatoire ou non de l’association d’une valeur à une
donnée élémentaire
• Les dépendances entre ces données élémentaires
• Tout autre invariant défini par un prédicat qui lie les valeurs de ces
données élémentaires ou leurs dépendances, ainsi que toute
condition associée à une opération de mise à jour.

À titre d’exemple, les règles de gestion d’une banque peuvent imposer une
contrainte d’intégrité formulée de la façon suivante : « un compte ne doit
appartenir qu’à un et un seul client ». Cette contrainte d’intégrité définit la
dépendance qui doit exister entre les clients et les comptes. On dira que
cette contrainte d’intégrité a été violée si à un moment donné les
informations contenues dans la base de données indiquent qu’un compte
n’appartient à aucun client ou qu’il appartient à plusieurs clients.

Lorsqu’il existe des redondances dans une base de données, les copies des
donnés concernées peuvent contenir des valeurs différentes, donc des
informations contradictoires, c'est-à-dire incohérentes. En outre, l’existence

17
Joachim TANKOANO

de données redondantes peut engendrer un accroissement inutile de


l’espace requis pour le stockage de ces données et des anomalies lors de
l’accès à ces données (insertion, modification, suppression).

L’un des points forts des fondements théoriques qui sous-tendent la


conception des bases de données relationnelles réside dans le fait que ces
fondements théoriques permettent la conception de bases de données :
• Qui n’autorisent pas de redondances
• Et qui sont telles que les mécanismes de contrôle prévus au niveau
des SGBD, pour fournir les garanties sur le maintien des contraintes
d’intégrité intégrées nativement dans la conception du modèle
relationnel, sont suffisants pour éviter la violation des contraintes
d’intégrité relatives aux dépendances entre données élémentaires
découlant des règles de gestion.

En outre, le modèle relationnel amène à formuler de façon déclarative, dans


le schéma logique global de la base des données (donc en dehors de tout
programme), les contraintes d’intégrité intégrées nativement dans la
conception de ce modèle. Ceci permet au SGBD de garantir le respect de ces
contraintes d’intégrité sans l’intervention des applications qui manipulent
les données concernées et à l’administrateur de la base de données de
modifier à sa guise ces contraintes d’intégrité sans que cela n’entraine une
répercussion au niveau de ces applications.

En plus de ces mécanismes automatiques de contrôle des contraintes


d’intégrité, les SGBD peuvent aussi déclencher, lors de la survenue
d’événements prédéterminés, l’exécution de procédures de traitement
définies par l’administrateur de la base de données afin de vérifier par
programmation des contraintes d’intégrité complexes, ce qui élargit les
possibilités de maintien des contraintes d’intégrité liées aux règles de
gestion d’une entreprise indépendamment du comportement des
applications qui manipulent les données concernées.

1.2.3. La confidentialité

Les acteurs d’une entreprise exercent en général des responsabilités

18
SGBD relationnels – Tome 1, État de l’art

différentes liées aux rôles qu’ils jouent au sein de cette entreprise. Les
informations que ces acteurs peuvent manipuler dans le cadre de leurs
activités découlent en général de ces responsabilités.

Lorsqu’un système d’information est partagé par plusieurs acteurs,


l’exigence liée à la confidentialité impose aux SGBD la capacité de pouvoir
garantir que les opérations élémentaires de base, notamment les opérations
dites « CRUD (CREATE, READ, UPDATE, DELETE) » ainsi que les
opérations métier qui manipulent la base de données ne sont accessibles
qu’aux acteurs autorisés par l’entreprise compte tenu de leurs
responsabilités.

La prise en compte par les SGBD de cette exigence est donc un besoin qui
découle du fait que la possibilité qu’on donne à plusieurs acteurs de
partager des données doit pouvoir s’accompagner de restrictions qui
tiennent compte des responsabilités de chaque acteur.

Pour répondre à cette exigence, les SGBD permettent à l’entreprise de


définir de façon déclarative, dans le schéma logique de la base des données
(donc en dehors de tout programme), les droits d’accès des acteurs aux
opérations de base et aux opérations métier de manipulation de la base de
données en fonction des rôles qu’ils jouent au sein de l’entreprise. Ceci
permet ensuite aux SGBD d’assurer le contrôle des accès de chaque acteur
à ces opérations. Ces droits d’accès étant définis en dehors des programmes,
l’entreprise peut les modifier sans toucher à ses programmes.

Les mécanismes mis en œuvre par les SGBD pour le contrôle de l’intégrité
et des droits d’accès sont comparables sur plusieurs points. Dans les deux
cas, les informations requises sont stockées dans le schéma logique de la
base des données et les mécanismes de contrôle mis en œuvre par le SGBD
lors de chaque demande d’accès aux données, peu importe la logique des
applications à l’origine de ces demandes.

1.2.4. La gestion des accès concurrents

Lorsqu’une base de données est partagée par plusieurs acteurs ou


applications, les accès aux données que ces acteurs effectuent

19
Joachim TANKOANO

simultanément peuvent altérer l’intégrité de ces données et conduire à des


données incohérentes.

À titre d’exemple, si deux acteurs A1 et A2 entreprennent simultanément


la modification du solde du compte d’un client dont la valeur initiale est de
100 000F, le premier pour le débiter de 60 000F et le second pour le débiter
de 30 000F, les opérations élémentaires exécutées pour le compte de ces
deux acteurs peuvent s’entrelacer de façon non prévisible comme suit :
1. Transfert pour l’acteur A1 de la valeur actuelle du solde du
compte (c’est-à-dire 100 000) de la base de données vers la
mémoire centrale
2. Transfert pour l’acteur A2 de la valeur actuelle du solde du
compte (c’est-à-dire 100 000 aussi) de la base de données vers
la mémoire centrale
3. Calcul en mémoire centrale de la nouvelle valeur du compte
pour l’acteur A2, c’est-à-dire : 100 000 – 60 000 = 40 000
4. Écriture dans la base de données de la nouvelle valeur du
compte (c’est-à-dire, 40 000) calculée en mémoire centrale
pour l’acteur A2
5. Calcul en mémoire centrale de la nouvelle valeur du compte
pour l’acteur A1, c’est-à-dire : 100 000 – 30 000 = 70 000
6. Écriture dans la base de données de la nouvelle valeur du
compte (c’est-à-dire, 70 000) calculée en mémoire centrale
pour l’acteur A1.

Dans cet exemple, à la fin des modifications entreprises simultanément par


ces deux acteurs le nouveau solde du compte sera 70 000 au lieu de 10 000,
introduisant ainsi dans la base de données une incohérence provoquée par
l’entrelacement des opérations élémentaires exécutées pour le compte de
ces deux acteurs.

L’exigence liée à la gestion des accès concurrents aux données impose aux
SGBD la capacité de pouvoir garantir que de telles incohérences liées à
l’entrelacement des opérations effectuées sur une base de données pour le
compte d’acteurs différents ne peuvent jamais survenir.

20
SGBD relationnels – Tome 1, État de l’art

Pour éviter de telles incohérences, le fonctionnement des SGBD repose sur


des mécanismes pouvant permettre, soit de prévenir leur survenue, soit de
les détecter et de les corriger lorsqu’elles surviennent.

Les propriétés de l’environnement d’exécution offert par les SGBD, grâce à


ces mécanismes, sont telles que les effets qui découlent de l’accès simultané
à une base de données par plusieurs acteurs, tant du point de vue des
valeurs lues que des modifications apportées aux données, sont toujours les
mêmes que ceux qui pourraient découler d’une situation où ces acteurs
auraient été contraints à accéder à cette base de données l’un après l’autre.
Ces propriétés de l’environnement d’exécution sont garanties par le SGBD
indépendamment du comportement individuel des applications et peu
importe l’ordre dans lequel s’entrelacent les opérations des applications qui
s’exécutent simultanément pour le compte d’utilisateurs différents.

1.2.5. La sécurité

Les systèmes d’information étant indispensables pour le bon


fonctionnement des entreprises, il est essentiel que les données qu’ils
contiennent soient protégées contre les conséquences de tout incident
(pannes matérielles, erreur logicielle, etc.) pouvant corrompre l’intégrité de
ces données ou entrainer leur perte, car l’indisponibilité des données
résultant d’un incident peut entrainer l’arrêt complet du fonctionnement de
l’entreprise concernée.

L’exigence liée à la sécurité des données lors de la survenue d’un incident


impose aux SGBD la capacité de pouvoir offrir aux entreprises des
mécanismes et des moyens éprouvés pouvant leur permettre de garantir
cette protection. Sans le respect de cette exigence, il est presque certain que
l’utilisation de la technologie des bases ne se serait pas répandue au sein
des entreprises.

Les mécanismes et les moyens qui concourent à la protection des données


visent à remettre la base de données dans son état antérieur cohérent le plus
récent après la survenue d’un incident et à éviter ainsi toute perte ou
altération accidentelle de données. Pour ce faire, ces mécanismes et moyens
reposent essentiellement sur :

21
Joachim TANKOANO

• La journalisation des modifications effectuées sur les données


contenues dans la base de données
• La pose régulière de points de reprise constitués de sauvegardes de
l’état du SGBD au moment des poses
• Les sauvegardes périodiques de la base de données.

Lorsqu’un incident survient, ces mécanismes et moyens permettent de


défaire ou de refaire les mises à jour effectuées avant l’incident afin de
remettre la base de données dans son dernier état cohérent.

Au-delà de la protection contre les incidents, la sécurité des données inclut


aussi la protection contre les accès non autorisés, la protection contre la
violation de l’intégrité des données par les utilisateurs autorisés, la
protection contre les interférences de mises à jour des données pouvant
altérer leur intégrité, présentées plus haut. En outre, la sécurité des données
nécessite que la vulnérabilité des données soit réduite en plaçant le serveur
de la base de données et ses sauvegardes dans des abris pouvant assurer
leur protection vis-à-vis :
• Des contraintes énergétiques
• Des conditions climatiques
• Des catastrophes naturelles comme les incendies, les inondations, et
les tremblements de terre
• Des dommages pouvant être provoqués par des guerres
• Des virus, des piratages ou de tout autre acte malveillant pouvant
compromettre l’intégrité des données ou s’apparenté à un vol ou à
une prise de contrôle des données par un tiers.

1.2.6. Les performances

L’exigence liée aux performances impose aux SGBD la capacité de pouvoir


garantir, aussi bien aux petites entreprises qu’aux très grandes entreprises,
la possibilité de créer et d’exploiter une base de données avec le même
niveau de performance. En d’autres termes, les SGBD doivent permettent
de gérer aussi bien un petit volume qu’un très gros volume de données tout

22
SGBD relationnels – Tome 1, État de l’art

en garantissant un temps d’accès à ces données acceptable par les


entreprises. La quantité de données ne doit pas être limitée pour des raisons
techniques et les temps d’accès aux données doivent être quasiment
indépendants de cette quantité.

Les techniques qui concourent à la satisfaction de cette exigence sont


intimement liées d’une part, aux techniques que les SGBD utilisent pour
organiser, stocker et rechercher des données sur les supports physiques et
d’autre part, aux techniques qu’ils utilisent pour optimiser l’exécution des
requêtes de manipulation de ces données.

Les SGBD offrent aux entreprises les moyens pouvant leur permettre de
contrôler et d’optimiser l’organisation et le stockage des données ainsi que
l’exécution des requêtes afin de tirer le meilleur parti de ces techniques.

1.2.7. L’adéquation de l’interface d’accès pour la manipulation


des données

Comme cela a déjà été précisé, l’un des apports fondamentaux de la


technologie des bases de données réside dans le fait que :
• Elle rend possible la description des données de l’entreprise en
dehors des programmes ceci afin de garantir l’indépendance des
données
• Elle renforce cette indépendance en distinguant trois niveaux de
perception de ces données, à savoir, le niveau conceptuel et logique,
le niveau externe et le niveau physique.

Les schémas qui décrivent les données stockées dans une base de données
sont avantageusement centralisés dans le catalogue de cette base de
données (appelé aussi dictionnaire de données) implémenté lui aussi
comme une base de données, donc comme une méta-base de métadonnées.

L’exigence liée à l’adéquation et à la simplicité de l’interface d’accès aux


données impose aux SGBD la capacité de pouvoir garantir que la définition,
la gestion, la manipulation et le contrôle des informations contenues dans
cette méta-base et dans la base de données peuvent se faire par l’utilisateur
de façon très simple et conviviale.

23
Joachim TANKOANO

Pour satisfaire cette exigence, les SGBD proposent en règle générale aux
entreprises un langage textuel de définition des données simple à utiliser et
pouvant avoir un équivalent graphique plus facile à appréhender par les
utilisateurs non informaticiens. Ce langage de définition des données offre
en général les instructions requises pour décrire de façon déclarative dans
le catalogue de la base de données :
• Le schéma logique de cette base de données
• Les schémas externes logiques
• Le schéma physique.

Les SGBD offrent en outre en général un puissant langage abstrait de


manipulation des données telles qu’elles sont perçues au niveau externe ou
au niveau logique. Ce langage qui dépend du modèle de données utilisé
doit en particulier faciliter la manipulation des données. Il permet en
général d’insérer de nouvelles données, de modifier ou de supprimer ces
données et de rechercher des informations dans la base de données en
faisant abstraction de la logique des techniques utilisées pour effectuer ces
opérations sur les supports physiques. Les instructions offertes à cet effet
peuvent s’exécuter en général de façon interactive sous forme de requêtes
ad-hoc en dehors de tout programme. Elles peuvent aussi en général
s’exécuter à l’intérieur de programmes écrits à l’aide d’un langage
autonome qui étend ce langage de manipulation des données en langage
complet de programmation ou à l’aide de langages hôtes existants (C, C++,
Java, PHP, etc.), la principale difficulté dans ce dernier cas étant de faire en
sorte que les types du langages hôtes soient compatibles aux types du
SGBD.

De façon plus spécifique, le langage de manipulation des données offert par


les SGBD relationnels se fonde à la fois sur la logique du 1er ordre et sur la
théorie des ensembles, ce qui lui confère des bases formelles solides. En
outre, ce langage a l’avantage d’être déclaratif et de permettre au
développeur d’accroître sa productivité en se contentant de spécifier le
résultat qu’il souhaite obtenir et en faisant ainsi abstraction des mécanismes
et algorithmes d’accès aux données requis pour le calcul de ce résultat. Les
mécanismes et algorithmes d’accès aux données sur les supports physiques

24
SGBD relationnels – Tome 1, État de l’art

sont déterminés par le SGBD au moment de l’exécution. En plus de


simplifier le travail du développeur, ceci garantit l’indépendance physique
des données, les requêtes effectuées dans les programmes pour accéder aux
données étant ainsi insensibles aux modifications qui peuvent être
apportées au niveau de l’organisation des données sur les supports
physiques.

Les SGBD offrent enfin en général un langage de contrôle que les


entreprises peuvent utiliser pour contrôler les autorisations d’accès et les
accès concurrents aux données.

1.2.8. Le langage SQL

Le langage de définition des données, le langage de manipulation des


données et le langage de contrôle des données proposés pour les SGBD
relationnels sont intégrés dans un seul et unique langage normalisé, appelé
SQL (Structured Query Language), le langage standard d’interface des
bases de données relationnelles. La normalisation de ce langage a été
facilitée par les fondements théoriques sur lesquels il repose, à savoir, les
fondements théoriques du modèle relationnel. Ces fondements théoriques
ont facilité le processus qui a été conduit, afin d’aboutir à une définition
consensuelle des concepts de base utilisés dans ce langage. Les chapitres 2
et 3 présentent le modèle relationnel et le chapitre 4 SQL.

25
Joachim TANKOANO

BIBLIOGRAPHIE
ANSI/X3/SPARC Study group on data management systems: Interim
report, Bulletin of ACM SIGMOD 7(2),1975

ANSI/X3/SPARC Study group on data management systems: The


ANSI/X3/SPARC DBMS framework report of the study group on database
management systems, Information Systems, Volume 3, Issue 3, 1978,
Pages 173-191

Audibert L. : UML 2 – De l’apprentissage à la pratique – 2è édition – Ellipse,


Octobre 2014

Codd E. F.: The relational model for database management - Second Edition,
Addison-Wesley Publishing Company, Inc., 1990

Date C.J : Introduction aux bases de données - 8è édition, Vuibert, Paris, 2004

Gardarin G. : Bases de données, 5e tirage 2003, EYROLLES

Kettani N., Mignet D., Paré P. & Rosenthal-Sabroux C. : De Merise à UML,


2è édition, Eyrolles, Octobre 2001

Miranda S. & Busta J-M. : L'art des bases de données, Tome 2, Les bases de
données relationnelles - 2è édition, Eyrolles, 1990

Object Management Group (OMG): OMG Unified Modeling Language


Specification – Version 1.3, First Edition, March 2000

Peter Pin-Shan Chen: The entity-relationship model—toward a unified view of


data - ACM Transactions on Database Systems, Volume 1, Issue 1,
March 1976 pp 9–36.

Reed P.: The Unified Modeling Language Take Shape – DBMS 11, No 8, Juillet
1988

Silberschatz A., Korth H.F. & Sudarshan S.: Database system concepts, sixth
edition, McGraw-Hill, 2011

26
Chapitre 2. : Concepts et langages formels du
modèle relationnel

2.1. Introduction
Comme indiqué dans la section 1.2.1, le modèle relationnel est le modèle de
données sur lequel s’appuient les SGBD de la 2ème génération. Il dit
comment les données sont présentées au développeur par les SGBD de cette
génération et comment ces données peuvent être manipulées dans un
programme.

Tout en relevant du domaine de la programmation, ce modèle a la


particularité de n’avoir pas été dérivé d’un paradigme de programmation
en particulier. Tel que défini en 1970 par E.F. CODD, ce modèle repose sur
un ensemble de concepts volontairement réduit au strict minimum
permettant nativement de donner au SGBD la capacité de fournir les
garanties pour le maintien de l’intégrité des données. Il conduit à une
organisation logique du contenu d’une base de données :
• Dénudée de toute ambiguïté susceptible d’induire des
interprétations différentes
• Indépendante des modèles sémantiques et des modèles de données
abstraites relevant du domaine de la programmation, et pouvant
servir pour leur implémentation logique
• Qui permet de dériver diverses implémentations physiques sur les
supports de stockage sans avoir à procéder à une décomposition
préalable des informations modélisées en informations plus
élémentaires
• Qui peut de ce fait servir de base commune à toutes les applications
devant avoir des perceptions différentes sur ces données.

Ce chapitre présente dans la section 2.2 les principaux concepts qui sous-
tendent ce modèle, à savoir, les concepts de : « relation »,
« domaine/attribut », « dépendance entre groupes d’attributs », « clé
primaire », « clé candidate », « clé unique » et « clé étrangère ».
Joachim TANKOANO

Ces concepts conduisent à une définition du schéma logique d’une base de


données en termes de relations constituées chacune d’un ensemble
d’agrégats de données atomiques de même nature appelés n-uplets. Ces
relations peuvent se faire référence les unes les autres et peuvent être
soumises à des contraintes d’intégrité. Ce schéma logique est appelé
« schéma logique relationnel ».

En outre, ce modèle fournit de puissants langages formels abstraits de


manipulation des données qu’il a servi à organiser, indépendants de la
manière dont ces données sont organisées et stockées sur les supports
physiques.

Ce chapitre présente également ces langages formels du modèle relationnel


qui permettent de manipuler de façon abstraite une base de données selon
une approche dite relationnelle. Il s’agit des « langages algébriques
relationnels » présentés dans la section 2.3 et des « langages prédicatifs
relationnels » présentés dans la section 2.4. Ces langages découlent des
représentations mentales qui sous-tendent le concept de relation et ont
fourni les bases qui ont permis la définition de SQL, le langage normalisé
d’interface des bases de données relationnelles.

Au-delà de ces aspects, comme les modèles sémantiques, le modèle


relationnel est également est un modèle de conception. Les fondements
théoriques des concepts sur lesquels il repose constituent le socle de
techniques rigoureuses d’ingénierie qui permettent d’organiser les données
d’une entreprise en termes de relations ne pouvant pas par construction
contenir des données redondantes ce qui facilite la mise à jour des données
par le développeur. Les propriétés des schémas logiques relationnels qui
résultent de l’application de ces techniques permettent en outre de donner
aux SGBD la capacité de pouvoir fournir les garanties d’intégrité relatives
aux données. Ces techniques sont présentées dans chapitre 3

De façon plus générale, la théorie développée autour des bases de données


relationnelles et les applications pratiques de cette théorie reposent sur les
propriétés formelles des concepts et des langages présentés dans ce
chapitre.

28
SGBD relationnels – Tome 1, État de l’art

2.2. Les concepts de base du modèle relationnel

2.2.1. Les concepts de relation et de domaine/attribut

Ces concepts sont utilisés pour définir une base de données relationnelle de
façon abstraite, en termes de relations où chaque relation contient une
partie des données élémentaires qui constituent la mémoire du système
d’information. Ces relations se définissent de diverses manières.

Les relations perçues comme des ensembles de n-uplets

Sous cet angle, chaque relation « R » d’une base de données relationnelle


est perçue comme étant un sous-ensemble du produit cartésien de « n »
ensembles de valeurs atomiques, appelés domaines (« Di » où i= 1..n).

Chaque élément « tj » d’une relation « R » est de ce fait un « n-uplet » ou


« tuple » composé de « n » valeurs atomiques « vj1, vj2, …, vjn » où chaque
valeur « vji » doit être contenue dans le domaine « Di ». Chaque valeur
« vji » doit correspondre à la valeur d’une donnée élémentaire du système
d’information. Les valeurs qui composent un tuple « tj » doivent
caractériser, soit une instance d’une entité du monde réel, soit une instance
d’une association qui implique une ou plusieurs entités.

De façon plus pragmatique, on considère qu’une relation « R » est définie


par « n » « attributs » notées « A1, A2, …, An » où chaque attribut « Ai »
prend ses valeurs dans le domaine « Di » de cette relation. Chaque attribut
« Ai » doit expliciter par son nom, son domaine « Di » ainsi que le rôle qu’il
joue dans cette relation et doit permettre la désignation d’une valeur dans
cette relation. Si « tj » désigne un tuple de « R » et « Ai » un attribut de
« R », « tj.Ai » a comme valeur courante, la valeur « vji » de « tj » et comme
valeurs légales, les valeurs contenues dans le domaine « Di ».

Dans cette définition d’une relation, « n » désigne son degré (ou arité) ,
c'est-à-dire son nombre de domaines ou d’attributs.

La notion de domaine est utilisée par les SGBD pour fournir la garantie de
l’intégrité de domaine en s’assurant que la valeur de chaque donnée
élémentaire dans une base de données relationnelle est légale parce que

29
Joachim TANKOANO

faisant partie d’un ensemble de valeurs prédéfinies.

N.B. : Le concept de relation tel que défini n’est pas à confondre avec le
concept de relation du modèle Entité/Association de P-P. CHEN qui est
une forme d’énoncé servant à relier les instances d’une ou de plusieurs
entités. Lorsqu’il s’agira du concept de « relation » du modèle
Entité/Association, nous utiliserons le terme d’« association » entre entités.

On a coutume de définir de façon informelle une relation par son schéma,


en utilisant une notation qui consiste à faire suivre le nom de cette relation
par la liste de ses attributs qu’on place entre parenthèses et qu’on sépare
par des virgules, en veillant à ce que le nom de cette relation et le nom de
chaque attribut soient suffisamment significatifs pour donner un sens à
cette relation et à chacun de ses attributs et domaines. Lorsqu’un ensemble
d’attributs définit plusieurs relations, on dit que ces relations ont le même
schéma de définition.

Exemple 2.2.1.i : Le schéma de la relation « Pilotes », notée Pilotes


(NoPilote, NomPilote, AdressePilote), la définit comme étant constituée
de trois attributs : « NoPilote », « NomPilote » et « AdressePilote ». Les
noms choisis pour ces attributs indiquent implicitement que le 1er domaine
de cette relation est un ensemble de numéros de pilotes dans une
compagnie, son 2ème domaine un ensemble de noms de pilotes dans cette
compagnie et son 3ème domaine un ensemble d’adresses de pilotes dans
cette compagnie. On peut donc déduire de ce schéma que la relation dont
le nom est « Pilotes », correspond à un ensemble de tuples, où chaque tuple
décrit un pilote d’une compagnie par son numéro, son nom et son adresse.

Une relation peut aussi se définir de façon extensive en énumérant, dans


une table relationnelle, la liste non ordonnée des « tuples » qui la
composent. On dit que cette table est une extension de la relation, où
chaque tuple est explicité par les valeurs qui le composent. Le nombre de
tuples (c'est-à-dire, la cardinalité de la relation) tout comme les valeurs qui
composent ces tuples peuvent tout naturellement évoluer dans le temps.

Exemple 2.2.1.ii : la table relationnelle qui suit est une extension de la


relation « Pilotes ».

30
SGBD relationnels – Tome 1, État de l’art

Pilotes
NoPilote NomPilote AdressePilote
1 Jean BP 1322 OUAGADOUGOU
2 Paul BP 10 BOBO-DIOULASSO
3 Amadou BP 213 LEO

Les relations perçues comme des ensembles d’assertions

Sous cet angle, une relation d’arité « n » se définit en intention à l’aide d’un
prédicat P à « n » variables prenant leurs valeurs dans les domaines « D1,
…, Dn » de cette relation. Un prédicat P à « n » variables qui sert à définir
une relation est une forme d’énoncé sur ces « n » variables qui devient une
proposition vraie, c’est-à-dire une assertion, quel que soit le n-uplet de
valeurs de cette relation qu’on affecte à ces variables. En se basant sur ce
prédicat, la définition d’une relation peut se faire de la façon suivante :
R = {(aj1, …, ajn) | j P (aj1, …, ajn)}.

Dans cette définition, « aji » désigne la valeur de l’attribut « Ai » dans le


tuple « tj » de la relation « R », mais aussi la valeur affectée à la variable du
prédicat « P » qui prend ses valeurs dans l’ensemble « Di ». Cette définition
indique que la relation « R » est constituée de l’ensemble des n-uplets de
valeurs « (aj1, …, ajn) », pour lesquels la valeur de vérité de la proposition
« P (aj1, …, ajn) » est vraie.

Cette définition amène donc à considérer qu’une base de données


relationnelle est constituée de relations qui sont des ensembles d’assertions
portant sur des instances d’entités ou d’associations, à partir desquelles
d’autres assertions peuvent se déduire.

Exemple 2.2.1.iii : Si le prédicat « Pilotes (NoPilote, NomPilote,


AdressePilote) » signifie que « Le pilote NoPilote se nomme NomPilote et a
comme adresse AdressePilote », la relation « Pilotes » pourrait alors se
définir comme suit :
Pilotes = {(noj, nomj, adrj) | j Pilotes (noj, nomj, adrj)}

Ce qui permet de déduire que la relation « Pilotes » est constituée de


l’ensemble des n-uplets de valeurs « (noj, nomj, adrj) » pour lesquels la

31
Joachim TANKOANO

valeur de vérité de la proposition « Pilotes (noj, nomj, adrj) » est vraie. Si


« 1, 'Jean', 'BP 1322 OUAGADOUGOU' » est un n-uplet de valeurs de la
relation « Pilotes », ceci voudrait alors dire que « le pilote 1 se nomme Jean
et a comme adresse BP 1322 OUAGADOUGOU » est une assertion. De
cette assertion on peut déduire par exemple que « le pilote 1 a comme
adresse BP 1322 OUAGADOUGOU » est aussi une assertion.

Exemple 2.2.1.iv: Si le prédicat « Vols (NoVol, NoPilote, NoAvion, VD, VA) »


signifie que « Le vol NoVol est piloté par le pilote NoPilote, est assuré avec
l’avion NoAvion et effectue le trajet allant de VD à VA », la relation « Vols »
pourrait de la même façon se définir comme suit :
Vols = {(novj, nopj, noaj, vdj, vaj) | j Vols (novj, nopj, noaj, vdj, vaj)}

Ce qui revient à dire que la relation « Vols » est constituée de l’ensemble


des n-uplets de valeurs « (novj, nopj, noaj, vdj, vaj) » pour lesquels la valeur
de vérité de la proposition « Vols (novj, nopj, noaj, vdj, vaj) » est vraie. Si
« 'BF101', 1004, 101, 'OUAGADOUGOU', 'ABIDJAN' » est un n-uplet de
valeurs de la relation « Vols », ceci voudrait dire alors que « Le vol BF101
est piloté par le pilote 1004, est assuré avec l’avion 101 et effectue le trajet
allant de OUAGADOUGOU à ABIDJAN » est une assertion. De cette
assertion on peut déduire par exemple que « le pilote 1004 pilote un vol
assuré avec l’avion 101 » est aussi une assertion.

En d’autres termes, une relation peut être définie formellement de façon


duale, soit à l’aide du concept d’ensemble, soit à l’aide du concept de
prédicat. Dans les deux cas, il s’agit d’un concept mathématique qui repose
sur un formalisme et une théorie bien établie : le formalisme et la théorie
des ensembles pour le premier et le formalisme et la théorie de la logique
du 1er ordre pour le 2ème. Comme cela a déjà été relevé, ce sont ces
formalismes qui ont servi de base à la définition de SQL, le langage
d’interface pour la manipulation d’une base de données relationnelle. Cet
aspect est étudié plus amplement dans les sections 2.3, 2.4 et 4.3 qui visent
à donner de bonnes bases sur le raisonnement à suivre pour formuler ou
pour comprendre une requête SQL.

32
SGBD relationnels – Tome 1, État de l’art

2.2.2. Le concept de dépendance entre groupes d’attributs

Dans une relation, les règles de gestion de l’entreprise peuvent imposer


entre des attributs des dépendances à respecter pour que les valeurs de ces
attributs soient considérées comme étant valides. Ces dépendances qui sont
des propriétés sémantiques du système d’information constituent des
contraintes d’intégrité qui doivent être maintenues sur toute extension de
cette relation pour qu’elle puisse être considérée comme étant légale, c'est-
à-dire conforme à ces règles de gestion.

Une dépendance fonctionnelle (DF) dans une relation « R » est une forme
particulière de dépendance entre deux attributs ou groupes d’attributs de
cette relation.

On dit qu’un attribut (ou groupe d’attributs) « Y » de « R » est en


dépendance fonctionnelle d’un attribut (ou groupe d’attributs) « X » de
« R » ou que « X » détermine fonctionnellement « Y », si et seulement si,
pour toute extension légale de « R » chaque valeur de « X » est associée à
une et une seule valeur de « Y ». En d’autres termes, si quels que soient
deux tuples « t1 » et « t2 » de « R », t1.X = t2.X  t1.Y = t2.Y. Par
convention, on note cette dépendance de cette façon : X → Y.

Une dépendance fonctionnelle entre deux attributs (ou groupes d’attributs)


« X » et « Y » est une contrainte d’intégrité qui doit être vérifiée entre les
valeurs de « X » et « Y » pour toute extension légale de « R », cohérente avec
les règles de gestion de l’entreprise.

Exemple 2.2.2.i: Si on considère que Pilotes (NoPilote, NomPilote,


AdressePilote) désigne le schéma de la relation « Pilotes » on peut déduire
les dépendances fonctionnelles ci-après :
NoPilote → NomPilote
NoPilote → AdressePilote

Ces dépendances fonctionnelles indiquent que dans une extension légale


de la relation « Pilotes » il ne doit pas être possible de trouver deux tuples
qui ont la même valeur pour l’attribut « NoPilote » mais des valeurs
différentes pour les attributs « NomPilote » et « AdressePilote ».

33
Joachim TANKOANO

Comme nous le verrons dans le chapitre 3, bien que plus rares, des
dépendances peuvent exister également entre plus de deux attributs (ou
groupes d’attributs).

Les axiomes identifiés par Armstrong, définissent un système de déduction


complet et fermé sur ces différentes formes de dépendances. Le rôle de ce
système de déduction dans le processus de conception d’une base de
données est étudié dans ce chapitre 3.

2.2.3. Les concepts de clé primaire, clé alternative et clé


étrangère

Les garanties sur l’intégrité des données intégrées nativement dans le


modèle relationnel pur repose sur la sémantique des concepts de clés
primaires, alternatives et étrangères.

La clé primaire d’une relation « R » est un attribut (ou groupe minimal


d’attributs) de « R » qui est tel que chacune de ses valeurs identifie un et un
seul tuple dans « R ». En d’autres termes, tout attribut de « R » qui ne fait
pas partie de sa clé primaire dépend fonctionnellement de celle-ci.

Lorsqu’une relation est bien conçue, sa clé primaire permet au SGBD de


fournir la garantie de l’intégrité de relation en s’assurant que chaque
valeur de cette clé n’identifie qu’un et un seul tuple de cette relation.

Dans le schéma d’une relation, on a coutume de souligner l’attribut (ou


groupe minimal d’attributs) qui constitue sa clé primaire.

Exemple 2.2.3.i : Dans le schéma Pilotes (NoPilote, NomPilote,


AdressePilote), l’attribut « NoPilote » est identifié comme étant la clé
primaire de la relation « Pilotes », ce qui a comme implication que chaque
valeur de « NoPilote » dans la relation « Pilotes » ne peut identifier qu’un
et un seul tuple de cette relation et aussi les deux dépendances
fonctionnelles : NoPilote → NomPilote et NoPilote → AdressePilote.

Lorsque plusieurs attributs (ou groupes d’attributs) peuvent jouer le rôle


de clé primaire, on parle de clés candidates. Les clés candidates d’une

34
SGBD relationnels – Tome 1, État de l’art

relation, autres que sa clé primaire, sont des clés alternatives (ou clés
uniques).

Le SGBD fournit pour chaque clé alternative la garantie de l’intégrité


d’unicité, en s’assurant que chaque valeur de cette clé alternative n’identifie
qu’un et un seul tuple de la relation.

Exemple 2.2.3.ii: Dans le schéma Pilotes (NoPilote, NoCNIB, NomPilote,


AdressePilote) où l’attribut « NoPilote » est identifié comme étant la clé
primaire de la relation « Pilotes », l’attribut « NoCNIB » qui désigne le
numéro de la carte nationale d’identité du pilote est une clé alternative.

Si un attribut (ou groupe d’attributs) noté « Z » d’une relation « R1 »


correspond à une clé primaire dans une autre relation « R2 », on dit que
« Z » est une clé étrangère dans la relation « R1 ». Ce qui veut dire que la
valeur de « Z » dans chaque tuple de « R1 » doit être la même que la valeur
de la clé primaire d’un tuple qui existe dans la relation « R2 ». Ceci signifie
que les deux relations « R1 » et « R2 » sont sémantiquement liées par un
lien référentiel matérialisé à l’aide de l’attribut (ou groupe d’attributs) « Z »
qui doit avoir le même domaine de définition dans les deux relations. La
relation qui contient la clé étrangère est considérée comme étant une
relation dépendante (ou référençant) et l’autre comme étant une relation
référencée ou parent.

Les clés étrangères permettent au SGBD de fournir la garantie de l’intégrité


référentielle (c’est-à-dire, du lien sémantique correspondant) en s’assurant
que chaque tuple « t1 » de « R1 » fait référence, à travers sa clé étrangère
« Z », à un tuple « t2 » de « R2 » dont la clé primaire a la même valeur que
« Z ».

Dans le schéma d’une relation, on a coutume de distinguer les clés


étrangères en faisant suivre ou en faisant précéder les attributs concernés
du caractère « # » et en donnant à chaque clé étrangère le nom de la clé
primaire qui lui correspond.

Exemple 2.2.3.iii : Les deux relations définies par les schémas ci-dessous
sont sémantiquement liées par l’attribut « NoPilote » qui est une clé

35
Joachim TANKOANO

étrangère dans la relation « Vols » et une clé primaire dans la relation


« Pilotes ». À cause de cette clé étrangère, on peut déduire que chaque tuple
de « Vols », qui décrit un vol, fait référence à un tuple de « Pilotes », qui
décrit le pilote qui conduit ce vol :
Vols (NoVol, NoPilote#, NoAvion, VD, VA)
Pilotes (NoPilote, NomPilote, AdressePilote)

Comme nous le verrons dans le chapitre 3, lorsque les relations sont bien
conçues, les clés primaires, alternatives et étrangères permettent aux SGBD
d’empêcher la violation des contraintes d’intégrité relatives aux
dépendances entre attributs découlant des règles de gestion de l’entreprise,
en garantissant le respect de l’intégrité de relation, de l’intégrité d’unicité et
de l’intégrité référentielle. De ces clés, on peut déduire toutes les
dépendances fonctionnelles qui découlent des règles de gestion de
l’entreprise et inversement.

2.2.4. Le pouvoir de modélisation du modèle relationnel et son


importance pour la conception

a) Le pouvoir de modélisation du modèle relationnel

Comme nous venons de le voir, le modèle relationnel pur tel que défini en
1970 par E.F. CODD est un modèle logique de données relevant du domaine
de la programmation sans être dérivé d’un paradigme de programmation
en particulier, strict sur l’intégrité. Il permet de modéliser une base de
données à l’aide d’un « schéma logique relationnel » comme étant
composée d’un ensemble de relations constituées de n-uplets de valeurs
typées atomiques. Ces relations, soumises à des contraintes d’intégrité, sont
sémantiquement liées par des liens référentiels matérialisés à l’aide de clés
étrangères. Elles sont manipulables à l’aide d’opérateurs algébriques
relationnels abstraits et génériques (voir section 2.3) et implémentables de
diverses manières sur les supports physiques de stockage sans avoir à
procéder à une décomposition préalable des informations modélisées en
informations plus élémentaires (voir chapitres 7 et 8).

36
SGBD relationnels – Tome 1, État de l’art

Lorsqu’une entité complexe du monde réel se décrit à l’aide d’un agrégat


d’informations typées persistantes, structurées en termes d’agrégats ou de
collections d’informations typées élémentaires ou structurées à leur tour,
ceci amène à organiser logiquement les informations décrivant cette entité
complexe pour qu’elles soient manipulables sous forme d’informations
décomposées en n-uplets de valeurs typées atomiques réparties dans
plusieurs relations, plutôt que sous forme d’informations structurées en
termes d’agrégats et/ou de collections, regroupées en une seule valeur
abstraite complexe.

Si par exemple une valeur persistante concernant un client est constituée


des informations élémentaires décrivant ce client, d’une collection
d’informations décrivant chaque commande passée par ce client et pour
chacune de ces commandes d’une collection d’informations décrivant
chaque produit commandé, ceci amène à décomposer cette valeur en n-
uplets de valeurs typées atomiques réparties dans plusieurs relations liées
par des clés étrangères, à savoir, la relation « Clients », la relation
« Commandes » et la relation « Produits », où cette valeur devient
manipulable à l’aide d’opérateurs algébriques relationnels abstraits et
génériques.

b) L’importance du modèle relationnel pour la conception

Comme nous le verrons dans le chapitre 3, l’une des particularités


importantes du modèle relationnel réside dans le fait qu’il rend possible la
conception d’un schéma logique relationnel dont les contraintes d’intégrité
découlent des règles de gestion de l’entreprise concernée, définies en termes
de dépendances entre informations élémentaires du système d’information.

Ce modèle rend aussi possible la transformation d’un schéma conceptuel


en un schéma logique relationnel qui prend en compte les contraintes
d’intégrité explicitées par ce schéma conceptuel, afin de rendre
manipulable, dans un programme, la base de données modélisée à l’aide de
ce schéma conceptuel.

Ainsi par exemple, pour rendre les données décrites par un schéma
conceptuel propre (c'est-à-dire bien conçu), manipulables dans un

37
Joachim TANKOANO

programme, si ce schéma conceptuel a été élaboré en s’appuyant sur le


modèle Entité/Association, son implémentation logique à l’aide d’un
schéma logique relationnel peut se faire par dérivation en s’appuyant sur
les règles de transformation ci-après :

A 0, n B
1, n R 1, 1 A (a1, …, ai)
a1
0, 1
b1 
….. r1, ….., rk ….. B (b1, …, bj, a1#, r1, …, rk)
1, 1
ai r bi

N.B. :
• L’entité « A » devient la relation « A » ayant comme attributs les
propriétés de l’entité « A »
• L’entité « B » devient la relation « B » ayant comme attributs les
propriétés de l’entité « B », l’identifiant « a1 » de l’entité « A » et
les propriétés de l’association « R ».

A B
0, n R 0, 1 A (a1, …, ai)
a1 b1 
1, n r1, ….., rk
….. ….. B (b1, …, bj, a1#, r1, …, rk)
0, 1
ai r bi

N.B. :
• L’entité « A » devient la relation « A » ayant comme attributs les
propriétés de l’entité « A »
• L’entité « B » devient la relation « B » ayant comme attributs les
propriétés de l’entité « B », l’identifiant « a1 » de l’entité « A » et
les propriétés de l’association « R ».
• Dans ce cas, les contraintes d’intégrité doivent exprimer en plus le
fait que les attributs « a1 », « r1 », …, « rk » de la relation « B »
doivent pouvoir prendre comme valeur, la valeur indéterminée
« NULL ».

38
SGBD relationnels – Tome 1, État de l’art

A 0, n 0, n B A (a1, …, ai)
1, n R 1, n
a1 b1  B (b1, …, bj)
0, 1 r1, ….., rk 0, 1
….. …..
R (a1#, b1#, r1, …, rk)
ai bi

N.B. :
• L’entité « A » devient la relation « A » ayant comme attributs les
propriétés de l’entité « A »
• L’entité « B » devient la relation « B » ayant comme attributs les
propriétés de l’entité « B »
• L’association « R » devient la relation « R » ayant comme attributs
l’identifiant « a1 » de l’entité « A », l’identifiant « b1 » de l’entité
« B » et les propriétés de l’association « R »
• Cette règle est également applicable aux associations où
participent plus de deux entités.

Exemple 2.2.4.i : Ce qui suit est un exemple de schéma conceptuel basé sur
le formalisme du modèle Entité / Association.

Client Commande
0, n Passe 1, 1 1, n
NoCli NoCmde
NomCli DateCmde
ComposéeDe
Qtée
Taxe Produit
0, n 1, n
TaxéA RefProd
CodeTaxe
TauxTaxe LibelléProd 0, n
PrixProd

La transformation de ce schéma conceptuel par application des règles ci-


dessus donne le schéma logique relationnel ci-après :
Clients (NoCli, NomCli)
Commandes (NoCmde, DateCmde, NoCli#)
Produits (RefProd, LibelléProd, PrixProd)
Taxes (CodeTaxe, TauxTaxe)

39
Joachim TANKOANO

CommandesProduit (NoCmde#, RefProd#, Qtée)


TaxesProduit (RefProd#, CodeTaxe#)

Comme nous le verrons dans le chapitre 3, la détermination du schéma


logique relationnel d’une base de données peut consister, comme nous
venons de le faire dans l’exemple 2.2.4.i, à concevoir dans un premier temps
le schéma conceptuel de cette base de données et à transformer dans un
deuxième temps le schéma conceptuel obtenu en schéma logique
relationnel en utilisant des règles de transformation prédéfinies. Une telle
démarche facilite le dialogue entre les informaticiens et les utilisateurs, ce
dialogue pouvant se focaliser sur le schéma conceptuel que les utilisateurs
peuvent lire et comprendre plus aisément. Cependant, il est important de
garder à l’esprit que le résultat qu’on obtient au bout d’une telle démarche
n’est dans certains cas qu’un résultat brut qu’il faut continuer à affiner :
• Dans le résultat qu’on obtient, la clé primaire de la relation issue
d’une association par application de la 3ème règle ci-dessus
n’exprime pas toujours les contraintes d’intégrité souhaitées. En
approfondissant l’analyse il peut s’avérer nécessaire de modifier la
solution obtenue.
• À cela, il faut ajouter le fait que les règles de transformation telles
que définies ci-dessus ne prennent pas en compte toutes les
contraintes d’intégrité définies à l’aide des cardinalités des
associations. En outre, le schéma conceptuel ne permet pas par
essence d’exprimer certains types de contraintes d’intégrité comme
celles qui ne peuvent être définies qu’à l’aide de prédicats sur des
attributs. Les contraintes d’intégrité concernées par ces deux types
d’insuffisance doivent être intégrées autrement dans le schéma
logique de la base des données concernée.

Par ailleurs, comme nous le verrons dans les chapitres 7 et 8, le modèle


relationnel facilite aussi la dérivation d’un schéma physique des données à
partir d’un schéma logique relationnel.

Enfin, le modèle relationnel rend aussi possible l’implémentation logique


des données décrites en utilisant un modèle de données abstraites relevant

40
SGBD relationnels – Tome 1, État de l’art

du domaine de la programmation, à l’aide de données définies par un


schéma logique relationnel dont l’intégrité est garantie par ce schéma
logique relationnel. Comme nous le verrons dans les chapitres 13, 14 et 15,
cette troisième capacité du modèle relationnel offre les bases nécessaires
pour une évolution vers une 3ème génération des SGBD à même de préserver
tous les acquis de la 2ème génération.

2.3. Le langage algébrique relationnel


Ce langage formel constitue le langage de référence du modèle relationnel.
Il a été proposé par E. F. CODD en 1970. Il repose sur le fait que les relations
peuvent être perçues comme étant des ensembles de tuples. Il permet de
manipuler ces relations à l‘aide d’opérateurs de l’algèbre relationnelle qui
font abstraction d’une part, de la manière dont les données sont organisées
et stockées sur les supports physiques et d’autres part, des algorithmes
utilisés pour accéder à ces données. Chaque opérateur prend en entrée une
ou deux relations et produit en résultat une nouvelle relation en traitant ces
relations comme des ensembles. Ces opérateurs peuvent s’utiliser pour
spécifier à l’aide d’un calcul algébrique relationnel (c'est-à-dire d’une
séquence d’opérateurs algébriques relationnels) les informations que l’on
souhaite obtenir à partir de relations existantes. On dit de ce fait qu’il s’agit
d’un langage formel procédural ensembliste fermé. Il est procédural parce
qu’il permet de spécifier le résultat recherché à l’aide d’une séquence
d’opérations pouvant calculer ce résultat. Il est ensembliste parce que ces
opérations manipulent des ensembles. Il est fermé parce que les séquences
d’opérations qu’il permet de définir prennent en entrée des ensembles et
produisent en sortie un nouvel ensemble.

2.3.1. Les opérateurs algébriques relationnels

Dans le langage algébrique relationnel, on distingue cinq (5) opérateurs


« primitives » à partir desquels les trois (3) autres opérateurs peuvent se
définir. Il s’agit de :

1) L’union, notée «  »

41
Joachim TANKOANO

2) La différence, notée « - »

3) Le produit cartésien, noté « * »

4) La projection, notée «  »

5) La sélection, notée «  ».

Les trois (3) autres opérateurs (qu’on peut définir à partir de ces opérateurs
« primitives ») sont :

1) L’intersection, notée «  »

2) La jointure, notée «  »

3) La division, notée « ÷ ».

a) L’union («  »)

L’union est un opérateur algébrique ensembliste qui prend en entrée deux


relations « R1 » et « R2 » ayant le même schéma de définition.

L’union de deux relations « R1 » et « R2 », notée « R1  R2 », est la relation


« R » constituée des tuples de « R1 » et de « R2 ».

Exemple 2.3.1.i : Si on considère les schémas « Clients (Code, Nom) » et


« Fournisseurs (Code, Nom) » des relations « Clients » et « Fournisseurs »,
on en déduit que « Tiers = clients  Fournisseurs » correspond à
l’ensemble des tiers. En considérant un exemple d’extensions de ces deux
relations on peut sur cette base écrire l’égalité qui suit :

Clients Fournisseurs Tiers

1 Pierre  3 André = 1 Pierre


2 Paul 4 Catherine 2 Paul
4 Catherine 3 André
4 Catherine

42
SGBD relationnels – Tome 1, État de l’art

b) La différence (« - »)

La différence est un opérateur algébrique ensembliste qui prend en entrée


deux relations « R1 » et « R2 » ayant le même schéma de définition.

La différence de deux relations « R1 » et « R2 », notée « R1 - R2 », est la


relation « R » composée des tuples de « R1 » qui ne sont pas dans « R2 ».

Exemple 2.3.1.ii : Si on considère les schémas « Clients (Code, Nom) » et


« Fournisseurs (Code, Nom) » des relations « Clients » et « Fournisseurs »,
on en déduit que « ClientsNonFournisseurs = clients – Fournisseurs »
correspond à l’ensemble des clients qui ne sont pas des fournisseurs. En
considérant un exemple d’extensions de ces deux relations, on peut sur
cette base écrire l’égalité qui suit :

Clients Fournisseurs ClientsNonFournisseurs

1 Pierre - 3 André = 1 Pierre


2 Paul 4 Catherine 2 Paul
4 Catherine

c) Le produit cartésien (« * »)

Le produit cartésien est un opérateur algébrique ensembliste qui prend en


entrée deux relations « R1 » et « R2 » ayant des schémas différents.

Le produit cartésien de deux relations « R1 » et « R2 », noté « R1 * R2 », est


la relation « R » où chaque tuple est une concaténation d’un tuple de « R1 »
et d’un tuple de « R2 ».

Exemple 2.3.1.iii : Si on considère les schémas « Etudiants (Matricule,


Nom) » et « Matières (CodeMat, LibMat) » des relations « Etudiants » et
« Matières », on en déduit que « Inscr = Etudiants * Matières » correspond
à un ensemble d’inscriptions qui exprime le fait que chaque étudiant est
inscrit à chaque cours. En considérant un exemple d’extensions de ces deux
relations on peut sur cette base écrire l’égalité qui suit :

43
Joachim TANKOANO

Etudiants Matières Inscr

1 Pierre * M1Algo = 1 Pierre M1 Algo


2 Paul M2 BD 1 Pierre M2 BD
M3 Réseaux 1 Pierre M3 Réseaux
2 Paul M1 Algo
2 Paul M2 BD
2 Paul M3 Réseaux

d) La projection («  »)

La projection est un opérateur algébrique relationnel qui prend en entrée


une relation « R1 » et un sous-ensemble d’attributs « a1, …, ak » de cette
relation.

La projection d’une relation « R1 » sur ses attributs « a1, …, ak » notée


« (a1, …, ak) (R1) », est la relation « R » composée des tuples de « R1 »
n’ayant comme attributs que « a1, …, ak ».

N.B. Dans la relation qui résulte d’une projection, les tuples qui se répètent
(c’est-à-dire les doublons) sont supprimés afin de se conformer à la
définition d’un ensemble.

Exemple 2.3.1.iv : Si on considère la relation « Inscr (Matricule, Nom,


CodeMat, LibMat) », on peut par projection déduire de cette relation de
nouvelles relations comme illustré ci-dessous sur un exemple d’extension :

Inscr (Nom, LibMat) (Inscr) (Nom) (Inscr)

1 Pierre M1 Algo  Pierre Algo Pierre


1 Pierre M2 BD Pierre BD Paul
1 Pierre M3 Réseaux Pierre Réseaux
2 Paul M1 Algo Paul Algo
2 Paul M2 BD Paul BD
2 Paul M3 Réseaux Paul Réseaux

44
SGBD relationnels – Tome 1, État de l’art

e) La sélection «  »

La sélection est un opérateur algébrique relationnel qui prend en entrée une


relation « R1 » et un prédicat « P » défini sur les attributs de cette relation.

La sélection d’une relation « R1 » par rapport à un prédicat « P », qu’on note


« (P) (R1) », est la relation « R » constituée du sous-ensemble des tuples de
« R1 » pour lesquels la valeur de vérité de « P » est vraie.

Exemple 2.3.1.v : Si on considère la relation « Villes (Nom, Population) »,


on peut par sélection déduire de cette relation de nouvelles relations comme
illustré ci-dessous sur un exemple d’extension :

Villes (population  500 000)(Villes)

Ouagadougou 1 600 000  Koudougou 300 000


Koudougou 300 000 Ouahigouya 400 000
Ouahigouya 400 000

f) L’intersection («  »)

L’intersection est un opérateur algébrique ensembliste qui prend en entrée


deux relations « R1 » et « R2 » ayant le même schéma de définition.

L’intersection de deux relations « R1 » et « R2 », notée « R1  R2 », est la


relation « R » constituée des tuples communs à « R1 » et « R2 ».

N.B. : L’intersection se définit à l’aide des opérateurs « Primitives » de la


façon suivante :
R1  R2 = R1  R2 – ((R1 – R2)  (R2 – R1)) = R1 – (R1 – R2)

Exemple 2.3.1.vi : Si on considère les relations « Clients (Code, Nom) » et


« Fournisseurs (Code, Nom) », on en déduit que « ClientsEtFournisseurs
= clients  Fournisseurs » représente l’ensemble des clients qui sont aussi
fournisseurs. En considérant un exemple d’extensions des relations
« Clients » et « Fournisseurs » on peut sur cette base écrire ce qui suit :

45
Joachim TANKOANO

Clients Fournisseurs ClientsEtFournisseurs

1 Pierre  3 André = 4 Catherine


2 Paul 4 Catherine
4 Catherine

g) La jointure («  »)

La jointure est un opérateur algébrique relationnel qui prend en entrée deux


relations « R1 » et « R2 », et un prédicat « P » défini sur un ensemble
d’attributs communs à « R1 » et « R2 ».

La jointure de deux relations « R1 » et « R2 » par rapport à un prédicat « P »,


qu’on note « R1 P R2 », est la relation « R » qui contient exclusivement
toutes les possibilités de concaténation des tuples de « R1 » et « R2 » pour
lesquelles la valeur de vérité de « P » est vraie.

N.B. : La jointure se définit à l’aide des opérateurs « primitives » de la façon


suivante :
R1 P R2 = P (R1 * R2).

Exemple 2.3.1.vii : Si on considère les relations « Prods (Ref, désign, PU) »


et « Cmdes (NoCli, Ref, Qtée) », on en déduit que dans la relation qui
résulte de « Cmdes (Commandes.Ref=Produits.Ref) Prods », chaque tuple est une
concaténation d’un tuple de « Cmdes » avec le tuple de « Prods »
correspondant au produit commandé. En considérant un exemple
d’extensions des relations « Prods » et « Cmdes » on peut écrire ceci :

Prods Cmdes Cmdes (Cmdes.Ref=Prods.Ref) Prods

1 Chemise 5 000 C1 1 5  1 Chemise 5 000 C1 5


2 Pantalon 10 000 C1 2 3 2 Pantalon 10 000 C1 3
3 Robe 75 000 C2 1 8 1 Chemise 5 000 C2 8
4 Chaussure 14 000

46
SGBD relationnels – Tome 1, État de l’art

On distingue plusieurs types de jointures dont :


• La theta-jointure, qui est une jointure dans laquelle le prédicat « P »
est une simple comparaison entre un attribut « a1 » de la relation
« R1 » et un attribut « a2 » de la relation « R2 »
• L’équijointure, qui est une theta-jointure dans laquelle le prédicat
« P » est un test d’égalité
• La jointure naturelle, qui est une jointure dans laquelle le prédicat
« P » est un test d’égalité entre les attributs qui portent le même nom
dans « R1 » et « R2 ». Dans la relation qui en résulte, ces attributs
communs ne sont pas dupliqués mais fusionnés en un seul attribut
par couple d’attributs
• L’auto-jointure, qui est une jointure où les relations « R1 » et « R2 »
désignent la même relation
• La semi-jointure, notée «  », qui est une jointure suivie d’une
projection sur les attributs de la relation « R1 ».

h) La division (« ÷ »)

La division est un opérateur algébrique relationnel qui prend en entrée


deux relations « R1 » (ayant pour attributs « A1, …, Ai, B1, …, Bj ») et
« R2 » (ayant pour attributs « B1, …, Bj »). En d’autres termes, les deux
relations « R1 » et « R2 » doivent avoir en commun des attributs, ayant les
mêmes domaines de définition.

La division de « R1 » par « R2 », notée « R1 ÷ R2 », est la relation « R »


(ayant pour attributs « A1, …, Ai ») telle que si « a1, …, ai » est un tuple de
« R », alors pour tout tuple « b1, …, bj » de « R2 », la concaténation « a1, …,
ai, b1, …, bj » doit être un tuple de « R1 ». En d’autres termes, si « R1 »
désigne le dividende, « R2 » le diviseur et « R » le quotient, on doit avoir
« R1 = R2 * R ».

N.B. : La division se définit à l’aide des opérateurs « primitives » de la façon


suivante :

47
Joachim TANKOANO

R1 (A1, …, Ai, B1, …, Bj) ÷ R2 (B1, …, Bj)


= (A1, …, Ai) (R1 (A1, …, Ai, B1, …, Bj))
- (A1, …, Ai)
(((A1, …, Ai) (R1 (A1, …, Ai, B1, …, Bj)) * R2 (B1, …, Bj))
– R1 (A1, …, Ai, B1, …, Bj))

Exemple 2.3.1.viii: Si on considère la relation « Fournisseurs (NoFour,


RefProd) » qui indique pour chaque fournisseur les références des produits
qu’il peut livrer et la relation « ACommander (RefProd) » qui indique les
numéros des produits à commander, on en déduit que la relation qui résulte
de « Fournisseurs / ACommander » contient les numéros des fournisseurs
qui peuvent livrer tous les produits à commander. En considérant un
exemple d’extensions des relations « Fournisseurs » et « ACommander »,
on peut sur cette base écrire ce qui suit :

Fournisseurs ACommander Fournisseurs ÷ ACommander

F1 P1 P1  F1
F4 P1 P3 F4
F3 P2
F4 P3
F1 P3

2.3.2. L’utilité du langage algébrique relationnel


Les huit opérateurs algébriques relationnels présentés constituent un
langage procédural abstrait. Étant donné un besoin d’informations se
trouvant dans une base de données, ce langage permet de définir un calcul
de l’algèbre relationnelle, c'est-à-dire une séquence de ces opérateurs
algébriques relationnels, qui indique comment construire la relation qui
contient ces informations, en faisant abstraction de l’organisation physique
des données et des algorithmes utilisés. Comme décrit au chapitre 8, les
propriétés de commutativité et d’associativité de ces opérateurs permettent
de simplifier ce calcul, à des fins d’optimisation du coût des traitements à
effectuer. De ce fait, c’est ce langage qui est utilisé par les SGBD pour
élaborer, dans une première approche, une solution abstraite optimale

48
SGBD relationnels – Tome 1, État de l’art

pouvant conduire à l’exécution d’une requête SQL. Ces aspects sont étudiés
en détails dans le chapitre 8.

En outre, ces opérateurs algébriques relationnels, conjointement avec les


différentes formes de dépendance qui peuvent exister entre les données,
sont à la base d’une définition de règles rigoureuses d’organisation des
données, qui permettent d’éviter les redondances et de faciliter la
répartition des données sur plusieurs serveurs. Ces aspects sont étudiés
dans les chapitres 3 et 10.

Enfin, le langage algébrique relationnel est le langage de référence pour


l’évaluation du pouvoir d’expression des autres langages relationnels. Pour
être un langage relationnel, tout autre langage doit avoir au moins le
pouvoir d’expression du langage algébrique relationnel basé sur les huit
opérateurs présentés. En d’autres termes, il doit être possible de définir à
l’aide de ce langage, la sémantique de chacun de ces huit opérateurs de
l’algèbre relationnelle.

2.3.3. Exemples de calculs algébriques relationnels


Les exemples de requêtes présentés dans ce paragraphe concernent le
schéma logique relationnel ci-après, de S. MIRANDA et J-M BUSTA :
Avions (NoAvion, NomAvion, CapAvion, LocAvion)
Pilotes (NoPilote, NomPilote, AdrPilote)
Vols (NoVol, #NoPilote, #NoAvion, VD, VA, HD, HA)

De façon générale, les informations que le développeur recherche dans une


base de données relationnelle ont dans la plupart des cas été volontairement
réparties dans plusieurs relations (lors de la conception), comme dans cet
exemple, dans le but d’éviter les redondances et de faciliter les mises à jour
(insertion, modification, suppression). Le chapitre 3 étudie de façon
détaillée les techniques utilisées pour cette répartition. Pour déterminer la
séquence d’opérations qui permet d’aboutir à une relation qui contient les
informations qu’il recherche dans une base de données, le développeur doit
de ce fait, identifier dans un premier temps, au regard du résultat recherché,
les relations existantes qui doivent participer au calcul qu’il doit effectuer.
Ensuite, pour obtenir le résultat recherché, il doit regrouper à nouveau les

49
Joachim TANKOANO

données réparties dans ces relations et procéder à des éliminations de


tuples et/ou d’attributs dans le résultat intermédiaire obtenu, en utilisant
les opérateurs algébriques relationnels appropriés.

Exemple 2.3.3.i : Quels sont les numéros des pilotes en service et les villes d’arrivée
de leurs vols ?

La relation « Vols » contient les informations sur les vols, y compris les
numéros des pilotes qui assurent ces vols (c'est-à-dire les numéros des
pilotes en service) ainsi que les villes d’arrivée de ces vols. Cette relation
contient donc toutes les données requises pour construire comme suit le
résultat recherché :
Résultat = (NoPilote, VA) (Vols)

Le calcul à effectuer consiste à restreindre les informations de chaque vol,


contenues dans un tuple de la relation « Vols », au numéro du pilote qui
assure ce vol et à la ville d’arrivée du vol.

Exemple 2.3.3.ii : Quels sont les numéros des vols au départ de Ouagadougou ?

Les données requises pour construire le résultat recherché se trouvent


également de façon intégrale dans la relation « Vols ». Ce résultat peut être
calculé par la séquence d’opérations suivante :
Volsx = (VD = 'Ouagadougou') (Vols)
Résultat = (NoVol) (Volsx)

La 1ère ligne sélectionne tous les vols qui sont au départ de Ouagadougou.
La 2ème ligne restreint cette sélection aux numéros de ces vols.

Le calcul défini est équivalent à l’expression algébrique relationnelle ci-


après :
(NoVol) ((VD = 'Ouagadougou') (Vols)).

Exemple 2.3.3.iii : Quels sont les noms des pilotes, autres qu’Amadou, qui
habitent Bobo-Dioulasso ?

Les données requises pour construire le résultat recherché se trouvent dans

50
SGBD relationnels – Tome 1, État de l’art

la relation « Pilotes ». La séquence d’opérations qui permet de calculer ce


résultat est :
Pilotesx = ((NomPilote  'Amadou')  (AdrPilote = 'Bobo-Dioulasso')) (Pilotes)
Résultat =  (NomPilote) (Pilotesx)

La 1ère ligne sélectionne tous les pilotes qui vérifient la condition de la


requête, c'est-à-dire, les pilotes qui habitent Ouagadougou et qui ne
s’appellent pas Amadou. La 2ème ligne restreint les attributs de cette
sélection aux noms de ces pilotes.

Le calcul défini est équivalent à l’expression algébrique relationnelle ci-


après :
(NomPilote) (((NomPilote  'Amadou')  (AdrPilote = 'Bobo-Dioulasso')) (Pilotes)).

Alternativement, on peut calculer l’ensemble des pilotes recherchés en


ôtant de l’ensemble des pilotes, les pilotes qui ne sont pas concernés par la
requête. Dans ce cas, le calcul à effectuer se définit comme suit :
Pilotesx = ((NomPilote = 'Amadou')  (AdrPilote  'Bobo-Dioulasso')) (Pilotes)
Pilotesy = Pilotes – Pilotesx
Resultat =  (NomPilote) (Pilotesy)

L’expression algébrique relationnelle équivalente est :


(NomPilote) (Pilotes - ((NomPilote = 'Amadou')  (AdrPilote  'Bobo-Dioulasso')) (Pilotes)).

Cet exemple montre que, comme dans les autres algèbres, des séquences
différentes d’opérations peuvent aboutir au même résultat.

Exemple 2.3.3.iv : Quels sont les numéros des pilotes, qui conduisent l’avion
numéro 104 et l’avion numéro 106 ?

Dans cet exemple, pour calculer comme indiqué ci-dessous le résultat


recherché, on n’a besoin que de la relation « Vols ».
Volsx = (NoAvion = 104) (Vols)
Volsy = (NoAvion = 106) (Vols)
Résultat =  (NoPilote) (Volsx)   (NoPilote) (Volsy)

Les deux premières lignes calculent l’ensemble des vols effectués avec
l’avion 104 et l’ensemble des vols effectués avec l’avion 106. La dernière

51
Joachim TANKOANO

ligne calcule d’abord l’ensemble des numéros des pilotes qui assurent un
vol avec l’avion 104 et l’ensemble des numéros des pilotes qui assurent un
vol avec l’avion 106 et effectue ensuite une intersection de ces deux résultats
intermédiaires pour obtenir le résultat recherché.

L’expression algébrique relationnelle correspondant à ce calcul est :


 (NoPilote) ((NoAvion = 104) (Vols))   (NoPilote) ((NoAvion = 106) (Vols)).

Exemple 2.3.3.v : Pour chaque pilote en service, quels sont les numéros des avions
conduits, le numéro et l’adresse du pilote ?

Dans cet exemple, pour construire le résultat recherché, on a besoin de deux


relations : (1) « Vols » où se trouvent les numéros des pilotes qui effectuent
des vols (donc qui sont en service) et les numéros des avions utilisés, (2)
« Pilotes » où se trouvent les adresses de ces pilotes. Ce qui suit définit une
séquence d’opérations qui permet d’obtenir le résultat recherché :
Volsx = Vols (Vols.NoPilote=Pilotes.Nopilote) Pilotes
Résultat =  (NoPilote, NoAvion, AdrPilote) (Volsx)

La 1ère ligne regroupe à l’aide de l’opérateur de jointure les informations


contenues dans la relation « Vols » et celles contenues dans la relation
« Pilotes » lorsque ces informations concernent le même pilote. Chaque
tuple de la relation qui en résulte contient donc toutes les informations
relatives à un vol, en y incluant celles relatives au pilote qui effectue ce vol.
De ce fait, on peut aussi dire que la relation qui en résulte contient pour
chaque pilote en service toutes les informations relatives aux vols qu’il
effectue. Pour chaque pilote en service, le nombre de tuples où se trouvent
ces informations est égal au nombre de vols qu’il effectue. La 2ème ligne
calcule le résultat recherché en retenant dans chaque tuple de la relation
calculée dans la 1ère ligne, le numéro du pilote, l’adresse du pilote et le
numéro de l’avion utilisé dans le vol assuré.

L’expression algébrique relationnelle équivalente à la séquence


d’opérations définie est :
 (NoPilote, NoAvion, AdrPilote) (Vols (Vols.NoPilote=Pilotes.Nopilote) Pilotes).

Pour cet exemple, il est possible de définir d’autres séquences d’opérations

52
SGBD relationnels – Tome 1, État de l’art

qui conduisent au même résultat. L’expression algébrique relationnelle qui


suit définit l’une de ces séquences :
 (NoPilote, NoAvion) (Vols) (Vols.NoPilote=Pilotes.Nopilote)  (NoPilote, AdrPilote) (Pilotes).

Exemple 2.3.3.vi : Quels sont les noms des pilotes qui conduisent un vol au départ
de Ouagadougou ?

Les données requises pour calculer le résultat recherché se trouvent dans


deux relations : (1) « Vols » où se trouvent les numéros des pilotes qui
effectuent des vols (donc qui sont en service) et les villes de départ de ces
vols, (2) « Pilotes » où se trouvent les noms des pilotes concernés. Ce qui
suit définit une séquence d’opérations qui permet d’obtenir le résultat
recherché :
Volsx = Vols (Vols.NoPilote=Pilotes.Nopilote) Pilotes
Volsy = (VD = 'Ouagadougou') (Volsx)
Résultat =  (NomPilote) (Volsy)

Comme dans l’exemple précédent, la 1ère ligne regroupe à l’aide de


l’opérateur de jointure les informations contenues dans la relation « Vols »
et celles contenues dans la relation « Pilotes » lorsque ces informations
concernent le même pilote. La 2ème ligne sélectionne dans la relation qui en
résulte les tuples qui concernent les vols au départ de Ouagadougou. La
3ème ligne calcule le résultat recherché.

L’expression algébrique relationnelle équivalente à la séquence


d’opérations définie est :
 (NomPilote) ((VD = 'Ouagadougou') (Vols (Vols.NoPilote=Pilotes.Nopilote) Pilotes)).

Ce qui suit définit une autre séquence d’opérations :


 (NomPilote) ((VD = 'Ouagadougou') (Vols) (Vols.NoPilote=Pilotes.Nopilote) Pilotes).

Exemple 2.3.3.vii : Quels sont les noms des pilotes qui conduisent un AIRBUS ?

Les données requises pour construire le résultat recherché dans cet exemple
se trouvent dans trois relations : (1) « Vols » où se trouvent les numéros des
pilotes qui assurent des vols et les numéros des avions qu’ils conduisent,

53
Joachim TANKOANO

(2) « Pilotes » où se trouvent les noms de ces pilotes, (3) « Avions » où se


trouvent les noms de ces avions. Ce qui suit définit une séquence
d’opérations qui permet d’obtenir le résultat recherché :
Volsx = Vols (Vols.NoPilote=Pilotes.Nopilote) Pilotes
Volsy = Volsx (Volsx.NoAvion=Avions.NoAvion) Avions
Volsz = (NomAvion = 'AIRBUS') (Volsy)
Résultat =  (NomPilote) (Volsz)

La 1ère ligne réunit à l’aide de l’opérateur de jointure les informations


contenues dans la relation « Vols » et celles contenues dans la relation
« Pilotes » lorsque ces informations concernent le même pilote. La 2ème
ligne réunit à l’aide de l’opérateur de jointure les informations contenues
dans la relation « Volsx » calculée dans la 1ère ligne et celles contenues dans
la relation « Avions » lorsque ces informations concernent le même avion.
Chaque tuple de la relation qui en résulte après cette 2ème jointure contient
donc toutes les informations relatives à un vol en y incluant celles relatives
au pilote qui effectue ce vol et celles relatives à l’avion utilisé. La 3 ème ligne
sélectionne dans la relation qui en résulte les tuples qui concernent un avion
AIRBUS. La 4ème ligne calcule le résultat recherché en restreignant les
attributs de la sélection aux noms des pilotes.

L’expression algébrique relationnelle équivalente à la séquence


d’opérations définie est :
Résultat =  (NomPilote) ((NomAvion = 'AIRBUS')
((Vols (Vols.NoPilote=Pilotes.NoPilote) Pilotes)
(Vols.NoAvion=Avions.NoAvion) Avions)).

Exemple 2.3.3.viii : Quels sont les numéros des pilotes qui conduisent tous les
avions de la compagnie ?

Les données requises pour calculer le résultat recherché dans cet exemple
se trouvent dans deux relations : (1) « Vols » où se trouvent les numéros
des pilotes qui assurent des vols et les numéros des avions qu’ils
conduisent, (2) « Avions » où se trouvent les numéros de tous les avions de
la compagnie. Pour qu’un pilote soit dans le résultat recherché, il faut qu’il
conduise chacun des avions de la compagnie. En d’autres termes le produit

54
SGBD relationnels – Tome 1, État de l’art

cartésien de ce pilote avec l’ensemble des avions de la compagnie doit être


inclus dans l’ensemble des vols assurés. On peut donc effectuer le calcul du
résultat recherché de la façon suivante à l’aide de l’opérateur de division :
Avionsx =  (NoAvion) (Avions)
Volsx =  (NoPilote, NoAvion) (Vols)
Résultat = Volsx / Avionsx

L’expression algébrique relationnelle équivalente à cette séquence


d’opérations est :
 (NoPilote, NoAvion) (Vols) /  (NoAvion) (Avions).

De façon générale, l’opérateur de division est requis chaque fois que la


condition pour inclure un tuple dans le résultat utilise le terme « tous » ou
« toutes » ou un terme qui s’apparente à « tous » ou « toutes ».

Exemple 2.3.3.ix : Quels sont les numéros des pilotes qui conduisent au moins
tous les AIRBUS de la compagnie ?

Tout comme dans l’exemple précédent, le calcul du résultat recherché peut


se faire de la façon suivante à l’aide de l’opérateur de division :
Avionsx = (NomAvion = 'AIRBUS') (Avions)
Avionsy =  (NoAvion) (Avionsx)
Volsx =  (NoPilote, NoAvion) (Vols)
Résultat = Volsx / Avionsy

L’expression algébrique relationnelle équivalente à cette séquence


d’opérations s’écrit de la façon suivante :
 (NoPilote, NoAvion) (Vols) /  (NoAvion) ((NomAvion = 'AIRBUS') (Avions)).

Exemple 2.3.3.x : Quels sont les noms des pilotes qui n’effectuent pas de vol au
départ de Ouagadougou ?

Les données requises pour calculer le résultat recherché se trouvent dans


deux relations : (1) « Vols » où se trouvent les numéros des pilotes qui
assurent des vols et les villes de départ de ces vols, (2) « Pilotes » où se
trouvent les noms de ces pilotes. Le calcul du résultat recherché dans cet

55
Joachim TANKOANO

exemple peut se faire comme suit à l’aide de l’opérateur de différence :


Volsx = (VD = 'Ouagadougou') (Vols)
Pilotesx =  (NoPilote) (Volsx)
Pilotesy =  (NoPilote) (Pilotes)
Pilotesz = Pilotesy – Pilotesx
Pilotesu = Pilotes (Pilotes.NoPilote=Pilotesz.Nopilote) Pilotesz
Résultat =  (NomPilote) (Pilotesu)

Les quatre premières lignes calculent les numéros des pilotes qui
n’effectuent pas des vols au départ de Ouagadougou en ôtant de l’ensemble
des numéros des pilotes, l’ensemble des pilotes qui effectuent un vol au
départ de Ouagadougou. Quant aux deux dernières lignes, elles calculent
le résultat recherché à partir du résultat des quatre premières lignes.

L’expression algébrique relationnelle équivalente à cette séquence


d’opérations s’écrit de la façon suivante :
 (NomPilote) (Pilotes ( Pilotes.NoPilote=Vols.Nopilote)
( (NoPilote) (Pilotes) –  (NoPilote) ((VD = 'Ouagadougou') (Vols)))).

De façon générale, l’opérateur de soustraction est requis chaque fois que la


condition pour inclure un tuple dans le résultat utilise la forme négative.

Exemple 2.3.3.xi : Quels sont les numéros des pilotes qui conduisent un avion
conduit par le pilote n° 32 ?

Les données requises pour calculer le résultat recherché se trouvent


intégralement dans la relation « Vols ». La séquence d’opérations qui
permet de calculer ce résultat est :
Volsx = (NoPilote = 32) (Vols)
Avionx =  (NoAvion) (Volsx)
Volsy = Vols ((Vols.NoAvion = Avionsx.NoAvion)  (Vols.NoPilote  32)) Avionsx
Résultat =  (NoPilote) (Volsy)

Les trois premières lignes utilisent l’opérateur de jointure pour sélectionner


les vols effectués avec un avion conduit par le pilote numéro 32. La dernière
ligne calcule le résultat recherché en restreignant l’ensemble qui en résulte
aux numéros des pilotes.

56
SGBD relationnels – Tome 1, État de l’art

L’expression algébrique relationnelle équivalente à cette séquence


d’opérations est :
 (NoPilote) (Vols ((Vols.NoAvion = Vols.NoAvion)  (Vols.NoPilote  32))
 (NoAvion) ((NoPilote = 32) (Vols)))

Exemple 2.3.3.xii : Quelles sont les villes desservies par les pilotes dont le numéro
est plus grand que celui de Pierre et Paul ?

Les données requises pour calculer le résultat recherché dans cet exemple
se trouvent dans deux relations : (1) « Vols » où se trouvent les numéros
des pilotes qui assurent des vols et les villes desservies par ces vols, (2)
« Pilotes » où se trouvent les noms de ces pilotes. Le calcul du résultat
recherché dans cet exemple peut se faire comme suit :
Pilotesx = (NomPilote = 'Pierre') (Pilotes)
Pilotesy =  (NoPilote) (Pilotesx)
Volsx = Vols (Vols.NoPilote  Pilotesy.Nopilote) Pilotesy
Pilotesu = (NomPilote = 'Paul') (Pilotes)
Pilotesv =  (NoPilote) (Pilotesu)
Volsy = Volsx (Volsx.NoPilote  Pilotesv.Nopilote) Pilotesv
Résultat =  (VA) (Volsy)

Les trois premières lignes utilisent l’opérateur de jointure pour sélectionner


les vols pilotés par des pilotes dont les numéros sont plus grands que celui
de Pierre. Les trois lignes qui suivent utilisent également l’opérateur de
jointure pour sélectionner les vols pilotés par des pilotes dont les numéros
sont en outre plus grands que celui de Paul. La dernière ligne calcule le
résultat recherché en restreignant l’ensemble qui en résulte aux villes
desservies.

L’expression algébrique relationnelle équivalente à cette séquence


d’opérations est :
 (VA) ((Vols (Vols.NoPilote  Pilotes.Nopilote)  (NoPilote) ((NomPilote = 'Pierre') (Pilotes)))
(Vols.NoPilote  Pilotes.Nopilote)  (NoPilote) ((NomPilote = 'Paul') (Pilotes)))

Exemple 2.3.3.xiii : Ajoutez l’avion N°4, de nom AIRBUS, de capacité 200, de


localité Ouagadougou.

57
Joachim TANKOANO

Cette requête peut être traitée à l’aide de l’opération d’union comme ci-
dessous :
Avions = Avions  {4, 'AIRBUS', 200, 'Ouagadougou'}

Exemple 2.3.3.xiv : supprimez l’avion N°4.

Cette requête peut être traitée à l’aide de la séquence d’opérations ci-


dessous :
Avionsx = (NoAvion = 4) (Avions)
Avions = Avions – Avionsx

Ce qui correspond à l’expression suivante :


Avions – (NoAvion = 4) (Avions).

Exemple 2.3.3.xv : Changez le nom de l’avion n°4 en AIRBUS.

La séquence d’opérations qui permet de traiter cette requête peut se définir


comme suit :
Avionsx = (NoAvion = 4) (Avions)
Avionsy = Avions – Avionsx
Avions = Avionsy  {4, ‘AIRBUS’,  (CapAvion) (Avionsx),
 (LocAvion) (Avionsx)}

Ce qui correspond à l’expression suivante :


(Avions – (NoAvion = 4) (Avions))  {4, ‘AIRBUS’,
 (CapAvion) ((NoAvion = 4) (Avions)),
 (LocAvion) ((NoAvion = 4) (Avions))}

2.3.4. Exercice

Soit le schéma logique relationnel ci-après défini en vue d’une bonne


gestion du FESPACO :
Salles (nom, horaire, titre)
A_Produit (producteur, titre)
Joue_Dans (acteur, titre, producteur)

58
SGBD relationnels – Tome 1, État de l’art

A_Vu (spectateur, titre)


Aime (spectateur, titre)

Dans ce schéma relationnel, les attributs prennent leurs valeurs dans les
domaines ci-après :
nom : noms de salles de projection
horaire : heures de la journée
titre : titres de films
acteur, producteur, spectateur : noms de personnes

Déterminez pour chacune des requêtes ci-après la séquence d’opérations


algébriques qui permet de calculer le résultat recherché :
1) Où peut-on voir un film où joue « Idrissa OUEDRAOGO » ?
2) Quels sont les acteurs qui jouent dans tous les films produits par
« Idrissa OUEDRAOGO » ?
3) Quels sont les spectateurs qui aiment un film qu’ils n’ont pas vus ?
4) Quels sont les spectateurs qui aiment tous les films qu’ils ont vus ?
5) Quels sont les producteurs qui n’ont vu que les films qu’ils ont
produits ?

2.4. Les langages prédicatifs relationnels

2.4.1. Quelques rappels sur le formalisme de la logique du 1 er


ordre

La logique du 1er ordre est un formalisme qui sert à la formulation


d’énoncés formels sur des objets de n’importe quel type (des nombres, des
noms, des personnes, des articles, des classes d’associations, etc.), à la
construction de raisonnements formels sur ces énoncés et à la
détermination de la valeur de vérité (« vrai » ou « faux ») des affirmations
basées sur ces énoncés.

a) Les symboles utilisés

Les symboles qu’on peut utiliser dans la formulation d’un énoncé de la


logique du 1er ordre sont les suivants :

59
Joachim TANKOANO

• Les symboles de ponctuation : « ( » et « ) » (pour les parenthèses)


• Les symboles des constantes
• Les symboles des variables
• Les symboles des fonctions
• Les symboles des prédicats
• Les opérateurs ou connecteurs logiques
o «  » (pour la négation)
o «  » (pour l’implication)
o «  » (pour la conjonction)
o «  » (pour la disjonction)
• Le quantificateur universel : «  » (pour quel que soit)
• Le quantificateur existentialiste : «  » (pour il existe).

b) La signature d’un langage du 1er ordre

Il existe un nombre infini de langages du 1er ordre.


Chacun de ces langages est basé sur sa signature (notée «  = (c, f, p)) »
constituée :

• D’un ensemble « c » de symboles, appelés symboles des constantes

• D’un ensemble « f » de symboles, appelés symboles des fonctions

• D’un ensemble « p » de symboles, appelés symboles des prédicats.

Cette signature fixe les symboles de constantes, de fonctions et de prédicats


autorisés dans un énoncé, où ils ont une interprétation définie par ce
langage.

Exemple 2.4.1.i : La signature «  = ({0, 1}, {+, -}, {=, }) » permet de formuler
des énoncés sur l’ensemble « ℕ » des objets, correspondants aux entiers
naturels. Le symbole des constantes « 0 » doit être interprété comme étant
l’entier « 0 », le symbole des constantes « 1 » comme étant l’entier « 1 », le

60
SGBD relationnels – Tome 1, État de l’art

symbole des fonctions « + » comme étant l’addition, le symbole des


fonctions « - » comme étant la soustraction, le symbole des prédicats « = »
comme étant le prédicat de comparaison qui établit une égalité entre deux
entiers et le symbole des prédicats «  » comme étant le prédicat de
comparaison qui établit un ordre entre deux entiers.

Les symboles des constantes de l’ensemble « c » désignent des objets en


particulier, qui ont chacun une signification particulière. Pour désigner un
objet quelconque de « c » on utilise un symbole des variables.

Les symboles des fonctions de l’ensemble « f » servent à spécifier des objets


de « c » qui se calculent par composition d’autres objets.

Les symboles des prédicats de l’ensemble « p » se définissent en considérant


des ensembles d’objets (appelés aussi « domaines »), non nécessairement
disjoints « X1, X2, …, Xn » et des variables « x1, x2, …, xn », qui prennent
respectivement leurs valeurs dans ces ensembles.

Un prédicat « P (x1, x2, …, xn) » est une forme d’énoncée sur « x1, x2, …,
xn » qui devient une proposition, c'est-à-dire, une affirmation, lorsque des
valeurs sont affectées à « x1 », « x2 », …, « xn ». En fonction de la
signification du prédicat « P » et des valeurs affectées à « x1 », « x2 », …,
« xn », cette affirmation peut être, soit « vraie », soit « fausse ».

Exemple 2.4.1.ii : Soient « ℕ » l’ensemble des entiers naturels, « x » une


variable qui prend ses valeurs dans « ℕ », et le prédicat « x  5 » où «  » est
le symbole du prédicat de comparaison « est supérieur à ». Si on affecte à
« x » la valeur « 7 » de « ℕ », « 7  5 » pour « 7 est supérieur à 5 » est une
proposition (i.e. une affirmation) qui est « vraie ». En revanche, si on affecte
à « x » la valeur « 3 » de « ℕ », « 3  5 » pour « 3 est supérieur à 5 » est une
affirmation qui est « fausse ».

Exemple 2.4.1.iii : Soient les ensembles (ou domaines) « HOMMES » et


« FEMMES » qui désignent respectivement l’ensemble des hommes et
l’ensemble des femmes, les variables « x » et « y » qui prennent leurs

61
Joachim TANKOANO

valeurs respectivement dans ces ensembles et le prédicat « Marié (x, y) »,


pour « x et y sont mariés », qu’on peut voir comme étant une classe de
propositions ou d’associations entre individus. Lorsqu’on affecte à « x » la
valeur « Paul » et à « y » la valeur « Jeanne », « Marié (Paul, Jeanne) » est
une affirmation qui est « vraie » si et seulement si Paul et Jeanne sont mariés
dans le monde réel et « fausse » sinon.

Les connecteurs logiques (, , , ) et les quantificateurs (, ) sont


utilisés pour construire de nouveaux prédicats à partir d’un ou de plusieurs
prédicats existants.

c) Le concept de formule bien formée

On définit les termes d’une expression de la logique du 1er ordre par


induction de la façon suivante :
• Les symboles des variables et des constantes sont des termes
• Si « f » est le symbole d’une fonction d’arité « n » et « t1, t2, …, tn »
des termes, alors « f (t1, t2, …, tn) » est un terme.

Les énoncés (ou phrases) de la logique du 1er ordre sont des formules bien
formées, construites par induction de la façon suivante :
• Si « P » est le symbole d’un prédicat d’arité « n » et « t1, …, tn » des
termes, alors « P (t1, …, tn) » est une formule atomique
• Une formule atomique est une formule
• Si « F1 » et « F2 » sont des formules, alors « F1  F2 », « F1  F2 »,
« F1  F2 » et «  F1 » sont aussi des formules
• Si « F1 » est une formule et « x1, …, xn » des variables, alors «  x1,
…, xn F1 » et «  x1, …, xn F1 » sont aussi des formules
• Si « F1 » est une formule, alors « (F1) » est une formule.

N.B. : Si « F1 » et « F2 » sont des formules et « x1, …, xn » des variables, on


note entre autres les équivalences ci-après :

62
SGBD relationnels – Tome 1, État de l’art

• F1  F2   F1  F2   (F1   F2)
•  x1, …, xn F1   ( x1, x2, …, xn  F1)
•  x1, x2, …, xn F1   ( x1, x2, …, xn  F1)

Les équivalences sont utiles pour la construction de raisonnements formels.

La formulation d’un énoncé de la logique du 1er ordre peut servir à la


caractérisation des propriétés des objets d’un ensemble ou à la
caractérisation des propriétés des associations qui existent entre les objets
d’un ou de plusieurs ensembles.

La signification que l’on donne à une formule dépend de la signification


donnée dans cette formule, à travers la signature du langage utilisé, à
chaque symbole de constante, de fonction et de prédicat utilisé.

d) Les variables liées et les variables libres

Dans une sous-formule, lorsqu’une variable « x » est immédiatement


précédée d’un quantificateur (« x » ou « x »), cette variable est dite liée.
Ainsi, dans les formules « x F » et « x F », la variable « x » est une variable
liée, ce qui implicitement veut dire que la valeur de vérité de ces deux
formules ne dépend pas de la valeur de « x ». « x F » veut dire que « F »
est vraie quelle que soit la valeur légale de « x » qu’on considère. « x F »
veut dire que « F » est vraie peu importe la valeur légale de « x ».

Lorsque dans une formule une variable « x » n’est pas liée, elle est dite libre.

Lorsque dans une formule toutes les variables sont liées, on dit que cette
formule est close ou fermée.

Lorsque dans une formule il existe des variables libres, cette formule est
dite ouverte et est considérée comme étant un prédicat sur ces variables
libres.

Exemple 2.4.1.iv : Ainsi, si on considère la formule « x (P (x, y)  (y  5)) »,


« x » est une variable liée, « y » une variable libre et la formule un prédicat
portant sur « y ».

63
Joachim TANKOANO

2.4.2. Les requêtes des langages prédicatifs relationnels

a) Les fondements de l’utilisation des langages prédicatifs comme


langages de requêtes

Le lien entre la logique du 1er ordre et les bases de données relationnelles se


fonde sur un constat qui se résume comme suit :
• Le résultat d’une requête est toujours une nouvelle relation,
construite à partir de relations existantes
• Comme nous l’avons vu dans le paragraphe 2.2.1, chacune des
relations existantes peut être définie par un prédicat « P » sur « n »
variables, qui devient une proposition dont la valeur de vérité est
vraie, lorsqu’un n-uplet de valeurs de cette relation est affecté à ces
variables. En d’autres termes, chacune des relations existantes peut
être perçue comme étant constituée d’un ensemble d’assertions
• La nouvelle relation qui constitue le résultat recherché à travers la
formulation d’une requête, peut se définir quant à elle par un
prédicat « P » sur « n » variables, pour lequel on suppose qu’il existe
un ensemble de combinaisons de n-uplets de valeurs dans des
relations existantes, qui, lorsqu’elles sont appliquées à ces variables,
induisent chacune une proposition vraie. Cette nouvelle relation
doit donc être constituée des assertions déductibles du prédicat qui
la définit et des assertions des relations existantes.

Toute requête, peut de ce fait, se formuler à l’aide du prédicat que chaque


tuple de la relation correspondant au résultat recherché doit vérifier par
déduction en partant des assertions qui existent dans la base de données,
sans avoir à dire, explicitement, comment construire cette relation.

Les langages prédicatifs ont pour finalité d’offrir cette possibilité. Ces
langages permettent, pour chaque requête, de construire une formule de la
logique du 1er ordre, contenant des variables libres, de sorte que cette
formule puisse être interprétée comme étant le prédicat que chaque tuple
de la relation, correspondant au résultat recherché, doit vérifier, par
déduction en partant des assertions des relations existantes. Cette formule

64
SGBD relationnels – Tome 1, État de l’art

doit être construite en prenant comme signature minimale :


• Les symboles de constantes, constitués des n-uplets de valeurs et des
valeurs d’attributs de la base de données, qui sont considérés comme
étant les objets sur lesquels les énoncés doivent être formulés
• Les symboles de fonctions, constitués des opérations usuelles sur les
nombres et les chaînes de caractères
• Les symboles de prédicats, constitués des noms des prédicats qui
servent à définir les relations de la base de données et des opérateurs
de comparaison des nombres et des chaînes de caractères.

En construisant cette formule en guise de requête, l’utilisateur doit tout


simplement chercher à reformuler de façon formelle, la requête initialement
formulée de façon informelle, dans le but de définir le prédicat que chaque
tuple de la relation à produire comme résultat doit vérifier, par déduction
en partant des assertions de la base de données. On peut aborder ce
processus comme étant une simple traduction d’un énoncé dans un autre
formalisme.

Tenant compte du fait que l’énoncé d’une requête peut être formulé sur des
symboles de constantes qui sont, soient des tuples de relations, soient des
valeurs d’attributs, on distingue les requêtes en calcul relationnel à
variables n-uplets et les requêtes en calcul relationnel à variables domaines.

b) Les requêtes en calcul relationnel à variables n-uplets

Elles ont la forme générale suivante :


{(t1.A1, t2.A2, …, tn.An) | F (t1, …, tn, …, tm)}
Où :
• « t1, …, tn, …, tm » désignent des variables n-uplets (non
nécessairement distinctes), c'est-à-dire des variables qui
prennent leurs valeurs dans des ensembles de n-uplets
correspondant à des relations
• « R(ti) » est un prédicat qui est vrai si « ti » contient un n-
uplet de la relation « R »

65
Joachim TANKOANO

• « ti.Ai » désigne une variable qui contient la valeur de


l’attribut « Ai » dans le n-uplet « ti »
• F (t1, t2, …, tn, …, tm) est une formule de la logique du 1er
ordre où « t1, …, tn, …, tm » correspond à l’ensemble des
variables n-uplets utilisées dans la formule
• « t1.A1, t1.A2, …, tn.An » sont les variables libres qui doivent
figurer dans la relation à construire.

La partie gauche de la requête (c'est-à-dire « (t1.A1, t1.A2, …, tn.An)) »


définit donc les attributs qui doivent composer chaque n-uplet du résultat.
Quant à la partie droite (c'est-à-dire « F (t1, …, tn, …, tm) », elle définit le
prédicat que chaque n-uplet du résultat doit vérifier, par déduction en
partant des assertions des relations de la base de données. Ensemble, les
deux parties doivent constituer une traduction en logique du 1 er ordre de
l’énoncé informel initial.

Exemple 2.4.2.i : Soit la requête formalisée de la façon suivante :


{(v.NoVol) | Vols (v)  (v.VD ='Bobo-Dioulasso')}

Les variables utilisées pour la formalisation de cette requête sont : « v »,


« v.NoVol » et « v.VD ». Elles sont toutes les trois des variables libres.

La formule spécifiée dans la partie droite de la requête définit le prédicat


que chaque tuple du résultat doit vérifier, par déduction en partant des
assertions de la base de données. Le prédicat « Vols (v) » indique que dans
cette formule, la variable « v » désigne un tuple quelconque de la relation
« Vols ». Les variables « v.NoVol » et « v.VD » désignent des attributs de
ce tuple. Le prédicat « v.VD = 'Bobo-Dioulasso' » indique que pour ce
tuple « v » de la relation « Vols », son attribut « VD » doit indiquer qu’il
s’agit d’un vol qui effectue un trajet allant de « 'Bobo-Dioulasso' ».

La partie gauche de la requête indique que chaque tuple du résultat doit


être constitué de la valeur de l’attribut « NoVol » d’un tuple « v » de la
relation « Vols » qui vérifie cette formule.

On peut donc par interprétation conclure que la signification de cette


requête est : déduire à partir des assertions de la relation « Vols » tous les

66
SGBD relationnels – Tome 1, État de l’art

tuples « v » de « Vols » dont la valeur de l’attribut « NoVol » vérifie le


prédicat « le vol NoVol effectue un trajet allant de Bobo-Dioulasso », ce qui
peut aussi se dire « rechercher tous les numéros de vols au départ de
Bobo-Dioulasso ».

c) Les requêtes en calcul relationnel à variables domaines

Elles ont quant à elles la forme générale suivante :


{(x1, …, xn) | F (x1, …, xn, …, xm)}
Où :
• « x1, …, xn, …, xm » désignent des variables domaines (non
nécessairement distinctes), c'est-à-dire, des variables qui
prennent leurs valeurs dans des ensembles de valeurs
correspondant à des domaines de relations
• « R (x1, …, xk) » est vrai si les valeurs contenues dans « x1, …,
xk » correspondent aux valeurs qui composent un n-uplet de
la relation « R »
• « F (x1, …, xn, …, xm) » est une formule de la logique du 1er
ordre où « x1, …, xn, …, xm » désignent les variables
domaines, utilisées dans la formule
• « x1, …, xn » désignent les variables libres qui doivent figurer
dans la relation à construire.

Ici également, la partie gauche (c'est-à-dire « (x1, …, xn) ») définit les


attributs qui doivent composer les tuples du résultat et la partie droite
(c'est-à-dire « F (x1, …, xn, …, xm) »), le prédicat que chaque tuple du
résultat doit vérifier, par déduction en partant des propositions vraies
existant dans la base de données.

Exemple 2.4.2.ii : Soit la requête formalisée de la façon


suivante conformément à ces règles de construction :

67
Joachim TANKOANO

{(NoVol)|  NoPilote, NoAvion, VA


(Vols (NoVol, NoPilote, NoAvion, VD, VA)
 (VD = 'Bobo-Dioulasso'))}

Les variables utilisées dans cette formalisation de la requête sont : « NoVol,


NoPilote, NoAvion, VD, VA ». Parmi ces variables, les variables « NoVol »
et « VD » sont des variables libres et les autres des variables liées.

La formule spécifiée dans la partie droite définit le prédicat que chaque


tuple du résultat doit vérifier, par déduction en partant des assertions de la
base de données. Le prédicat « Vols (NoVol, NoPilote, NoAvion, VD,
VA) » indique que les variables utilisées dans cette formule désignent les
attributs d’un tuple quelconque de la relation « Vols ». Le prédicat « VD =
'Bobo-Dioulasso' » indique que pour ce tuple, son attribut « VD » doit
indiquer qu’il s’agit d’un vol qui effectue un trajet allant de « 'Bobo-
Dioulasso' », peu importe la valeur de ses attributs « NoPilote »,
« NoAvion » et « VA.

La partie gauche de la requête indique que chaque tuple du résultat doit


être constitué de la valeur de l’attribut « NoVol » d’un tuple qui vérifie cette
formule.

Cette requête a donc aussi comme signification : déduire de la relation


« Vols », « tous les numéros de vols au départ de 'Bobo-Dioulasso' ». Elle
peut être simplifiée comme suit :
{(NoVol)|  NoPilote, NoAvion, VA
Vols (NoVol, NoPilote, NoAvion, 'Bobo-Dioulasso', VA)}

2.4.3. Le pouvoir d’expression des langages prédicatifs


relationnels

Dans une requête formulée à l’aide d’un langage prédicatif relationnel, la


partie gauche définit les attributs qui doivent composer les tuples du
résultat recherché, mais spécifie aussi de ce fait, de façon implicite, la
projection éventuelle à effectuer pour aboutir à ce résultat. Par ailleurs,
comme cela a été vu dans la section 2.4.1, la construction de la formule de

68
SGBD relationnels – Tome 1, État de l’art

la partie droite, qui spécifie le prédicat que chaque tuple du résultat doit
vérifier, se fait selon des règles qui amènent à combiner des formules
atomiques à l‘aide des connecteurs logiques et des quantificateurs (, , ,
  ). Chaque formule atomique pouvant être utilisée est : (i) soit un
prédicat de la forme Ri(t) évalué à vraie si « t » est un tuple de la relation
« Ri », (ii) soit un prédicat de comparaison de la forme « P(t) » où les termes
comparés ne désignent que des attributs d’un même tuple « t », (iii) soit un
prédicat de comparaison de la forme « P(t1, t2) » où les termes comparés
désignent des attributs de deux tuples différents « t1 » et « t2 ».
L’expression définie par une sous-formule formée à l’aide de ces formules
atomiques peut correspondre ou ne pas correspondre à un prédicat que doit
satisfaire les tuples d’une relation correspondant à un résultat
intermédiaire. Les sous-formules correspondant à un prédicat que doit
satisfaire les tuples d’une relation sont considérées comme étant saines et
les autres comme étant non saines.

Exemples 2.4.3.i : Ce qui suit constitue des exemples de sous-formules


saines et non saines pouvant être utilisées à leur tour dans d’autres sous-
formules englobantes.
• R1(t) est une sous-formule saine qui spécifie un résultat
intermédiaire correspondant à R1.
• R1(t1) est une sous-formule non saine qui spécifie un résultat
intermédiaire qui ne correspond pas à la définition d’une relation.
• P(t) est une sous-formule non saine qui spécifie un résultat
intermédiaire qui ne correspond pas à la définition d’une relation.
• P(t1, t2) est une sous-formule non saine qui spécifie un résultat
intermédiaire qui ne correspond pas à la définition d’une relation.
• R1(t)  P(t) est une sous-formule saine qui spécifie un résultat
intermédiaire correspondant au résultat d’une sélection dans R1 à
l’aide du prédicat P(t).
• R1(t)  R2(t) est une sous-formule saine qui spécifie un résultat
intermédiaire correspondant au résultat de l’union de R1 et R2.

69
Joachim TANKOANO

• R1(t)  R2(t) / R1(t)  R2(t) sont des sous-formules non saines


qui spécifient un résultat intermédiaire qui ne correspond pas à la
définition d’une relation.
• R1(t)  R2(t) est une sous-formule saine qui spécifie un résultat
intermédiaire correspondant au résultat de l’intersection de R1 et R2.
• R1(t)  R2(t) est une sous-formule saine qui spécifie un résultat
intermédiaire correspondant au résultat de la différence de R1 et R2.
• R1(t)  R2(t) est une sous-formule non saine qui spécifie un
résultat intermédiaire qui ne correspond pas à la définition d’une
relation.

En d’autres termes, une formule spécifiée à l’aide d’un langage prédicatif


relationnel peut correspondre ou ne pas correspondre au prédicat que doit
satisfaire les tuples d’une relation. Les langages prédicatifs relationnels ne
sont donc pas de base, comme le langage algébrique relationnel, des
langages fermés. Pour qu’un langage prédicatif relationnel soit un langage
fermé, en plus des règles de construction des formules de la logique du 1 er
ordre, il doit aussi être basé sur des contrôles sémantiques pouvant garantir
que chaque formule qu’il permet de construire correspond à la définition
d’un prédicat que doit satisfaire les tuples d’une relation, c’est-à-dire à une
formule saine.

Les équivalences ci-dessous montrent que les langages prédicatifs


relationnels, munis de contrôle sémantiques appropriés, ont le même
pouvoir d’expression que le langage algébrique relationnel.
R1  R2 ≡ {(t) | R1 (t)  R2 (t)}
R1  R2 ≡ {(t) | R1 (t)  R2 (t)}
R1 – R2 ≡ {(t) | R1 (t)  R2 (t)}
R1 * R2 ≡ {(t1, t2) | R1 (t1)  R2 (t2)}
(a1,…, an) (R1) ≡ {(t.a1, …, t.an) | R1 (t)}
P(t) (R1) ≡ {(t) | R1 (t)  P(t)}

70
SGBD relationnels – Tome 1, État de l’art

R1P(t1, t2) R2 ≡ {(t1, t2) | R1 (t1)  R2 (t2)  P(t1, t2)}


R1 ÷ R2 ≡ {(a) | b (R2 (b)  R1 (a, b))}
≡ {(a) | (b (R2 (b)  R1 (a, b)))}.

Toute requête qui peut se formuler à l’aide d’un langage algébrique


relationnel peut aussi se formuler en utilisant un langage prédicatif
relationnel. Inversement, toute requête qui peut se formuler à l’aide d’un
langage prédicatif relationnel, en respectant les équivalences qui précèdent,
peut aussi se formuler à l’aide du langage algébrique relationnel.

2.4.4. L’utilité des langages prédicatifs relationnels

Les langages prédicatifs relationnels constituent une alternative au langage


algébrique relationnel.

Le langage algébrique relationnel oblige l’utilisateur à définir ses requêtes


à l‘aide de calculs algébriques relationnels abstraits qui indiquent comment
construire le résultat recherché, en utilisant des opérateurs qui font
abstraction de la manière dont les données sont organisées et stockées sur
les supports physiques et des algorithmes utilisés.

Quant aux langages prédicatifs relationnels, ils permettent à l’utilisateur de


formuler des requêtes purement déclaratives, non procédurales et proches
des formulations informelles. Dans ces requêtes, l’utilisateur se contente de
dire, dans un formalisme précis et non ambigu, ce qu’il veut comme
résultat, sans dire comment calculer ce résultat. Ces requêtes peuvent
ensuite être transformées par le SGBD en séquences d’opérations
algébriques qui explicitent ce qui doit être fait pour construire le résultat
souhaité.

Pour ces raisons, on peut être tenté de dire que les langages prédicatifs
relationnels sont de bons candidats pour la formulation des requêtes par les
humains et les langages algébriques relationnels de bons candidats pour la
dérivation par les SGBD d’un calcul abstrait pour la construction du résultat
recherché. Comme présenté de façon plus détaillée dans le chapitre 4, ce
sont ces deux types de langages qui ont fourni en grande partie les bases
conceptuelles qui ont servi à la définition de SQL, le langage de requête des

71
Joachim TANKOANO

SGBD relationnels, qui est de ce fait un langage mi-ensembliste, mi-


prédicatif.

Les bases conceptuelles de SQL qui reposent sur les langages prédicatifs
relationnels ont la particularité qu’elles permettent son enrichissement
continuel à l’aide de nouvelles fonctions de composition et de nouveaux
prédicats.

Au-delà de cet apport très important, les langages prédicatifs relationnels,


les axiomes et les règles d’inférence de la théorie de la logique du 1 er ordre
ont permis d’ajouter aux bases de données relationnelles une dose
d’intelligence artificielle, afin de les transformer en base de données
déductives et en systèmes intelligents d’aide à la décision.

2.4.5. Exemples de formulation de requêtes prédicatives


relationnelles

Ce paragraphe propose, à titre d’exemples, une traduction des énoncés


informels du paragraphe 2.3.3 en énoncés formels, exprimés à l’aide des
langages prédicatifs relationnels présentés dans le paragraphe 2.4.2.
Comme dans le paragraphe 2.3.3, ces énoncés concernent le schéma logique
relationnel ci-après :
Avions (NoAvion, NomAvion, CapAvion, LocAvion)
Pilotes (NoPilote, NomPilote, AdrPilote)
Vols (NoVol, #NoPilote, #NoAvion, VD, VA)

La traduction d’un énoncé informel en énoncé formel, nécessite dans un


premier temps, d’identifier les objets sur lesquels l’énoncé informel est
formulé et de déterminer comment désigner ces objets dans le langage cible
de la logique du 1er ordre, en tenant compte du schéma logique qui
modélise la base de données. Ce travail préalable doit mettre en évidence
le résultat recherché, les données à utiliser et comment désigner ces
éléments dans un énoncé formel. Ceci doit permettre, dans une deuxième
étape, de reformuler l’énoncé initial, de façon informelle, en utilisant cette
fois-ci des termes proches du langage cible de la logique du 1er ordre. La
nouvelle formulation de l’énoncé informel doit permettre, dans une
troisième étape, de formuler le prédicat qui en découle à l’aide du

72
SGBD relationnels – Tome 1, État de l’art

formalisme du langage cible de la logique du 1er ordre.

Pour décrire dans ce qui suit ce processus de façon plus détaillée, nous nous
appuyons sur l’énoncé informel suivant : déduire de la base de données
« tous les numéros de vols au départ de Bobo-Dioulasso ».

Tout comme pour les langages algébriques relationnels, la formulation d’un


énoncé formel de la logique du 1er ordre nécessite d’identifier au préalable
les relations qui contiennent les informations recherchées et qui sont de ce
fait nécessaires pour cette formulation. Pour ce qui concerne notre exemple,
en examinant l’énoncé informel, il est évident qu’on n’a besoin que de la
relation « Vols ».

Lorsque le langage cible est le langage prédicatif en calcul relationnel à


variables n-uplets, les variables qui doivent être utilisées dans la
formulation de l’énoncé formel sont les suivantes :
• Une variable n-uplet pour chacune des relations identifiées comme
contenant toute ou partie des informations recherchées (par
exemple, pour ce qui concerne notre exemple, la variable n-uplet
« v » a été retenue pour la relation « Vols »)
• Les variables qui servent à désigner les valeurs citées explicitement
ou implicitement dans l’énoncé informel comme devant figurer dans
le résultat ou comme devant déterminer le résultat ou comme devant
être indépendant du résultat (pour ce qui concerne notre exemple, il
s’agit de la variable « v.NoVol » qui sert à désigner les valeurs
correspondant à des numéros de vols devant figurer dans le résultat
et de la variable « v.VD » qui sert à désigner la valeur correspondant
à la ville de départ d’un vol devant déterminer le résultat).

Au regard de cette analyse, le prédicat qu’on peut déduire de notre exemple


d’énoncé informel peut être formulé dans le langage prédicatif en calcul
relationnel à variables n-uplets, comme on l’a déjà vu dans la section 2.4.2,
de la façon suivante :
{(v.NoVol) | Vols (v)  (v.VD ='Bobo-Dioulasso')}.

Cet énoncé est la traduction de l’énoncé informel initial, reformulé en ces


termes, après analyse, en tenant compte du schéma logique et du langage

73
Joachim TANKOANO

cible : déduire à partir des assertions de la relation « Vols » tous les tuples
« v » de « Vols » dont la valeur de l’attribut « NoVol » vérifie le prédicat
« le vol NoVol effectue un trajet allant de Bobo-Dioulasso ». Ceci peut aussi
se dire « rechercher tous les numéros de vols au départ de Bobo-
Dioulasso ».

En revanche, lorsque le langage utilisé est un langage prédicatif en calcul


relationnel à variables domaines, pour chaque attribut d’une relation
identifiée comme contenant toute ou partie des informations recherchées,
on doit prévoir l’utilisation d’une variable domaine pour la formulation de
l’énoncé formel. L’énoncé informel doit permettre de distinguer parmi ces
variables, celles qui servent à désigner les valeurs qui doivent figurer dans
le résultat, celles qui servent à désigner les valeurs qui déterminent le
résultat et celles qui servent à désigner les valeurs dont ne dépend pas le
résultat. Par exemple, pour ce qui concerne notre exemple d’énoncé
informel pour lequel on n’a besoin que de la relation « Vols », les variables
à utiliser pour la formalisation de l’énoncé formel sont : « NoVol »,
« NoPilote », « NoAvion », « VD » et « VA ». Parmi ces variables, la
variable « NoVol » sert à désigner les valeurs qui doivent figurer dans le
résultat, la variable « VD » à désigner les valeurs qui déterminent le résultat
et les variables « NoPilote », « NoAvion » et « VA » à désigner les valeurs
dont ne dépend pas le résultat.

Au regard de cette analyse, l’énoncé formel qu’on peut déduire de notre


exemple d’énoncé informel peut être formulé dans le langage prédicatif en
calcul relationnel à variables domaines, de la façon suivante :
{(NoVol)|  NoPilote, NoAvion, VA
(Vols (NoVol, NoPilote, NoAvion, VD, VA)
 (VD = 'Ouagadougou'))}.

Cet énoncé est la traduction de l’énoncé informel initial reformulé en ces


termes, après analyse, en tenant compte du schéma logique et du langage
cible : déduire à partir des assertions de la relation « Vols » tous les tuples
de « Vols » où la valeur de l’attribut « NoVol » vérifie le prédicat « le vol
NoVol effectue un trajet allant de Ouagadougou, peu importe la valeur
des attributs « NoPilote », « NoAvion » et « VA » ».

74
SGBD relationnels – Tome 1, État de l’art

Pour chacun des énoncés informels des requêtes qui suivent, nous
proposons une traduction en langage prédicatif en calcul relationnel à
variables n-uplets et une autre en langage prédicatif en calcul relationnel à
variables domaines.

Exemple 2.4.5.i : Quels sont les numéros des pilotes en service et les villes d’arrivée
de leurs vols ?

Pour cette requête, sa formulation dans le langage prédicatif en calcul


relationnel à variables n-uplets requiert la variable n-uplet « v » pour
désigner un tuple de la relation « Vols », la variable « v.Nopilote » pour
désigner la valeur de l’attribut « NoPilote » de ce tuple et la variable
« v.VA » pour désigner la valeur de l’attribut « VA » de ce tuple. La requête
peut être reformulée comme suit pour dire que chaque tuple du résultat doit
être constitué des valeurs des attributs « NoPilote » et « VA » d’un tuple de
« Vols » :
{(v.NoPilote, v.VA) | Vols (v)}.

Les variables requises pour la formulation de cette requête dans le langage


prédicatif en calcul relationnel à variables domaines sont : « NoVol »,
« NoPilote », « NoAvion », « VD » et « VA ». Parmi ces variables
domaines, « NoVol » et « VD » sont à la fois les variables libres et les
variables dont les valeurs doivent apparaître dans le résultat. La
formulation qui suit indique que chaque tuple du résultat doit être constitué des
valeurs des attributs « NoPilote » et « VA » d’un tuple de « Vols », peu importe
la valeur des attributs « NoPilote », « NoAvion » et « VD » de ce tuple :
{(NoPilote, VA) |  NoVol, NoAvion, VD
Vols (NoVol, NoPilote, NoAvion, VD, VA)}.

Exemple 2.4.5.ii : Pour chaque vol au départ de « Ouagadougou », quels sont le


numéro du vol et le numéro du pilote qui assure ce vol ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être la valeur
des attributs « NoVol » et « NoPilote » d’un tuple de « Vols » où l’attribut
« VD » a comme valeur « Ouagadougou » :

75
Joachim TANKOANO

{(v.NoVol, v.NoPilote) | Vols (v)  (v.VD ='Ouagadougou')}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaine que chaque tuple du résultat doit être la valeur
des attributs « NoVol » et « NoPilote » d’un tuple de « Vols » où l’attribut
« VD » a comme valeur « Ouagadougou », peu importe la valeur des attributs
« NoAvion » et « VD » de ce tuple :
{(NoVol, NoPilote)| NoAvion, VA
Vols (NoVol, NoPilote, NoAvion, 'Ouagadougou', VA)}

Exemple 2.4.5.iii : Quels sont les noms des pilotes, autres que « Amadou », qui
habitent « Bobo-Dioulasso » ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être une valeur
(différente de « Amadou ») de l’attribut « NomPilote » d’un tuple de « Pilotes »
où l’attribut « AdrPilote» a comme valeur « Bobo-Dioulasso » :
{(p.NomPilote) | Pilotes (p)  (p.NomPilote  'Amadou')
 (p.AdrPilote = 'Bobo-Dioulasso')}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être une
valeur (différente de « Amadou ») de l’attribut « NomPilote » d’un tuple de
« Pilotes » où l’attribut « AdrPilote» a comme valeur « Bobo-Dioulasso », peu
importe la valeur de l’attribut « NoPilote » de ce tuple :
{(NomPilote) |  NoPilote
Pilotes (NoPilote, NomPilote, 'Bobo-Dioulasso')
 (NomPilote  'Amadou')}

Exemple 2.4.5.iv : Quels sont les numéros des pilotes, qui conduisent l’avion
numéro « 104 » et l’avion numéro « 106 » ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être une valeur
de l’attribut « NoPilote » dans au moins deux tuples de « Vols » où l’attribut
« NoAvion » a comme valeur 104 dans l’un et 106 dans l’autre :

76
SGBD relationnels – Tome 1, État de l’art

{(v1.NoPilote) | Vols (v1)  (v1.NoAvion = 104)


 ( v2 (Vols (v2)  (v2.NoAvion = 106)))
 (v1.NoPilote = v2.NoPilote)}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être une
valeur de l’attribut « NoPilote » dans au moins deux tuples de « Vols » où
l’attribut « NoAvion » a comme valeur 104 dans l’un et 106 dans l’autre, peu
importe la valeur des attributs « NoVol », « VD » et « VA » de ces deux tuples :
{(NoPilote) | ( NoVol1, VD1, VA1 Vols (NoVol1, NoPilote, 104, VD1, VA1))
 ( NoVol2, VD2, VA2
Vols (NoVol2, NoPilote, 106, VD2, VA2))}

Exemple 2.4.5.v : Pour chaque pilote en service, quels sont les numéros des avions
conduits, le numéro et le nom du pilote ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être constitué
d’une part, d’une valeur de l’attribut « NoAvion » d’un tuple de « Vols » et
d’autre part, des valeurs des attributs « NoPilote » et « NomPilote » d’un tuple
de « Pilotes ». La valeur de l’attribut « NoPilote » du tuple de « Pilotes » doit
être la même que celle de l’attribut de même nom dans le tuple de « Vols » (en
d’autres termes, le tuple de « Vols » doit référencer le tuple de « Pilotes ») :
{(p.NoPilote, p.NomPilote, v.NoAvion) | Vols (v)  Pilotes (p)
 (p.NoPilote = v.NoPilote)}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être
constitué d’une part, d’une valeur de l’attribut « NoAvion » d’un tuple de
« Vols » et d’autre part, des valeurs des attributs « NoPilote » et « NomPilote »
d’un tuple de « Pilotes ». La valeur de l’attribut « NoPilote » du tuple de
« Pilotes » doit être la même que celle de l’attribut de même nom dans le tuple de
« Vols », peu importe la valeur des attributs « NoVol », « VD » et « VA » du
tuple de « Vols » et de l’attribut « AdrPilote » du tuple de « Pilotes » (en
d’autres termes, le tuple de « Vols » doit référencer le tuple de « Pilotes ») :

77
Joachim TANKOANO

{(NoPilote, NomPilote, NoAvion) |  NoVol, VD, VA, AdrPilote


(Vols (NoVol, NoPilote, NoAvion, VD, VA)
 Pilotes (NoPilote, NomPilote, AdrPilote))}

Exemple 2.4.5.vi : Quels sont les noms des pilotes qui conduisent un vol au départ
de « Ouagadougou » ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être constitué
d’une valeur de l’attribut « NomPilote » d’un tuple de « Pilotes » pour lequel il
existe un tuple de « Vols » dont la valeur de l’attribut « NoPilote » est la même
que celle de l’attribut de même nom dans le tuple de « Pilotes » et dont la valeur
de l’attribut « VD » est « Ouagadougou » :
{(p.NomPilote) | Pilotes (p)   v (Vols (v)  ( v.NoPilote = p.NoPilote)
 (v.VD = 'Ouagadougou'))}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être
constitué d’une valeur de l’attribut « NomPilote » d’un tuple de « Pilotes » pour
lequel un tuple de « Vols » a comme valeur de l’attribut « NoPilote » la même que
celle de l’attribut de même nom dans le tuple de « Pilotes » et comme valeur de
l’attribut « VD » « Ouagadougou », peu importe la valeur de l’attribut
« AdrPilote » du tuple de « Pilotes » et la valeur des attributs « NoVol »,
« NoAvion » et « VA » du tuple de « Vols » :
{(NomPilote) |  AdrPilote, NoVol, NoAvion, VA
(Pilotes (NoPilote, NomPilote, AdrPilote)
 Vols (NoVol, NoPilote, NoAvion, 'Ouagadougou', VA))}

Exemple 2.4.5.vii : Quels sont les noms des pilotes qui conduisent un AIRBUS ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être constitué
d’une valeur de l’attribut « NomPilote » d’un tuple de « Pilotes » pour lequel il
existe un tuple de « Vols » dont la valeur de l’attribut « NoPilote » est la même
que celle de l’attribut de même nom dans le tuple de « Pilotes » et dont la valeur
de l’attribut « NoAvion » est la même que celle de l’attribut de même nom dans

78
SGBD relationnels – Tome 1, État de l’art

un tuple de « Avions » dont la valeur de l’attribut « NomAvion » est 'AIRBUS'


(en d’autres termes, le tuple de « Vols » doit référencer le tuple de « Pilotes » et
le tuple de « Avions ») :
{(p.NomPilote) | Pilotes (p)  ( v, a (Vols (v)  Avions (a)
 (v.NoPilote = p.NoPilote)
 (v.NoAvion = a.NoAvion)
 (a.NomAvion = 'AIRBUS')))}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être
constitué d’une valeur de l’attribut « NomPilote » d’un tuple de « Pilotes » pour
lequel il existe un tuple de « Vols » dont la valeur de l’attribut « NoPilote » est la
même que celle de l’attribut de même nom dans le tuple de « Pilotes » et dont la
valeur de l’attribut « NoAvion » est la même que celle de l’attribut de même nom
dans un tuple de « Avions » dont la valeur de l’attribut « NomAvion » est
« AIRBUS », peu importe la valeur de l’attribut « AdrPilote » du tuple de
« Pilotes », la valeur des attributs « NoVol », « VD » et « VA » du tuple de
« Vols » et la valeur des attributs « LocAvion » et « CapAvion » du tuple de
« Avions » (en d’autres termes, le tuple de « Vols » doit référencer le tuple de
« Pilotes » et le tuple de « Avions ») :
{(NomPilote) |  AdrPilote, LocAvion, CapAvion, NoVol, VA, VD
(Pilotes (NoPilote, NomPilote, AdrPilote)
 Vols (NoVol, NoPilote, NoAvion, VD, VA)
 Avions (NoAvion, 'AIRBUS', LocAvion, CapAvion))}

Exemple 2.4.5.viii : Quels sont les numéros des pilotes qui conduisent tous les
avions de la compagnie ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être constitué
d’une valeur de l’attribut « NoPilote » d’un tuple de « Pilotes », pour lequel,
pour tout tuple de « Avions » il existe un tuple de « Vols » dont la valeur de
l’attribut « NoPilote » est la même que celle de l’attribut de même nom dans le
tuple de « Pilotes » et dont la valeur de l’attribut « NoAvion » est la même que
celle de l’attribut de même nom dans le tuple de « Avions » :

79
Joachim TANKOANO

{(p.NoPilote) | Pilotes (p)


  a (Avions(a)  ( v (Vols (v)
 (v.NoPilote = p.NoPilote)
 (v.NoAvion = a.NoAvion))))}.

En tenant compte des équivalences « x F1   (x  F1) » et « F1  F2  


(F1   F2)) », cette formulation peut être réécrite sans le quantificateur
«  » et sans l’implication de la façon suivante :
{(p.NoPilote) | Pilotes (p)
  ( a (Avions (a)   ( v (Vols (v)
 (v.NoPilote = p.NoPilote)
 (v.Novion = a.NoAvion))))}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être
constitué d’un numéro de pilote, pour lequel, pour tout tuple de « Avions » il existe
un tuple de « Vols » dont la valeur de l’attribut « NoPilote » est la même que ce
numéro de pilote et dont la valeur de l’attribut « NoAvion » est la même que celle
de l’attribut de même nom dans le tuple de « Avions », peu importe la valeur des
attributs « NoVol », « VD » et « VA » du tuple de « Vols » et quelle que soit la
valeur des attributs « NoAvion », « NomAvion », « LocAvion » et
« CapAvion » dans le tuple de « Avions » :
{(NoPilote) |  NoAvion, NomAvion, LocAvion, CapAvion
(Avions (NoAvion, NomAvion, LocAvion, CapAvion)
  NoVol, VD, VA
Vols (NoVol, NoPilote, NoAvion, VD, VA))}

Cette formulation peut aussi se réécrire sans le quantificateur «  » et sans


l’implication de la façon suivante :
{(NoPilote) |  ( NoAvion, NomAvion, LocAvion, CapAvion
(Avions (NoAvion, NomAvion, LocAvion, CapAvion)
  ( NoVol, VD, VA
Vols (NoVol, NoPilote, NoAvion, VD, VA)))}

Exemple 2.4.5.ix : Quels sont les numéros des pilotes qui conduisent au moins
tous les AIRBUS de la compagnie ?

80
SGBD relationnels – Tome 1, État de l’art

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être constitué
d’une valeur de l’attribut « NoPilote » d’un tuple de « Pilotes » pour lequel pour
tout tuple de « Avions » dont la valeur de l’attribut « NomAvion » est
« AIRBUS », il existe un tuple de « Vols » dont la valeur de l’attribut
« NoPilote » est la même que celle de l’attribut de même nom dans le tuple de
« Pilotes » et dont la valeur de l’attribut « NoAvion » est la même que celle de
l’attribut de même nom dans le tuple de « Avions » :
{(p.NoPilote) | Pilotes (p)
  a ((Avions(a)  (a.NomAvion = 'AIRBUS'))
  v (Vols (v)
 (v.NoPilote = p.NoPilote)
 (v.NoAvion = a.NoAvion)))}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être
constitué d’un numéro de pilote pour lequel pour tout tuple de « Avions » dont la
valeur de l’attribut « NomAvion » est « AIRBUS », il existe un tuple de « Vols »
dont la valeur de l’attribut « NoPilote » est la même que ce numéro de pilote et
dont la valeur de l’attribut « NoAvion » est la même que celle de l’attribut de même
nom dans le tuple de « Avions », peu importe la valeur des attributs « NoVol »,
« VD » et « VA » du tuple de « Vols » et quel que soit la valeur des attributs
« NoAvion », « LocAvion » et « CapAvion » dans le tuple de « Avions » :
{(NoPilote) |  NoAvion, LocAvion, CapAvion
(Avions (NoAvion, 'AIRBUS', LocAvion, CapAvion)
  NoVol, VD, VA
Vols (NoVol, NoPilote, NoAvion, VD, VA))

Exemple 2.4.5.x : Quels sont les numéros d’avions qui sont supérieurs à tous les
numéros des avions conduits par le pilote n° 2 ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être une valeur
de l’attribut « NoAvion » d’un tuple de « Avions » pour lequel, quel que soit le
tuple de « Vols » dont la valeur de l’attribut « NoPilote » est « 2 », cette valeur
de l’attribut « NoAvion » doit être supérieure à l’attribut de même nom dans ce

81
Joachim TANKOANO

tuple de « Vols ».
{(a.NoAvion) |Avions (a)   v (Vols(v)  v.Nopilote = 2)
 a.NoAvion  v.NoAvion}

En tenant compte de l’équivalence « x F   (x  F) » et de l’équivalence


« F1  F2   (F1   F2) », on peut réécrire cette formulation sans le
quantificateur «  » de la façon suivante :
{(a.NoAvion) |Avions (a)   ( v (Vols(v)  v.Nopilote = 2
 a.NoAvion  v.NoAvion))

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être une
valeur de l’attribut « NoAvion » d’un tuple de « Avions » pour lequel, quel que
soit le tuple de « Vols » dont la valeur de l’attribut « NoPilote » est « 2 », cette
valeur de l’attribut « NoAvion » du tuple de « Avions » doit être supérieure à
l’attribut de même nom dans ce tuple de « Vols », peu importe la valeur des
attributs « NomAvion », « LocAvion » et « CapAvion » dans le tuple de
« Avions».
{(NoAvion) | NomAvion, CapAvion, LocAvion
Avions (NoAvion, NomAvion, CapAvion, LocAvion)
  NoVol, NoAvion2, VD, VA
(Vols (NoVol, 2, NoAvion2, VD, VA)
 a.NoAvion  v.NoAvion)}

Exemple 2.4.5.xi : Quels sont les noms des pilotes qui n’effectuent pas de vol au
départ de « Ouagadougou » ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être une valeur
de l’attribut « NomPilote » d’un tuple de « Pilotes » pour lequel il n’existe pas
de tuple de « Vols » dont la valeur de l’attribut « NoPilote » est la même que celle
de l’attribut de même nom dans le tuple de « Pilotes » et dont la valeur de l’attribut
« VD » est « Ouagadougou » :

82
SGBD relationnels – Tome 1, État de l’art

{(p.NomPilote) | Pilotes (p)


  ( v (Vols (v)
 (v.NoPilote = p.NoPilote)
 (v.VD = 'Ouagadougou')))}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être une
valeur de l’attribut « NomPilote » d’un tuple de « Pilotes » pour lequel il n’existe
pas de tuple de « Vols » dont la valeur de l’attribut « NoPilote » est la même que
celle de l’attribut de même nom dans le tuple de « Pilotes » et dont la valeur de
l’attribut « VD » est « Ouagadougou », peu importe la valeur de l’attribut
« AdrPilote » du tuple de « Pilotes » et la valeur des attributs « NoVol »,
« NoAvion » et « VA » du tuple de « Vols » :
{(NomPilote) |  AdrPilote
(Pilotes (NoPilote, NomPilote, AdrPilote)
  ( NoVol, NoAvion, VA
Vols (NoVol, Nopilote, NoAvion,
'Ouagadougou', VA))}

Exemple 2.4.5.xii : Quels sont les numéros des pilotes qui conduisent un avion
conduit par le pilote n° 32 ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être une valeur
différente de 32 de l’attribut « NoPilote » d’un tuple de « Vols » pour lequel il
existe un autre tuple de « Vols » dont la valeur de l’attribut « NoPilote » est 32
et dont la valeur de l’attribut « NoAvion » est la même que celle de l’attribut de
même nom dans le 1er tuple de « Vols » :
{(v1.NoPilote) | Vols (v1)  (v1.NoPilote  32)
 ( v2 (Vols (v2)  (v2.NoPilote = 32)
 (v2.NoAvion = v1.NoAvion))}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être une
valeur différente de 32 de l’attribut « NoPilote » d’un tuple de « Vols » pour
lequel il existe un autre tuple de « Vols » dont la valeur de l’attribut « NoPilote »
est 32 et dont la valeur de l’attribut « NoAvion » est la même que celle de l’attribut

83
Joachim TANKOANO

de même nom dans le 1er tuple de « Vols », peu importe la valeur des attributs
« NoVol », « VD » et « VA » des deux tuples de « Vols » :
{(NoPilote) |  NoVol1, VD1, VA1
(Vols (NoVol1, NoPilote, NoAvion1, VD1, VA1))
 (NoPilote  32))
  NoVol2, VD2, VA2
(Vols (NoVol2, NoPilote, NoAvion2, VD2, VA2)
 (NoPilote = 32)
 (NoAvion2 = NoAvion1))}

Exemple 2.4.5.xiii : Quelles sont les villes desservies par les pilotes dont le numéro
est plus grand que celui de Pierre et Paul ?

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables n-uplets que chaque tuple du résultat doit être une valeur
de l’attribut « VA » d’un tuple de « Vols » pour lequel il existe d’une part, un tuple
de « Pilotes » dont la valeur de l’attribut « NomPilote » est « Pierre » et dont la
valeur de l’attribut « NoPilote » est plus petite que celle de l’attribut de même
nom dans le tuple de « Vols » et d’autre part, un tuple de « Pilotes » dont la
valeur de l’attribut « NomPilote » est « Paul » et dont la valeur de l’attribut
« NoPilote » est aussi plus petite que celle de l’attribut de même nom dans le tuple
de « Vols » :
{(v.VA) |Vols (v)
  p1 (Pilotes (p1)  (p1.NomPilote = 'Pierre')
 (v.NoPilote  p1.NoPilote))
  p2 (Pilotes (p2)  (p2.NomPilote = 'Paul')
 (v.NoPilote  p2.NoPilote))}.

La formulation qui suit indique dans le langage prédicatif en calcul


relationnel à variables domaines que chaque tuple du résultat doit être une
valeur de l’attribut « VA » d’un tuple de « Vols » pour lequel il existe d’une part,
un tuple de « Pilotes » dont la valeur de l’attribut « NomPilote » est « Pierre »
et dont la valeur de l’attribut « NoPilote » est plus petite que celle de l’attribut de
même nom dans le tuple de « Vols » et d’autre part, un tuple de « Pilotes » dont
la valeur de l’attribut « NomPilote » est « Paul » et dont la valeur de l’attribut
« NoPilote » est aussi plus petite que celle de l’attribut de même nom dans le tuple

84
SGBD relationnels – Tome 1, État de l’art

de « Vols », peu importe la valeur des attributs « NoVol », « NoAvion » et


« VD » du tuple de « Vols » et la valeur de l’attribut « AdrPilote » des deux
tuples de « Pilotes » :
{(VA) |  NoVol, NoAvion, VD, AdrPilote1, AdrPilote2,
(Vols (NoVol, NoPilote, NoAvion, VD, VA)
 Pilotes (NoPilote1, 'Pierre', adrPilote1)
 (Nopilote  NoPilote1)
 Pilotes (NoPilote2, 'Paul', adrPilote2)  (Nopilote  NoPilote2))}.

2.4.6. Exercice

Soit le schéma relationnel de l’exercice de la section 2.3.4, défini de la façon


suivante :
Salles (nom, horaire, titre)
A_Produit (producteur, titre)
Joue_Dans (acteur, titre, producteur)
A_Vu (spectateur, titre)
Aime (spectateur, titre)

Pour chacune des requêtes informelles ci-après, traitées dans cet exercice
précédent, proposez une formulation en langage prédicatif en calcul
relationnel à variables n-uplets et une autre en langage prédicatif en calcul
relationnel à variables domaines :
1) Où peut-on voir un film où joue « Idrissa OUEDRAOGO » ?
2) Quels sont les acteurs qui jouent dans tous les films produits par
« Idrissa OUEDRAOGO » ?
3) Quels sont les spectateurs qui aiment un film qu’ils n’ont pas vus ?
4) Quels sont les spectateurs qui aiment tous les films qu’ils ont vus ?
5) Quels sont les producteurs qui n’ont vu que les films qu’ils ont
produits ?

85
Joachim TANKOANO

BIBLIOGRAPHIE
Codd E. F.: A relational model of data for large shared data banks - CACM 13,
No 6 juin 1970

Codd E. F.: Relational Completness of Data Base Sublanguages - Data Base


Systems, Courant Computer Science Symposia Series, n° 6, Prentice-
Hall, 1972

Codd E. F.: Data models in database management - Pro. Workshop on Data


Abstraction, Databases and Conceptual modelling, Pingree Park,
Colorado, juin 1980

Codd E. F.: The relational model for database management - Second Edition,
Addison-Wesley Publishing Company, Inc., 1990

Date C.J. : Introduction aux bases de données - 8è édition, Vuibert, Paris, 2004

Maier D.: The Theory of Relational Databases, 1st edition - Computer Science
Press, (March 1983)

Minker J.: Logic and Databases, Past, Present, and Future - AI Magazine
Volume 18 Number 3 (1997) p 21 à 48

Miranda S. & Busta J-M. : L'art des bases de données, Tome 2, Les bases de
données relationnelles - 2è édition, Eyrolles, 1990

Silberschatz A., Korth H.F. & Sudarshan S.: Database system concepts, sixth
edition - McGraw-Hill, 2011

Sumathi S. & Esakkirajan S.: Fundamentals of Relational Database


Management Systems – Springer. Studies in Computational
Intelligence, Volume 47. 2007 édition

86
Chapitre 3. : Conception des schémas logiques
relationnels (dépendances et normalisation)

3.1. Introduction
Nous avons vu dans le paragraphe 1.2.2 que pour pouvoir servir de support
pour le stockage des données d’une entreprise, la technologie des bases de
données doit fournir des garanties parmi lesquelles celles relatives aux
exigences relatives à la non-redondance et à l’intégrité des données figurent
en très bonne place.

Le simple fait de définir et de manipuler le contenu d’une base de données


relationnelle, comme nous l’avons fait dans le chapitre 2, en termes de
relations qui respectent les définitions de ce chapitre, ne suffit pas en soi
pour répondre à cette exigence. Pour y répondre, il faut qu’elle ait été au
centre des objectifs poursuivis lors de la conception de son schéma logique
relationnel, à savoir :
• Éviter toute redondance dans les relations
• Faire en sorte que les contraintes d’intégrité auxquelles les relations
définies sont soumises (intégrité de relation, intégrité d’unicité et
intégrité référentielle) soient suffisantes pour garantir le respect des
contraintes d’intégrité relatives aux dépendances entre attributs,
découlant des règles de gestion.

Ce chapitre est consacré à l’étude des fondements théoriques, des outils et


des démarches qui font qu’il est possible de concevoir le schéma logique
relationnel d’une base de données en se focalisant sur l’atteinte de ces deux
objectifs. Il aborde successivement :
• Les insuffisances qui caractérisent les relations mal conçues
• Le principe général de la normalisation qui est la technique utilisée
pour corriger ces insuffisances
• Les principaux outils de cette technique, à savoir les différentes
formes de dépendances qui peuvent exister entre les données
Joachim TANKOANO

élémentaires du système d’information


• Les différentes formes normales utilisées pour classifier les relations
par rapport à ces insuffisances
• Les approches de conception d’un bon schéma logique relationnel,
permettant au SGBD d’offrir les garanties de non-redondance et
d’intégrité des données.

3.2. Pourquoi normaliser une relation ?

3.2.1. Les anomalies possibles dans une extension légale de


relation

Considérons la relation suivante :


Avion (NoAvion, NomAvion, CapAvion, LocAvion).

Supposons que le prédicat qui définit cette relation s’énonce comme suit :
« L’avion NoAvion est un NomAvion qui possède CapAvion places et qui
est localisé à LocAvion ».

Si on suppose que cette relation est contrainte par la dépendance


fonctionnelle « NomAvion → CapAvion », on peut observer dans
l’extension légale ci-après, 5 types d’anomalies présentées ci-dessous :

NoAvion NomAvion CapAvion LocAvion


10 AIRBUS A310 200 Bobo-Dioulasso
20 AIRBUS A310 200 Ouagadougou
30 AIRBUS A340 400 Ouagadougou
40 BOING 707 150 Bobo-Dioulasso
50 BOING 707 150 Bobo-Dioulasso

a) La redondance logique

Dans cette extension, le couple de valeurs « ('AIRBUS A310', 200) » se


répète deux fois, c'est-à-dire, qu’on répète deux fois la même information,
à savoir qu’un « AIRBUS A310 est de 200 places ». Ceci dont la
conséquence est une mauvaise utilisation de la mémoire de stockage de la

88
SGBD relationnels – Tome 1, État de l’art

relation « Avion (NoAvion, NomAvion, CapAvion, LocAvion) » est une


anomalie liée à une mauvaise conception de cette relation.

b) L’anomalie d’insertion

Si la compagnie achète un BOING 747 de 600 places, elle ne pourra insérer


dans cette extension l’information qui dit que les BOING 747 de la
compagnie ont une capacité de 600 places que lorsqu’un numéro aura été
attribué à ce nouvel avion et qu’un lieu d’affectation le concernant aura été
décidé. Cette impossibilité d’ajouter cette information dans la relation après
l’achat de l’avion et avant l’immatriculation et l’affectation de l’avion est
aussi une anomalie liée à une mauvaise conception de la relation « Avion
(NoAvion, NomAvion, CapAvion, LocAvion) ».

c) L’anomalie de suppression

Une suppression de l’avion n°30 de cette extension supprimerait en même


temps l’information qui indique que les AIRBUS A340 de la compagnie ont
une capacité de 400 places. Cette possibilité de perdre l’information sur la
capacité des avions d’un type donné, lors de la suppression d’un avion, est
aussi une anomalie liée à une mauvaise conception de la relation « Avion
(NoAvion, NomAvion, CapAvion, LocAvion) ».

d) L‘anomalie de modification

Si la capacité des avions AIRBUS A310 passe de 200 à 175 places à la suite
d’une transformation des appareils, la prise en compte de cette
transformation dans cette extension entraînera :
• Soit une incohérence, si on se contente de la répercuter sur un seul
des tuples de l’extension
• Soit un coût élevé de mise à jour, si cette mise à jour doit s’effectuer
sur tous les tuples qui concernent un avion AIRBUS A310.

Cette possibilité, de complexification de la mise à jour de la capacité des


avions, est aussi une anomalie liée à une mauvaise conception de la relation
« Avion (NoAvion, NomAvion, CapAvion, LocAvion) ».

89
Joachim TANKOANO

e) Le problème de la reconnexion

La décomposition d’une relation en deux relations par projection peut ne


pas préserver les informations et les dépendances fonctionnelles qui y
existent. Cette décomposition peut entraîner une perte d’information (donc
ne pas être réversible) ou une perte de dépendance fonctionnelle. C’est le
cas notamment de la décomposition par projection ci-après :

Avions1= (NoAvion, NomAvion) (Avions)

NoAvion NomAvion

10 AIRBUS A310
20 AIRBUS A310
30 AIRBUS A340
40 BOING 707
50 BOING 707

Avions2= (NomAvion, CapAvion, LocAvion) (Avions)

NomAvion CapAvion LocAvion

AIRBUS A310 200 Bobo-Dioulasso


AIRBUS A310 200 Ouagadougou
AIRBUS A410 400 Ouagadougou
BOING 707 150 Bobo-Dioulasso

La jointure naturelle des extensions des deux relations issues de cette


décomposition produit dans le résultat la ligne « (10, 'AIRBUS A310', 200,
'Ouagadougou') » qui n’existe pas dans la relation initiale, ce qui montre
bien que cette décomposition ne préserve pas les informations.

3.2.2. Ce que permet la normalisation d’une relation

La normalisation est le processus qui permet de décomposer par projection


une relation en plusieurs relations, en préservant les informations et les
dépendances, dans le but d’éviter :
• La redondance
• Les anomalies de stockage, c'est-à-dire les anomalies d’insertion, de
suppression et de modification présentées dans le paragraphe 3.2.1.

90
SGBD relationnels – Tome 1, État de l’art

La normalisation vise donc la conception de bons schémas logiques


relationnels pour la mise en mémoire des données du système
d’information d’une entreprise. Il s’agit d’une étape clé dans le processus
de développement d’une application.

Exemple 3.2.2.i : Telle qu’effectuée dans cet exemple, la décomposition de


la relation « Avion (NoAvion, NomAvion, CapAvion, LocAvion) »
(définie précédemment dans le paragraphe 3.2.1) est un exemple de
décomposition par projection qui préserve les informations et les
dépendances. Dans cet exemple, les relations issues de la décomposition
n’ont aucune redondance et ne provoque aucune anomalie de stockage.

Il est aisé de vérifier que l’extension de « Avions » et la jointure naturelle


des extensions de « Avions1 » et de « Avions2 », issues de la décomposition
de l’extension de « Avions », sont identiques, ce qui montre bien que les
données de la relation initiale ont été préservées par cette décomposition.

La dépendance fonctionnelle « NomAvion → CapAvion » a aussi été


préservée dans la relation « Avions1 ».

Cette décomposition fait qu’il devient possible d’insérer les informations


relatives au nouveau BOING 747 de 600 places avant son immatriculation
et son affectation, de supprimer l’avion n°30 dans la relation « Avions2 »
sans perdre l’information qui indique que les AIRBUS A340 de la
compagnie ont une capacité de 400 places et de prendre en compte le fait
que les avions AIRBUS A310 de la compagnie ont été transformés en avions
de 175 places en effectuant une seule mise à jour.

Avion (NoAvion, NomAvion, CapAvion, LocAvion)

NoAvion NomAvion CapAvion LocAvion


10 AIRBUS A310 200 Bobo-Dioulasso
20 AIRBUS A310 200 Ouagadougou
30 AIRBUS A340 400 Ouagadougou
40 BOING 707 150 Bobo-Dioulasso
50 BOING 707 150 Bobo-Dioulasso

91
Joachim TANKOANO

Avions1= (NomAvion, CapAvion) (Avions)

NomAvion CapAvion

AIRBUS A310 200


AIRBUS A340 400
BOING 707 150

Avions2= (NoAvion, NomAvion, LocAvion) (Avions)

NoAvion NomAvion LocAvion

10 AIRBUS A310 Bobo-Dioulasso


20 AIRBUS A310 Ouagadougou
30 AIRBUS A410 Ouagadougou
40 BOING 707 Bobo-Dioulasso
50 BOING 707 Bobo-Dioulasso

On appelle décomposition atomique d’une relation « R », la décomposition


réversible de « R », par un algorithme approprié, en un ensemble de
relations irréductibles, c'est-à-dire, de relations qu’on ne peut pas
décomposer en préservant les informations.

Dans un système d’information, ces relations irréductibles constituent les


primitives sémantiques, à l’aide desquelles le bon schéma relationnel peut
être construit par composition dans le but de préserver les dépendances et
de réduire le coût des traitements effectués par les applications en réduisant
le nombre de relations.

3.3. Les outils de la normalisation : les différentes


formes de dépendances
Tout système d’information est caractérisé par un ensemble de
dépendances entre ses attributs ou groupes d’attributs. Comme nous
l’avons déjà vu, ces dépendances qui découlent des règles de gestion
peuvent être vues comme étant les propriétés sémantiques de ce système
d’information. Pour que les données d’un système d’information contenues
dans une base de données soient valides, c'est-à-dire, en cohérence avec les
règles de gestion, ces dépendances doivent être maintenues en permanence

92
SGBD relationnels – Tome 1, État de l’art

à l’intérieur de cette base de données. En d’autres termes, ces dépendances


induisent sur cette base de données des contraintes d’intégrité.

L’identification des attributs (ou données élémentaires) et des dépendances


qui existent entre ces attributs ou groupes d’attributs est de ce fait une
activité clé dans le processus de conception d’une base de données.

L’identification des dépendances qui existent entre attributs ou groupe


d’attributs d’un système d’information est essentielle pour trois raisons
principales : (1) ces dépendances permettent de déterminer à priori les
types de redondances logiques qui peuvent se retrouver dans les extensions
de relations où sont réparties les données de ce système d’information ainsi
que les anomalies de stockage qui leur sont associées, 2) elles permettent
par ailleurs l’identification des situations pouvant conduire à une
décomposition de ces relations dans le but d’éviter ces redondances, (3)
elles déterminent les contraintes d’intégrité auxquelles ces relations doivent
être soumises pour que la base de données soit valide, c'est-à-dire, en
cohérence avec les règles de gestion.

Ces dépendances peuvent être de différentes formes que nous présentons


dans cette section, à savoir : des dépendances fonctionnelles (DF), des
dépendances multivaluées (DM) ou des dépendances de jointure (DJ).

3.3.1. Les dépendances fonctionnelles (DF)

Dans un système d’information, les dépendances fonctionnelles constituent


la forme de dépendance entre attributs ou groupes d’attributs la plus
simple et la plus courante. Ce paragraphe examine de façon détaillée cette
forme de dépendance.

a) Définitions

Commençons par rappeler quelques définitions.

Soient :
• « R (A) » une relation où « A » désigne l’ensemble de ses attributs
• « X1, X2, X3 » une partition de « A »

93
Joachim TANKOANO

• « a » un attribut de « A ».

Dépendance fonctionnelle (DF)

« X1 » détermine « X2 » (ce qui se note « X1 → X2 ») si quels que soient deux


tuples « t1 » et « t2 » de toute extension légale de « R (A) », t1.X1 = t2.X1 
t1.X2 = t2.X2. On dit que chaque valeur de « X1 » n’est associée qu’à une et
une seule valeur de « X2 » et que « X1 » est le déterminant et « X2 » le
dépendant.

Dépendance fonctionnelle triviale

« X1 → X2 » est une dépendance fonctionnelle triviale si « X2  X1 ».

Dépendance fonctionnelle partielle (ou réductible à gauche)

« X1 → X2 » est une dépendance fonctionnelle partielle si elle n’est pas


triviale et s’il existe « X1’  X1 » tel que « X1’ → X2 ».

Dépendance fonctionnelle élémentaire (ou irréductible à gauche et à droite


ou totale)

« X1 → X2 » est une dépendance fonctionnelle élémentaire s’il n’existe pas


« X1’  X1 » tel que « X1’ → X2 » et si en plus « X2 » contient un seul attribut.

Clé candidate

« X1 » est une clé candidate de « R (A) » si « X1 → X2 » et « X1 → X3 » et il


n’existe pas « X1’  X1 » tel que « X1’ → X2 » et « X1’ → X3 ».

Surclé

« X1 » est une surclé de « R » si « X1 → X2, X3 » et il existe « X1’  X1 » tel


que « X1’ → X2, X3 ».

Attribut clé

« a » est un attribut clé de « R (A) » s’il appartient à une clé candidate de


« R (A) ».

Exemple 3.3.1.i : soit l’extension ci-dessous de la relation « Vols (NoPilote,


NoAvion, VD, VA, CapAvion) ».

94
SGBD relationnels – Tome 1, État de l’art

NoPilote NoAvion VD VA CapAvion


p1 a1 Ouagadougou Koudougou 20
p1 a2 Kaya Ouagadougou 10
p1 a3 Ouagadougou Fada 15
p2 a3 Fada Ouagadougou 15
p3 a1 Koudougou Bobo-Dioulasso 20
p2 a4 Koudougou Léo 10

En considérant les définitions ci-dessus, on pourrait déduire ce qui suit :


• {NoPilote, NoAvion} → {VA}, {NoPilote, NoAvion} → {VD},
{NoAvion} → {CapAvion} sont des dépendances fonctionnelles
élémentaires ;
• {NoPilote, NoAvion} → {CapAvion} est une dépendance
fonctionnelle partielle ;
• {NoPilote, NoAvion} est une clé candidate ;
• {NoPilote, NoAvion, VD} est une surclé.

Une dépendance fonctionnelle déduite de l’extension d’une relation « R


(A) » comme nous venons de le faire n’est toutefois pas nécessairement une
dépendance fonctionnelle de cette relation.

b) Les propriétés des dépendances fonctionnelles

Lors de la conception d’un schéma logique relationnel, les propriétés des


dépendances fonctionnelles peuvent s’utiliser pour conduire des
raisonnements formels. Ce paragraphe présente ces propriétés.

Soient :
• « R (A) » une relation où « A » désigne l’ensemble de ses attributs,
• « X1, X2, X3, X4 » des sous-ensembles de « A ».

Étant donné un ensemble de dépendances fonctionnelles dans « R (A) », les


trois règles de déduction ci-après constituent les axiomes d’Armstrong qui
permettent de déduire de nouvelles dépendances fonctionnelles :

95
Joachim TANKOANO

La réflexivité

Si « X2  X1 » alors « X1 → X2 ».

L’augmentation

Si « X1 → X2 » alors « X1, X3 → X2, X3 ».

La transitivité

Si « X1 → X2 » et « X2 → X3 » alors « X1 → X3 ». (On dit que « X3 » n’est pas


directement dépendant de « X1 » ou que « X3 » dépend de « X1 » par
transitivité).

De ces trois règles, on peut inférer les autres règles de déduction ci-après :

La pseudo-transitivité

Si « X1 → X2 » et « X2, X3 → X4 » alors « X1, X3 → X4 ».

En effet :
(i) X1 → X2 étant donné
(ii) X2, X3 → X4 étant donné
(iii) X1, X3 → X2, X3 par augmentation de (i)
(iv) X1, X3 → X4 par transitivité de (iii) et (ii)

L’union

Si « X1 → X2 » et « X1 → X3 » alors « X1 → X2, X3 ».

En effet :
(i) X1 → X2 étant donné
(ii) X1 → X3 étant donné
(iii) X1 → X1, X2 par augmentation de (i)
(iv) X1, X2 → X2, X3 par augmentation de (ii)
(v) X1 → X2, X3 par transitivité de (iii) et (iv)

96
SGBD relationnels – Tome 1, État de l’art

La décomposition

Si « X1 → X2, X3 » alors « X1 → X2 » et « X1 → X3 ».

En effet :
(i) X1 → X2, X3 étant donné
(ii) X2, X3 → X2 par réflexivité
(iii) X2, X3 → X3 par réflexivité
(iv) X1 → X2 par transitivité de (i) et (ii)
(v) X1 → X3 par transitivité (1) et (iii)

La composition

Si « X1 → X2 » et « X3 → X4 » alors « X1, X3 → X2, X4 ».

En effet :
(i) X1 → X2 étant donné
(ii) X3 → X4 étant donné
(iii) X1, X3 → X2, X3 par augmentation de (i)
(iv) X2, X3 → X2, X4 par augmentation de (ii)
(v) X1, X3 → X2, X4 par transitivité de (iii) et (iv)

Exemple 3.3.1.ii : Soit la relation « Vols (NoVol, NoPilote, NoAvion, VD,


VA, CapAvion) ».

Par transitivité :
{NoVol} → {NoAvion} et {NoAvion} → {CapAvion}  {NoVol} → {CapAvion}.

c) La fermeture d’un ensemble de dépendances fonctionnelles

Soit le schéma relationnel « R(A), F » où :


• « R(A) » désigne une relation ;
• « A » désigne l’ensemble des attributs de « R(A) » ;

97
Joachim TANKOANO

• « F » désigne un ensemble de dépendances fonctionnelles que les


extensions légales de « R(A) » doivent vérifier.

On appelle fermeture de « F », l’ensemble « F+ » de toutes les dépendances


fonctionnelles que toute extension légale de « R(A) » qui vérifie « F » doit
vérifier.

« F+ » peut se déduire formellement à partir de « F » à l’aide des axiomes


d’Armstrong. On dit de ce fait que cet ensemble d’axiomes forment un
système de déduction complet.

Par ailleurs, il n’est pas possible de déduire à l’aide de ces axiomes, une
dépendance fonctionnelle qui n’appartient pas à « F+ ». On dit de ce fait que
cet ensemble d’axiomes constituent également un système de déduction
fermé.

Exemple 3.3.1.iii : Considérons la relation « R (E, M, P) » où « E, M, P » sont


les attributs de cette relation contrainte par « F = {« E, M → P », « P →
M »} », un ensemble de dépendances fonctionnelles. En s’appuyant sur les
axiomes d’Armstrong « F+ », la fermeture de « F », peut être déduite de la
façon suivante :

Par application de l’axiome sur la réflexivité

DF01 : E, M, P → E, M, P DF02 : E, M, P → E, M DF03 : E, M, P → E, P


DF04 : E, M, P → M, P DF05 : E, M, P → E DF06 : E, M, P → M
DF07 : E, M, P → P DF08 : E, M → E, M DF09 : E, M → E
DF10 : E, M → M DF11 : E, P → E, P DF12 : E, P → E
DF13 : E, P → P DF14 : M, P → M, P DF15 : M, P → M
DF16 : M, P → P DF17 : E → E DF18 : M → M
DF19 : P → P

Par inclusion des DF contenues dans F

DF20 : E, M → P DF21 : P → M

Par application de l’axiome d’augmentation à DF20

DF22 : E, M → E, P DF23 : E, M → M, P DF24 : E, M → E, M, P

98
SGBD relationnels – Tome 1, État de l’art

Par application de l’axiome d’augmentation à DF21

DF25 : E,P → E,M DF26 : P → M, P DF27 : E, P → E, M, P

Par application de l’axiome de transitivité à DF13 et DF21

DF28 : E, P → M

Par application de l’axiome de transitivité à DF25 et DF23

DF29 : E, P → M, P

d) L’équivalence de deux ensembles de dépendances fonctionnelles

Soit la relation « R(A) » où « A » désigne l’ensemble de ses attributs.

Deux ensembles quelconques de dépendances fonctionnelles, « F » et « F’ »


définis sur « R(A) » sont équivalents si « F+ = F’+ ».

N.B. : Deux équipes différentes peuvent après analyse d’un système


d’information identifier deux ensembles différents de dépendances
fonctionnelles « F » et « F’ ». Ce théorème permet de dire si ces deux
ensembles sont équivalents en calculant « F+ » et « F’+ » tel que nous l’avons
fait dans l’exemple précédent 3.2.1.iii.

e) La fermeture d’un ensemble d’attributs

Soit le schéma relationnel « R(A), S » où « R(A) » désigne une relation,


« A » l’ensemble des attributs de cette relation contrainte par « S » un
ensemble de dépendances fonctionnelles.

Si « X » désigne un sous ensemble de « A », on appelle fermeture de « X »


par rapport à « S », l’ensemble « X+ » des attributs de « R (A) » qui
dépendent fonctionnellement de « X ».

Telle que définie, la fermeture de « X » par rapport à « S » peut se calculer


à l’aide des axiomes d’Armstrong mais peut aussi se calculer plus
simplement à l’aide de l’algorithme ci-après :

99
Joachim TANKOANO

X+  X ;
DFNU  S ; /* DF non encore utilisés */
Tant que il existe une DF « G → D » dans DFNU telle que G  X+ faire
X+  X+  {D} ;
DFNU  DFNU – {G → D} ;
Fin faire ;

Exemple 3.3.1.iv : Soit la relation « R (A, B, C, D, E, F) » pour laquelle « S »


est composé des dépendances fonctionnelles ci-après : « A → B, C », « E →
C, F », « B → E », « C, D → E, F ». L’application de cet algorithme pour le
calcul de la fermeture de « {A, B} » par rapport « S » conduit aux étapes
suivantes :
• Initialisation : « {A, B}+ = {A, B} » et DFNU = « A → B, C », « E → C,
F », « B → E », « C, D → E, F »
• 1ère itération : « {A, B}+ = {A, B, C} » et DFNU = « E → C, F », « B →
E », « C, D → E, F »
• 2ème itération : « {A, B}+ = {A, B, C, E} » et DFNU = « E → C, F », « C,
D → E, F »
• 3ème itération : « {A, B}+ = {A, B, C, E, F} » et DFNU = « C, D → E, F »

Ce qui permet de conclure que {A, B}+ = {A, B, C, E, F}.

On peut de la même façon montrer que {A, D}+ = {A, B, C, D, E, F}, ce qui
permet de conclure que {A, D} est une clé candidate de cette relation.

f) Les applications de l’algorithme de calcul de la fermeture d’un


ensemble d’attributs

Si on considère le schéma relationnel « R(A), S » où « R(A) » désigne une


relation, « A » l’ensemble des attributs de cette relation contrainte par « S »
un ensemble de dépendances fonctionnelles, en lieu et place des axiomes
d’Armstrong, on peut utiliser l’algorithme de calcul de la fermeture d’un
ensemble d’attributs pour :
• Vérifier qu’une dépendance fonctionnelle « {X, Y → Z}  S » est

100
SGBD relationnels – Tome 1, État de l’art

élémentaire : « X, Y → Z » peut être réduite à « X → Z » si « X+ » par


rapport à « S » contient « Z »
• Vérifier que « X », un sous ensemble de « A », est une surclé de la
relation « R » : c’est le cas si « X+ = A »
• Calculer une clé candidate de « R » à de partir « X », une surclé de
la relation « R » : on recherche itérativement « X’ », un sous-
ensemble minimal de « X » qui est tel que « X’+ = A »
• Calculer l’ensemble des clés candidates de « R » : on recherche tout
sous-ensemble minimum d’attributs de « R » pouvant être une clé
en procédant comme suit : rechercher les clés candidates parmi les
sous-ensembles singletons de « A » ; ensuite rechercher les clés
candidates parmi les sous-ensembles doublets de « A » qui ne
peuvent pas être des surclés inférées des clés candidates
précédemment identifiées ; ainsi de suite ; ce qui revient à tester
systématiquement tous les sous-ensembles de « A » (N.B.: cette
approche peut être améliorée à l’aide d’heuristiques)
• Vérifier que « S » et « S’ », deux ensembles de DF sont équivalents
(i.e. que « S+ = S’+ ») : c’est le cas si pour chaque DF « G → D » de
« S » on a « D  G+ » par rapport à « S’ » et si pour chaque DF « G
→ D » de « S’ » on a « D  G+ » par rapport à « S »
• Vérifier qu’on peut éliminer une DF « G → D » de « S » pour obtenir
un ensemble « S’ » plus réduit de DF : on peut le faire si « G+ » par
rapport à « S’ » contient « D ».

Exemple 3.3.1.v : Soit la relation « R (A, B, C, D, E, G) » pour laquelle les


dépendances fonctionnelles identifiées dans « S » sont les suivantes : « A, B
→ C », « C → A », « B, C → D », « A, C, D → B », « D → E, G », « B, E →
C », « C, G → B, D », « C, E → A, G ».

Pour déterminer les clés candidates de cette relation on peut procéder


comme suit :
• Effectuer le calcul de {A}+, {B}+, {C}+, {D}+, {E}+ et {G}+, ce qui montre
qu’un attribut tout seul ne peut pas être une clé candidate.

101
Joachim TANKOANO

• Effectuer le calcul de {A, B}+, {A, C}+, {A, D}+, {A, E}+, {A, G}+, {B, C}+,
{B, D}+, {B, E}+, {B, G}+, {C, D}+, {C, E}+, {C, G}+, {D, E}+, {DG}+ et
{EG}+, ce qui montre que {A, B}, {B, C}, {B, D}, {B, E}, {C, D}, {C, E},
{C, G} sont des clés candidates.
• Procéder de la même manière pour les triplets qui ne sont pas des
surclés (à savoir {A, D, E}, {A, D, G}, {A, E, G}, {D, E, G}), ce qui
permet de dire qu’aucun de ces triplets ne peut être une clé
candidate.
• Montrer de la même manière que les quadruplets et quintuplets qui
ne sont pas des surclés ne sont pas non plus des clés candidates.

g) Le calcul de la couverture minimale d’un ensemble de dépendances


fonctionnelles

La couverture minimale (ou irredondante) d’un ensemble « S » de


dépendances fonctionnelles d’un système d’information correspond au
plus petit sous-ensemble « S’ » de « S » qui est tel que « S+ » = « S’+ ». On
parle aussi d’ensemble irréductible de dépendances fonctionnelles de « S ».

Le but de la conception d’un bon schéma relationnel est entre autres de


garantir que les contraintes d’intégrité auxquelles les relations définies sont
soumises (intégrité de relation, intégrité d’unicité et intégrité référentielle)
sont suffisantes pour garantir les contraintes d’intégrité relatives aux
dépendances découlant des règles de gestion. Il est donc essentiel que cet
exercice prenne appui sur un nombre minimal de règles de gestion. Plus ce
nombre est petit, plus le nombre de contraintes d’intégrité à maintenir sur
la base de données sera petit et plus le coût des contrôles à effectuer sera
faible. Lorsqu’on veut définir un schéma logique relationnel en s’appuyant
sur un ensemble « S » de dépendances fonctionnelles, le calcul de la
couverture minimale de cet ensemble de dépendances fonctionnelles
permet de s’assurer que toutes les dépendances fonctionnelles retenues à
l’issue de cet exercice sont essentielles pour la garantie de la cohérence des
données et qu’aucune de ces dépendances fonctionnelles ne peut être
supprimée pour alléger les contrôles sans remettre en cause cette garantie

102
SGBD relationnels – Tome 1, État de l’art

de cohérence.

Soit le schéma relationnel « R(A), S » où « R(A) » désigne une relation,


« A » l’ensemble des attributs de cette relation contrainte par « S » un
ensemble de dépendances fonctionnelles. Le calcul d’une couverture
minimale ou irredondante de « S » s’effectue en procédant comme suit :
1) S’assurer que le dépendant (c’est-à-dire, la partie droite) de chaque
dépendance fonctionnelle contient un seul attribut (c'est-à-dire que
« S » est sous sa forme canonique)
2) S’assurer que chaque dépendance fonctionnelle est élémentaire ou
totale (c'est-à-dire que le déterminant de chaque dépendance
fonctionnelle est irréductible)
3) S’assurer qu’aucune dépendance fonctionnelle ne peut être éliminée
de « S » pour donner « S’ », un sous-ensemble de dépendances
fonctionnelles de « S » tel que « S+ = S’+ ».

N.B. : Une relation peut avoir plusieurs ensembles irréductibles de


dépendances fonctionnelles.

Exemple 3.3.1.vi : Supposons la relation « R (A, B, C, D) » pour laquelle


« S » contient les DF suivantes : « A → B, C », « B → C », « A → B », « A, B
→ C », « A, C → D ». Le calcul de la couverture minimale de « S » par
application de ce qui précède donne ce qui suit :

1ère étape : Cette étape amène à réécrire « S » comme suit :

S’ = {DF01 : A → B, DF02 : A → C, DF03 : B → C, DF04 : A, B → C, DF05 :


A, C → D}

2ème étape :

En calculant « {A}+ » ou par application de la pseudo-transitivité entre DF01


et DF04, on peut déduire que « A → C » peut remplacer « A, B → C ».

En calculant « {A}+ » ou par application de la pseudo-transitivité entre DF02


et DF05, on peut aussi déduire que « A → D » peut remplacer « A, C → D ».

Ce qui donne après ces deux remplacements : S’ = {DF01 : A → B, DF02 : A


→ C, DF03 : B → C, DF05a : A → D}

103
Joachim TANKOANO

3ème étape :

Par application de la transitivité entre DF01 et DF03, « A → C », ce qui veut


dire qu’on peut supprimer DF02 de « S’ ».

Ce qui donne l’ensemble irréductible de DF : S’’ = {DF01 : A → B, DF03 : B


→ C, DF05a : A → D}

Exemple 3.3.1.vii : Supposons à présent la relation « R (A, B, C, D, E, G) »


contrainte par les dépendances fonctionnelles suivantes : « DF01 : A, B →
C », « DF02 : C → A », « DF03 : B, C → D », « DF04 : A, C, D → B », « DF05
: D → E », « DF06 : D → G », « DF07 : B, E → C », « DF08 : C, G → B »,
« DF09 : C, G → D », « DF10 : C, E → A », « DF11 : C, E → G ». Le calcul de
la couverture minimale de « S » par application de la démarche ci-dessus
donne ce qui suit :

1ère étape :

Toutes les dépendances fonctionnelles sont sous leur forme canonique.


Cette étape conduit donc au même ensemble de dépendances
fonctionnelles.

2ème étape :

« DF01 : A, B → C » peut être réduite à « A → C » si « C  {A}+ » par rapport


à « S », ce qui n’est pas le cas.

« DF01 : A, B → C » peut être réduite à « B → C » si « C  {B}+ » par rapport


à « S », ce qui n’est pas non plus le cas.

En répétant la même chose pour les autres DF (DF03, DF04, DF07, DF08,
DF09, DF10 et DF11), on trouve que « DF04 : A, C, D → B » peut être
remplacée par « DF04’ : C, D → B » et que « DF10 : C, E → A » peut être
remplacée par « DF02 : C → A ».

Ce qui donne après ces remplacements : S’ = {« DF01 : A, B → C », « DF02 :


C → A », « DF03 : B, C → D », « DF04’ : C, D → B », « DF05 : D → E »,
« DF06 : D → G », « DF07 : B, E → C », « DF08 : C, G → B », « DF09 : C, G
→ D », « DF11 : C, E → G »}

104
SGBD relationnels – Tome 1, État de l’art

3ème étape :

« DF01 : A, B → C » peut être supprimée si « C  {A, B}+ » par rapport à « S’


– {A, B → C} », ce qui n’est pas le cas.

En répétant la même chose pour les autres DF tout en tenant compte chaque
fois des DF déjà supprimées, on arrive à la conclusion que « DF04’ : C, D →
B », et « DF09 : C, G → D » peuvent être supprimées.

Ce qui donne l’ensemble minimal de dépendances fonctionnelles S’’ =


{« DF01 : A, B → C », « DF02 : C → A », « DF03 : B, C → D », « DF05 : D →
E », « DF06 : D → G », « DF07 : B, E → C », « DF08 : C, G → B », « DF11 : C,
E → G »}.

h) L’identification des redondances possibles dans une relation

Si « a → b » est une dépendance fonctionnelle élémentaire dans une


relation, un couple de valeurs de « (a, b) » peut se répéter dans une
extension légale de cette relation et engendrer des redondances, si et
seulement si le déterminant « a » de cette dépendance fonctionnelle
élémentaire n’est pas une clé candidate de cette relation.

i) Les règles de décomposition

Comme indiqué dans le paragraphe 3.2.2, la normalisation est le processus


qui permet de décomposer par projection une relation en plusieurs
relations, sans perte d’informations et de dépendances, afin d’éviter :
• La redondance
• Et les anomalies de stockage, i.e. les anomalies d’insertion, de
suppression et de modification.

Pour ce faire, cette décomposition doit se faire, soit en s’appuyant sur une
dépendance fonctionnelle et sur le théorème de la décomposition, soit en
s’appuyant sur la règle pour la préservation des dépendances
fonctionnelles ci-après :

105
Joachim TANKOANO

Le théorème de la décomposition

Soit « R(A) » une relation où « A » désigne l’ensemble de ses attributs, « X1,


X2 et X3 » une partition de « A » et « X1 → X2 » une dépendance
fonctionnelle où le déterminant « X1 » est irréductible.

Si R1 =  (X1, X2) (R) et R2 =  (X1, X3) (R)


Alors R (X1, X2, X3) = R1  (R1.X1 = R2.X1) R2.

Cette règle permet de décomposer une relation en créant une nouvelle


relation contenant le déterminant et le dépendant d’une dépendance
fonctionnelle où le déterminant est irréductible, et en supprimant le
dépendant de cette dépendance fonctionnelle dans la relation initiale. Toute
décomposition de relation sur la base de cette règle préserve les
informations et est donc réversible.

N.B. : La décomposition de la relation « Avions (NoAvion, NomAvion,


CapAvion, LocAvion) » effectuée dans l’exemple 3.2.2.i est un exemple de
décomposition qui s’appuie sur cette règle. Il est maintenant aisé de
comprendre pourquoi elle préserve les informations.

La règle pour la préservation des dépendances fonctionnelles

Soit « R(A) » une relation où « A » désigne l’ensemble de ses attributs, « X1,


X2 et X3 » une partition de « A », « X1 → X2 » et « X2 → X3 » deux
dépendances fonctionnelles où les déterminants « X1 » et « X2 » sont
irréductibles.

Plutôt que de décomposer « R (X1, X2, X3) » en « R1 (X1, X2) » et « R2 (X1,


X3) », ou en « R1 (X2, X3) » et « R2 (X2, X1) », par application du théorème
de la décomposition, « R (X1, X2, X3) » doit être décomposée en « R1 (X1,
X2) » et « R2 (X2, X3) », si on veut préserver à la fois les dépendances
fonctionnelles « X1 → X2 » et « X2 → X3 ».

3.3.2. Les dépendances multivaluées (DMV)

Dans un système d’information, les dépendances multivaluées sont moins


fréquentes que les dépendances fonctionnelles.

Une dépendance multivaluée se définit entre trois attributs ou groupes

106
SGBD relationnels – Tome 1, État de l’art

d’attributs.

a) Définition

Soit « R (X, Y, Z) » une relation où « X, Y et Z » est une partition des attributs


de cette relation.

On dit qu’il existe une dépendance multivaluée (DMV) de « Y » sur « X »


dans « R (X, Y, Z) » ou que « X » multi-détermine « Y » (ce qui se note « X -
 Y »), si, quelle que soit l’extension légale de « R (X, Y, Z) », chaque valeur
de « X » détermine le même ensemble de valeurs de « Y » quelle que soit la
valeur de « Z » associée à cette valeur de « X ». « X - Y » si et seulement
si « X - Z ».

Si « (xi, yi, zi) » désigne les valeurs d’un tuple « ti » de « R (X, Y, Z) », on dit
de façon équivalente que « X » multi-détermine « Y » si :
{(x1, y1, z1), (x1, y2, z2)}  R (X, Y, Z)
 {(x1, y1, z2), (x1, y2, z1)}  R (X, Y, Z)).

Une dépendance multivaluée « X - Y » dans « R (X, Y, Z) » caractérise le


fait que « Y » et « Z » dépendent de « X » tout en étant indépendants l’un
de l’autre. Chaque valeur de « X » doit être associée dans une extension
légale de la relation à toutes les combinaisons possibles d’un ensemble de
valeurs de « Y » et d’un ensemble de valeurs de « Z ».

Une dépendance multivaluée « X - Y » dans « R (X, Y, Z) » est élémentaire


si :
• « Y » n’est pas vide et est disjoint de « X »
• « R (X, Y, Z) » ne contient pas une autre dépendance multivaluée « X’
- Y’ » telle « X’  X » et « Y’  Y ».

Exemple 3.3.2.i : Considérons la relation « Vols (NoPilote, NoAvion, VD,


VA) » où, pour chaque extension légale, « chaque fois qu’un pilote conduit
plusieurs avions, il doit conduire chacun de ces avions sur chacun des trajets qu’il
effectue et chaque fois qu’il effectue plusieurs trajets, il doit effectuer chacun de ces
trajets avec chacun des avions qu’il conduit ». La dépendance définie par cette
règle de gestion entre les valeurs de « NoPilote », « NoAvion » et « VD,

107
Joachim TANKOANO

VA » est un exemple de dépendance multivaluée qu’on peut noter


« NoPilote - NoAvion » et « NoPilote - VD, VA ». Ce qui suit est un
exemple d’extension légale de cette relation :

NoPilote NoAvion VD VA
10 100 Ouagadougou Paris
10 101 Ouagadougou Paris
11 102 Ouagadougou Dakar
12 103 Cotonou Paris
11 100 Ouagadougou Dakar
10 100 Paris Lomé
10 101 Paris Lomé
11 100 Dakar Ouagadougou
11 102 Dakar Ouagadougou
11 100 Dakar Abidjan
11 102 Dakar Abidjan

On peut effectivement relever que la valeur « 10 » de « NoPilote » multi-


détermine les valeurs « 100, 101 » de « NoAvion », quel que soit le trajet
« (Ouagadougou, Paris) ou (Paris Lomé) » que ce pilote effectue.

On peut aussi noter que la valeur « 11 » de « NoPilote » multi-détermine


les valeurs « 100, 102 » de « NoAvion », quel que soit le trajet
« (Ouagadougou, Dakar), (Dakar, Ouagadougou) ou (Dakar, Abidjan) »
que ce pilote effectue.

On peut enfin noter que la valeur « 12 » de « NoPilote » détermine la valeur


« 103 » de « NoAvion » pour le trajet « (Cotonou, Paris) » que ce pilote
effectue.

b) Les anomalies liées aux dépendances multivaluées

Comme on peut le constater dans l’exemple précédent 3.2.2.i, l’existence


d’une dépendance multivaluée dans une relation est une condition
suffisante pour provoquer des redondances et des anomalies de stockage.

Dans l’extension de cet exemple, les valeurs « (10, Ouagadougou, Paris) et


(10, Paris, Lomé) » de « (NoPilote, VD, VA) » se répètent, ce qui veut dire

108
SGBD relationnels – Tome 1, État de l’art

que cette extension contient des redondances.

L’ajout du tuple « (10, 100, Ouagadougou, Niamey) » requiert l’ajout du


tuple « (10, 101, Ouagadougou, Niamey) » pour que la contrainte
d’intégrité soit maintenue, ce qui correspond à une anomalie d’insertion.

Si le pilote n° « 10 » n’assure plus le trajet « (Paris, Lomé) » mais le trajet


« (Ouagadougou, Niamey) », cette modification doit être répercutée pour
chacun des avions qu’il conduit sur le trajet « (Paris, Lomé), ce qui
correspond à une anomalie de modification.

S’il faut supprimer le tuple « (10, 100, Ouagadougou, Paris) » parce que le
pilote « 10 » n’effectue plus le trajet « (Ouagadougou, Paris) » avec l’avion
« 100 », il faut aussi supprimer le tuple « (10, 101, Ouagadougou, Paris) »
pour que la contrainte d’intégrité soit maintenue. Ce qui correspond à une
anomalie de suppression.

c) Les propriétés des dépendances multivaluées

Les dépendances multivaluées sont dotées de propriétés comparables à


celles des dépendances fonctionnelles.

Soient :
• « R (A) » une relation où « A » désigne l’ensemble de ses attributs,
• « W, X, Y, Z » des sous-ensembles de « A ».

La réflexivité

Si « Y  X » alors X - Y.

La complémentarité

Si Z = A – (X  Y) alors X - Y  X - Z.

L’augmentation

X - Y et V  W  X, W - Y, V.

La transitivité

X - Y et Y - Z  X - (Z-Y).

109
Joachim TANKOANO

La généralisation de la dépendance fonctionnelle

X → Y  X - Y. Ce qui veut dire que la dépendance fonctionnelle « X →


Y » est un cas particulier de dépendance multivaluée où l’ensemble de
valeurs de Y qui dépendent de chaque valeur de X est chaque fois un
ensemble réduit à un singleton.

d) Le théorème de la décomposition

Soit « R(A) » une relation où « A » désigne l’ensemble des attributs de


« R(A) », « X1, X2 et X3 » une partition de « A » et « X1 - X2 » une
dépendance multivaluée dan cette relation.

Si R1 =  (X1, X2) (R) et R2 =  (X1, X3) (R)


Alors R (X1, X2, X3) = R1  (R1.X1 = R2.X1) R2.

Toute décomposition de relation sur la base de cette règle préserve les


informations.

Exemple 3.3.2.ii : Sur la base de cette règle, l’extension de la relation « Vols


(NoPilote, NoAvion, VD, VA) » présentée dans l’exemple 3.3.2.i peut être
décomposée sans perte d’information comme suit :
Vols (NoPilote, NoAvion, VD, VA)
NoPilote NoAvion VD VA
10 100 Ouagadougou Paris
10 101 Ouagadougou Paris
11 102 Ouagadougou Dakar
12 103 Cotonou Paris
11 100 Ouagadougou Dakar
10 100 Paris Lomé
10 101 Paris Lomé
11 100 Dakar Ouagadougou
11 102 Dakar Ouagadougou
11 100 Dakar Abidjan
11 102 Dakar Abidjan

110
SGBD relationnels – Tome 1, État de l’art

AvionsPilotes =  (NoPilote, NoAvion) (Vols)


NoPilote NoAvion
10 100
10 101
11 100
11 102
12 103

TrajetsPilotes =  (NoPilote, VD, VA) (Vols)


NoPilote VD VA
10 Ouagadougou Paris
11 Ouagadougou Dakar
12 Cotonou Paris
10 Paris Lomé
11 Dakar Ouagadougou
11 Dakar Abidjan

Les extensions qu’on obtient après décomposition n’entrainent pas


d’anomalies comme celles relevées dans le paragraphe c) ci-dessus.

3.3.3. Les dépendances de jointure (DJ)

Dans un système d’information, les dépendances de jointure sont moins


fréquentes que les dépendances multivaluées qui elles-mêmes sont moins
fréquentes que les dépendances fonctionnelles. En outre, elles sont plus
compliquées à formuler que les dépendances multivaluées et les
dépendances fonctionnelles. Elles se définissent entre trois attributs ou
groupes d’attributs ou plus, entre lesquels il existe des liens sémantiques
indépendants.

a) Définition

Soient « R (A) » une relation où « A » désigne l’ensemble des attributs de


« R (A) » et « X1, X2, …, Xk » des groupes d’attributs de « A ».

On dit qu’il existe dans « R (A) » une dépendance de jointure (DJ) notée « 
(X1, X2, …, Xk) », où «  » désigne l’opérateur de jointure naturelle, si « R

111
Joachim TANKOANO

(A) » peut se décomposer en « X1 », « X2 », …, « Xk » sans perte


d’information, c'est-à-dire, si :
R (A) = ( (X1) (R))  ( (X2) (R))  …  ( (Xk) (R))

Si « k = 2 » on retrouve le cas spécifique des dépendances multivaluées.

Exemple 3.3.3.i : Considérons la relation « Vols (NoPilote, NoAvion, VD,


VA) » où pour toute extension légale, « si un pilote effectue un trajet et conduit
un avion et si cet avion est utilisé sur ce trajet, alors le pilote doit effectuer ce trajet
avec cet avion ». La dépendance définie par cette règle de gestion entre les
valeurs de « NoPilote », « NoAvion » et « VD, VA » est un exemple de
dépendance de jointure. Elle peut s’écrire de façon plus formelle de la façon
suivante :
(p1, v1, v2)  (NoPilote, VD, VA) (Vols) (trajets effectués par les pilotes)
 (p1, a1)  (NoPilote, NoAvion) (Vols) (avions conduits par les pilotes)
 (a1, v1, v2)  (NoAvion, VD, VA) (Vols) (avions utilisés sur les trajets)
 (p1, a1, v1, v2)  Vols (NoPilote, NoAvion, VD, VA).

Ce qui suit est un exemple d’extension légale de cette relation :


NoPilote NoAvion VD VA
11 100 Ouagadougou Paris
10 100 Paris Ouagadougou
10 100 Ouagadougou Paris
10 101 Ouagadougou Paris

Dans une relation, l’existence d’une dépendance de jointure est une


condition suffisante pour provoquer des redondances et les anomalies de
stockage dans les extensions légales de cette relation. Par rapport à
l’extension ci-dessus, on peut noter en termes de redondances ce qui suit :
• Le pilote « 10 » conduit l’avion « 100 » est répété deux fois, parce que
le pilote « 10 » effectue les trajets « (Paris, Ouagadougou) et
(Ouagadougou, Paris) » qui utilisent l’avion « 100 »
• L’avion « 100 » est utilisé sur le trajet « (Ouagadougou, Paris) » est
répété deux fois parce que les pilotes « 10 » et « 11 » effectuent le
trajet « (Ouagadougou, Paris) » et conduisent l’avion « 100 »
• Le pilote « 10 » effectue le trajet « (Ouagadougou, Paris) » est répété

112
SGBD relationnels – Tome 1, État de l’art

deux fois parce que le pilote « 10 » conduit les avions « 100 » et


« 101 » qui sont utilisés sur le trajet « (Ouagadougou, Paris) ».

b) Les propriétés des dépendances de jointure

Il n’existe pas un ensemble d’axiomes fermé et complet pour les


dépendances de jointure partielles.

En revanche, la réflexivité, l’augmentation et la décomposition forment un


ensemble d’axiomes fermé et complet pour la dépendance de jointure
totale.

c) Le théorème de la décomposition

Toute décomposition de relation basée sur les groupes d’attributs


impliqués dans une dépendance de jointure préserve les informations.

Exemple 3.3.3.ii : Sur la base de cette règle, l’extension de la relation « Vols


(NoPilote, NoAvion, VD, VA) » présentée dans l’exemple 3.2.3.i peut se
décomposer comme indiqué ci-dessous, où les trois relations obtenues
après décomposition sont sans perte d’information et sans redondance.

Vols = ((AvionsPilotes  TrajetsPilotes)  TrajetsAvions)


NoPilote NoAvion VD VA
11 100 Ouagadougou Paris
10 100 Paris Ouagadougou
10 100 Ouagadougou Paris
10 101 Ouagadougou Paris

AvionsPilotes = (NoPilote, NoAvion) (Vols)


NoPilote NoAvion
11 100
10 100
10 101

TrajetsPilotes = (NoPilote, VD, VA) (Vols)


NoPilote VD VA
11 Ouagadougou Paris
10 Paris Ouagadougou

113
Joachim TANKOANO

10 Ouagadougou Paris
TrajetsAvions = (NoAvion, VD, VA) (Vols)
NoAvion VD VA
100 Ouagadougou Paris
100 Paris Ouagadougou
101 Ouagadougou Paris

3.4. Les formes normales


Les formes normales définissent six classes de relations de plus en plus
restrictives en termes de types de situations pouvant engendrer des
redondances et des anomalies de stockage. En partant de la moins
restrictive autorisant tous les types de situations pouvant engendrer des
redondances et des anomalies de stockage, vers la plus restrictive
n’autorisant aucune de ces situations possibles, ces classes de relations
s’incluent les unes dans les autres et fournissent chacune une règle
permettant de passer à la suivante.

Ce qui suit définit chaque forme normale de relation en précisant d’une


part, les restrictions qu’elle impose en termes de types de situations
pouvant engendrer des redondances et des anomalies de stockage et
d’autre part, la règle qui permet de passer à cette forme normale à partir de
la forme normale qui la précède, afin de la rendre plus restrictive.

La définition de ces formes normales de relations s’appuie d’une part, sur


les conditions nécessaires et suffisantes requises pour que des redondances
puissent se produire dans une extension de relation et d’autre part, sur les
théorèmes de la décomposition, énoncés dans la section 3.3, relatifs aux
dépendances fonctionnelles, multivaluées et de jointure.

3.4.1. La 1ère forme normale (1FN)

a) Définition

Une relation est dite normalisée ou en 1ère forme normale, si aucun de ses
attributs n’est ni un agrégat (c'est-à-dire un attribut à valeurs composées),

114
SGBD relationnels – Tome 1, État de l’art

ni un groupe répétitif (c'est-à-dire un attribut à valeurs relationnelles).

Une relation en 1ère forme normale est une relation pour laquelle la seule
restriction qu’on lui impose est d’être conforme à la définition formelle
d’une relation, c'est-à-dire, être un sous-ensemble du produit cartésien de « n »
ensembles de valeurs atomiques. Une relation en 1ère forme normale peut
comporter tous les types de situations pouvant engendrer des redondances
et des anomalies de stockage.

Exemple 3.4.1.i (relation avec un attribut à valeurs composées) : La relation


« Vols (NoVol, Pilote {NoPilote, NomPilote, AdrPilote}, NoAvion) » (où
l’attribut à valeurs composées « Pilote » est constitué des attributs
« NoPilote », « NomPilote » et « AdrPilote ») est un exemple de relation
qui n’est pas en 1ère forme normale. Cette relation peut avoir comme
extension ce qui suit :

NoVol Pilote {NoPilote, NomPilote, AdrPilote} NoAvion


J10 {100, Pierre, Ouagadougou} 10
J15 {102, André, Bobo-Dioulasso} 20
J16 {103, Amadou, Bobo-Dioulasso} 10
J20 {100, Pierre, Ouagadougou} 10

Exemple 3.4.1.ii (relation avec un attribut à valeurs relationnelles) : La


relation « Fournisseurs (NoFour, NomFour, Livraisons [{RefPièce,
Quantité, Date}]) » (où l’attribut « Livraisons » est un attribut à valeurs
relationnelles dont chaque valeur est une relation imbriquée ayant comme
attributs « RefPièce », « Quantité » et « Date ») est aussi un exemple de
relation qui n’est pas en 1ère forme normale. Cette relation peut avoir
comme extension ce qui suit :

NoFour NomFour Livraisons


RefPièce Quantité Date
10 Pierre P1 100 27/11/2019
P2 20 12/01/2020
P1 200 25/07/2020
20 Alassane P3 400 15/08/2019
30 André P1 250 04/03/2020
P3 50 06/09/2020

115
Joachim TANKOANO

40 Arouna P2 600 25/04/2019


50 Bernard P1 400 06/09/2020

b) La mise en 1ère forme normale

Une relation non normalisée peut être mise en 1ère forme normale :
• En remplaçant récursivement, dans chaque tuple de relation, chaque
valeur d’attribut à valeurs composées par les valeurs plus
élémentaires qui résultent de sa décomposition
• En répliquant récursivement chaque tuple de relation contenant une
relation imbriquée en autant de tuples qu’il y a de tuples dans cette
relation imbriquée, et en désimbriquant cette relation imbriquée
dans les tuples qui résultent de la réplication.

Exemple 3.4.1.iii (décomposition des éléments d’un agrégat) : L’application


de cette règle pour la mise en 1ère forme normale de la relation « Vols
(NoVol, Pilote {NoPilote, NomPilote, AdrPilote}, NoAvion) » de
l’exemple 3.4.1.i donne la relation et l’extension ci-après :

Vols (NoVol, NoPilote, NomPilote, AdrPilote, NoAvion)


NoVol NoPilote NomPiote AdrPiote NoAvion
J10 100 Pierre Ouagadougou 10
J15 102 André Bobo-Dioulasso 20
J16 103 Amadou Bobo-Dioulasso 10
J20 102 Pierre Ouagadougou 10

Exemple 3.4.1.iv (désimbrication des tables imbriquées) : Quant à


l’application de cette règle pour la mise en 1ère forme normale de la relation
« Fournisseurs (NoFour, NomFour, Livraisons {[RefPièce], [Quantité],
[Date]}) » de l’exemple 3.4.1.ii, elle donne la relation et l’extension ci-
dessous.

Dans les deux cas, la relation obtenue après mise en 1ère forme normale
contient les mêmes informations que la relation initiale.

116
SGBD relationnels – Tome 1, État de l’art

Fournisseurs (NoFour, NomFour, RefPièce, Quantité, Date)


NoFour NomFour RefPièce Quantité Date
10 Pierre P1 100 27/11/2019
10 Pierre P2 20 12/01/2020
10 Pierre P1 200 25/07/2020
20 Alassane P3 400 15/08/2019
30 André P1 250 04/03/2020
30 André P3 50 06/09/2020
40 Arouna P2 600 25/04/2019
50 Bernard P1 400 06/09/2020

3.4.2. La 2ème forme normale (2FN)

a) Définition

Une relation est en 2ème forme normale si elle est en 1ère forme normale et si
tous les attributs non-clés dépendent de sa clé primaire par une dépendance
fonctionnelle élémentaire, ce qui revient à dire que tous les attributs non-
clés dépendent de cette clé primaire par une dépendance fonctionnelle
irréductible à gauche ou totale.

Exemple 3.4.2.i (relation en 1FN et non en 2FN) : La relation « LignesCmdes


(NoCmde, RefProduit, LibProduit, Quantité) » contrainte par la
dépendance fonctionnelle « RefProduit → LibProduit » est en 1ère forme
normale mais n’est pas en 2ème forme normale parce que la dépendance
fonctionnelle « NoCmde, RefProduit → LibProduit » n’est pas une
dépendance fonctionnelle élémentaire à cause de la dépendance
fonctionnelle « RefProduit → LibProduit ». Comme pour toutes les autres
relations qui sont en 1ère forme normale sans être en 2ème forme normale, on
peut constater que l’extension légale ci-après de cette relation comporte
toutes les anomalies, à cause de la dépendance fonctionnelle « RefProduit
→ LibProduit » qui fait que cette relation n’est pas en 2ème forme normale
et qui a de ce fait un déterminant qui n’est pas une clé candidate.

Le couple de valeurs « P1, Crayon » se répète de ce fait trois fois.

On ne peut pas ajouter des informations sur un nouveau produit sans les

117
Joachim TANKOANO

associer à une commande.

La suppression du 4ème tuple a comme conséquence la perte de


l’information qui indique que la référence produit « P3 » correspond à
« agrafeuse ».

La modification du libellé produit « Crayon » en « Crayon de papier »


nécessite trois mises à jour.

NoCmde RefProduit LibProduit Quantité


10 P1 Crayon 100
20 P1 Crayon 200
10 P2 Rame de papier A3 200
20 P3 Agrafeuse 4
30 P1 Crayon 400

b) La mise en 2ème forme normale

La mise en 2ème forme normale d’une relation « R (a, b, c, d) », qui est en 1ère
forme normale et qui est contrainte par une dépendance fonctionnelle « b
→ d » qui fait que cette relation n’est pas en 2ème forme normale, s’effectue
en décomposant « R (a, b, c, d) » en « R’ (b, d) = (b, d) (R) » et « R’’ (a, b,
c) = (a, b, c) (R) » par application du théorème de la décomposition.

Cette mise en 2ème forme normale permet d’éviter les redondances et les
anomalies de stockage qui caractérisent les relations qui sont en 1ère forme
normale sans être en 2ème forme normale.

Exemple 3.4.2.ii : La mise en 2ème forme normale de la relation de l’exemple


3.4.2.i, « LignesCmdes (NoCmde, RefProduit, LibProduit, Quantité) »,
contrainte par la dépendance fonctionnelle « RefProduit → LibProduit »,
par application de cette règle conduit à la décomposition comme indiqué
ci-dessous.

Les deux relations issues de cette décomposition contiennent les mêmes


informations que la relation initiale et ne peuvent pas contenir des

118
SGBD relationnels – Tome 1, État de l’art

redondances, ou engendrer des anomalies de stockage.


LignesCmdes (NoCmde, RefProduit, LibProduit, Quantité)
NoCmde RefProduit LibProduit Quantité
10 P1 Crayon 100
20 P1 Crayon 200
10 P2 Rame de papier A3 200
20 P3 Agrafeuse 4
30 P1 Crayon 400

Produits (RefProduit, LibProduit)


RefProduit LibProduit
P1 Crayon
P2 Rame de papier A3
P3 Agrafeuse

LignesCmdes (NoCmde, RefProduit, Quantité)


NoCmde RefProduit Quantité
10 P1 100
20 P1 200
10 P2 200
20 P3 4
30 P1 400

3.4.3. La 3ème forme normale (3FN)

a) Définition

Une relation est en 3ème forme normale si elle est en 2ème forme normale et
si tous les attributs qui ne sont pas dans sa clé primaire sont directement
dépendants de cette clé primaire, ce qui revient à dire que dans cette
relation aucun attribut non-clé ne dépend d’un autre attribut non-clé, ou
qu’il n’existe pas dans cette relation une dépendance transitive.

Exemple 3.4.3.i (relation en 2FN et non en 3FN) : La relation « Vols (NoVol,


JourVol, NoPilote, NoAvion) » contrainte par la dépendance fonctionnelle
« NoPilote → NoAvion » est en 2ème forme normale mais n’est pas en 3ème
forme normale à cause de la dépendance fonctionnelle « NoPilote →

119
Joachim TANKOANO

NoAvion » qui fait que dans cette relation, un attribut non-clé dépend d’un
autre attribut non-clé. Comme pour toutes les autres relations qui sont en
2ème forme normale sans être en 3ème forme normale, on peut constater que
l’extension légale ci-après de cette relation comporte toutes les anomalies
parce que le déterminant de la dépendance fonctionnelle « NoPilote →
NoAvion » qui fait que cette relation n’est pas en 3ème forme normale n’est
pas une clé candidate.

NoVol JourVol NoPilote NoAvion

J10 Lundi Pilote1 Avion1


J20 Jeudi Pilote1 Avion1
J30 Lundi Pilote2 Avion2
J10 Dimanche Pilote3 Avion1

Le couple de valeurs « Pilote1, Avion1 » se répète de ce fait deux fois.

On ne peut pas ajouter des informations sur un nouveau pilote sans créer
un nouveau vol.

La suppression du 3ème tuple a comme conséquence la perte de


l’information qui indique que le pilote « Pilote2 » conduit l’avion
« Avion2 ».

La modification de l’avion conduit par le pilote « Pilote1 » nécessite deux


mises à jour.

b) La mise en 3ème forme normale

La mise en 3ème forme normale d’une relation « R (a, b, c) », qui est en 2ème
forme normale et qui est contrainte par la dépendance fonctionnelle « b →
c » qui fait que cette relation n’est pas en 3ème forme normale, s’effectue en
décomposant « R (a, b, c) » en « R’ (b, c) = (b, c) (R) » et « R’’ (a, b) = (a, b)
(R) » par application du théorème de la décomposition.

Cette mise en 3ème forme normale permet d’éviter les redondances et les

120
SGBD relationnels – Tome 1, État de l’art

anomalies de stockage qui caractérisent les relations qui sont en 2ème forme
normale sans être en 3ème forme normale.

Exemple 3.4.3.ii : La mise en 3ème forme normale de la relation de l’exemple


3.4.3.i, « Vols (NoVol, JourVol, NoPilote, NoAvion) » contrainte par la
dépendance fonctionnelle « NoPilote → NoAvion », par application de
cette règle conduit à la décomposition ci-dessous, où les deux relations
issues de cette décomposition contiennent les mêmes informations que la
relation initiale et ne peuvent pas contenir des redondances, ou engendrer
des anomalies de stockage.
Vols (NoVol, JourVol, NoPilote, NoAvion)
NoVol JourVol NoPilote NoAvion
J10 Lundi Pilote1 Avion1
J20 Jeudi Pilote1 Avion1
J30 Lundi Pilote2 Avion2
J10 Dimanche Pilote3 Avion1

Pilotes (NoPilote, NoAvion) Vols (NoVol, JourVol, NoPilote)


NoPilote NoAvion NoVol JourVol NoPilote
Pilote1 Avion1 J10 Lundi Pilote1
Pilote2 Avion2 J20 Jeudi Pilote1
Pilote3 Avion1 J30 Lundi Pilote2
J10 Dimanche Pilote3

3.4.4. La forme normale de BOYCE-CODD (FNBC)

a) Définition

Une relation en 3ème forme normale est en forme normale de BOYCE-


CODD si pour chacune de ses dépendances fonctionnelles « G → D », le
déterminant « G » est une clé candidate.

Exemple 3.4.4.i (relation en 3FN et non en FNBC) : La relation « Vols


(NoVol, JourVol, NoPilote) » contrainte par la dépendance fonctionnelle
« NoPilote → JourVol » est en 3ème forme normale mais n’est pas en forme
normale de BOYCE-CODD parce que dans la dépendance fonctionnelle
« NoPilote → JourVol », « NoPilote » n’est pas une clé candidate. Comme

121
Joachim TANKOANO

pour toutes les autres relations qui sont en 3ème forme normale sans être en
forme normale de BOYCE-CODD, on peut constater que l’extension légale
ci-après de cette relation comporte toutes les anomalies parce que la
dépendance fonctionnelle « NoPilote → JourVol » qui fait que cette
relation n’est pas en forme normale de BOYCE-CODD a un déterminant
qui n’est pas une clé candidate :

NoVol JourVol NoPilote

J10 Lundi Pilote1


J20 Jeudi Pilote2
J30 Lundi Pilote1
J10 Dimanche Pilote3

Le couple de valeurs « Lundi, Pilote1 » se répète de ce fait deux fois.

On ne peut pas ajouter des informations sur un nouveau pilote sans créer
un nouveau vol.

La suppression du 2ème tuple a comme conséquence la perte de


l’information qui indique que le pilote « Pilote2 » conduit le « Jeudi ».

La modification du jour où le pilote « Pilote1 » conduit nécessite deux mises


à jour.

b) La mise en forme normale de BOYCE-CODD

La mise en forme normale de BOYCE-CODD d’une relation « R (a, b, c) »,


qui est en 3ème forme normale et qui est contrainte par une dépendance
fonctionnelle « c → b » qui fait que cette relation n’est pas en forme normale
de BOYCE-CODD, s’effectue en décomposant « R (a, b, c) » en « R’ (c, b) =
(c, b) (R) » et « R’’ (a, c) = (a, c) (R) » par application du théorème de la
décomposition.

Cette mise en forme normale de BOYCE-CODD permet d’éviter les


redondances et les anomalies de stockage qui caractérisent les relations qui

122
SGBD relationnels – Tome 1, État de l’art

sont en 3ème forme normale sans être en forme normale de BOYCE-CODD.

Exemple 3.4.4.ii : La mise en forme normale de BOYCE-CODD de la relation


de l’exemple 3.4.4.i, « Vols (NoVol, JourVol, NoPilote) » contrainte par la
dépendance fonctionnelle « NoPilote → JourVol », par application de cette
règle donne ce qui suit :
Vols (NoVol, JourVol, NoPilote)
NoVol JourVol NoPilote
J10 Lundi Pilote1
J20 Jeudi Pilote2
J30 Lundi Pilote1
J10 Dimanche Pilote3

Pilotes (NoPilote, JourVol) Vols (NoVol, NoPilote)


NoPilote JourVol NoVol NoPilote

Pilote1 Lundi J10 Pilote1


Pilote2 Jeudi J20 Pilote2
Pilote3 Dimanche J30 Pilote1
J10 Pilote3
Les deux relations issues de cette décomposition contiennent les mêmes
informations que la relation initiale et ne peuvent pas contenir des
redondances, ou engendrer des anomalies de stockage.

c) Le dilemme de la mise en forme normale de BOYCE-CODD

Comme nous l’avons vu dans le paragraphe a) ci-dessus, une relation en


3ème forme normale qui n’est pas en forme normale de BOYCE-CODD peut
engendre des redondances et des anomalies de stockage.

En plus, le SGBD ne peut pas garantir de façon automatique la contrainte


d’intégrité découlant de la dépendance fonctionnelle qui fait que cette
relation n’est pas en forme normale de BOYCE-CODD.

Par ailleurs, lorsqu’une relation en 3ème forme normale n’est pas en forme
normale de BOYCE-CODD, sa mise en forme normale de BOYCE-CODD a
comme conséquence la perte d’une dépendance fonctionnelle, ce qui ne
permet plus de garantir le respect de la règle de gestion que cette

123
Joachim TANKOANO

dépendance fonctionnelle traduit.

Exemple 3.4.4.iii : La décomposition effectuée dans l’exemple 3.4.4.ii


entraîne de ce fait la perte de la dépendance fonctionnelle « NoVol, Jour →
NoPilote » qui peut être violée dans des extensions légales comme celles
qui suivent :
Pilotes (NoPilote, JourVol) Vols (NoVol, NoPilote)
NoPilote JourVol NoVol NoPilote
Pilote1 Lundi J10 Pilote1
Pilote2 Jeudi J10 Pilote3
Pilote3 Lundi J20 Pilote2
J30 Pilote1
La jointure naturelle de ces deux extensions donne l’extension ci-après où
« Pilote1 » et « Pilote3 » sont associés au couple « J10, Lundi », ce qui viole
la dépendance fonctionnelle « NoVol, Jour → NoPilote » :

NoVol JourVol NoPilote


J10 Lundi Pilote1
J10 Lundi Pilote3
J20 Jeudi Pilote2
J30 Lundi Pilote1

Nous sommes donc ici face à un dilemme, car conserver la relation « Vols
(NoVol, JourVol, NoPilote) » en 3ème forme normale tout comme la mettre
en forme normale de BOYCE-CODD a une conséquence indésirable sur
l’intégrité des données.

Quel que soit le choix effectué, l’administrateur de la base de données doit


mettre en place, lors de l’implémentation de la base de données, un
mécanisme approprié pour la gestion automatique de la contrainte
d’intégrité qui peut être violée.

Si le choix a été de conserver la relation « Vols (NoVol, JourVol,


NoPilote) » en 3ème forme normale, ce mécanisme doit garantir le respect de
la dépendance fonctionnelle « NoPilote → JourVol » afin d’éviter toute
situation indiquant qu’un pilote conduit deux jours différents, sans garantir
pour autant l’absence de redondance.

124
SGBD relationnels – Tome 1, État de l’art

Si le choix a été de mettre cette relation en forme normale de BOYCE-


CODD, ce mécanisme doit garantir le respect de la dépendance
fonctionnelle perdue « NoVol, Jour → NoPilote ».

Le paragraphe 4.4.3.d traite de la mise en place de tels mécanismes à l’aide


de vues.

3.4.5. La 4ème forme normale (4FN)

a) Définition

Une relation en forme normale de BOYCE-CODD est en 4ème forme


normale, si pour toute dépendance multivaluée « X - Y » dans cette
relation, « X » est une surclé de la relation. En d’autres termes, dans cette
relation, toutes les dépendances fonctionnelles ou multivaluées non
triviales doivent être des dépendances fonctionnelles de la forme « X → Y »
où « X » est une surclé de la relation.

Exemple 3.4.5.i (relation en FNBC et non 4FN) : La relation « Vols


(NoPilote, NoAvion, VD, VA) » contrainte par la dépendance multivaluée
« NoPilote - VD, VA » est en forme normale de BOYCE-CODD mais
n’est pas en 4ème forme normale, parce que « NoPilote » est le déterminant
d’une dépendance multivaluée mais n’est pas une clé dans la relation.

NoPilote NoAvion VD VA

10 100 Ouagadougou Paris


10 101 Ouagadougou Paris
11 102 Ouagadougou Dakar
12 103 Cotonou Paris
11 100 Ouagadougou Dakar
10 100 Paris Lomé
10 101 Paris Lomé
11 100 Dakar Ouagadougou
11 102 Dakar Ouagadougou
11 100 Dakar Abidjan
11 102 Dakar Abidjan

125
Joachim TANKOANO

Comme pour toutes les autres relations qui sont en forme normale de
BOYCE-CODD sans être en 4ème forme normale, on peut constater que
l’extension légale ci-dessus de cette relation comporte toutes les anomalies
parce que le déterminant de la dépendance multivaluée « NoPilote - VD,
VA » qui fait que cette relation n’est pas en 4ème forme normale n’est pas
une clé candidate.

b) La mise en 4ème forme normale

La mise en 4ème forme normale d’une relation « R (a, b, c) », qui est en forme
normale de BOYCE-CODD et qui est contrainte par la dépendance
multivaluée « a - b » qui fait que cette relation n’est pas en 4ème forme
normale, s’effectue en décomposant « R (a, b, c) » en « R’ (a, b) = (a, b) (R)
» et « R’’ (a, c) = (a, c) (R) » par application du théorème de la
décomposition.

Cette mise en 4ème forme normale permet d’éviter les redondances et les
anomalies de stockage qui caractérisent les relations qui sont en forme
normale de BOYCE-CODD sans être en 4ème forme normale.

Exemple 3.4.5.ii : La mise en 4ème forme normale de la relation de l’exemple


3.4.5.i, « Vols (NoPilote, NoAvion, VD, VA) » contrainte par la
dépendance multivaluée « NoPilote - VD, VA », par application de cette
règle donne ce qui suit :
Vols (NoPilote, NoAvion, VD, VA)
NoPilote NoAvion VD VA
10 100 Ouagadougou Paris
10 101 Ouagadougou Paris
11 102 Ouagadougou Dakar
12 103 Cotonou Paris
11 100 Ouagadougou Dakar
10 100 Paris Lomé
10 101 Paris Lomé
11 100 Dakar Ouagadougou
11 102 Dakar Ouagadougou
11 100 Dakar Abidjan
11 102 Dakar Abidjan

126
SGBD relationnels – Tome 1, État de l’art

AvionsPilotes =  (NoPilote, NoAvion) (Vols)


NoPilote NoAvion
10 100
10 101
11 100
11 102
12 103

TrajetsPilotes =  (NoPilote, VD, VA) (Vols)


NoPilote VD VA
10 Ouagadougou Paris
11 Ouagadougou Dakar
12 Cotonou Paris
10 Paris Lomé
11 Dakar Ouagadougou
11 Dakar Abidjan
Les deux relations issues de cette décomposition contiennent les mêmes
informations que la relation initiale et ne peuvent pas contenir des
redondances, ou engendrer des anomalies de stockage.

3.4.6. La 5ème forme normale (5FN)

a) Définition

Une relation en 4ème forme normale est en 5ème forme normale si pour
chaque dépendance de jointure «  (X1, X2, …, Xk) » dans cette relation,
chacun des « Xi » est une surclé d’une clé candidate de la relation.

Une relation qui satisfait cette condition ne peut contenir aucune


redondance. Pour garantir l’absence de redondance dans une base de
données, son schéma relationnel doit être constitué que de relations en 5FN.

Exemple 3.4.6.i (relation en 4FN et non en 5FN) : La relation « Vols


(NoPilote, NoAvion, VD, VA) » contrainte par la dépendance de jointure
«  ({NoPilote, NoAvion}, {NoPilote, VD, VA}, {NoAvion, VD, VA}) » est
en 4ème forme normale mais n’est pas en 5ème forme normale parce que,
« (NoPilote, NoAvion, VD, VA) », la seule clé de cette relation n’est

127
Joachim TANKOANO

contenue ni dans « NoPilote, NoAvion », ni dans « NoPilote, VD, VA », ni


dans « NoAvion, VD, VA ». Comme pour toutes les autres relations qui
sont en 4ème forme normale sans être en 5ème forme normale, on peut
constater que l’extension légale ci-après de cette relation comporte toutes
les anomalies parce que les valeurs des groupes d’attributs de la
dépendance de jointure peuvent se répéter car ces groupes d’attributs ne
contiennent pas des clés candidates :

NoPilote NoAvion VD VA

11 100 Ouagadougou Paris


10 100 Paris Ouagadougou
10 100 Ouagadougou Paris
10 101 Ouagadougou Paris

b) La mise en 5ème forme normale

La mise en 5ème forme normale d’une relation « R (a, b, c) », qui est en 4ème
forme normale et qui est contrainte par la dépendance de jointure «  ({a,
b}, {a, c}, {b, c}) » qui fait que cette relation n’est pas en 5ème forme normale,
s’effectue en décomposant « R (a, b, c) » en « R’ (a, b) = (a, b) (R) », « R’’ (a,
c) = (a, c) (R) » et « R’’’ (b, c) = (b, c) (R) » par application du théorème de
la décomposition.

Cette mise en 5ème forme normale permet d’éviter toute redondance et toute
anomalie de stockage.

Exemple 3.4.6.ii : La mise en 5ème forme normale de la relation de l’exemple


3.4.6.i, « Vols (NoPilote, NoAvion, VD, VA) » contrainte par la
dépendance de jointure «  ({NoPilote, NoAvion}, {NoPilote, VD, VA},
{NoAvion, VD, VA}) », par application de cette règle conduit à la
décomposition ci-après où les trois relations issues de cette décomposition
contiennent les mêmes informations que la relation initiale et ne peuvent
pas contenir des redondances ou engendrer des anomalies de stockage :

128
SGBD relationnels – Tome 1, État de l’art

Vols = ((AvionsPilotes  TrajetsPilotes)  TrajetsAvions)


NoPilote NoAvion VD VA
11 100 Ouagadougou Paris
10 100 Paris Ouagadougou
10 100 Ouagadougou Paris
10 101 Ouagadougou Paris
AvionsPilotes = (NoPilote, NoAvion) (Vols)
NoPilote NoAvion

11 100
10 100
10 101
TrajetsPilotes = (NoPilote, VD, VA) (Vols)
NoPilote VD VA

11 Ouagadougou Paris
10 Paris Ouagadougou
10 Ouagadougou Paris
TrajetsAvions = (NoAvion, VD, VA) (Vols)
NoAvion VD VA

100 Ouagadougou Paris


100 Paris Ouagadougou
101 Ouagadougou Paris

3.5. Les approches de conception d’un schéma logique


relationnel

3.5.1. L’objectif général de ces approches

Comme nous l’avons déjà souligné, tout système d’information a comme


caractéristiques intrinsèques :
• Ses attributs, c'est-à-dire, les données élémentaires qu’il doit contenir
• Et les dépendances (fonctionnelles, multivaluées et de jointure) qui
existent entre ces attributs.

Les dépendances spécifient des contraintes d’intégrité qui doivent être

129
Joachim TANKOANO

maintenues en permanence entre les données élémentaires afin que le


système d’information soit en cohérence avec les règles de gestion.

L’analyse des besoins pour la mise en place de la base de données d’un


système d’information doit avoir pour finalité l’inventaire de ces deux
caractéristiques intrinsèques, à savoir, les attributs de ce système
d’information et les dépendances entre ces attributs.

Quant à la conception du schéma logique relationnel de la base de données


d’un système d’information, qui vise la définition d’une organisation
logique des données selon une approche relationnelle, elle doit avoir pour
finalité la recherche d’un bon regroupement des informations du système
d’information en termes de relations.

Étant donné un système d’information caractérisé par un ensemble


d’attributs et de dépendances entre ces attributs, il est possible de concevoir
pour la base de données de ce système d’information plusieurs schémas
logiques relationnels différents mais équivalents. Parmi ces schémas
logiques relationnels, il en existe qu’on considère comme étant de bons
schémas logiques relationnels ou qu’on considère comme étant de mauvais
schémas logiques relationnels.

Un bon schéma logique relationnel est celui qui possède les propriétés ci-
après :
• Les relations qui le composent ne peuvent pas contenir de données
redondantes
• Ces relations n’ont pas été excessivement décomposées et sont de ce
fait pertinentes par rapport aux problèmes à résoudre. En général,
chaque relation doit regrouper des informations sur des entités du
monde réel de même nature ou sur des associations de même nature
entre ces entités
• La décomposition en plusieurs relations a été sans perte
d’information parce qu’elle a été effectuée par projection en
s’appuyant sur des dépendances entre attributs
• La décomposition en plusieurs relations n’a pas non plus induit des
pertes de dépendances fonctionnelles, multivaluées ou de jointure,

130
SGBD relationnels – Tome 1, État de l’art

ce qui garantit que les contraintes d’intégrité exprimées au niveau


des relations (unicité des clés et contraintes référentielles
notamment) sont suffisantes pour garantir le respect des règles de
gestion exprimées par ces dépendances.

La conception du schéma relationnel de la base de données d’un système


d’information est donc dictée en grande partie par les dépendances qui
doivent être maintenues entre les données élémentaires de ce système
d’information au regard des règles de gestion.

3.5.2. Les approches relationnelles et les approches orientées


sémantique

Les approches de conception du schéma relationnel de la base de données


d’un système d’information se sont développées selon deux grands axes :
d’une part en s’appuyant sur les concepts du modèle relationnel et d’autre
part, en s’appuyant sur des outils de modélisation conceptuelle, appelés
aussi outils de modélisation sémantique. Les approches utilisées en
pratique combinent ces deux types d’approches. Ce qui suit présente ces
deux types d’approches et celle qui les combine.

a) Les approches de conception basées sur le modèle relationnel

Dans ces approches, les concepts proposés par le modèle relationnel sont
utilisés pour conduire l’analyse des besoins qui doit précéder la mise en
place de la base de données. L’analyse à l’aide de ces concepts doit conduire
à l’identification des attributs de ce système d’information qui découlent
des besoins des utilisateurs et à l’identification des dépendances qui
doivent exister entre ces attributs qui elles doivent découler des règles de
gestion de l’entreprise concernée. Ces concepts sont ensuite utilisés pour
déterminer, sur cette base, le meilleur schéma logique relationnel à même
de garantir le respect des dépendances identifiées tout en évitant les
redondances et les anomalies de stockage associées.

Ces concepts sont de très bas niveau pour la conduite de la première phase
de ce processus, visant l’identification des attributs et des dépendances
entre attributs, car ils amènent le concepteur à se focaliser beaucoup plus

131
Joachim TANKOANO

sur les propriétés formelles des dépendances identifiées entre attributs que
sur la perception des utilisateurs et la sémantique des attributs et des
dépendances. De ce fait, ces concepts ne permettent pas, lors d’une analyse,
un haut niveau d’abstraction. Il n’est pas aisé, de ce fait, de faire lors de cette
analyse l’inventaire des attributs et des dépendances sans en oublier.

En contrepartie, ces concepts sont très bien adaptés pour la deuxième phase
du processus, visant la détermination du meilleur schéma relationnel à
même de garantir le respect des dépendances identifiées tout en évitant les
redondances et les anomalies de stockage associées. Ces concepts
permettent des analyses fines et rigoureuses des propriétés du système
d’information et du schéma logique relationnel de sa base de données,
basées sur des raisonnements formels. Certains aspects difficiles à
déterminer de façon intuitive peuvent être déduits de façon formelle grâce
à ces analyses. Par exemple, étant donné un ensemble de dépendances
auxquelles les extensions des relations d’une base de données sont
contraintes, il est possible de déterminer formellement les dépendances
élémentaires, la couverture minimale de cet ensemble de dépendances, les
clés candidates des relations, les redondances possibles dans les extensions
de ces relations, la forme normale de chaque relation, etc. En somme, pour
la phase de conception, ces concepts permettent de définir de véritables
techniques d’ingénierie du schéma logique relationnel d’une base de
données basées sur des procédés automatisables.

b) Les approches de conception basées sur des outils de modélisation


conceptuelle

Les outils de modélisation conceptuelle comme le modèle


entité/association et UML ont été développés avec comme finalité de
permettre la conception du schéma conceptuel de la base de données d’un
système d’information selon une démarche plus intuitive, qui fait
abstraction du schéma logique de cette base de données.

Ces outils de modélisation permettent de découpler le schéma conceptuel


qui sert à définir le « QUOI » et le schéma logique qui dit comment les
données sont organisées et peuvent être manipulées par le développeur.

132
SGBD relationnels – Tome 1, État de l’art

Ces outils de modélisation offrent des concepts abstraits de haut niveau


orientés sémantique, permettant une représentation graphique de la
perception du monde réel, découlant de l’analyse des besoins exprimés par
les utilisateurs. Ces outils de modélisation permettent d’expliciter dans
cette représentation graphique, la perception intuitive de l’univers
considéré, en termes d’entités de cet univers et d’associations entre entités,
de propriétés portées par ces entités et ces associations, et de contraintes
d’intégrité exprimées en termes de dépendances entre propriétés d’une
même entité, de dépendances entre entités et de cardinalités des
associations.

Ceci permet de définir un schéma conceptuel des données du système


d’information, qui identifie et explicite la signification des attributs et des
dépendances entre attributs, en s’appuyant sur la perception que les
utilisateurs ont du monde réel et en faisant abstraction de tout modèle de
données du niveau externe ou logique.

À partir d’un schéma conceptuel des données, le schéma logique relationnel


de la base de données peut ensuite être dérivé à l’aide de règles prédéfinies
comme celles rappelées dans le paragraphe 2.2.4.

Ces outils de modélisation conceptuelle sont de ce fait particulièrement


bien adaptés pour l’analyse des besoins des utilisateurs, c’est-à-dire
l’analyse du « QUOI ». Ils permettent d’aborder cette analyse avec le
niveau d’abstraction souhaité en se focalisant sur une perception du monde
réel, ce qui a l’avantage de faciliter le dialogue entre le développeur et les
utilisateurs.

c) Les approches de conception utilisées en pratique

Dans la pratique, l’utilisation des outils de modélisation conceptuelle et


celle des concepts du modèle relationnel et des raisonnements formels que
ces concepts permettent doivent être combinées. Dans les premières étapes
du processus d’analyse et de conception, l’identification, dans l’univers
considéré, des entités et de leurs associations, doit être abordée de façon
intuitive en s’appuyant sur la perception qu’on a de cet univers et sur les
concepts sémantiques permettant un niveau d’abstraction qu’offrent les

133
Joachim TANKOANO

outils de modélisation conceptuelle. Dans les étapes plus avancées de ce


processus, l’analyse plus fine des propriétés de ces entités et de ces
associations doit quant à elle s’appuyer sur les raisonnements formels que
permettent les concepts du modèle relationnel.

3.5.3. Exemples de méthodes relationnelles de conception d’un


schéma relationnel

Les deux méthodes présentées ici prennent en entrée une relation et les
dépendances qui doivent être maintenues entre les attributs dans toute
extension légale de cette relation. Cette relation peut être une abstraction
d’un système d’information. Dans ce cas, on parle de relation universelle.
Cette relation peut aussi être qu’une abstraction d’un sous-ensemble
cohérent du système d’information ou d’un objet complexe dans l’univers
étudié.

a) Méthode de conception à l’aide d’un algorithme de décomposition en


FNBC

Cette méthode amène le concepteur à procéder à des décompositions de la


relation en entrée, afin de la mettre successivement en 2FN, en 3FN et en
FNBC.

Elle comporte deux grandes étapes :


• Le calcul de la couverture minimale des dépendances fonctionnelles
de la relation initiale
• L’utilisation de la couverture minimale calculée pour décomposer
successivement la relation initiale en 2FN, en 3FN et en FNBC.

Cette méthode comporte deux grands inconvénients :


• Elle peut amener à trop décomposer, notamment à décomposer des
relations qui sont déjà en FNBC
• Sans heuristiques appropriés, elle peut aussi conduire à des pertes
de dépendances fonctionnelles.

134
SGBD relationnels – Tome 1, État de l’art

Cette méthode n’est donc pas recommandable pour une application réelle.

Exemple 3.5.3.i : Soit la relation « R (A, B, C, D, E, F, G) » contrainte par les


dépendances fonctionnelles ci-après : « A, B → C », « A, B → D », « A, B →
E », « A, B → F », « B → C », « D → E », « D → F », « G → A ».

Pour ce qui concerne le calcul de la couverture minimale des dépendances


fonctionnelles, on peut noter ce qui suit :
• Toutes les dépendances fonctionnelles sont déjà sous leur forme
canonique
• Comme « C  B+ », « B → C » peut remplacer « A, B → C »
• On peut supprimer « A, B → E » car déductible par transitivité de
« A, B → D » et de « D → E »
• On peut supprimer « A, B → F » car déductible par transitivité de
« A, B → D » et de « D → F »
• Ceci donne comme couverture minimale : « A, B → D », « B → C »,
« D → E », « D → F », « G → A ».

En tenant compte de cette couverture minimale et en examinant toutes les


combinaisons d’attributs possibles, on peut établir que la clé primaire de la
relation « R (A, B, C, D, E, F, G) » est « B, G » car « (B, G)+ » = « (A, B, C, D,
E, F, G) ».

L’utilisation de cette couverture minimale pour décomposer


successivement la relation « R (A, B, C, D, E, F, G) » en 2FN, en 3FN et en
FNBC donne ce qui suit :

1ère mise en 2FN : « R1 (B, C) », « R2 (B, G, A, D, E, F) »


2ème mise en 2FN : « R1 (B, C) », « R3 (G, A) », « R4 (B, G, D, E, F) »
1ère mise en 3FN : « R1 (B, C) », « R3 (G, A) », « R5 (D, E) », « R6 (B, G, D, F) »
2ème mise en 3FN : « R1 (B, C) », « R3 (G, A) », « R5 (D, E) », « R7 (D, F) »,
« R8 (B, G, D) »
Mise en FNBC : Les relations obtenues après la mise en 3FN sont aussi en
FNBC.

La décomposition finale obtenue, c'est-à-dire, « R1 (B, C) », « R3 (G, A) »,


« R5 (D, E) », « R7 (D, F) » et « R8 (B, G, D) », est sans perte d’information.

135
Joachim TANKOANO

Cependant on peut constater que :


• La dépendance fonctionnelle « A, B → D » a été perdue parce
qu’aucune des relations résultantes ne contient à la fois les trois
attributs concernés
• La décomposition en « R5 (D, E) » et « R7 (D, F) » est excessive. Un
regroupement de ces deux relations pourrait constituer une
meilleure solution.

b) Méthode de conception à l’aide d’un algorithme de synthèse

Cette méthode qui conduit à de meilleurs résultats, permet d’obtenir des


schémas relationnels en 3ème forme normale. Elle est constituée des étapes
ci-après :
• Calcul de la couverture minimale des dépendances
fonctionnelles élémentaires.
• Calcul des clés candidates de la relation initiale en s’appuyant sur la
couverture minimale calculée.
• Création, pour chaque source de dépendance
fonctionnelle élémentaire, d’une relation comprenant comme
attributs, la source et tous ses dépendants. Cette règle applique le
théorème de la décomposition en évitant la perte des dépendances
fonctionnelles et en regroupant au mieux les attributs.
• Si aucune des clés candidates de la relation initiale n'est présente
dans au moins une des relations créées, ajouter une relation
supplémentaire constituée des attributs d’une des clés candidates de
la relation initiale. Cette nouvelle relation a pour vocation de
traduire la matérialisation d’un type d’association non porteuse
d’attributs (avec des cardinalités (*,n) de part et d’autre) entre des
entités dont les identifiants font partie des attributs de cette relation
additionnelle. Si la relation ajoutée est composée de plus de deux
attributs, une analyse fine peut permettre de la décomposer si elle
recouvre plusieurs associations.

136
SGBD relationnels – Tome 1, État de l’art

• Élimination des relations incluses dans d’autres, c'est-à-dire, des


relations dont tous les attributs sont contenus dans une autre
relation. Si de telles relations existent, elles indiquent la possibilité
d’une mise en forme normale de BOYCE-CODD des relations qui les
incluent.

Exemple 3.5.3.ii : Soit la relation « R (A, B, C, D) » contrainte par les


dépendances fonctionnelles ci-après : « A → B » et « B, C → D ».

On peut vérifier aisément que ces deux dépendances fonctionnelles forment


une couverture minimale en montrant d’une part que sans « A → B », « B »
n’appartient pas à « {A}+ » et d’autre part que sans « B, C → D », « D »
n’appartient pas à « {B, C}+ ».

En calculant la fermeture de toutes les combinaisons possibles des attributs


« A », « B », « C » et « D », on peut montrer que la relation « R (A, B, C, D) »
a comme clé primaire « A, C ».

Sur cette base, l’application du 3ème point de cette méthode de synthèse


conduit à la création des relations ci-après :
R1 (A, B)
R2 (B, C, D)

La clé primaire « A, C » n’étant incluse dans aucune de ces deux relations,


l’application du 4ème point de la méthode conduit à la décomposition finale
ci-après :
R1 (A, B)
R2 (B, C, D)
R3 (A, C)

Cette décomposition est sans perte d’information et de dépendance


fonctionnelle.

Une application de la méthode de conception à l’aide de l’algorithme de


décomposition en FNBC aurait pu conduire à une mise en 2FN avec la
décomposition finale ci-après, où on perd la dépendance fonctionnelle « B,
C→D»:
R1 (A, B)

137
Joachim TANKOANO

R2 (A, C, D)

Exemple 3.5.3.iii : Soit la relation « R (A, B, C, D, E, F, G) » contrainte par


les dépendances fonctionnelles ci-après : « A, B → C », « A, B → D », « A,
B → E », « A, B → F », « B → C », « D → E », « D → F », « G → A ».

L’exemple 3.4.3.i a montré que cette relation a comme couverture minimale


des dépendances fonctionnelles : « A, B → D », « B → C », « D → E », « D
→ F », « G → A ».

Nous avons aussi montré que sur cette base, cette relation « R (A, B, C, D,
E, F, G) » a comme clé primaire « B, G ».

L’application du 3ème point de cette deuxième méthode sur la relation « R


(A, B, C, D, E, F, G) » conduit à la décomposition ci-après :
R1 (D, E, F)
R2 (A, B, D)
R3 (B, C)
R4 (G, A)

La clé « B, G » n’étant incluse dans aucune de ces quatre relations,


l’application du 4ème point de la méthode conduit à la décomposition finale
ci-après :
R1 (D, E, F)
R2 (A, B, D)
R3 (B, C)
R4 (G, A)
R5 (B, G)

Contrairement au résultat obtenu dans l’exemple 3.4.3.i avec la méthode de


conception à l’aide de l’algorithme de décomposition en FNBC, cette
décomposition est non seulement sans perte d’information, mais elle est
aussi sans perte de dépendance fonctionnelle et ne conduit pas une
décomposition excessive.

Exemple 3.5.3.iv : Soit la relation « Pièces (NoPièce, PrixUnit, TVA, Libellé,


Catégorie) » contrainte par les dépendances fonctionnelles ci-après :

138
SGBD relationnels – Tome 1, État de l’art

« NoPièce → PrixUnit, TVA, Libellé, Catégorie » et « Catégorie → TVA ».

Pour ce qui concerne le calcul de la couverture minimale des dépendances


fonctionnelles on peut noter ce qui suit :
• La mise des dépendances fonctionnelles sous leur forme canonique
donne ce qui suit :
DF01 : NoPièce → PrixUnit
DF02 : NoPièce → TVA
DF03 : NoPièce → Libellé
DF04 : NoPièce → Catégorie
DF05 : Catégorie → TVA

• On peut noter que toutes ces dépendances fonctionnelles sont


élémentaires
• Sans la dépendance fonctionnelle DF04, l’attribut « Catégorie »
appartient à « {NoPièce}+ ». On peut donc de ce fait supprimer cette
dépendance fonctionnelle pour obtenir la couverture minimale des
dépendances fonctionnelles ci-après :
DF01 : NoPièce → PrixUnit
DF02 : NoPièce → TVA
DF03 : NoPièce → Libellé
DF05 : Catégorie → TVA.

En calculant la fermeture de toutes les combinaisons des attributs


« NoPièce », « PrixUnit », « TVA », « Libellé » et « Catégorie », on peut
montrer que la clé primaire de la relation « Pièces (NoPièce, PrixUnit, TVA,
Libellé, Catégorie) » est « NoPièce ».

À partir de ces résultats, on peut déduire que dans une extension légale de
la relation « Pièces (NoPièce, PrixUnit, TVA, Libellé, Catégorie) », un
couple de valeurs de « {Catégorie, TVA} » peut se répéter plusieurs fois et
engendrer ainsi des redondances, parce que dans la dépendance
fonctionnelle « Catégorie → TVA », l’attribut « Catégorie » n’est pas une
clé candidate.

139
Joachim TANKOANO

On peut aussi déduire de ces résultats que la relation « Pièces (NoPièce,


PrixUnit, TVA, Libellé, Catégorie) » est en 2FN mais n’est pas en 3FN car
l’attribut non-clé « Catégorie » est un déterminant.

La décomposition de la relation « Pièces (NoPièce, PrixUnit, TVA, Libellé,


Catégorie) » à l’aide de l’algorithme de synthèse donne le schéma
relationnel ci-après :
Catégories (Catégorie, TVA)
Pièces (NoPièce, PrixUnit, Libellé, Catégorie).

Exemple 3.5.3.v : Soit la relation « Primes (NoTypeMachine, NomMachine,


NoTechnicien, MontantPrime, Nom-technicien) » contrainte par les
dépendances fonctionnelles ci-après : « NoTypeMachine →
NomMachine », « NoTechnicien → NomTechnicien » et
« NoTypeMachine, NoTechnicien → MontantPrime ».

En procédant comme dans les autres exemples ci-dessus, on peut montrer


ce qui suit :
• L’ensemble des dépendances fonctionnelles constitue une
couverture minimale
• La clé de la relation est « NoTypeMachine, NoTechnicien »
• Dans une extension légale de cette relation, des couples de valeurs
de « NoTypeMachine, NomMachine » et de « NoTechnicien,
NomTechnicien » peuvent se répéter plusieurs fois et engendrer des
redondances
• La relation est en 1FN mais n’est pas en 2FN car chacun des 2
attributs qui composent la clé primaire est le déterminant d’un
attribut non-clé.
• La décomposition de la relation « Primes (NoTypeMachine,
NomMachine, NoTechnicien, MontantPrime, Nom-technicien) »
par application de la méthode de conception à l’aide de l’algorithme
de synthèse est :
Techniciens (NoTechnicien, NomTechnicien)
Machines (NoTypeMachine, NomMachine)

140
SGBD relationnels – Tome 1, État de l’art

Primes (NoTypeMachine, NoTechnicien, MontantPrime).


Exemple 3.5.3.vi : Soit la relation « Employés (NoEmployé, NoEquipe,


NoProjet, NomEmployé, NomProjet, AdresseEmployé) » contrainte par
les dépendances fonctionnelles ci-après : « NoEmployé, NoEquipe →
NoProjet », « NoEmployé → NomEmployé », « NoEmployé →
AdresseEmployé » et « NoProjet → NomProjet ».

En procédant comme dans les autres exemples ci-dessus, on peut montrer


ce qui suit :
• L’ensemble des dépendances fonctionnelles constitue une
couverture minimale
• La clé primaire de la relation est « NoEmployé, NoEquipe »
• Dans une extension légale de cette relation, des couples de valeurs
de « NoEmployé, NomEmployé », de « NoEmployé,
AdresseEmployé » et de « NoProjet, NomProjet » peuvent se
répéter plusieurs fois et engendrer des redondances.
• La relation est en 1FN mais n’est pas en 2FN car « NoEmployé » qui
fait partie de la clé primaire est le déterminant d’un attribut non-clé.
• La décomposition de la relation « Employés (NoEmployé,
NoEquipe, NoProjet, NomEmployé, NomProjet,
AdresseEmployé) » par application de la méthode de conception à
l’aide de l’algorithme de synthèse est :
Employés (NoEmployé, NomEmployé, AdresseEmployé)
Equipes (NoEmployé, NoEquipe, NoProjet)
Projets (NoProjet, NomProjet).

Exemple 3.5.3.vii : Soit la relation Adresses (Rue, Ville, CodePostal)


contrainte par les dépendances fonctionnelles ci-après : « CodePostal →
Ville » et « Rue, Ville → CodePostal ».

En procédant comme dans les autres exemples ci-dessus, on peut montrer


ce qui suit :

141
Joachim TANKOANO

• L’ensemble des dépendances fonctionnelles constitue une


couverture minimale
• Les clés candidates de la relation sont « Rue, Ville » et « CodePostal,
Rue »
• Dans une extension légale de cette relation un couple de valeurs de
« CodePostal, Ville » peut se répéter plusieurs fois et engendrer des
redondances
• La relation est en 3FN mais n’est pas en FNBC car « CodePostal » est
un déterminant d’une DF sans être une clé primaire. Le passage à la
FNBC conduirait à une perte de DF
• La décomposition de la relation Adresses (Rue, Ville, CodePostal)
par application de la méthode de conception à l’aide de l’algorithme
de synthèse donne dans une première phase ce qui suit :
Adresses (Rue, Ville, CodePostal)
CP (CodePostal, Ville).

La 2ème relation est incluse dans la 1ère et doit de ce fait être


supprimée pour donner le schéma relationnel final ci-après qui est
en 3FN sans être en FNBC :
Adresses (Rue, Ville, CodePostal)

Exemple 3.5.3.viii : Soit la relation « Professeur (NoProfesseur,


NomProfesseur, Certificat, NomDépartement,
ResponsableDépartement) » contrainte par les dépendances fonctionnelles
ci-après : « NoProfesseur → NomProfesseur », « NoProfesseur →
NomDépartement » et « NomDépartement →
ResponsableDépartement ».

En procédant comme dans les autres exemples ci-dessus, on peut montrer


ce qui suit :
• L’ensemble des dépendances fonctionnelles constitue une
couverture minimale
• La clé primaire de la relation est « NoProfesseur, Certificat »

142
SGBD relationnels – Tome 1, État de l’art

• Dans une extension légale de cette relation des couples de valeurs de


« NoProfesseur, NomProfesseur », « NoProfesseur,
NomDépartement » et « NomDépartement,
ResponsableDépartement » peuvent se répéter plusieurs fois et
engendrer des redondances
• La relation est en 1FN mais n’est pas en 2FN car « NoProfesseur »
qui fait partie de la clé primaire est le déterminant d’un attribut non-
clé
• La décomposition de la relation « Professeur (NoProfesseur,
NomProfesseur, Certificat, NomDépartement,
ResponsableDépartement) » par application de la méthode de
conception à l’aide de l’algorithme de synthèse est :
Professeur (NoProfesseur, NomProfesseur, NomDépartement)
Département (NomDépartement, ResponsableDépartement),
CertificatsProf (NoProfesseur, Certificat).

Exemple 3.5.3.ix : Soit la relation « Employés (NoEmployé, Aptitude,


Pays) » contrainte par les dépendances multivaluées ci-après :
« NoEmployé - Aptitude » et « NoEmployé - Pays ». Ces
dépendances expriment le fait que chaque employé a plusieurs aptitudes
(programmer, exploiter, ...) et intervient dans plusieurs pays avec la contrainte que
dans chaque pays où il intervient il met en oeuvre toutes les aptitudes qu’il possède
et pour chaque aptitude qu’il possède il la met en œuvre dans tous les pays où il
intervient.

En procédant comme dans les autres exemples ci-dessus, on peut montrer


ce qui suit :
• L’ensemble des dépendances multivaluées constitue un ensemble
irréductible, donc une couverture minimale
• La clé primaire de la relation est « NoEmployé, Aptitude, Pays »
• Dans une extension légale de cette relation des couples de valeurs de
« NoEmployé, Aptitude » et « NoEmployé, Pays » peuvent se
répéter plusieurs fois et engendrer des redondances

143
Joachim TANKOANO

• La relation est en FNBC, car elle n’a pas de dépendance


fonctionnelle, mais n’est pas en 4FN car les déterminants des
dépendances multivaluées ne sont pas des clés
• La décomposition de la relation par application de la méthode de
conception à l’aide de l’algorithme de synthèse est :
EmplApt (NoEmployé, Aptitude)
EmplPays (NoEmployé, Pays).

Exemple 3.5.3.x : Soit la relation « Employés (NoEmployé, Aptitude,


Pays) » contrainte par aucune dépendance, permettant d’exprimer le fait
que chaque employé a certaines aptitudes (programmer, exploiter, ...) et que pour
chacune des aptitudes qu’il possède il la met en œuvre dans certains pays.

En procédant comme dans les autres exemples ci-dessus, on peut conclure


ce qui suit :
• Cette relation n’a ni dépendance fonctionnelle, ni dépendance
multivaluée, ni dépendance de jointure
• La clé primaire de cette relation est « NoEmployé, Aptitude, Pays »
• Dans une extension légale de cette relation, aucune redondance n’est
possible
• Cette relation est en 5FN car il n’y a ni dépendance fonctionnelle, ni
dépendance multivaluée, ni dépendance de jointure
• Cette relation n’est pas décomposable.

Exemple 3.5.3.xi : L’objectif de cet exemple est de mettre en évidence la


similarité qui caractérise les méthodes basées sur le modèle relationnel et
celles basées sur les outils de modélisation conceptuelle. Cette similarité est
mise en évidence en montrant à travers cet exemple qu’il est possible de
déduire à partir du schéma conceptuel de la base des données d’un système
d’information, tous les résultats attendus d’une méthode de conception
entièrement basée sur le modèle relationnel, à savoir :

144
SGBD relationnels – Tome 1, État de l’art

• La liste des attributs de ce système d’information et des


dépendances qui existent entre ces attributs
• Les clés candidates de la relation universelle qui schématise ce
système d’information
• La décomposition sans perte de données et de dépendance de cette
relation universelle en relations du schéma logique relationnel de la
base de données du système d’information.

Considérons à cet effet le schéma conceptuel défini à l’aide du modèle


Entité/Association dans l’exemple 2.2.4.i.

Ce schéma conceptuel explicite la liste et la signification des attributs du


système d’information à travers une modélisation de la perception que les
utilisateurs ont sur le monde réel, définissant le « QUOI ». Il s’agit des
attributs ci-après : « Nocli », « NomCli », « NoCmde », « DateCmde »,
« Qtée », « RefProd », « LibelléProd », « PrixProd », « CodeTaxe »,
« TauxTaxe ».

Ce schéma conceptuel explicite aussi comme suit les dépendances qui


existent entre ces attributs :
Nocli → NomCli
NoCmde → DateCmde, Nocli
RefProd → LibelléProd, PrixProd
NoCmde, RefProd → Qtée
CodeTaxe → TauxTaxe

En calculant la fermeture de chaque combinaison des attributs identifiés, on


peut montrer que la clé de la relation universelle qui schématise le système
d’information concerné est « NoCmde, RefProd, CodeTaxe ».

La décomposition de cette relation universelle par application de la


méthode de conception à l’aide de l’algorithme de synthèse donne dans une
première phase ce qui suit :
Clients (Nocli, NomCli) (1)
Commandes (NoCmde, DateCmde, Nocli) (2)
Produits (RefProd, LibelléProd, PrixProd) (3)
ProduitsCommandes (NoCmde, RefProd, Qtée) (4)

145
Joachim TANKOANO

Taxes (CodeTaxe, TauxTaxe) (5)

L’absence de la clé « NoCmde, RefProd, CodeTaxe » de la relation


universelle dans ces relations entraine l’ajout de la relation :
R (NoCmde, Ref, CodeTaxe) (6)

L’analyse fine de cette relation permet de mettre en évidence qu’elle


recouvre deux types d’associations de type « n, m » : les associations entre
les commandes et les produits et les associations entre les produits et les
taxes. Ceci permet de remplacer cette relation par la relation (4) et la
relation :
TaxesProduits (RefProd, CodeTaxe) (7)

On peut noter que dans le schéma logique relationnel qui en résulte, les
contraintes d’intégrité exprimées par les dépendances fonctionnelles
identifiées dans le système d’information sont garanties à l’aide de clés
primaires et de clés étrangères.

146
SGBD relationnels – Tome 1, État de l’art

BIBLIOGRAPHIE
Amstrong W.W.: Dependency Structures of Database Relationships -
IFIP World Congress, North-Holland Ed., p. 580-583, 1974.

Beeri C., Fagin R., & Howard J. H.: A Complete Axiomatization for
Functional and Multi-Valued Dependencies in Database Relations
- Proc. A CM SIGMOD 1977 Int. Conf. on Management of Data (Los
Angeles, 1977).

Bernstein P.A.: Synthesizing Third Normal Form Relations from


Functional Dependencies - ACM Transactions on Database
Systems, Vol.1, n° 4, p. 277-298, 1976.

Bouzeghoub M., Gardarin G. & Métais E.: SECSI: An Expert System


for Database Design - 11th Very Large Data Base International
Conference, Morgan Kaufman Pub., Stockolm, Suède, 1985.

Date C.J. : Introduction aux bases de données - 8è édition, Vuibert,


Paris, 2004

Codd E. F.: Further Normalization of the Data Base Relational Model.


- Courant Computer Science Symposia 6, Data Base Systems. (New
York, May 24-25,1971). Prentice-Hall.

Codd E. F.: Normalized Database structure: A Brief Tutorial - ACM


SIGFIDET Workshop on Data Description, Access and Control, p.
1-17, Nov. 1971.

Codd E. F.: The relational model for database management - Second


Edition, Addison-Wesley Publishing Company, Inc., 1990

Fagin R.: Multivalued Dependencies and a New Normal Form for


Relational Databases - ACM Transactions on Database Systems,
Vol.2, n° 3, p. 262-278, Sept. 1977.

Fagin R.: Normal Forms and Relational Database Operators - Proc.


ACM SIGMOD Intl. Conf. on Management of Data, Boston, p.153-
160, Juin 1979.

Fagin R.: A Normal Form for Relational Databases that is Based on


Domains and Keys - ACM TODS, vol. 6, n° 3, septembre 1981, p. 387-

147
Joachim TANKOANO

415.

Gardarin G. : Bases de données, 5e tirage 2003 - EYROLLES

Maier D.: The Theory of Relational Databases, 1st edition - Computer


Science Press, (March 1983)

MIRANDA S. & BUSTA J-M. : L'art des bases de données, Tome 2, Les
bases de données relationnelles - 2è édition, Eyrolles, 1990

Silberschatz A., Korth H.F. & Sudarshan S.: Database system


concepts, sixth edition - McGraw-Hill, 2011

148
Chapitre 4. : Présentation du langage SQL

4.1. Introduction
Le rôle, les origines et les évolutions successives du langage SQL

SQL (Structured Query Language) est le langage de définition, de


manipulation, et de contrôle des bases de données relationnelles, que tout
utilisateur qui veut créer, manipuler y compris dans un programme, ou
administrer une base de données relationnelle doit connaître et maîtriser.
Ce langage constitue le seul moyen qu’un utilisateur (développeur
d’applications ou administrateur de bases de données) peut utiliser pour
interagir avec un SGBD relationnel, qu’il ne peut percevoir et manipuler
que comme étant une boîte noire.

Ce langage est issu du langage SEQUEL (Structured English Query


Language) conçu par IBM pour son SGBD relationnel SYSTEM-R.

Seize ans après l’apparition du modèle relationnel de E.F. CODD en 1970,


ce langage a été proposé et retenu en 1986 par l’ANSI (American National
Standards Institute) comme étant le langage d’interface standard des SGBD
relationnels.

Après sa reconnaissance par l’ANSI, ce langage normalisé a été enrichi


progressivement : SQL1 86 version minimale, SQL1 89 intégré, SQL2 92
langage complet à 3 niveaux, SQL3 99. L’objectif principal poursuivi a
d’abord été de prendre en compte les principaux concepts et fonctionnalités
du modèle relationnel tels que définis par E.F. CODD. Ensuite, il s’est agi
d’inclure dans ce modèle de nouveaux concepts visant la prise en charge de
nouveaux besoins. En rappel, les principaux concepts et fonctionnalités du
modèle relationnel tels que définis par E.F. CODD sont les suivants :
• Pour ce qui concerne la définition de la structure logique des
données, c'est-à-dire du schéma logique de la base de données :
− Le concept de relation n-aire (constituée de tuples)
− Le concept de domaine / attribut
Joachim TANKOANO

− Le concept de clé primaire


− Le concept de clé étrangère.
• Pour ce qui concerne l’intégrité des données liée à leur structure
logique :
− L’intégrité de domaine
− L’intégrité de relation
− L’intégrité de référence.
• Pour ce qui concerne la manipulation des données :
− Les fonctions applicables aux attributs (fonctions de
composition et prédicats pour la formulation des requêtes
selon une approche prédicative)
− Les opérateurs algébriques ensemblistes (union, intersection,
différence, produit cartésien)
− Les opérateurs algébriques relationnels (sélection, projection,
jointure, division).

Aujourd’hui, SQL est considéré comme étant le nom générique d’une


famille de langages en perpétuelle évolution, pas toujours compatibles
entre eux, proposés par les éditeurs de SGBD relationnels en s’appuyant
chacun à sa façon sur la norme internationale.

Comme nous le verrons en partie dans le tome 2 de l’ouvrage, les dernières


versions des éditeurs les plus avancés ont intégré dans la technologie des
bases de données relationnelles, des extensions concernant la prise en
compte de concepts et fonctionnalités additionnels comme ceux relatifs à la
répartition, aux objets persistants, à XML, aux données spatiales et à l’aide
à la décision.

Les apports des langages formels du modèle relationnel dans le langage


SQL

Inspiré au départ des langages prédicatifs en calcul relationnel à variables


n-uplets, le langage de manipulation des données de SQL est parvenu à un
stade où il intègre aussi les opérateurs de l’algèbre relationnelle de façon

150
SGBD relationnels – Tome 1, État de l’art

explicite (*, , , , -) et implicite (, ). Il est de ce fait un langage hybride,


mi-prédicatif et mi-ensembliste, avec de grandes possibilités pour la
formulation de requêtes équivalentes, en raisonnant conjointement ou
alternativement en termes de prédicats ou d’opérateurs algébriques
relationnels.

Le fait d’être un langage prédicatif relationnel permet à SQL d’être plus


proche du langage parlé. En outre, en tant que langage prédicatif
relationnel, sa signature a été fortement enrichie au niveau des symboles de
fonctions et des symboles de prédicats (qui intègrent dorénavant les
prédicats ensemblistes), ce qui lui confère un grand pouvoir d’expression.
Cette possibilité, que la logique du 1er ordre permet, fait de SQL un langage
extensible, apte à faciliter la prise en compte de nouvelles fonctionnalités
par simple ajout de nouvelles fonctions et de nouveaux prédicats.

Toutefois, en tant que langage prédicatif relationnel, SQL n’inclut pas les
symboles de prédicats du type « R (t) » dont la valeur de vérité est « vraie »
si « t » est un tuple de la relation « R », pouvant servir à définir une relation
comme étant constituée de propositions qui vérifient ce prédicat. Ceci a
comme conséquence que SQL ne permet pas, sous sa forme prédicative,
d’effectuer l’union de deux relations. Pour effectuer cette opération à l’aide
de SQL, il faut obligatoirement utiliser l’opérateur algébrique « UNION »
qu’il propose.

En outre, en tant que langage prédicatif relationnel, SQL ne propose ni


d’opérateur pour le quantificateur universel («  »), ni d’opérateur pour
l’implication («  »). Toutefois, en tenant compte de l’équivalence « x F 
 (x  F) », l’opérateur « NOT EXIST » qui existe dans le langage, peut
être utilisé à la place du quantificateur universel («  »). De même,
l’équivalence « F1  F2  (F1  F2) » permet de se passer de
l’implication «  ».

En tant que langage algébrique relationnel, SQL ne propose pas d’opérateur


algébrique relationnel pour la division (« ÷ »). Toutefois, l’opérateur « NOT
EXIST » qui existe dans le langage peut s’utiliser pour effectuer une
division.

Tout ceci, ajouté aux écarts par rapport au modèle relationnel de E.F. CODD

151
Joachim TANKOANO

dans le but d’intégrer des fonctionnalités additionnelles, telles que celles


relatives aux traitements à appliquer au résultat (agrégation du résultat, tri
du résultat selon une relation d’ordre ou hiérarchique) afin de coller au plus
près à certains besoins pratiques récurrents des développeurs, fait dire aux
puristes que SQL est loin d’être un langage relationnel parfait.

La prise en compte par SQL des contraintes d’intégrité découlant des règles
de gestion de l’entreprise

Comme nous l’avons déjà indiqué, les contraintes d’intégrité d’une base de
données relationnelle, découlant des règles de gestion de l’entreprise,
concernent principalement :
• Le domaine de définition des valeurs légales de chaque attribut
• La non-nullité de ces attributs
• Les dépendances entre attributs
• Les autres invariants, définis par un prédicat, qui lient les valeurs de
groupes d’attributs ou des dépendances entre attributs.

Comme nous l’avons vu dans le chapitre 3, l’une des principales finalités


des techniques de conception d’une base de données relationnelle est de
faire en sorte que le contrôle des contraintes d’intégrité auxquelles les
relations sont soumises (intégrité de relation, intégrité d’unicité et intégrité
référentielle) soit suffisant pour garantir le respect des contraintes
d’intégrité relatives aux dépendances qui doivent être maintenues entre des
groupes d’attributs découlant des règles de gestion de l’entreprise.

Le langage de définition des données de SQL permet (voir paragraphe


4.4.2) d’associer à la définition de chaque table servant au stockage d’une
relation, les contraintes d’intégrité relatives :
• Aux valeurs légales des attributs (y compris à leur non-nullité)
• Aux contraintes d’intégrité intégrées nativement dans le modèle
relationnel, à savoir, l’intégrité de relation, l’intégrité d’unicité et
l’intégrité référentielle, ce qui permet de facto de prendre en compte
les contraintes de dépendances entre attributs lorsqu’une base de
données est bien conçue

152
SGBD relationnels – Tome 1, État de l’art

• À d’autres prédicats sur les attributs que la base de données doit


vérifier en permanence.

SQL donne aussi la possibilité, à travers son langage de définition des


données, de créer une table virtuelle (voir paragraphe 4.4.3) ou un
déclencheur (voir chapitre 5) associé à une table réelle ou virtuelle, ayant la
capacité dans les deux cas de garantir une contrainte d’intégrité définie sur
des attributs par un prédicat, découlant d’une règle de gestion de
l’entreprise. En s’appuyant sur des calculs de valeurs effectués à l’aide de
sous-requêtes SQL, ceci peut concerner de façon plus large des
dépendances entre groupes d’attributs, des équations entre des données
élémentaires, ensemblistes ou agrégées, mais aussi des règles
comportementales relatives à l’évolution de la valeur d’un attribut.

Toutefois, il est important de retenir que les contraintes d’intégrité définies


à l’aide de prédicats sur des attributs appartenant à des lignes différentes
d’une table ou de plusieurs tables peuvent avoir comme conséquence un
accroissement du coût des opérations de mise à jour de la base de données
concernée, parce que ces opérations peuvent nécessiter une évaluation de
ces prédicats sur un très grand nombre de lignes. Il est donc essentiel que
cette possibilité ne soit pas utilisée comme étant une panacée.

Ces différentes possibilités définies par la norme sont mises en œuvre


différemment, entièrement ou en partie, par les éditeurs des SGBD soit pour
prévenir la violation des contraintes d’intégrité découlant des règles de
gestion de l’entreprise, soit pour détecter la survenue de la violation d’une
contrainte d’intégrité afin d’annuler les opérations qui ont conduit à cette
violation.

Les différents modes d’utilisation du langage SQL

L’utilisateur peut exécuter les ordres SQL de diverses manières.

L’exécution peut se faire de façon interactive. Ce mode d’utilisation permet


à l’utilisateur de soumettre à un programme utilitaire, appelé client SQL,
son ordre SQL de définition, de contrôle ou de manipulation des données.
Ce programme utilitaire se charge, après soumission par l’utilisateur, de
transmettre cette requête à un moteur SQL du SGBD pour exécution, de

153
Joachim TANKOANO

récupérer le résultat et de l’afficher à l’écran de l’utilisateur avant de se


mettre en attente de la prochaine requête. Il s’agit du mode d’utilisation le
plus prisé par les administrateurs de bases de données.

En outre, l’extension du langage SQL en langage de programmation


complet, permet d’exécuter et de traiter le résultat d’un ordre SQL à partir
d’un programme. Cette approche, définie dans la norme et mise en œuvre
par certains éditeurs de SGBD, est présentée dans le chapitre 5.

Il est enfin possible de soumettre l’exécution d’un ordre SQL et de traiter le


résultat de cette exécution à partir de programmes écrits à l’aide d’un
langage de programmation quelconque existant (C, C++, Java, PHP, etc.) en
s’appuyant sur une API (Applications Programming Interface) de bas
niveau, offert par un connecteur de bases de données comme ODBC ou
JDBC, ou sur une API de plus haut niveau construit au-dessus de ces API
de bas niveau. Dans ce cas, l’ordre SQL est passé en paramètre en cours
d’exécution, lors de l’appel d’une fonction ou d’une méthode de l’API et ne
peut être analysé qu’à ce moment. On parle dans ce cas d’ordres SQL
dynamiques (« dynamic SQL »). Des pseudo-instructions définies et
incorporées comme une extension du langage hôte peuvent aussi servir
pour l’encapsulation des ordres SQL. On parle « d’embedded SQL ». Dans
ce cas, un pré-compilateur doit analyser ces pseudo-instructions, afin de
traduire les ordres SQL qu’elles contiennent en appels de fonctions ou de
méthodes de l’API utilisé pour l’accès à la base de données. Le chapitre 6
présente les connecteurs ODBC et JDBC.

Le contenu du chapitre

Ce qui suit est une présentation introductive du langage de manipulation


des données (LMD) de SQL, de son langage de définition des données
(LDD) et de son langage de contrôle des données (LCD) pour ce qui
concerne la confidentialité. Le langage de contrôle des données (LCD) pour
ce qui concerne la concurrence est présenté dans le chapitre 9.

Cette présentation et celles qui suivront dans les autres chapitres,


privilégient la syntaxe proposée par ORACLE 12c. Ce choix a été effectué
pour deux raisons. L’offre d’ORACLE figure parmi celles qui sont les plus
avancées en termes de fonctionnalités. Elle recouvre de ce fait la presque

154
SGBD relationnels – Tome 1, État de l’art

totalité des problématiques traitées dans cet ouvrage. En outre, ORACLE


DATABASE EXPRESS EDITION et SQL DEVELOPER peuvent être
téléchargés, installés et utilisés gratuitement. Le lecteur peut de ce fait créer
un environnement de travail pouvant lui permettre d’expérimenter de
façon concrète les fonctionnalités présentées.

4.2. Les concepts propres au langage SQL


Certains concepts du model relationnel ont été renommés dans le langage
SQL :
• À la place de « relation », SQL utilise le terme « table » et le terme
« vue » (pour « table virtuelle ou dérivée ») pour désigner le
conteneur d’une relation et pour définir respectivement le schéma
logique et les schémas externes d’une base de données relationnelle
• À la place de « tuple », « attribut » et « domaine », SQL utilise
respectivement les termes « ligne », « colonne » et
« type prédéfini » découlant du concept de « table ».

Par ailleurs, le concept de « confidentialité des données » qui n’existe pas


dans le modèle relationnel a été intégré dans le langage SQL. La
confidentialité et la concurrence ne sont pas des aspects liés à un modèle de
données en particulier, mais des aspects transversaux que doit intégrer tout
SGBD, indépendamment du modèle de données sur lequel il s’appuie.

Ainsi, au niveau logique, l’utilisateur voit une base de données, à travers


ces concepts, comme étant constituée de tables (réelles ou virtuelles) qui
font référence les unes aux autres et qui sont soumises à un certain nombre
de contraintes d’intégrité et de confidentialité.

Chaque table est constituée de lignes et de colonnes, les colonnes étant


typées à l’aide de types préfinis.

Comme les relations, une table peut être définie par une « clé primaire »,
des « clés uniques » et des « clés étrangères », sur la base desquelles des
contraintes d’intégrité peuvent être déduites et garanties par le SGBD.

En revanche, le concept de « type prédéfini » se focalise sur la

155
Joachim TANKOANO

représentation interne des données. Il ne permet donc pas d’expliciter


précisément l’ensemble des valeurs que peut prendre une colonne, mais
peut être renforcé par des contraintes d’intégrité définies à l’aide de
prédicats que le SGBD doit garantir.

Des contraintes de confidentialité basées sur des spécifications


d’autorisations d’accès peuvent aussi être définies par l’administrateur de
la base de données et garanties sur chaque table par le SGBD.

Toutefois, contrairement à une relation, une table peut contenir des


doublons, c'est-à-dire, des lignes qui se répètent, ce qui remet en cause la
fermeture du langage, étant entendu que le contenu d’une table qui contient
des doublons ne peut pas être assimilé à un ensemble.

4.3. Le langage de manipulation des données de SQL


La manipulation par l’utilisateur des tables qui composent une base de
données relationnelle, à l’aide du langage SQL, s’effectue au moyen des
ordres ci-après :
• SELECT … FROM … WHERE …, utilisé pour rechercher des
informations se trouvant dans plusieurs tables
• INSERT INTO …, utilisé pour insérer des lignes dans une table
• UPDATE …SET … WHERE …, utilisé pour modifier des lignes dans
une table
• DELETE … FROM … WHERE …, utilisé pour supprimer des lignes
dans une table.

Ce qui suit présente leurs usages les plus courants.

4.3.1. L’ordre SELECT / FROM / WHERE

a) Syntaxe de l’ordre

La syntaxe simplifiée de l’ordre SELECT présenté dans cette section est la


suivante :

156
SGBD relationnels – Tome 1, État de l’art

SELECT [DISTINCT] Liste_colonnes_résultat


FROM {Table_source} [,..n]
[WHERE Prédicat_résultat]
[GROUP BY Liste_de_colonnes_de_groupage]
[HAVING Condition_de_filtrage_de_groupe]
[START WITH Condition_de_départ_de_parcours_hiérarchiques]
[CONNECT BY Critère_de_parcours_hiérarchiques]
[{UNION [ALL] | INTERSECT | MINUS} (Sous_requête)]
[ORDER BY Liste_de_colonnes [ASC | DESC]]

(1) Cette syntaxe indique qu’un ordre SELECT peut être constitué de
plusieurs clauses, les deux premières étant obligatoires et les autres
facultatives.

(2) Les trois premières clauses (« SELECT », « FROM » et « WHERE »),


permettent de formuler des requêtes dans un style comparable à celui du
langage prédicatif en calcul relationnel à variables n-uplets, présenté dans
le paragraphe 2.4.2. Les requêtes formulées à l’aide de ces trois clauses
s’appuient sur une signature enrichie des langages prédicatifs en calcul
relationnel à variables n-uplets. Elles amènent à spécifier dans la clause
« SELECT » les colonnes du résultat recherché, dans la clause « FROM » les
tables requises pour le calcul de ce résultat et dans la clause « WHERE » le
prédicat que chaque ligne de ce résultat doit vérifier, par déduction à partir
des assertions contenues dans les tables citées dans la clause « FROM ». De
ce fait, les requêtes SQL formulées à l’aide de ces trois clauses ne sont rien
d’autres que des énoncés formulés dans un style proche du langage parlé,
qu’on peut interpréter comme s’il s’agissait d’énoncés de la logique du 1 er
ordre portant sur les lignes des tables citées dans la clause « FROM ». La
syntaxe proposée, permet aussi d’interpréter ces requêtes en raisonnant en
termes d’opérateurs de l’algèbre relationnelle.

Exemple 4.3.1.i : En considérant les trois tables relatives aux vols d’une
compagnie aérienne introduites dans la section 2.3.3, si le raisonnement se
fait dans la logique du 1er ordre, l’ordre SQL ci-après peut s’interpréter
comme voulant dire que « chaque ligne du résultat doit être une valeur de la
colonne « NoPilote » d’une ligne « v » de la table « Vols » ».

157
Joachim TANKOANO

SELECT v.NoPilote
FROM Vols v ;

Cet ordre peut aussi s’interpréter comme voulant dire : « faire une projection
de la table « Vols » sur la colonne « NoPilote » ».

Dans le langage prédicatif en calcul relationnel à variables n-uplets, ceci


s’écrirait :
{(v.NoPilote) | Vols (v)}.

Exemple 4.3.1.ii : De même, l’ordre SQL qui suit peut s’interpréter dans la
logique du 1er ordre comme voulant dire que « chaque ligne du résultat doit
être constituée d’une valeur de la colonne « NomPilote » d’une ligne « p » de la
table « Pilotes » et d’une valeur de la colonne « VA » d’une ligne « v » de la table
« Vols » qui vérifient le prédicat suivant : la valeur de la colonne « NoPilote » de
la ligne « v » de la table « Vols » et la valeur de la colonne « NoPilote » de la ligne
« p » de la table « Pilotes » doivent être identiques » et la valeur de la colonne
« VD » de la ligne « v » de la table « Vols » doit être « OUGADOUGOU ». » :

SELECT p.NomPilote, v.VA


FROM Vols v, Pilotes p
WHERE v.NoPilote = p.NoPilote AND v.VD = 'OUAGADOUGOU';

En observant que le prédicat défini dans la clause « WHERE » contient un


prédicat qui définit une condition de jointure et un autre prédicat qui
définit une condition de sélection, cet ordre SQL peut aussi s’interpréter
comme voulant dire : « faire une jointure naturelle de la table « Vols » et de la
table « Pilotes », suivie d’une sélection des lignes du résultat où la colonne « VD »
contient « OUAGADOUGOU », suivie d’une projection du résultat sur la
colonne « NomPilote » qui vient de la table « Pilotes » et sur la colonne « VA »
qui vient de la table « Vols » ».

Dans le langage prédicatif en calcul relationnel à variables n-uplets, ceci


s’écrirait comme suit :
{(p.NoPilote, v.VA) | Pilotes(p)  Vols(v)
 (p.NoPilote = v.Nopilote)
 (v.VD = 'OUAGADOUGOU')}.

158
SGBD relationnels – Tome 1, État de l’art

(3) Les clauses « GROUP BY » et « HAVING » offrent des possibilités


qui, de base, n’existent ni dans la logique du 1er ordre, ni dans l’algèbre
relationnelle. Elles permettent d’appliquer un traitement additionnel aux
lignes du résultat d’un ordre SELECT. Dans un ordre SELECT, la clause
« GROUP BY » permet, de regrouper les lignes du résultat qui ont une
valeur identique pour un certain nombre de colonnes, afin de remplacer,
dans le résultat final, chaque groupe de lignes ainsi constitué par une seule
ligne où les valeurs des autres colonnes ont été agrégées à l’aide de
fonctions de groupe (moyenne, somme, minimum, maximum, etc. des
valeurs d’une colonne dans le groupe). La clause « HAVING » permet de
sélectionner parmi les lignes agrégées, celles qui doivent être retenues dans
le résultat final.

(4) Les clauses « START WITH » et « CONNECT BY » offrent


également des possibilités qui n’existent ni dans la logique du 1er ordre, ni
dans algèbre relationnelle. Lorsque dans une table il existe un lien
hiérarchique entre les lignes, ces deux clauses permettent d’ordonner les
lignes du résultat pour un parcourt qui suit ce lien hiérarchique. La clause
« START WITH » permet de définir les lignes qui doivent servir de point
départ pour ce parcours et la clause « CONNECT BY » d’indiquer comment
déterminer les lignes « fils » qui doivent être liées à leur ligne « père ».

(5) La clause « ORDER BY » permet quant à elle de trier les lignes du


résultat de l’ordre SELECT selon le critère spécifié.

(6) Enfin, la syntaxe d’un ordre SELECT indique qu’il est aussi possible
d’effectuer l’union, l’intersection ou la différence du résultat de deux ordres
SELECT en utilisant les opérateurs algébriques relationnels « UNION »,
« INTERSECT » et « MINUS ».

Ce qui suit présente de façon plus détaillée les possibilités offertes par ces
différentes clauses.

b) La clause FROM

Les tables qui doivent être utilisées comme sources pour l’exécution d’un
ordre SELECT doivent être mentionnées dans sa clause « FROM » en

159
Joachim TANKOANO

respectant la syntaxe simplifiée ci-après :


FROM {Table_source} [,..n]
Table_source ::= {Nom_de_table | Nom_de_vue | (Sous_requête)} [Alias_de_table]

(1) Dans une clause « FROM », on peut mentionner plusieurs sources,


chaque source pouvant être, soit une table ou une vue (c'est-à-dire une table
virtuelle) créée dans le schéma logique de la base de données, soit le résultat
de l’exécution d’un ordre SELECT imbriqué dans la requête. En d’autres
termes, on peut imbriquer un ordre SELECT dans un autre, soit
explicitement, soit implicitement à l’aide d’une vue, comme cela peut se
faire avec le langage algébrique relationnel où tout argument d’un
opérateur algébrique relationnel peut être le résultat de l’exécution d’une
séquence quelconque d’opérateurs algébriques relationnels.

Exemple 4.3.1.iii : On peut écrire ce qui suit, pour dire que les tables qui
doivent être utilisées pour l’exécution d’un ordre SELECT sont, la table
« Avions » et la table « Vols » restreinte aux lignes où la valeur de la
colonne « VA » est « OUAGADOUGOU » :
FROM Avions, (SELECT * FROM Vols WHERE VA = 'OUAGADOUGOU')

(2) En outre, il est possible comme dans l’exemple qui suit, d’associer à
chaque source un alias de table, qui est en général un pseudonyme plus
court qu’on peut utiliser en lieu et place du nom de la source.

Exemple 4.3.1.iv :

FROM Avions a,
(SELECT * FROM Vols WHERE VA = 'OUAGADOUGOU') v

c) La clause SELECT

La clause « SELECT » permet de demander au SGBD la suppression des


doublons dans le résultat d’un ordre SELECT. Toutefois, son principal rôle
est d’expliciter la provenance des colonnes du résultat de diverses manières
en se conformant à la syntaxe simplifiée ci-après :

160
SGBD relationnels – Tome 1, État de l’art

SELECT [DISTINCT] Liste_colonnes_résultat


Liste_colonnes_résultat ::= {*|{Nom_table | Nom_vue | alias_table}.*
|{{Colonne | Expression} [[AS] Alias_colonne]} [,..n]
}
Colonne ::= [{Nom_table | Nom_vue | alias_table}.] Nom_colonne

(1) L’option « * » permet d’indiquer que les colonnes du résultat sont


constituées de l’ensemble des colonnes des tables ou des vues citées dans la
clause « FROM ».

Exemple 4.3.1.v : L’ordre qui suit produit ainsi un tableau où les colonnes
sont les mêmes que celles de la table « Avions ».
SELECT *
FROM Avions ;

(2) Les options « Nom_table.* », « Nom_vue.* » et « Alias_de_table.* »


permettent d’indiquer que les colonnes du résultat sont constituées de
l’ensemble des colonnes d’une table ou d’une vue en particulier, citée dans
la clause « FROM ».

Exemple 4.3.1.vi : L’ordre ci-après produit un tableau où les colonnes sont


les mêmes que celles de la table « Vols ».

SELECT v.*
FROM Avions a, Vols v ;

(3) Il est aussi possible d’indiquer distinctement la provenance de


chaque colonne du résultat.

Dans ce format, la provenance indiquée pour la valeur de chaque colonne


d’une ligne du résultat peut être, soit une colonne d’une ligne de table ou
de vue citée dans la clause « FROM », soit le résultat d’une expression qui
permet de calculer chaque valeur de cette colonne à partir d’une ou de
plusieurs colonnes de tables ou de vues citées dans la clause « FROM ».

En outre, il est possible, pour chaque colonne du résultat, de lui associer un


« alias de colonne », c'est-à-dire, un libellé qui sera utilisé comme titre de

161
Joachim TANKOANO

colonne lors de l’affichage du résultat, en lieu et place du nom de la colonne.

Exemple 4.3.1.vii : L’ordre qui suit produit un tableau à deux colonnes où,
pour chaque ligne, la valeur de la 1ère colonne provient de la colonne
« NoAvion » d’une ligne de la table « Avions » alors que la valeur de la 2ème
colonne est le produit de la colonne « CapAvion » de cette ligne de la table
« Avions » avec la valeur 300000. En outre, le titre de la 1ère colonne de ce
tableau sera « NoAvion » et le titre de la 2ème colonne « Chiffre d’affaire ».

SELECT a.NoAvion, a.CapAvion*300000 'Chiffre d’affaire'


FROM Avions a ;

N.B. : Pour ce cas de figure et les autres cas qui précèdent, on peut dire que
les colonnes d’une ligne du résultat sont des « termes » tels que définis dans
la logique du 1er ordre. Chacun de ses termes est, soit une constante, soit un
symbole de variable qui désigne une valeur de la base de données, soit un
symbole de fonction définie par une expression qui calcule une nouvelle
valeur par composition de valeurs qui existent dans la base. Les SGBD
proposent un très grand nombre d’opérateurs et de fonctions prédéfinis,
applicables aux colonnes qui contiennent des nombres, des chaînes de
caractères ou des dates. Ces opérateurs et fonctions peuvent aussi s’utiliser
dans la clause « WHERE ». Leur existence, qui répond à l’une des dix
exigences définies par E.F. CODD, contribue grandement à la puissance du
langage et simplifie le travail des développeurs. Ces fonctions existent en
général pour différents domaines d’application (fonctions financières,
fonctions statistiques, fonctions trigonométriques, etc.). Les tableaux qui
suivent présentent succinctement quelques exemples d’opérateurs et de
fonctions ORACLE applicables aux nombres, aux chaines de caractères et
aux dates.

Exemples de fonctions de type numérique


Fonction Description Exemples d’appels Résultats

ROUND Effectue un arrondi à une précision ROUND (45.926, 2) 45.93


(col|expr, n) -n
de 10 . Si n est négatif, l’arrondi se ROUND (45.926, 0) 46
fait sur la partie entière ROUND (45.926, -1) 50

162
SGBD relationnels – Tome 1, État de l’art

TRUNC Effectue une troncation à une TRUNC (45.926, 2) 45.92


(col|expr, n) précision de 10
-n TRUNC (45.926, 0) 45
TRUNC (45.926, -1) 40
MOD (m, n) Retourne le reste de la division de m MOD (1600, 300) 100
par n

Exemples de fonctions de type caractère ou chaîne de caractères


Fonction Description Exemples d’appels Résultats

LOWER (col|expr) Convertit en LOWER ('Cours SQL') cours sql


minuscules
UPPER (col|expr) Convertit en UPPER ('Cours SQL') COURS SQL
majuscules
INITCAP 1
er
caractère en INITCAP ('Cours SQL') Cours sql
(col|expr) majuscule et le reste
en minuscules
CONCAT Equivalent à || CONCAT ('Une', 'chaîne') Une chaîne
(col|expr,
col|expr)
SUBSTR Extrait n caractères SUBSTR ('Chaîne', 1, 3) Cha
(col|expr, m[,n]) à partir de la
position m. Si n est
négatif, le décompte
s’effectue en sens
inverse
LENGTH Retourne la LENGTH ('Chaîne') 6
(col|expr) longueur de la
chaîne
INSTR (col|expr, Retourne le rang du INSTR ('Chaîne', 'a') 3
c) caractère c
LPAD Complète une LPAD ('Chaîne', 10, '*') ****Chaîne
(col|expr, n, ch) chaîne sur la gauche
pour obtenir une
longueur de n
caractères

Exemples d’opérateurs arithmétiques sur les dates


Fonction Type résultat Exemples Résultats

date {+|-} date '11-JAN-18' + 6 '17-JAN-18'


nombre_de_jours
date - date Nombre de jours '17-JAN-18' – '11-JAN-18' 6
date + (nombre date '11-JAN-18' + 27/24 '12-JAN-18'
d’heures) / 24

163
Joachim TANKOANO

Exemples de fonctions de type date


Fonction Description Exemples d’appels Résultats

MONTHS_BETWEEN Retourne le MONTHS_BETWEEN 9


(d1, d2) nombre de mois ('04-FEV-18', '09-NOV-18')
entre les dates d1
et d2
ADD_MONTH (d, n) Ajoute à la date ADD_MONTH '01-DEC-18'
d, n mois ('15-NOV-18',16)
calendaires
NEXT_DAY (d, j) Retourne la date NEXT_DAY '17-NOV-18'
du jour j qui suit ('15-NOV-18', 'FRIDAY')
la date d
LAST_DAY (d) Retourne
dernier jour du
mois
ROUND (d, fmt) Arrondit la date ROUND '01-DEC-18'
d au format fmt ('27-NOV-18', 'MONTH')
TRUNC (d, fmt) Tronque la date TRUNC '01-JAN-18'
d au format fmt ('27-NOV-10', 'YEAR')
SYSDATE Retourne la date
système

(4) Dans le format où la provenance de la valeur de chaque colonne


d’une ligne du résultat est spécifiée distinctement, la provenance indiquée
pour chaque colonne d’une ligne du résultat peut aussi être le résultat, soit
d’un appel de fonction de groupe, soit d’une expression qui contient des
appels à des fonctions de groupe. Une fonction de groupe est une fonction
d’agrégation qui reçoit en paramètre l’ensemble des valeurs d’une colonne
du résultat d’un ordre SELECT et qui retournent une seule valeur pour cette
colonne. Lorsqu’elles sont utilisées dans un ordre SELECT, les fonctions de
groupe transforment le résultat initialement constitué de plusieurs lignes
en un résultat agrégé qui ne contient qu’une seule ligne. Le tableau qui suit
contient quelques exemples de fonctions de groupe ORACLE.

Exemples de fonctions de groupes


Fonction Description Exemples d’appels

AVG Retourne la moyenne des AVG (CapAvion)


([DISTINCT|ALL] col) valeurs de la colonne « col »
en ignorant les lignes où
« col » est NULL

164
SGBD relationnels – Tome 1, État de l’art

COUNT Retourne le nombre de lignes COUNT(*)


({*|[DISTINCT|ALL] où « expr » est différent de COUNT (CapAvion)
expr}) NULL. « * » compte toutes les
lignes
MAX Retourne la valeur maximale MAX (CapAvion)
([DISTINCT|ALL] expr) de « expr » en ignorant les
valeurs NULL
MIN Retourne la valeur minimale MIN (CapAvion)
([DISTINCT|ALL] expr) de « expr » en ignorant les
valeurs NULL
STDDEV Retourne l’écart standard de STDDEV (CapAvion)
([DISTINCT|ALL] expr) « expr » en ignorant les
valeurs NULL
SUM Retourne la somme des SUM (CapAvion)
([DISTINCT|ALL] expr) valeurs de « expr »
VARIANCE Retourne la variance des VARIANCE (CapAvion)
([DISTINCT|ALL] expr) valeurs de « expr »

Bien entendu, le résultat d’un ordre SELECT ne peut pas être constitué à la
fois de colonnes agrégées et de colonnes non agrégées.

Exemple 4.3.1.viii : L’ordre qui suit produit un tableau qui contient dans
chaque ligne, la valeur de la colonne « CapAvion » d’une ligne de la table
« Avions ».
SELECT a.CapAvion
FROM Avions a ;

L’ordre qui suit produit quant à lui un tableau qui contient une seule ligne
ayant une seule colonne qui contient la moyenne des valeurs contenues
dans la colonne « CapAvion » de la table « Avions ».

SELECT AVG (a.CapAvion)


FROM Avions a ;

Comme nous le verrons dans le paragraphe f), l’utilisation de la clause


« GROUP BY » modifie quelque peu le fonctionnent décrit dans ce
paragraphe pour les fonctions de groupe.

Enfin, on peut noter que les fonctions de groupe n’existent pas, de base,
dans le formalisme de la logique du 1er ordre, tel que présenté dans le
paragraphe 2.4.1. Elles reçoivent en paramètre un ensemble alors que les
fonctions n-aires de la logique du 1er ordre reçoivent en paramètre des

165
Joachim TANKOANO

valeurs atomiques.

(5) La présence de l’option « DISTINCT » dans une clause « SELECT »


supprime les doublons dans le résultat.

Exemple 4.3.1.ix : L’ordre qui suit produit un tableau qui contient, pour
chaque ligne de la table « Avions », une ligne constituée de la valeur de la
colonne « NomAvion » et de la valeur de la colonne « CapAvion », en
faisant en sorte qu’il n’y ait pas de lignes identiques dans ce tableau.

SELECT DISTINCT a.NomAvion, a.CapAvion


FROM Avions a ;

d) La clause WHERE

Cette clause est la plus importante de l’ordre SELECT. Dans un ordre


SELECT, la clause « WHERE » définit le prédicat que chaque ligne du
résultat doit vérifier, par déduction à partir des assertions contenues dans
les tables spécifiées par la clause « FROM ». La grammaire simplifiée de
cette clause est la suivante :
[WHERE Prédicat_résultat]
Prédicat_résultat ::= [NOT] {Prédicat | (Prédicat )}
[{{AND | OR} [NOT] {Prédicat | (Prédicat )}} [..n]]

Prédicat ::= {Expression {=|||=|  |=} {Expression |(Sous_requête)}


| Expression IS [NOT] NULL
| Expression_chaîne [NOT] LIKE Expression_chaîne
[ESCAPE 'caractère_échap']
| Expression [NOT] BETWEEN Expression AND Expression
| Expression [NOT] IN ({{Expression} [,..n]} |Sous_requête })
| Expression {=|||=||=}
{ALL|ANY|SOME} ({Sous_requête |{Expression} [,..n]})
| (Liste_expr) {=|} ({Liste_expr | Sous_requête})
| (Liste_expr) [NOT] IN ({{Liste_expr [,..n]}| Sous_requête})
| (Liste_expr) {=|}
{ALL|ANY|SOME} ({{ Liste_expr} [,..n]} | Sous_requête})
| EXISTS (Sous_requête)}

166
SGBD relationnels – Tome 1, État de l’art

(1) Comme indiqué dans cette syntaxe, le prédicat d’une clause


« WHERE » se définit en combinant, à l’aide des connecteurs logiques
(« NOT », « AND », « OR »), des prédicats de comparaison et des prédicats
existentialistes « EXISTS » («  ») portant sur le contenu des lignes des
tables citées dans la clause « FROM ». Les prédicats de comparaison sont,
soient des prédicats de comparaison de valeurs atomiques, soient des
prédicats de comparaison de la théorie des ensembles formulés sur des
ensembles de valeurs atomiques ou de lignes (vues comme des listes de
lignes composées d’une ou de plusieurs valeurs) spécifiés explicitement ou
calculés par des sous-requêtes imbriquées. Les prédicats de comparaison
sains, c’est-à-dire qui peuvent avoir un sens ne peuvent correspondre qu’à
des prédicats de sélection de lignes («  ») dans une table et qu’à des
prédicats de jointure de tables («  »).

(2) Les symboles des prédicats de comparaison sont les suivants : « =,


, <, >, <=, >=, IS [NOT] NULL, [NOT] LIKE, [NOT] BETWEEN, [NOT]
IN ». Ces symboles de prédicats permettent de :
• Comparer deux valeurs atomiques (=, , <, >, <=, >=, IS [NOT]
NULL)
• Vérifier la syntaxe d’une valeur chaîne de caractères par rapport à
un motif donné ([NOT] LIKE)
• Vérifier l’appartenance d’une valeur atomique à un intervalle de
valeurs donné ([NOT] BETWEEN)
• Vérifier qu’une valeur atomique est membre d’une liste de valeurs
donnée ([NOT] IN)
• Comparer une valeur atomique à une liste de valeurs en utilisant un
quantificateur (« ALL », « ANY » ou « SOME ») et un opérateur de
comparaison (=, , <, >, <=, >=)
• Comparer deux listes de valeurs atomiques (=,)
• Vérifier qu’une liste de valeurs atomiques est membre d’une liste de
listes de valeurs atomiques donnée ([NOT] IN)

167
Joachim TANKOANO

• Comparer une liste de valeurs atomiques à une liste de listes de


valeurs en utilisant un quantificateur (« ALL », « ANY » ou
« SOME ») et un opérateur de comparaison (=,).

Exemple 4.3.1.x : L’ordre qui suit produit un tableau qui contient toutes les
lignes de la table « Avions » où la colonne « NoAvion » contient 10, 20 ou
30. Le prédicat de comparaison ensembliste utilisé n’est vrai que si la valeur
de la colonne « NoAvion » dans une ligne de la table « Avions » est incluse
dans un ensemble donné de valeurs. Ce prédicat définit un critère de
sélection de lignes dans la table « Avions ».
SELECT *
FROM Avions
WHERE NoAvion IN (10, 20, 30) ;

Exemple 4.3.1.xi : L’ordre qui suit produit un tableau qui contient toutes les
lignes de la table « Avions » où la colonne « CapAvion » contient la valeur
« NULL ». Le prédicat de comparaison utilisé compare la valeur de la
colonne « CapAvion » dans une ligne de la table « Avions » à la valeur
« NULL ». Ce prédicat définit un critère de sélection de lignes dans la table
« Avions ».
SELECT *
FROM Avions
WHERE CapAvion IS NULL ;

Exemple 4.3.1.xii : L’ordre qui suit produit un tableau qui contient la liste
des avions dont le nom commence par 'AIRBUS' :
SELECT *
FROM Avions
WHERE NomAvion LIKE 'AIRBUS%' ;

Le prédicat de comparaison utilisé vérifie la syntaxe de la valeur de la


colonne « NomAvion » dans une ligne de la table « Avions » par rapport
au motif « AIRBUS% ». Ce prédicat définit un critère de sélection de lignes
dans la table « Avions ».

168
SGBD relationnels – Tome 1, État de l’art

Exemple 4.3.1.xiii : L’ordre qui suit produit un tableau qui contient pour
chaque vol de la table « Vols », son numéro et le nom de l’avion utilisé pour
effectuer ce vol :
SELECT v.NoVol, a.NomAvion
FROM Vols v, Avions a
WHERE v.NoAvion = a.NoAvion ;

Le prédicat de comparaison utilisé compare la valeur de la colonne


« NoAvion » dans une ligne de la table « Vols » à la valeur de la colonne
« NoAvion » dans une ligne de la table « Avions ». Ce prédicat définit donc
une condition de jointure de la table « Vols » et de la table « Avions ».

Exemple 4.3.1.xiv : L’ordre qui suit produit un tableau qui contient le


numéro du vol et la localisation de l’avion utilisé pour effectuer ce vol, pour
chaque vol au départ de « OUAGADOUGOU » qui utilise un avion
« AIRBUS A310 » :
SELECT v.NoVol, a.NomAvion
FROM Vols v, Avions a
WHERE (v.NoAvion = a.NoAvion)
AND (v.VD = 'OUAGADOUGOU')
AND (a.NomAvion = 'AIRBUS A310');

Le 1er prédicat de comparaison utilisé compare la valeur de la colonne


« NoAvion » dans une ligne de la table « Vols » à la valeur de la colonne
« NoAvion » dans une ligne de la table « Avions ». Ce prédicat définit donc
une condition de jointure de la table « Vols » et de la table « Avions ». Le
2ème prédicat de comparaison compare la valeur de la colonne « VD » de la
table « Vols » à une valeur donnée. Ce prédicat définit donc un critère de
sélection dans le résultat de la jointure de la table « Vols » et de la table
« Avions ». Il en est de même pour le 3ème prédicat qui définit un 2ème critère
de sélection dans le résultat de la jointure de la table « Vols » et de la table
« Avions ».

(3) Les listes de valeurs qui interviennent dans les prédicats de


comparaison ensemblistes peuvent être calculées dynamiquement à l’aide

169
Joachim TANKOANO

de sous-requêtes imbriquées. Ces sous-requêtes imbriquées peuvent être


indépendantes ou synchronisées.

Une sous-requête synchronisée (ou dépendante) est caractérisée par le fait


qu’elle fait référence pour son évaluation à une colonne d’une table de la
requête principale. Sinon elle est dite indépendante ou non synchronisée.

(4) Lorsqu’un ordre SELECT contient une sous-requête imbriquée


indépendante, cette sous-requête est exécutée en premier. Son résultat est
ensuite utilisé pour l’exécution de l’ordre SELECT principal. En d’autres
termes, les sous-requêtes imbriquées indépendantes ne sont exécutées
qu’une seule fois au cours de l’exécution de l’ordre SELECT principal.

Exemple 4.3.1.xv : L’ordre qui suit produit un tableau qui contient la liste
des avions conduits par le pilote n°2 :
SELECT a.*
FROM Avions a
WHERE a.NoAvion IN (SELECT v.NoAvion
FROM Vols v
WHERE v.NoPilote = 2) ;

Le prédicat de comparaison ensembliste utilisé dans la requête principale,


teste l’inclusion de la valeur de la colonne « NoAvion » dans une ligne de
la table « Avions », dans la liste des numéros d’avions conduits par le pilote
numéro 2, calculée dynamiquement par une sous-requête imbriquée
indépendante. Ce prédicat ensembliste définit donc un critère de sélection
dans la table « Avions ». Cette requête peut être reformulée de façon
équivalente de la façon suivante en utilisant une condition qui exprime une
semi-jointure :
SELECT a.*
FROM Avions a, Vols v
WHERE (a.NoAvion = v.NoAvion) AND (v.NoPilote = 2) ;

Exemple 4.3.1.xvi : L’ordre qui suit produit un tableau qui contient la liste
des avions dont le numéro est supérieur à tous les numéros des avions
conduits par le pilote n°2 :

170
SGBD relationnels – Tome 1, État de l’art

SELECT a.*
FROM Avions a
WHERE a.NoAvion ALL (SELECT v.NoAvion
FROM Vols v
WHERE v.NoPilote = 2) ;

Le prédicat de comparaison ensembliste utilisé dans la requête principale


compare, en utilisant le quantificateur « ALL », la valeur de la colonne
« NoAvion » dans une ligne de la table « Avions », à la liste des numéros
d’avions conduits par le pilote numéro 2, calculée dynamiquement par une
sous-requête imbriquée indépendante. Ce prédicat définit donc un critère
de sélection dans la table « Avions ».

(5) En revanche, lorsqu’un ordre SELECT contient une sous-requête


imbriquée synchronisée, cette sous-requête est exécutée en premier avant
le traitement de chaque ligne candidate de l’ordre SELECT principal. En
d’autres termes, la sous-requête imbriquée est exécutée autant de fois qu’il
existe de lignes candidates à traiter au niveau de l’ordre SELECT principal.

Exemple 4.3.1.xvii : L’ordre qui suit produit un tableau qui contient la liste
des numéros des pilotes qui conduisent plus d’un avion :
SELECT DISTINCT NoPilote
FROM Vols Vols_x
WHERE NoPilote IN (SELECT NoPilote
FROM Vols Vols_y
WHERE Vols_x.NoAvion  Vols_y.NoAvion) ;

Le prédicat de comparaison ensembliste utilisé dans la requête principale


teste, pour chaque vol « v » de la table « Vols », l’inclusion de la valeur de
sa colonne « NoPilote » dans la liste des numéros de pilotes, calculée
dynamiquement par une sous-requête imbriquée synchronisée. Pour
chaque vol « v » de la table « Vols », la sous-requête imbriquée
synchronisée calcule la liste des numéros des pilotes qui conduisent un vol
avec un avion dont le numéro est différent du numéro de l’avion utilisé
dans ce vol « v ». Le prédicat de la requête principale définit donc un critère
de sélection dans la table « Vols » et le prédicat de la sous-requête
imbriquée synchronisée, un critère de sélection, dans la table « Vols », qui

171
Joachim TANKOANO

dépend d’une valeur contenue dans la ligne candidate de la requête


principale.

(6) Toute sous-requête imbriquée dans un prédicat existentialiste est


forcément synchronisée. Elle est exécutée pour chaque ligne candidate de
l’ordre SELECT principal. Cette exécution s’arrête dès la rencontre de la 1ère
ligne qui satisfait la condition que chaque ligne du résultat de la sous-
requête imbriquée doit vérifier. Il s’agit donc d’une sous-requête imbriquée
synchronisée optimisée.

Exemple 4.3.1.xviii : L’ordre qui suit produit un tableau qui contient la liste
des noms des pilotes qui sont en service.
SELECT NomPilote FROM Pilotes P
WHERE EXISTS (SELECT *
FROM Vols V
WHERE V.NoPilote = P.NoPilote) ;

Le prédicat utilisé est un prédicat existentialiste. La sous-requête imbriquée


synchronisée s’exécute pour chaque ligne candidate de l’ordre SELECT
principal (c'est-à-dire pour chaque pilote) et s’arrête dès qu’elle rencontre
le premier vol conduit par ce pilote. Ce prédicat définit donc un critère de
sélection dans la table « Pilotes ».

e) La jointure selon la syntaxe normalisée de SQL2

Lorsque le prédicat d’une clause « WHERE » combine plusieurs prédicats


de comparaison, il n’est pas toujours aisé de prime abord de faire la
distinction entre ce qui relève du filtrage (c'est-à-dire de la sélection de
lignes) et ce qui relève de la jointure. SQL2 a de ce fait introduit de façon
explicite dans sa syntaxe normalisée à base de JOIN, l’opérateur de jointure
de l’algèbre relationnelle. Cette syntaxe permet de spécifier le résultat
recherché en termes d’opérations algébriques à effectuer, en dédiant la
clause « SELECT » à la spécification de l’opération de projection, la clause
« FROM » à la spécification des opérations de jointure et la clause
« WHERE » à la spécification des opérations de sélection de lignes. En

172
SGBD relationnels – Tome 1, État de l’art

outre, cette syntaxe permet de distinguer avec clarté différents types de


jointures et les couples de tables à joindre.

Cette syntaxe normalisée dans sa forme simplifiée peut être décrite de la


façon suivante :
SELECT [DISTINCT] Liste_colonnes_résultat
FROM Table_source {{NATURAL JOIN Table_source [USING noms_colonnes]}
|{[INNER] JOIN Table_source ON Prédicat_jointure}
|{{LEFT|RIGHT|FULL} OUTER JOIN Table_source
ON Prédicat_jointure}
|{CROSS JOIN Table_source}
} [..n]
[WHERE Prédicat_sélection]

(1) Cette syntaxe normalisée consacre la clause « WHERE » à la


formulation des prédicats de sélection.

(2) Elle permet de spécifier au niveau de la clause « FROM », à l’aide


d’opérateurs explicites, le produit cartésien (CROSS JOIN) et trois types de
jointure : les jointures internes ([INNER] JOIN), les jointures naturelles
(NATURAL JOIN) et les jointures externes ({LEFT|RIGHT|FULL}
OUTER JOIN).

(3) Les jointures internes sont considérées comme étant les jointures par
défaut. Chaque ligne du résultat d’une jointure interne doit contenir les
colonnes de deux lignes qui vérifient cette condition de jointure.

(4) Les jointures naturelles sont des cas particuliers de jointures internes
où le prédicat de jointure est le test d’égalité sur les colonnes de même nom
qui existent dans les deux tables à joindre.

(5) La jointure externe permet d’inclure dans le résultat de la jointure,


les lignes des deux tables à joindre qui ne vérifient pas la condition de
jointure. « LEFT OUTER JOIN » permet d’inclure, dans le résultat de la
jointure, les lignes de la table de gauche qui ne vérifient pas la condition de
jointure, complétées par des valeurs « NULL » pour les colonnes de la table
de droite. « RIGHT OUTER JOIN » permet d’inclure dans le résultat de la
jointure, les lignes de la table de droite qui ne vérifient pas la condition de
jointure, complétées par des valeurs « NULL » pour les colonnes de la table

173
Joachim TANKOANO

de gauche. « FULL OUTER JOIN » permet d’inclure dans le résultat de la


jointure, les lignes des deux tables qui ne vérifient pas la condition de
jointure complétées, selon le cas, par des valeurs « NULL » pour les
colonnes de la table de gauche ou de droite.

Exemple 4.3.1.xix : Les deux requêtes qui suivent sont équivalentes. La 1ère
effectue une jointure interne et la 2ème une jointure naturelle. Les deux
requêtes spécifient explicitement une jointure (entre « Vols », « Pilotes » et
« Avions »), une sélection de lignes (sur « Vols ») et une projection (sur
« NoVol », « NomPilote » et « NomAvion »).
SELECT v.NoVol, p.NomPilote, a.NomAvion
FROM Vols v INNER JOIN Pilotes p ON v.NoPilote = p.NoPilote
JOIN Avions a ON v.Avion = a.NoAvion
WHERE v.VD = 'OUAGADOUGOU' ;
SELECT v.NoVol, p.NomPilote, a.NomAvion
FROM Vols v NATURAL JOIN Pilotes p
NATURAL JOIN Avions a
WHERE v.VD = 'OUAGADOUGOU' ;

f) L’agrégation des lignes du résultat d’un ordre SELECT

SQL offre la possibilité d’agréger les lignes du résultat d’un ordre SELECT,
au niveau de granularité souhaité, en utilisant conjointement les fonctions
de groupe, présentées dans le paragraphe c) de cette section, et les clauses
« GROUP BY » et « HAVING ».

Le traitement d’un ordre SELECT qui contient des fonctions de groupe et


les clauses « GROUP BY » et « HAVING » s’effectue en trois phases. Au
cours de la 1ère phase, cet ordre SELECT est exécuté sans tenir compte des
fonctions de groupe et des clauses « GROUP BY » et « HAVING ». Au
cours de la 2ème phase, le résultat de la 1ère phase est divisé en groupes de
lignes en faisant en sorte que les lignes qui ont les mêmes valeurs par
rapport aux colonnes citées dans la clause « GROUP BY » forment un
groupe. Au cours de la 3ème phase, les fonctions de groupe citées dans la
clause « SELECT » sont appliquées à chaque groupe de lignes constitué au
cours de la 2ème phase, afin d’agréger chacun de ces groupes de lignes en

174
SGBD relationnels – Tome 1, État de l’art

une seule ligne. Ensuite, la clause « HAVING » est appliquée aux lignes
agrégées, afin de sélectionner celles qui doivent être incluses dans le
résultat final.

Ainsi, la liste des colonnes du résultat spécifiée dans une clause « SELECT »
peut faire référence à des fonctions de groupe à appliquer à des colonnes
non citées dans la clause « GROUP BY ». Elle peut aussi faire référence à
toute ou partie des colonnes citées dans la clause « GROUP BY ».

Tout ordre SELECT, où la liste des colonnes du résultat définie dans la


clause « SELECT » fait référence à la fois à des colonnes de table ou de vue
non agrégées et à des colonnes de table ou de vue agrégées à l‘aide de
fonctions de groupes, doit comporter une clause « GROUP BY » qui cite au
moins toutes les colonnes non agrégées citées dans la clause « SELECT ».

Lorsque la liste des colonnes du résultat définies dans la clause « SELECT »


ne font référence qu’à des fonctions de groupe et que la clause « GROUP
BY » est absente, la totalité des lignes du résultat de la 1ère phase est
considérée comme constituant un seul groupe.

Exemple 4.3.1.xx : L’ordre qui suit produit un tableau qui contient la liste
des types d’avions dans la table « Avions » en fournissant pour chaque type
d’avions la moyenne de la capacité des avions de ce type :
SELECT NomAvion, AVG (CapAvion) FROM Avions
GROUP BY NomAvion ;

Exemple 4.3.1.xxi : Soit la table définie par la relation :


Personnel (NoEmpl, NomEmpl, DeptEmpl#, FctEmp, PatronEmpl#,
SalEmpl)

Cette table contient des informations relatives au personnel d’une


entreprise. L’ordre qui suit produit un tableau qui contient, pour chaque
type de fonction dans un département, la moyenne et la somme des salaires
des employés :
SELECT DeptEmpl, FctEmpl, AVG (SalEmpl), SUM (SalEmpl)
FROM Personnel
GROUP BY DeptEmpl, FctEmpl ;

175
Joachim TANKOANO

Quant à l’ordre qui suit, il produit un tableau qui contient, les départements
où le salaire maximum est supérieur à 300 000, et pour chacun de ces
départements le salaire maximum :
SELECT DeptEmpl, MAX (SalEmpl)
FROM Personnel
GROUP BY DeptEmpl
HAVING MAX (SalEmpl)  300000 ;

g) L’ordonnancement hiérarchique des lignes du résultat d’un ordre


SELECT

Soit la table définie par la relation :


Personnel (No, Nom, Fonction, Chef#)
Où : chaque tuple décrit un employé par son numéro (l’attribut « No »), son
nom (l’attribut « Nom »), sa fonction (l’attribut « Fonction ») et le
numéro de son chef (l’attribut « Chef »).

Telle que définie, il existe entre les lignes de cette table une relation
hiérarchique. Comme on peut le voir dans l’extension ci-dessous, cette
relation hiérarchique correspond à la relation hiérarchique qui existe entre
les employés de l’entreprise.

No Nom Fonction Chef

1 Jean Programmeur 7
2 Amadou Programmeur 6
3 Fati Programmeur 6
4 Francis Programmeur 7
5 Francine Programmeur 7
6 André Chef de projet 8
7 Marie Chef de projet 8
8 Bill Chef d’entreprise -

La finalité des requêtes hiérarchiques est la production de résultats où les


lignes sont ordonnées selon un ordre hiérarchique basé sur ce type de
relation inter lignes, tel que visualisé dans l’arbre ci-dessous.

176
SGBD relationnels – Tome 1, État de l’art

Bill

André Marie

Amadou Fati Jean Francis Francine

La formulation d’une requête hiérarchique se fait en ajoutant dans l’ordre


SELECT les clauses « START WITH » et « CONNECT BY » qui servent à
définir le lien inter lignes requis pour ordonner hiérarchiquement les lignes
du résultat. La syntaxe à respecter est la suivante :
[START WITH Prédicat] CONNECT BY [NOCYCLE] Prédicat

La clause « START WITH » permet de déterminer les lignes qui doivent


servir de point de départ pour le parcours du résultat. Si le prédicat spécifié
dans cette clause conduit à l’identification d’une seule ligne, la structure du
résultat sera celle d’un arbre où la racine est la ligne identifiée. Si le prédicat
spécifié conduit à l’identification de plusieurs lignes, la structure du résultat
sera celle d’une forêt où chaque arbre a comme racine l’une des lignes
identifiées. L’absence de la clause « START WITH » génère un arbre pour
chaque ligne du résultat.

Quant à la clause « CONNECT BY », elle sert à la spécification du lien


« père – fils » à l’aide d’un prédicat de comparaison. Dans ce prédicat, le
nom de la colonne considéré comme étant celui d’un « père » doit être
précédé du mot clé « PRIOR », les autres noms de colonnes étant considérés
par déduction comme étant ceux des colonnes d’un « fils ». Par exemple, le
prédicat « PRIOR No = Chef » signifie que, pour une ligne donnée, si la
valeur de sa colonne de père « No » est « v1 », ses fils sont toutes les autres
lignes où la colonne « Chef » contient cette valeur « v1 ».

Le prédicat de la clause « CONNECT BY » peut comporter en plus des


prédicats de sélection de lignes. Par exemple, dans la clause « CONNECT
BY PRIOR No = Chef AND No  8 », le prédicat « No  8 » est un
prédicat de sélection de lignes pères qui permet d’éliminer dans le résultat la

177
Joachim TANKOANO

ligne de l’employé no 8 et celles des employés qui dépendent de lui, donc


de supprimer un sous-arbre. De même, dans la clause « CONNECT BY
PRIOR No = Chef AND Nom  'Amadou' », le prédicat « Nom 
'Amadou' » est un prédicat de sélection de lignes fils qui permet d’éliminer
dans le résultat la ligne de l’employé « Amadou » comme employé
dépendant de son chef hiérarchique.

Lorsque le lien hiérarchique défini par les clauses « START WITH » et


« CONNECT BY » peut conduire à un cycle, l’option « NOCYCLE » permet
de demander au SGBD de détecter et d’empêcher les répétions de cycles,
pouvant conduire à une boucle infinie.

N.B. : Les requêtes hiérarchiques ne doivent pas contenir une clause


« GROUP BY » ou une clause « ORDER BY ». Toutefois, la clause
« ORDER SIBLINGS BY » peut être utilisée pour ordonner les nœuds
« frères ». Par ailleurs, à chaque ligne retournée par une requête
hiérarchique est associée une pseudo-colonne « LEVEL » qui indique le
niveau d’imbrication de la ligne dans l’arbre produit comme résultat.

Exemple 4.3.1.xxii : Si on considère l’exemple d’extension de la table


« Personnel » utilisé en introduction de ce paragraphe, la requête
hiérarchique ci-après produit le résultat listé à sa suite :
SELECT No, LPAD (' ', (LEVEL-1)*3, ' ') || Nom, Fonction
FROM Personnel
START WITH Chef IS NULL
CONNECT BY PRIOR No = Chef;

No Nom Fonction Chef

8 Bill Chef d’entreprise -


6 André Chef de projet 8
2 Amadou Programmeur 6
3 Fati Programmeur 6
7 Marie Chef de projet 8
1 Jean Programmeur 7
4 Francis Programmeur 7
5 Francine Programmeur 7

Exemple 4.3.1.xxiii : Dans cet exemple, la clause « WHERE » est utilisée

178
SGBD relationnels – Tome 1, État de l’art

pour supprimer du résultat, le nœud de l’arbre correspondant à « André ».


SELECT No, LPAD(' ', (LEVEL-1)*3, ' ') || Nom, Fonction
FROM Personnel
WHERE NomEmpl  'André'
START WITH Chef IS NULL
CONNECT BY PRIOR No = Chef;

No Nom Fonction Chef

8 Bill Chef d’entreprise -


2 Amadou Programmeur 6
3 Fati Programmeur 6
7 Marie Chef de projet 8
1 Jean Programmeur 7
4 Francis Programmeur 7
5 Francine Programmeur 7

Exemple 4.3.1.xxiv : Dans cet exemple, le prédicat de sélection de lignes


dans la clause « CONNECT BY » est utilisé pour supprimer du résultat la
branche de l’arbre où le chef a comme numéro la valeur « 6 ».
SELECT No, LPAD(' ', (LEVEL-1)*3, ' ') || Nom, Fonction
FROM Personnel
START WITH Chef IS NULL
CONNECT BY PRIOR No = Chef AND No  6;

No Nom Fonction Chef

8 Bill Chef d’entreprise -


7 Marie Chef de projet 8
1 Jean Programmeur 7
4 Francis Programmeur 7
5 Francine Programmeur 7

Exemple 4.3.1.xxv : Dans cet exemple, le prédicat de sélection de lignes dans


la clause « CONNECT BY » est utilisé pour supprimer du résultat un nœud
fils correspondant à « Fati ».
SELECT No, LPAD(' ', (LEVEL-1)*3, ' ') || Nom, Fonction
FROM Personnel
START WITH Chef IS NULL

179
Joachim TANKOANO

CONNECT BY PRIOR No = Chef AND Nom  'Fati';

No Nom Fonction Chef

8 Bill Chef d’entreprise -


6 André Chef de projet 8
2 Amadou Programmeur 6
7 Marie Chef de projet 8
1 Jean Programmeur 7
4 Francis Programmeur 7
5 Francine Programmeur 7

h) Le tri des lignes du résultat d’un ordre SELECT

Lorsque la clause « ORDER BY » est absente d’un ordre SELECT, les lignes
apparaissent dans le résultat dans un ordre quelconque qui peut changer
d’une exécution à une autre.

La clause « ORDER BY » permet de spécifier un critère de tri du résultat en


se conformant à la syntaxe simplifiée ci-après :
[ORDER [SIBLINGS] BY {Colonne_résultat [{DESC | ASC}]} [, ..n]]

Cette clause entraine le tri des lignes du résultat par rapport à la 1ère colonne
spécifiée et en cas d’égalité par rapport à la 2ème colonne spécifiée, ainsi de
suite. Par défaut, les lignes sont triées selon un ordre croissant, en plaçant
les valeurs NULL en dernier et en commençant par :
• Les valeurs les plus petites, pour ce qui concerne les valeurs
numériques
• Les dates les plus anciennes, pour ce qui concerne les dates
• Les caractères ou les chaînes de caractères les plus petits dans l’ordre
alphabétique, pour ce qui concerne les caractères et les chaînes de
caractères.

Le mot clé « SIBLINGS » permet d’ordonner les nœuds « frères » lorsqu’il


s’agit d’une requête hiérarchique.

N.B. Lorsque la clause « ORDER BY » est utilisée, elle doit être la dernière
clause de l’ordre SELECT.

180
SGBD relationnels – Tome 1, État de l’art

Exemple 4.3.1.xxvi : Soit la table définie par la relation Personnel (No, Nom,
Fonction, Salaire). L’ordre SELECT qui suit produit un résultat où les
lignes sont triées dans un ordre descendant selon la fonction et le salaire :
SELECT No, Nom, Fonction, Salaire
FROM Personnel
ORDER BY Fonction DESC, Salaire DESC ;

i) Le pouvoir d’expression de l’ordre SELECT

Ce qui suit définit chaque opérateur de l’algèbre relationnelle à l’aide d’un


ordre SELECT.
R1  R2
≡ SELECT R1.*
FROM R1
UNION SELECT R2.* FROM R2;
R1  R2
≡ SELECT R1.*
FROM R1
WHERE (R1.a1, …, R1.an) IN (SELECT R2.* FROM R2);
≡ SELECT R1.* FROM R1
INTERSECT SELECT R2.* FROM R2;
R1 – R2
≡ SELECT R1.*
FROM R1
WHERE (R1.a1, …, R1.an) NOT IN (SELECT R2.* FROM R2);
≡ SELECT R1.* FROM R1 MINUS SELECT R2.* FROM R2;
R1 * R2
≡ SELECT R1.*, R2.*
FROM R1, R2 ;
≡ SELECT R1.*, R2.*
FROM R1 CROSS JOIN R2 ;
(a1,…, an)(R1)
≡ SELECT DISTINCT R1.a1, …, R1.an
FROM R1 ;

181
Joachim TANKOANO

 P(R1.a1, …, R1.an)(R1)
≡ SELECT R1.*
FROM R1
WHERE P(R1.a1, …, R1.an);
R1P(R1.a1, …, R1.an, R2.b1, …, R2.bn)R2
≡ SELECT R1.*, R2.*
FROM R1, R2
WHERE P(R1.a1, …, R1.an, R2.b1, …, R2.bn);
≡ SELECT R1.*, R2.*
FROM R1 INNER JOIN R2 ON P(R1.a1, …, R1.an, R2.b1, …,
R2.bn) ;
R1(a1, ...ai, b1, …, bj) ÷ R2(b1, …, bj)
≡ SELECT DISTINCT t1.a1, …, t1.ai
FROM R1 t1
WHERE NOT EXISTS
(SELECT t2.b1, …, t2.bj
FROM R2 t2
WHERE NOT EXISTS
(SELECT R1.*
FROM R1
WHERE (t1.a1, …, t1.ai, t2.b1, …, t2.bj)
IN (SELECT * FROM R1)));

Ceci montre que, tel que présenté dans cette section, l’ordre SELECT a le
pouvoir d’expression du langage algébrique relationnel. Bien entendu,
l’inverse n’est pas vrai, notamment à cause des extensions apportées au
langage SQL, telles les prédicats ensemblistes incluant des requêtes
imbriquées synchronisées, les fonctions de groupe et les clauses « GROUP
BY », « HAVING », « START WITH », « CONNECT BY ».

4.3.2. Exemples de formulation de requêtes à l’aide d’un ordre


SELECT

Cette section propose à titre d’exemples une formulation SQL des requêtes
formulées dans la section 2.4.5 en langage prédicatif en calcul relationnel à
variables n-uplets. Pour les requêtes prédicatives formulées en utilisant le
quantificateur «  », les requêtes SQL équivalentes ont été formulées à
l’aide du quantificateur « EXISTS » en tenant compte de l’équivalence « x

182
SGBD relationnels – Tome 1, État de l’art

F   (x  F) » et de l’équivalence « F1  F2   (F1   F2) ». L’objectif


poursuivi à travers ces exemples est de mettre en évidence la très grande
similarité qui existe entre l’ordre SELECT de SQL et le langage prédicatif en
calcul relationnel à variables n-uplets. Quelques exemples de requêtes SQL
qui ne peuvent pas être formulées à l’aide du langage prédicatif en calcul
relationnel à variables n-uplets sont également traités à la suite.

Exemple 4.3.2.i : Quels sont les numéros des pilotes en service et les villes d’arrivée
de leurs vols ?
{(v.NoPilote, v.VA) | Vols (v)}
SELECT v.NoPilote, v.VA
FROM Vols v ;

Exemple 4.3.2.ii : Quels sont les numéros des vols au départ de Ouagadougou ?
{(v.NoVol) | Vols (v)  (v.VD = 'Ouagadougou')}

SELECT v.NoVol
FROM Vols v
WHERE v.VD = 'Ouagadougou' ;

Exemple 4.3.2.iii : Quels sont les noms des pilotes, autres qu’Amadou, qui
habitent Bobo-Dioulasso ?
{(p.NomPilote) | Pilotes (p)  (p.NomPilote  'Amadou')
 (p.AdrPilote = 'Bobo-Dioulasso')}

SELECT p.NomPilote
FROM Pilotes p
WHERE p.NomPilote  'Amadou' AND p.AdrPilote = 'Bobo-Dioulasso';

Exemple 4.3.2.iv : Quels sont les numéros des pilotes, qui conduisent l’avion
numéro 104 et l’avion numéro 106 ?
{(v1.NoPilote) | Vols (v1)  (v1.NoAvion = 104)
 ( v2 (Vols (v2)  (v2.NoAvion = 106)
 (v1.NoPilote = v2.NoPilote)))}

183
Joachim TANKOANO

SELECT v1.NoPilote
FROM Vols v1
WHERE v1.NoAvion = 104
AND EXISTS (SELECT * FROM Vols v2
WHERE v2.NoAvion = 106
AND v1.NoPilote = v2.NoPilote);

Exemple 4.3.2.v : Pour chaque pilote en service, quels sont les numéros des avions
conduits, le numéro et le nom du pilote ?
{(p.NoPilote, p.NomPilote, v.NoAvion) | Vols (v)  Pilotes (p)
 (p.NoPilote = v.NoPilote)}

SELECT p.NoPilote, p.NomPilote, v.NoAvion


FROM Vols v, Pilotes p
WHERE p.NoPilote = v.NoPilote;

Exemple 4.3.2.vi : Quels sont les noms des pilotes qui conduisent un vol au départ
de Ouagadougou ?
{(p.NomPilote) | Pilotes (p)   v (Vols (v)
 ( v.NoPilote = p.NoPilote)  (v.VD = 'Ouagadougou'))}

SELECT p.NomPilote FROM Vols v, Pilotes p


WHERE v.NoPilote = p.NoPilote AND v.VD = 'Ouagadougou';

Exemple 4.3.2.vii : Quels sont les noms des pilotes qui conduisent un « AIRBUS
A310 » ?
{(p.NomPilote) | Pilotes (p)  ( v, a (Vols (v)  Avions (a)
 (v.NoPilote = p.NoPilote)
 (v.NoAvion = a.NoAvion)
 (a.NomAvion = 'AIRBUS A310')))}
SELECT p.NomPilote
FROM Vols v, Avions a, Pilotes p
WHERE v.NoPilote = p.NoPilote
AND v.NoAvion = a.NoAvion
AND a.NomAvion = 'AIRBUS A310';

184
SGBD relationnels – Tome 1, État de l’art

Exemple 4.3.2.viii : Quels sont les numéros des pilotes qui conduisent tous les
avions de la compagnie ?
{(p.NoPilote) | Pilotes (p)
  ( a (Avions (a)   ( v (Vols (v)
 (v.NoPilote = p.NoPilote)
 (v.Novion = a.NoAvion))))}

SELECT p.NoPilote
FROM Pilotes p
WHERE NOT EXISTS
(SELECT *
FROM Avions a
WHERE NOT EXISTS
(SELECT *
FROM Vols v
WHERE v.NoPilote = p.NoPilote
AND v.NoAvion = a.NoAvion));

SELECT v.NoPilote FROM Vols v


MINUS
SELECT p.NoPilote
FROM Pilotes p
WHERE EXISTS
(SELECT a.*
FROM Avions a
WHERE NOT EXISTS
(SELECT v2.*
FROM Vols v2
WHERE v2.NoAvion=a.NoAvion
AND v2.NoPilote = p.NoPilote));

Exemple 4.3.2.ix : Quels sont les numéros des pilotes qui conduisent au moins
tous les AIRBUS A310 de la compagnie ?
{(p.NoPilote) | Pilotes (p)
  ( a (Avions (a)  a.NomAvion = 'AIRBUS A310'
  ( v (Vols (v)
 (v.NoPilote = p.NoPilote)
 (v.Novion = a.NoAvion))))}

185
Joachim TANKOANO

SELECT p.NoPilote
FROM Pilotes p
WHERE NOT EXISTS
(SELECT *
FROM Avions a
WHERE a.NomAvion = 'AIRBUS A310'
AND NOT EXISTS
(SELECT *
FROM Vols v
WHERE v.NoPilote = p.NoPilote
AND v.NoAvion = a.NoAvion));

Exemple 4.3.2.x : Quels sont les numéros d’avions qui sont supérieurs à tous les
numéros des avions conduits par le pilote n° 2 ?

{(a.NoAvion) |Avions (a)   ( v (Vols(v)  v.Nopilote = 2


 a.NoAvion  v.NoAvion))

SELECT a.NoAvion
FROM Avions a
WHERE NOT EXISTS
(SELECT v.* FROM Vols v
WHERE v.NoPilote = 2 AND v.NoAvion  = a.NoAvion) ;

On peut aussi formuler comme suit cette requête en utilisant le prédicat de


comparaison « ALL » :
SELECT a.NoAvion
FROM Avions a
WHERE a.NoAvion  ALL (SELECT v.NoAvion
FROM Vols v
WHERE v.NoPilote = 2) ;

Exemple 4.3.2.xi : Quels sont les noms des pilotes qui n’effectuent pas de vol au
départ de Ouagadougou ?
{(p.NomPilote) | Pilotes (p)
  ( v (Vols (v)
 (v.NoPilote = p.NoPilote)
 (v.VD = 'Ouagadougou')))}

186
SGBD relationnels – Tome 1, État de l’art

SELECT p.NomPilote
FROM Pilotes p
WHERE NOT EXISTS
(SELECT *
FROM Vols v
WHERE v.NoPilote = p.NoPilote
AND v.VD = 'Ouagadougou'));

Exemple 4.3.2.xii : Quels sont les numéros des pilotes qui conduisent un avion
conduit par le pilote n° 32 ?
{(v1.NoPilote) | Vols (v1)  (v1.NoPilote  32)
 ( v2 (Vols (v2)  (v2.NoPilote = 32)
 (v2.NoAvion = v1.NoAvion))}

SELECT v1.NoPilote FROM Vols v1


WHERE v1.NoPilote  32
AND EXISTS
(SELECT * FROM Vols v2
WHERE v2.NoPilote = 32 AND v2.NoAvion = v1.NoAvion);

Exemple 4.3.2.xiii : Quelles sont les villes desservies par les pilotes dont le numéro
est plus grand que celui de Pierre et Paul ?
{(v.VA) |Vols (v)
  p1 (Pilotes (p1)  (p1.NomPilote = 'Pierre')
 (v.NoPilote  p1.NoPilote))
  p2 (Pilotes (p2)  (p2.NomPilote = 'Paul')
 (v.NoPilote  p2.NoPilote))}
SELECT v.VA FROM Vols v
WHERE EXISTS (SELECT * FROM Pilotes p1
WHERE p1.NomPilote = 'Pierre'
AND v.NoPilote  p1.NoPilote)
AND EXISTS (SELECT * FROM Pilotes p2
WHERE p2.NomPilote = 'Paul'
AND v.NoPilote  p2.NoPilote);

187
Joachim TANKOANO

Exemple 4.3.2.xiv : Quelle est la capacité moyenne des avions par ville ainsi que
par type ?
SELECT a.LocAvion, a.NomAvion, AVG (a.CapAvion)
FROM Avions a
GROUP BY a.LocAvion, a.NomAvion ;

Exemple 4.3.2.xv : Quels sont les avions dont la capacité est supérieure à toutes
les moyennes de capacité d’avion par ville ?
SELECT * FROM Avions a1
WHERE a1.CapAvion  ALL (SELECT AVG(a2.CapAvion)
FROM Avions a2
GROUP BY a2.LocAvion) ;

Exemple 4.3.2.xvi : Quels sont les vols (NoVol, VD, VA) en correspondance
directe ou indirecte au départ de Bobo-Dioulasso ?
SELECT v.NoVol, v.VD, v.VA FROM Vols v
START WITH v.VD = 'Bobo-Dioulasso'
CONNECT BY NOCYCLE PRIOR v.VA = v.VD;

4.3.3. Exercice

Soit le schéma relationnel de l’exercice de la section 2.3.4 et de la section


2.4.6, défini comme suit :
Salles (nom, horaire, titre)
A_Produit (producteur, titre)
Joue_Dans (acteur, titre, producteur)
A_Vu (spectateur, titre)
Aime (spectateur, titre)

Pour chacune de ces requêtes informelles traitées dans ces exercices


précédents, proposez une formulation SQL :
1) Où peut-on voir un film où joue « Idrissa OUEDRAOGO » ?

188
SGBD relationnels – Tome 1, État de l’art

2) Quels sont les acteurs qui jouent dans tous les films produits par
« Idrissa OUEDRAOGO » ?
3) Quels sont les spectateurs qui aiment un film qu’ils n’ont pas vu ?
4) Quels sont les spectateurs qui aiment tous les films qu’ils ont vus ?
5) Quels sont les producteurs qui n’ont vu que les films qu’ils ont
produits ?

4.3.4. L’ordre INSERT INTO

L’ordre INSERT INTO permet l’insertion de lignes dans une ou plusieurs


tables. Toutefois, nous ne nous intéresserons ici qu’à l‘insertion de lignes
dans une seule table en se conformant à la syntaxe simplifiée ci-après :
INSERT INTO Table_destination [({Nom_colonne} [,.. n])]
{VALUES ({Expression | (Sous_requête)|NULL | DEFAULT} [,..n])
| Sous_requête}

a) La spécification des colonnes des lignes à insérer

Dans la clause « INSERT INTO » d’un ordre INSERT, on peut, soit ne pas
citer, soit citer dans un ordre quelconque, les noms des colonnes des lignes
à insérer.

Lorsqu’on cite les noms des colonnes des lignes à insérer, on peut omettre
les noms des colonnes qui peuvent recevoir une valeur NULL ou une valeur
par défaut, au regard de la définition de la table de destination, si la valeur
de ces colonnes doit être la valeur NULL ou leur valeur par défaut.

b) La spécification de la ligne à insérer à l’aide de la clause VALUES

La clause « VALUES » ne permet que l’insertion d’une seule ligne. C’est la


clause la plus utilisée pour mettre à jour une table.

Si les noms des colonnes de la ligne à insérer ne sont pas cités explicitement
dans la clause « INSERT INTO », la valeur de chaque colonne de cette ligne
doit être fournie explicitement dans le bon ordre dans la clause
« VALUES ».

189
Joachim TANKOANO

Si au contraire les noms des colonnes de la ligne à insérer sont cités


explicitement dans la clause « INSERT INTO », dans la clause
« VALUES », une valeur doit être fournie dans le même ordre, pour chaque
colonne citée.

Les valeurs « DEFAULT » et « NULL » sont autorisées dans la clause


« VALUES ».

Les sous-requêtes qui retournent une seule ligne sont aussi autorisées dans
la clause « VALUES ».

Une erreur d’insertion se produit si pour une colonne de la ligne à insérer,


aucune valeur n’est fournie alors que, dans la définition de la table
concernée, cette colonne n’a pas été autorisée à recevoir une valeur par
défaut ou une valeur NULL.

Exemple 4.3.4.i : Si on fait l’hypothèse que, par définition, la colonne


« CapAvion » de la table « Avions » peut recevoir des valeurs « NULL »,
alors les deux ordres ci-dessous sont équivalents. Ils insèrent un BOING 707
avec la valeur NULL dans la colonne « CapAvion ».
INSERT INTO Avions
VALUES (50, 'BOING 707', NULL, 'Bobo-Dioulasso') ;

INSERT INTO Avions (NoAvion, NomAvion, LocAvion)


VALUES (50, 'BOING 707', 'Bobo-Dioulasso') ;

Exemple 4.3.4.ii : L’ordre INSERT qui suit insert un avion qui a la même
localisation que l’avion dont le numéro est 20.
INSERT INTO Avions (NoAvion, NomAvion, LocAvion)
VALUES (50, 'BOING 707', (SELECT a.LocAvion FROM Avions a
WHERE a.NoAvion = 20)) ;

c) La spécification des lignes à insérer à l’aide d’une sous-requête

Dans un ordre INSERT, l’utilisation d’une sous-requête permet d’insérer


dans la table de destination plusieurs lignes dont les données proviennent
d’autres tables. Les colonnes du résultat de la sous-requête et les colonnes

190
SGBD relationnels – Tome 1, État de l’art

des lignes dans la table où les lignes doivent être insérées doivent être de
types compatibles.

Exemple 4.3.4.iii : Si on considère la table définie par la relation « AIRBUS


(No, Nom, Cap, Loc) », l’ordre ci-après insère dans cette table tous les
AIRBUS de la compagnie.
INSERT INTO AIRBUS (No, Nom, Cap, Loc)
SELECT NoAvion, NomAvion, CapAvion, LocAvion FROM Avions
WHERE NomAvion LIKE 'AIRBUS%';

4.3.5. L’ordre UPDATE

L’ordre UPDATE permet de modifier une ou plusieurs colonnes d’une ou


de plusieurs lignes d’une table en se conformant à la syntaxe simplifiée ci-
après :
UPDATE Nom_table
SET {{Nom_colonne = {Expression | DEFAULT | (Sous_requête)}}
|{({Nom_colonne} [,..n]) = (Sous_requête)}} [,..n]
[WHERE Prédicat]

La clause « WHERE » sert à sélectionner dans la table concernée la ou les


ligne(s) à modifier.

Lorsque plusieurs lignes sont sélectionnées, l’ordre UPDATE effectue la


même modification sur chacune de ces lignes. Lorsque la clause
« WHERE » n’est pas spécifiée, chaque ligne de la table reçoit la même
modification.

Les valeurs utilisées pour modifier les colonnes des lignes concernées
peuvent être, soient données, soient calculées dynamiquement par une
expression, soient calculées dynamiquement par une sous-requête basée
sur d’autres tables.

Sur la base de cette syntaxe, on peut distinguer les deux grandes variantes
ci-après pour l’utilisation de cet ordre :

191
Joachim TANKOANO

a) La première variante

La syntaxe simplifiée de cette variante est la suivante :


UPDATE Nom_table
SET Nom_colonne = Expression [, Nom_colonne = Expression] …
[WHERE Prédicat]

Cette variante permet de modifier une ou plusieurs colonnes en fournissant


une valeur distincte pour chaque colonne. C’est la variante la plus utilisée
pour mettre à jour une ligne dans une table.

Exemple 4.3.5.i : L’ordre UPDATE qui suit affecte la valeur 500 à la colonne
« CapAvion » de la ligne de la table « Avions » où la colonne « NoAvion »
contient la valeur 40. En d’autres termes, elle affecte la valeur 500 à la
capacité de l’avion dont le numéro est 40.
UPDATE Avions
SET CapAvion = 500 WHERE NoAvion = 40 ;

b) La deuxième variante

La syntaxe simplifiée de cette variante est schématisée par les deux sous-
variantes ci-après :
UPDATE Nom_table
SET (Nom_colonne, Nom_colonne, …) = (Sous_requête)
[WHERE Prédicat]

UPDATE Nom_table
SET Nom_colonne = (Sous_requête) [, Nom_colonne = (Sous_requête)] …
[WHERE Prédicat]

Dans cette variante, les valeurs utilisées pour modifier les colonnes sont
calculées dynamiquement par des sous-requêtes. Chaque sous-requête ne
doit retourner qu’une seule ligne.

Les sous-requêtes utilisées peuvent être indépendantes.

Exemple 4.3.5.ii : L’ordre UPDATE qui suit calcule la capacité de l’avion


dont le numéro est 30 à l’aide d’une sous-requête indépendante et affecte

192
SGBD relationnels – Tome 1, État de l’art

cette valeur à la capacité de l’avion numéro 40.


UPDATE Avions
SET CapAvion = (SELECT CapAvion
FROM Avions WHERE NoAvion = 30)
WHERE NoAvion = 40 ;

Les sous-requêtes utilisées peuvent être aussi des sous-requêtes


synchronisées.

Exemple 4.3.5.iii : L’exemple qui suit fait l’hypothèse qu’une colonne


« NomPilote » a été ajoutée à la table « Vols ». L’ordre UPDATE permet,
pour chaque vol, d’ajouter dans cette colonne le nom du pilote qui assure
ce vol. Le nom du pilote est calculé dynamiquement en utilisant une sous-
requête synchronisée.
UPDATE Vols v
SET v.NomPilote = (SELECT p.NomPilote
FROM Pilote p WHERE v.NoPilote = p.NoPilte) ;

4.3.6. L’ordre DELETE

L’ordre DELETE supprime d’une table les lignes sélectionnées par sa clause
« WHERE ». Sa syntaxe simplifiée est la suivante :
DELETE [FROM] Nom_table [WHERE Prédicat] ;

Exemple 4.3.6.i : L’ordre DELETE qui suit supprime de la table « Avions »


la ligne où la colonne « NoAvion » a comme valeur 40.
DELETE FROM Avions WHERE NoAvion = 40 ;

Exemple 4.3.6.ii : Sous ORACLE, « ROWID » est une pseudo-colonne


associée implicitement à chaque table. Pour chaque ligne d’une table,
« ROWID » est accessible au développeur et contient l’adresse physique de
cette ligne. L’ordre DELETE qui suit conserve parmi les lignes qui ont le
même nom d’avion, celle qui a le « ROWID » le plus petit et supprime les
autres, ce qui revient à supprimer les doublons.

193
Joachim TANKOANO

DELETE FROM Avions a1


WHERE ROWID  (SELECT MIN (ROWID) FROM Avions a2
WHERE a1.NomAvion = a2.NomAvion) ;

L’ordre DELETE supprime toutes les lignes de la table lorsque la clause


« WHERE » est absente.

N.B. : L’ordre DELETE n’effectue pas une suppression physique des lignes
mais plutôt une suppression logique. Pour libérer l’espace occupé par les
lignes logiquement supprimées, il faut utiliser l’ordre TRUNCATE
présentée dans la section 4.4.

4.4. Le langage de définition des données de SQL


Les ordres SQL qui permettent à l’utilisateur d’agir sur la description des
objets gérés par le SGBD (tables, vues, index, utilisateurs, procédures
stockées, etc.) sont les suivants :
• CREATE, utilisé pour créer ces objets avec les métadonnées qui les
décrivent ;
• ALTER, utilisé pour modifier les métadonnées de ces objets ;
• TRUNCATE TABLE, utilisé pour supprimer les lignes d’une table
tout en libérant l’espace disque occupé par ces lignes ;
• DROP, utilisé pour supprimer ces objets et leurs métadonnées ;
• GRANT, utilisé pour accorder à un utilisateur des privilèges (c'est-
à-dire les droits requis pour interagir avec le SGBD à l’aide d’ordres
SQL) ;
• REVOKE, utilisé pour retirer à des utilisateurs les privilèges qui
leurs ont été accordés ;
• RENAME, utilisé pour renommer des objets ;
• COMMENT ON, utilisé pour associer des commentaires à une table,
à une vue ou à une colonne, afin de documenter le schéma logique
d’une base de données.

194
SGBD relationnels – Tome 1, État de l’art

Ensemble, ces ordres constituent le langage de définition des données


(LDD) de SQL. Les informations gérées à l’aide de ces ordres sont des
métadonnées qui décrivent d’un point de vue logique et physique les objets
gérés par le SGBD. De façon plus précise, ces ordres permettent de définir
et de modifier, dans le schéma d’un utilisateur, le schéma logique global,
les schémas logiques externes et le schéma physique d’une base de données
relationnelle, incluant la définition des procédures et fonctions stockées,
des packages et des déclencheurs (voir le chapitre 5 pour les procédures et
fonctions stockées, les packages et les déclencheurs). Ces ordres permettent
aussi de définir et de modifier la description d’objets gérés par le SGBD
comme les utilisateurs et les rôles (voir le paragraphe 4.4.5).

4.4.1. Le dictionnaire des données et l’architecture des schémas

Les objets qui appartiennent à un utilisateur sont logiquement regroupés à


l‘intérieur de son schéma et sont gérés par le SGBD comme étant les
éléments d’une base de données appartenant à cet utilisateur. Le schéma de
chaque utilisateur porte son nom d’utilisateur qui sert de préfixe dans la
formation du nom de chaque objet qui lui appartient. Deux objets qui
appartiennent à des schémas différents peuvent ainsi porter le même nom
sans que cela n’engendre de conflit.

Les métadonnées qui décrivent les objets définis dans les schémas
appartenant aux différents utilisateurs, sont centralisées dans un catalogue
appelé aussi dictionnaire de données, implémenté à l’aide d’une méta-
base, c'est-à-dire, d’une base de données de métadonnées. Cette méta-base
est un composant essentiel du SGBD. Elle est indispensable pour la gestion
des accès à ces objets qui se font sous le contrôle du SGBD et de façon plus
générale pour son fonctionnement. Les utilisateurs qui ont les droits requis
peuvent accéder à cette méta-base à l’aide de l’ordre SELECT, étant entendu
que cette méta-base est aussi une base de données relationnelle. En outre,
les outils et environnements d’aide au développement et les progiciels
peuvent eux aussi agir sur le contenu de cette méta-base en utilisant les
ordres du langage de définition des données, ce qui fait de cette méta-base
un élément structurant et central de ces produits.

195
Joachim TANKOANO

La différence entre la base de données d’un utilisateur définie dans son


schéma et cette méta-base qui correspond au dictionnaire des données,
réside dans le fait que la vocation de la base de données d’un utilisateur est
le stockage des données gérées par cet utilisateur, alors que la vocation de
cette méta-base est le stockage des méta-données qui décrivent tous les
objets (tables, vues, index, utilisateurs, procédures stockées, etc.) gérés par
le SGBD, incluant tous les objets stockés dans la base de données de chaque
utilisateur.

Le catalogue d’un SGBD qui adhère à l’architecture des schémas à trois


niveaux, définie par l’ANSI/SPARC (voir paragraphe 1.2.1) peut ainsi se
définir de façon cohérente et structurée comme étant une métabase qui
contient, pour la base de données de chaque système d’information, la
définition de cette architecture des schémas dans le schéma d’un utilisateur,
à l’aide :
• D’un schéma logique global (qui définit des tables)
• De schémas logiques externes (qui définissent des vues dérivées de
ce schéma logique global), destinés chacun à une application
distincte
• D’un schéma physique (qui définit l’implémentation sur les
supports de stockage de ce schéma logique global).
Ce chapitre ne traite que des clauses des ordres du langage de définition
des données de SQL qui concernent la définition du schéma logique global
d’une base de données et de ses schémas logiques externes, c'est-à-dire, la
définition de cette base de données, vue comme étant constituée de tables
(réelles ou virtuelles) qui se font référence les unes les autres et qui sont
soumises à des contraintes d’intégrité et de confidentialité. En d’autres
termes, dans ce chapitre, la présentation des ordres de définition des
données fait abstraction du schéma physique de la base de données. Le
schéma physique d’une base de données créée à l’aide de ces ordres est
déterminé par le SGBD sur la base d’options par défaut. Les ordres et les
clauses des ordres qui concernent la définition du schéma physique d’une
base de données sont abordés dans le chapitre 7.

196
SGBD relationnels – Tome 1, État de l’art

4.4.2. La création, modification et suppression de la définition


d’une table

La création d’une table se fait à l’aide de l’ordre CREATE TABLE qui


permet d’allouer de l’espace mémoire pour son stockage et de créer sa
description logique et sa description physique dans le dictionnaire de
données. Quant à la modification de la description de la table ainsi créés,
elle se fait à l’aide de l’ordre ALTER TABLE.

Définir la description logique d’une table revient à définir cette table


comme servant à stocker les données d’une relation, tout en prenant en
compte l’aspect confidentialité. La définition de la description logique
d’une table consiste donc à définir les colonnes qui constituent cette table,
sa clé primaire, ses clés uniques, ses clés étrangères, toute autre contrainte
d’intégrité que SQL permet d’expliciter dans la définition d’une table, ainsi
que les contraintes de confidentialité auxquelles cette table doit être
soumise.

Ce qui suit présente l’ordre CREATE TABLE pour la création d’une table
et de sa description logique, l’ordre ALTER TABLE pour la modification
de cette description, l’ordre TRUNCATE TABLE pour la suppression
physique des lignes d’une table et l’ordre DROP TABLE pour la
suppression de cette table et de sa description. Ces ordres, limités aux
fonctionnalités présentées, permettent de définir et de gérer la structure
logique d’une table ainsi que les contraintes d’intégrité associées à cette
table. Elles participent donc à la création et à la gestion du schéma logique
relationnel d’une base de données. Les ordres SQL qui permettent de
définir et de gérer les contraintes de confidentialité sont présentés dans le
paragraphe 4.4.5.

a) L’ordre CREATE TABLE

La syntaxe simplifiée de l’ordre CREATE TABLE qui suit permet de définir


une table en faisant abstraction de son organisation physique qui sera de ce
fait déterminée par le SGBD sur la base d’options par défaut.

197
Joachim TANKOANO

CREATE TABLE [Nom_schéma.]Nom_table


[([{Définition_colonne} [,..n]]
[{Contrainte_ligne} [,..n]])]
[AS Sous_requête]
Définition_colonne ::= Nom_col Type_colonne [DEFAULT Expression]
[Contrainte_colonne]
Contrainte_colonne ::= [CONSTRAINT Nom_contrainte]
Type_contrainte_colonne [Cond_activation]
Contrainte_ligne ::= [CONSTRAINT Nom_contrainte]
Type_contrainte_ligne [Cond_activation]

Type_contrainte_colonne ::= {NOT NULL | UNIQUE | PRIMARY KEY


| Référence_table | CHECK (Prédicat)}

Type_contrainte_ligne ::= {UNIQUE ({Nom_colonne}[,..n])


| PRIMARY KEY ({Nom_colonne}[,..n])
| FOREIGN KEY ({Nom_colonne}[,..n])
Référence_table
| CHECK (Prédicat)}

Référence_table ::= REFERENCES [Nom_schéma.]Nom_table


({Nom_colonne}[,..n])
[ON DELETE {CASCADE | SET NULL}]

Cond_activation ::= [[NOT] DEFERRABLE]


[INITIALLY {IMMEDIATE | DEFERRED}]

(1) Cette syntaxe permet de définir la description logique d’une table,


c'est-à-dire, les colonnes qui la composent et les contraintes d’intégrité
auxquelles elle doit être soumise.

(2) Chaque colonne de cette table doit être définie obligatoirement par
son nom et son type, et optionnellement par la valeur qu’elle prend par
défaut et par la contrainte d’intégrité de colonne qu’elle doit respecter. Des
contraintes d’intégrité de ligne, qui concernent en général plusieurs
colonnes d’une ligne, peuvent être définies en plus, indépendamment de
toute colonne.

(3) Le typage des colonnes est assuré à l’aide de types prédéfinis. Le


tableau qui suit contient quelques exemples de types prédéfinis ORACLE.

198
SGBD relationnels – Tome 1, État de l’art

Type Signification

VARCHAR2 (n) Chaîne de taille variable de « n » caractères pouvant varier de


1 à 4000
CHAR (n) Chaîne de taille fixe de « n » caractères pouvant varier de 1 à
2000
NUMBER (p, e) Nombre de « p » chiffres avec « e » chiffres après la virgule
DATE Date
LONG Chaîne de caractères de taille variable pouvant atteindre 2 Go
RAW (n) Donnée binaire de « n » octets (« n » pouvant aller jusqu’à
2000)
LONGRAW Donnée binaire de longueur variable pouvant aller jusqu’à 2
Go
BFILE Donnée binaire stockée dans un fichier externe (jusqu’à 4 Go)

(4) Les contraintes d’intégrité de colonnes ne sont applicables qu’à une


seule colonne à la fois. Dans une table, elles permettent de définir :
• La clé primaire, lorsque cette clé n’est constituée que d’une seule
colonne : PRIMARY KEY
• Les clés alternatives ou uniques, lorsque ces clés ne sont constituées
que d’une seule colonne : UNIQUE
• Les clés étrangères, lorsque ces clés ne sont constituées que d’une
seule colonne : REFERENCES Nom_table (Nom_colonne)
• Les colonnes qui doivent obligatoirement être renseignées : NOT
NULL
• Les contraintes d’intégrité, spécifiées par un prédicat, lorsque ce
prédicat ne concerne qu’une seule colonne : CHECK (Prédicat).

Quant aux contraintes d’intégrité de lignes, nécessaires lorsque ces


contraintes d’intégrité impliquent plusieurs colonnes d’une ligne, elles
permettent dans une table de définir :
• La clé primaire : PRIMARY KEY (Nom_colonne, Nom_colonne, …)
• Les clés alternatives ou uniques : UNIQUE (Nom_colonne,
Nom_colonne, …)
• Les clés étrangères : FOREIGN KEY (Nom_colonne, …)
REFERENCES Nom_table (Nom_colonne, …)

199
Joachim TANKOANO

• Les contraintes d’intégrité, spécifiées par un prédicat : CHECK


(Prédicat).

La contrainte d’intégrité « PRIMARY KEY » combine les contraintes


d’intégrité « UNIQUE » et « NOT NULL ». En d’autres termes, la valeur
d’une clé primaire, en plus d’identifier une et une seule ligne, doit être
obligatoirement renseignée à l’aide d’une valeur différente de NULL.

Les colonnes et valeurs utilisées dans la définition du prédicat d’une


contrainte d’intégrité spécifiée par « CHECK (Prédicat) », doivent
obligatoirement concerner la même ligne. Ce type de contrainte d’intégrité
peut permettre la prise en compte de certaines contraintes d’intégrité de
domaine qui ne sont pas pris en compte avec les types prédéfinis utilisés.
Contrairement aux spécifications de la norme, le paramètre « Prédicat » ne
peut pas contenir une sous-requête ou un appel à une fonction stockée.

La suppression d’une ligne référencée par des lignes dépendantes à l’aide


de clés étrangères n’est pas autorisée.

L’option « ON DELETE CASCADE » permet de demander au SGBD la


suppression de toutes les lignes dépendantes lorsqu’une ligne référencée
doit être supprimée. Dans l’exemple ci-dessous, la suppression de l’avion
numéro 10 de la table « Avions » doit entrainer la suppression de toutes les
lignes de la table « Vols » où la colonne « NoAvion » contient la valeur 10.

Quant à l’option « ON DELETE SET NULL », elle permet de demander au


SGBD de remplacer dans toutes les lignes dépendantes, la valeur de la clé
étrangère par NULL, lorsqu’une ligne référencée doit être supprimée. Dans
l’exemple ci-dessous, la suppression du pilote numéro 5 de la table
« Pilotes » doit entrainer la recherche de toutes les lignes de la table « Vols »
où la colonne « NoPilote » contient la valeur 5 et le remplacement de cette
valeur par la valeur NULL.

Les options « NOT DEFERRABLE », « DEFERRABLE », « INITIALLY


IMMEDIATE » et « INITIALLY DEFERRED » sont utiles lorsqu’il est
nécessaire que la vérification d’une contrainte d’intégrité ne soit effectuée
qu’au moment de la validation de la transaction en cours (voir le chapitre 9
pour la notion de transaction).

200
SGBD relationnels – Tome 1, État de l’art

L’option « INITIALLY IMMEDIATE » est l’option par défaut. Elle permet


d’indiquer que la vérification de la contrainte d’intégrité spécifiée doit
s’effectuer après l’exécution de chaque ordre SQL de modification de la
table concernée.

L’option « INITIALLY DEFERRED » permet d’indiquer que la vérification


de la contrainte d’intégrité spécifiée ne doit s’effectuer que lors de la
validation d’une transaction.

L’option « NOT DEFERRABLE » est l’option par défaut. Elle permet


d’indiquer, en exécutant l’ordre « SET CONSTRAINT Nom_contrainte
DEFERRED », que la vérification de la contrainte d’intégrité spécifiée ne
doit pas être repoussée jusqu’à la validation de la transaction en cours.

L’option « DEFERRABLE » permet d’indiquer, en exécutant l’ordre « SET


CONSTRAINT Nom_contrainte DEFERRED », que la vérification de la
contrainte d’intégrité spécifiée doit être repoussée pour qu’elle n’ait lieu
qu’au moment de la validation de la transaction en cours.

Exemple 4.4.2.i : Soit le schéma logique relationnel ci-après :


Avions (NoAvion, NomAvion, CapAvion, LocAvion)
Pilotes (NoPilote, NomPilote, AdrPilote)
Vols (NoVol, #NoPilote, #NoAvion, HD, HA)

Les tables qui servent à stocker ces relations peuvent être créées à l’aide des
ordres ci-après :
CREATE TABLE Avions
(NoAvion NUMBER (5) PRIMARY KEY,
NomAvion VARCHAR2 (15) NOT NULL,
CapAvion NUMBER (4) DEFAULT NULL,
LocAvion VARCHAR2 (30) DEFAULT NULL) ;

CREATE TABLE Pilotes


(NoPilote NUMBER (3) PRIMARY KEY,
NomPilote VARCHAR2 (30) NOT NULL,
AdrPilote VARCHAR2 (50) NOT NULL) ;

CREATE TABLE Vols


(NoVol NUMBER (5) PRIMARY KEY,
NoPilote NUMBER (3) DEFAULT NULL,

201
Joachim TANKOANO

NoAvion NUMBER (5) NOT NULL,


HD DATE NOT NULL,
HA DATE NOT NULL,
CONSTRAINT fk_Pilote FOREIGN KEY (NoPilote)
REFERENCES Pilotes (NoPilote)
ON DELETE SET NULL,
CONSTRAINT fk_Avion FOREIGN KEY (NoAvion)
REFERENCES Avions (NoAvion)
ON DELETE CASCADE,
CHECK (HA  HD)) ;

(5) La spécification d’une sous-requête dans la clause « AS » entraine,


après la création d’une table à l’aide de l’ordre CREATE TABLE, l’insertion
dans cette table des lignes du résultat de cette sous-requête. Lorsque l’ordre
CREATE TABLE contient cette clause, la définition des colonnes de la table
à créer peut se réduire aux noms des colonnes ou peut tout simplement être
ignorée. Dans ce cas, les informations manquantes relatives aux colonnes
de la table à créer sont déduites des tables de cette sous-requête. Cette
clause est particulièrement intéressante pour la création de tables
temporaires.

Exemple 4.4.2.ii : L’ordre CREATE TABLE ci-dessous crée la table


« AvionsAIRBUS ». Cet ordre, déduit du résultat de l’ordre SELECT
imbriqué, les informations relatives aux colonnes de la table qu’elle crée et
insère dans cette table, le résultat de cet ordre SELECT imbriqué.
CREATE TABLE AvionsAIRBUS
AS SELECT *
FROM Avions
WHERE NomAvion LIKE 'AIRBUS%' ;

b) L’ordre ALTER TABLE

La syntaxe simplifiée de l’ordre ALTER TABLE qui suit permet de modifier


la description logique des tables créées à l’aide de l’ordre CREATE TABLE
présenté plus haut, donc de modifier le schéma logique d’une base de
données.

202
SGBD relationnels – Tome 1, État de l’art

ALTER TABLE [Nom_schéma.]Nom_table


[ADD ({Définition_colonne} [,..n])]
[MODIFY ({Modification_définition_colonne} [,..n])]
[ADD {Contrainte_ligne} [,..n]]
[DROP CONSTRAINT Nom_contrainte [CASCADE]]
[DROP PRIMARY KEY [CASCADE]]
[DROP UNIQUE ({Nom_colonne}[,..n]) [CASCADE]]
[{ENABLE | DISABLE} CONSTRAINT Nom_contrainte [CASCADE]]
[{ENABLE | DISABLE} PRIMARY KEY [CASCADE]]
[{ENABLE | DISABLE} UNIQUE ({Nom_colonne}[,..n]) [CASCADE]]
Définition_colonne ::= Nom_colonne Type_colonne
[DEFAULT Expression] [Contrainte_colonne]
Modification_définition_colonne ::= Nom_colonne [Type_colonne]
[DEFAULT Expression] [Contrainte_colonne]
Contrainte_colonne ::= [CONSTRAINT Nom_contrainte]
Type_contrainte_colonne [Cond_activation]
Contrainte_ligne ::= [CONSTRAINT Nom_contrainte]
Type_contrainte_ligne [Cond_activation]
Type_contrainte_colonne ::= {NOT NULL | UNIQUE | PRIMARY KEY
| Référence_table | CHECK (Prédicat)}
Type_contrainte_ligne ::= {UNIQUE ({Nom_colonne}[,..n])
| PRIMARY KEY ({Nom_colonne}[,..n])
| FOREIGN KEY ({Nom_colonne}[,..n]) Référence_table
| CHECK (Prédicat)}
Référence_table ::= REFERENCES [Nom_schéma.]Nom_table
({Nom_colonne}[,..n])
[ON DELETE {CASCADE | SET NULL}]
Cond_activation ::= [[NOT] DEFERRABLE]
[INITIALLY {IMMEDIATE | DEFERRED}]

(1) Un ordre ALTER TABLE, construit en se basant sur cette syntaxe,


permet d’apporter des modifications au niveau de la définition des
colonnes et des contraintes d’intégrité d’une table. On peut noter que la
définition des colonnes et des contraintes d’intégrité suit la même syntaxe
dans l’ordre CREATE TABLE et dans l’ordre ALTER TABLE.

203
Joachim TANKOANO

(2) La clause « ADD ({Définition_colonne}[,..n]) » de l’ordre ALTER


TABLE permet l’ajout dans une table, de nouvelles colonnes. Cependant,
lorsque la table concernée contient déjà des lignes, les nouvelles colonnes
doivent, soient être autorisées à recevoir la valeur NULL, soient avoir une
clause « DEFAULT » qui définit pour chaque colonne sa valeur par défaut.
Après l’ajout de nouvelles colonnes, dans les lignes qui existent déjà, le
SGBD initialise à NULL les nouvelles colonnes qui sont autorisées à
recevoir cette valeur, et à la valeur par défaut, les nouvelles colonnes pour
lesquelles une clause « DEFAULT » est définie.

(3) La clause « MODIFY ({Modification_définition_colonne}[,..n])}) » de


l’ordre ALTER TABLE permet la modification de tous les paramètres de
définition d’une colonne, à l’exception de son nom. Lorsque la table
concernée contient déjà des lignes, le type d’une colonne peut être remplacé
par un autre type, si dans chaque ligne de la table, la valeur de cette colonne
est NULL. Dans tous les cas, il est possible de modifier le nombre maximum
de caractères des colonnes qui contiennent des chaines de caractères et la
précision des colonnes qui contiennent des valeurs numériques.

(4) L’ordre ALTER TABLE permet aussi d’ajouter, de supprimer, de


désactiver ou de réactiver des contraintes d’intégrité.

Exemples 4.4.2.iii : Ci-dessous quelques exemples d’ordres ALTER TABLE.


ALTER TABLE Avions
MODIFY (NomAvion VARCHAR2 (30));
ALTER TABLE Vols
ADD CONSTRAINT Vols_NoPilote_FK
FOREIGN KEY (NoPilote) REFERENCES Pilotes (NoPilote);

ALTER TABLE Avions


DROP PRIMARY KEY CASCADE;

c) L’ordre TRUNCATE TABLE

La syntaxe simplifiée de l’ordre TRUNCATE TABLE qui suit permet de


supprimer physiquement les lignes d’une table :

204
SGBD relationnels – Tome 1, État de l’art

TRUNCATE TABLE [Nom_schéma.]Nom_table ;

L’ordre TRUNCATE TABLE supprime les ligne d’une table et libère


l’espace physique occupé par les lignes supprimées. Toutefois, la
description logique et physique de la table concernée et les droits d’accès
accordés sur cette table sont conservés. L’utilisation de cet ordre est donc
plus efficace que la suppression d’une table à l’aide de l’ordre DROP
TABLE suivie de sa création à nouveau avec l’ordre CREATE TABLE.

d) L’ordre DROP TABLE

La syntaxe simplifiée de l’ordre DROP TABLE qui suit permet de


supprimer physiquement une table, toutes les informations descriptives qui
la concernent ainsi que les droits d’accès accordés sur cette table :
DROP TABLE [Nom_schéma.]Nom_table
[CASCADE CONSTRAINTS] [PURGE] ;

La clause « CASCADE CONSTRAINTS » permet de demander au SGBD


la suppression des contraintes d’intégrité référentielles qui concernent la
table. L’absence de cette clause génère une erreur lorsque ces contraintes
d’intégrité référentielles existent.

La clause « PURGE » permet de demander au SGBD la libération de


l’espace mémoire occupé par les objets supprimés. L’absence de cette clause
conduit à la non-libération de cet espace mémoire, ce qui rend possible
l’annulation des effets de l’ordre DROP TABLE à l’aide de l’ordre
ROLLBACK (voir chapitre 9).

4.4.3. La création, modification, suppression et manipulation


d’une vue

Une vue est une table virtuelle (on dit aussi dérivée) qui n’a pas
d’existence réelle. Elle n’existe qu’à travers l’ordre « SELECT » qui lui est
associé pour le calcul de son contenu, chaque fois que de besoin, à l’aide de
tables qui ont une existence réelle. SQL donne la possibilité d’utiliser dans
un ordre « SELECT », « INSERT », « UPDATE » ou « DELETE » ces tables
virtuelles, comme s’il s’agissait de tables réelles, pour consulter ou pour

205
Joachim TANKOANO

modifier le contenu des tables réelles qui servent au calcul de leur contenu.
Le traitement d’une requête SQL sur une vue s’effectue en substituant tout
simplement le nom de cette vue par la sous-requête qui sert au calcul de
son contenu.

Ce concept permet de répondre à de nombreux besoins très importants.


Dans ce tome, nous ne retiendrons pour le moment que les trois types
d’utilisation ci-après :
1) Comme nous l’avons déjà vu dans la section 1.2.1, l’un des
principaux rôles du schéma externe d’une application est de
délimiter la portion des données définies par le schéma logique
global que cette application peut voir et peut manipuler.
Dans la pratique, le schéma externe d’une application se définit
d’une part, en créant des vues qui restreignent, notamment à l’aide
de sélections de lignes et de projections de colonnes, les données des
tables réelles du schéma logique global, aux données que cette
application peut voir et peut manipuler et d’autre part, en retirant à
cette application tout droit d’accès direct à ces tables réelles du
schéma logique global afin de contraindre cette application à accéder
au contenu de ces tables du schéma logique global qu’à travers les
vues de son schéma externe.
Ce faisant, dans ce premier type d’utilisation, les vues combinées à
une définition adéquate des droits d’accès servent à garantir la
confidentialité des données voulue à travers la définition du schéma
externe d’une application, mais aussi à garantir l’indépendance
logique des données. Pour cette application, du fait des restrictions
qui lui sont ainsi imposées :
• Elle n’a aucune possibilité pour voir et pour manipuler les
données qui ne sont pas dans les vues de son schéma externe
• Et toute modification de la définition des tables réelles qui n’a
pas d’impact sur le contenu des vues auxquelles elle a accès,
ne pourra pas avoir d’impact sur ses programmes.
2) On peut aussi utiliser ce concept dans le but de prendre en compte
une contrainte d’intégrité complexe non prise en compte à travers la

206
SGBD relationnels – Tome 1, État de l’art

définition des tables réelles. Il peut s’agir d’une contrainte d’intégrité


définie par un prédicat sur des attributs, découlant d’une règle de
gestion de l’entreprise. Ce prédicat peut concerner de façon très large
des dépendances entre groupes d’attributs, des équations entre des
données élémentaires provenant de lignes ou de tables différentes,
des collections de données élémentaires ou des données élémentaires
agrégées. Ce faisant, dans ce deuxième cas, le recours à une vue sert
à garantir l’intégrité des données.
3) Enfin, il est aussi possible d’exploiter ce concept pour simplifier la
formulation des requêtes complexes fréquentes. En créant dans le
schéma logique de la base de données des vues définies par ces
requêtes, les utilisateurs peuvent les utiliser avec plus de simplicité,
comme s’il s’agissait de tables réelles qui contiennent le résultat de
l’exécution de ces requêtes. Ce faisant, dans ce troisième type
d’utilisation, les vues servent à simplifier l’accès à la base de données.

De ce fait, l’utilisation du concept de vue est fréquente et essentielle dans la


définition du schéma logique relationnel d’une base de données.

Ce qui suit présente les ordres SQL qui permettent de créer, modifier et
supprimer une vue, les règles auxquelles sont soumises la manipulation
d’une vue à l’aide des ordres « DELETE », « UPDATE » et « INSERT »
ainsi que l’utilisation des vues pour la prise en compte des contraintes
d’intégrité qui ne peuvent pas être prises en compte dans la définition d’une
table réelle.

a) Création et modification d’une vue

La syntaxe simplifiée de l’ordre CREATE VIEW qui suit permet de créer


ou de modifier une vue.
CREATE [OR REPLACE] [FORCE | NO FORCE] VIEW
[Nom_schéma.]Nom_vue
[({Alias_de_colonne}[,..n])]
AS Sous_requête
[WITH CHECK OPTION [CONSTRAINT Nom_contrainte]]
[WITH READ ONLY];

207
Joachim TANKOANO

(1) L’utilisation d’une vue dans une requête SQL est traitée comme s’il
s’agissait d’une table qui a comme contenu le résultat de l’exécution de la
sous-requête qui la définit.

(2) Lorsque des alias de colonnes sont spécifiés, leur nombre doit être
égal au nombre de colonnes du résultat de la sous-requête. Dans ce cas,
chaque nom d’alias définit le nom d’une colonne de la vue. Lorsque les
colonnes de la sous-requête sont spécifiées en utilisant l’option « * », la vue
doit être recréée chaque fois qu’une nouvelle colonne est ajoutée à l‘une des
tables à partir desquelles la vue est dérivée. Lorsque des noms d’alias ne
sont pas spécifiés, le SGBD déduit de la sous-requête, le nom de chaque
colonne de la vue.

(3) L’option « OR REPLACE » permet de recréer une vue si elle existe


déjà, sans avoir à toucher aux droits accordés par ailleurs sur cette vue.
C’est donc l’option qui doit être utilisée lorsqu’on veut modifier la
définition d’une vue existante.

(4) L’option « FORCE » permet de créer une vue même si les tables à
partir desquelles elle doit être dérivée n’existent pas encore, alors qu’avec
l’option « NO FORCE », la vue n’est créée que si ces tables existent déjà.

(5) La clause « WITH CHECK OPTION » permet de demander au


SGBD d’interdire toute modification d’une table à partir de laquelle une
vue est dérivée, qui conduirait à un résultat où des lignes insérées ne sont
pas dans la vue, ou à un résultat où des lignes modifiées sont exclues de la
vue. En d’autres termes, lorsque cette clause est spécifiée, toute ligne qu’on
veut modifier à travers une vue doit être dans cette vue et les lignes qu’on
obtient après modification ou insertion à travers cette vue doivent être aussi
dans la vue.

(6) Quant à la clause « WITH READ ONLY », elle rend non modifiables
les lignes visibles à travers une vue.

Exemple 4.4..3.i : Les deux ordres SQL ci-dessous sont équivalents. Ils créent
une vue qui ne contient que les avions AIRBUS. Le 1er ordre amène le SGBD
à déduire le nom de chaque colonne de cette vue à partir de la sous-requête
qui sert au calcul de son contenu. Dans le 2ème ordre, les noms des colonnes

208
SGBD relationnels – Tome 1, État de l’art

de cette vue sont définis explicitement à l’aide d’alias de colonnes.


CREATE VIEW AvionsAIRBUS
AS SELECT NoAvion, CapAvion, LocAvion
FROM Avions
WHERE NomAvion LIKE 'AIRBUS%';

CREATE VIEW AvionsAIRBUS (No, Cap, Loc)


AS SELECT NoAvion, CapAvion, LocAvion
FROM Avions
WHERE NomAvion LIKE 'AIRBUS%';

L’ordre « SELECT » qui suit affiche les numéros des avions « AIRBUS »
localisés à « OUAGADOUGOU » en s’appuyant sur la vue ainsi définie.
SELECT NoAvion
FROM AvionsAIRBUS
WHERE LocAvion = 'OUAGADOUGOU';

Pour traiter une telle requête, le SGBD remplace préalablement le nom de


la vue « AvionsAIRBUS » par une sous-requête correspondant à la requête
qui sert au calcul de son contenu, ce qui donne ce qui suit :
SELECT NoAvion
FROM (SELECT NoAvion, CapAvion, LocAvion
FROM Avions
WHERE NomAvion LIKE 'AIRBUS%')
WHERE LocAvion = 'OUAGADOUGOU';

Nous verrons au chapitre 12 que la simplification de cette requête à l’aide


de règles de réécriture donne un résultat équivalent à la requête ci-après :

SELECT NoAvion
FROM Avions
WHERE NomAvion LIKE 'AIRBUS%' AND LocAvion = 'OUAGADOUGOU';

b) Suppression d’une vue

La suppression d’une vue et des droits accordés sur cette vue se fait à l’aide

209
Joachim TANKOANO

de l’ordre DROP VIEW dont la syntaxe est la suivante :


DROP VIEW [Nom_schéma.]Nom_vue ;

c) Manipulation d’une vue

En plus des restrictions imposées par les clauses « WITH CHECK


OPTION » et « READ ONLY », la manipulation d’une vue dérivée d’une
seule table (à l’aide des ordres « DELETE », « UPDATE » et « INSERT »)
est soumise aux restrictions imposées sur sa définition par les trois règles
suivantes, visant le respect du principe général ci-après : l’utilisateur ne
peut agir à travers une vue sur une ligne ou sur une colonne d’une table
réelle que si cette vue lui permet de voir cette ligne ou cette colonne. Ces
règles s’énoncent de la façon suivante :

Règle n°1 : On ne peut supprimer aucune ligne de la table à partir de


laquelle une vue est dérivée, si la définition de cette vue qui est utilisée pour
accéder à cette table contient :
1) Des fonctions de groupes
2) Une clause GROUP BY
3) Le mot clé DISTINCT.

Pour ce qui concerne les deux premières restrictions, on peut en effet noter
que les lignes qui existent dans cette table ne sont pas visibles dans la vue
qui ne contient que des lignes agrégées. Pour ce qui concerne la troisième
restriction, on peut aussi noter qu’aucune des lignes visibles dans la vue ne
peut se répéter, même si la table à partir de laquelle cette vue est dérivée
contient des lignes qui se répètent. De ce fait, la ligne qu’on veut supprimer
de la table peut ne pas être visible dans la vue.

Règle n°2 : on ne peut modifier aucune ligne de la table à partir de laquelle


une vue est dérivée, si la définition de cette vue qui est utilisée pour accéder
à cette table contient :
1) Une des conditions restrictives de la règle n°1
2) Des colonnes définies par des expressions.

210
SGBD relationnels – Tome 1, État de l’art

La justification de la première restriction est la même que celle indiquée


dans la règle n°1. Pour ce qui concerne la deuxième restriction, on peut en
effet noter que les valeurs des colonnes utilisées dans l’expression ne sont
pas visibles dans la vue.

Règle n°3 : on ne peut ajouter aucune ligne dans la table à partir de laquelle
une vue est dérivée, si la définition de cette vue qui est utilisée pour accéder
à cette table :
1) Contient une des conditions restrictives des règles n° 1 et 2
2) Ne contient pas toutes les colonnes de la table définies avec la
contrainte d’intégrité « NOT NULL ».
La justification de la première restriction est la même que dans la règle n°2.
Pour ce qui concerne la deuxième restriction, on peut en effet noter que
dans la nouvelle ligne à insérer, la valeur d’une colonne non incluse dans la
vue qui a une contrainte d’intégrité « NOT NULL » ne peut pas être
déterminée.

N.B. : Il est aussi possible de manipuler une vue dérivée de plusieurs tables
(à l’aide des ordres « DELETE », « UPDATE » et « INSERT »). Dans ce cas,
la mise à jour de ces tables doit se faire à l’aide d’algorithmes spécifiques
conçus pour ce type de vues modifiables ou dans un déclencheur en
utilisant la clause « INSTEAD OF » comme expliqué dans la section 5.5.

d) Exemples d’implémentation de contraintes d’intégrité à l‘aide de vues

Dans ce paragraphe, nous supposons le schéma logique relationnel ci-


dessous, introduit dans le paragraphe 3.4.4. En rappel, le processus de
normalisation de ce schéma logique relationnel a conduit à des relations qui
sont en forme normale de BOYCE-CODD (FNBC), à l’exception de la
relation « Vols » qui, tout en étant en 3ème forme normale n’est pas en forme
normale de BOYCE-CODD à cause de la dépendance fonctionnelle
« NoPilote → JourVol ». De ce fait, cette relation « Vols » peut entrainer
des anomalies de stockage (anomalies d’insertion, de suppression et de
modification) et des couples de valeurs de « NoPilote, JourVol » peuvent
se répéter dans une extension légale et engendrer des redondances, parce
que le déterminant de la dépendance fonctionnelle « NoPilote → JourVol »

211
Joachim TANKOANO

n’est pas une clé. En outre, le SGBD ne peut pas maintenir de façon
automatique la contrainte d’intégrité découlant de la dépendance
fonctionnelle « NoPilote → JourVol », parce que « NoPilote » n’est pas une
clé candidate.
Avions (NoAvion, NomAvion, CapAvion, LocAvion)
Pilotes (NoPilote, NomPilote, AdrPilote)
Vols (NoVol, JourVol, #NoPilote, #NoAvion, VD, VA)

La mise de la relation « Vols » en forme normale de BOYCE-CODD conduit


au schéma logique relationnel ci-dessous qui permet d’éviter les
redondances et les anomalies de stockage au niveau de cette relation.
Toutefois, cette mise en forme normale de BOYCE-CODD a comme
conséquence la perte de la dépendance fonctionnelle « NoVol, JourVol →
NoPilote », qui de ce fait ne peut plus être garantie de façon automatique
par le SGBD.
Avions (NoAvion, NomAvion, CapAvion, LocAvion)
Pilotes (NoPilote, JourVol, NomPilote, AdrPilote)
Vols (NoVol, #NoPilote, #NoAvion, VD, VA)

L’utilisation des vues peut permettre dans les deux cas, de maintenir la
dépendance fonctionnelle qui ne peut pas l’être de façon automatique, afin
de garantir l’intégrité des données.

Pour ce faire, pour ce qui concerne le 1er schéma relationnel, pour garantir
de façon automatique le maintien de la dépendance fonctionnelle
« NoPilote → JourVol », sans toutefois éviter les redondances et les
anomalies de stockage, l’accès à la table « Vols » ne doit se faire qu’à travers
la vue « Vw_Vols_FNBC » définie comme ci-dessous.

CREATE OR REPLACE VIEW Vw_Vols_FNBC


(NoVol, JourVol, NoPilote, NoAvion, VD, VA)
AS SELECT v1.NoVol, v1.JourVol, v1.Nopilote, v1.NoAvion, v1.VD, v1.VA
FROM Vols v1
WHERE NOT EXISTS (SELECT *
FROM Vols v2
WHERE v1.NoPilote = v2.NoPilote
AND v1.Jour  v2.Jour)
WITH CHECK OPTION ;

212
SGBD relationnels – Tome 1, État de l’art

Toute insertion ou modification de ligne dans la table « Vols », en utilisant


la vue « Vw_Vols_FNBC » ainsi définie, entrainera la vérification de la
contrainte d’intégrité qui découle de la dépendance fonctionnelle
« NoPilote → JourVol », en s’assurant qu’il n’existera pas après cette
insertion ou modification, deux vols « v1 » et « v2 » ayant la même valeur
dans « NoPilote » alors que leurs valeurs dans « JourVol » sont différentes.
Pour ce qui concerne le 2ème schéma relationnel, pour garantir de façon
automatique le maintien de la dépendance fonctionnelle perdue « NoVol,
JourVol → NoPilote », l’accès aux tables « Vols » et « Pilotes » ne doit se
faire qu’à travers les vues « Vw_ Vols_FNBC » et « Vw_ Pilotes_FNBC ».

La vue « Vw_ Vols_FNBC » doit être définie comme suit :


CREATE OR REPLACE VIEW Vw_Vols_FNBC
(NoVol, NoPilote, NoAvion, VD, VA)
AS SELECT v1.NoVol, v1.Nopilote, v1.NoAvion, v1.VD, v1.VA
FROM Vols v1
WHERE NOT EXISTS
(SELECT *
FROM Pilotes p1
WHERE v1.NoPilote = p1.NoPilote
AND EXISTS
(SELECT *
FROM Vols v2, Pilotes p2
WHERE v2.NoPilote = p2.NoPilote
AND v1.NoPilote  v2.NoPilote
AND v1.NoVol = v2.NoVol
AND p1.JourVol = p2.JourVol))
WITH CHECK OPTION ;

Toute insertion ou modification de ligne dans la table « Vols », en utilisant


la vue « Vw_Vols_FNBC » ainsi définie, entrainera la vérification de la
contrainte d’intégrité découlant de la dépendance fonctionnelle « NoVol,
JourVol → NoPilote », en s’assurant qu’il n’existera pas après cette
insertion ou modification, deux vols « v1 » et « v2 » ayant la même valeur
dans « NoVol » et la même valeur dans « JourVol » alors que leurs valeurs
dans « Nopilote » sont différentes.

213
Joachim TANKOANO

Quant à la vue « Vw_ Pilotes_FNBC », elle doit être définie comme suit :
CREATE OR REPLACE VIEW Vw_Pilotes_FNBC
(NoPilote, JourVol, NomPilote, AdrPilote)
AS SELECT p1.NoPilote, p1.JourVol, p1.NomPilote, p1.AdrPilote
FROM Pilotes p1
WHERE NOT EXISTS
(SELECT *
FROM Vols v1
WHERE v1.NoPilote = p1.NoPilote
AND EXISTS
(SELECT *
FROM Vols v2, Pilotes p2
WHERE v2.NoPilote = p2.NoPilote
AND v1.NoPilote  v2.NoPilote
AND v1.NoVol = v2.NoVol
AND p1.JourVol = p2.JourVol))
WITH CHECK OPTION ;

Toute insertion ou modification de ligne dans la table « Pilotes », en


utilisant la vue « Vw_Pilotes_FNBC » ainsi définie, entrainera la
vérification de la contrainte d’intégrité découlant de la dépendance
fonctionnelle « NoVol, JourVol → NoPilote », en s’assurant qu’il n’existera
pas après cette insertion ou modification, deux vols « v1 » et « v2 » ayant la
même valeur dans « NoVol » et la même valeur dans « JourVol » alors que
leurs valeurs dans « Nopilote » sont différentes.

4.4.4. La création, modification et suppression d’une séquence

Une séquence est un objet partageable que des transactions concurrentes


peuvent utiliser pour acquérir des numéros séquentiels uniques. Ces objets
sont en général utilisés dans des programmes applicatifs pour obtenir la
prochaine valeur d’une clé primaire ou d’une clé unique. Les séquences
sont donc des objets utilitaires d’une base de données qui présentent un
intérêt certain, étant entendu que la gestion dans une table, accessible par
plusieurs transactions, du prochain numéro à attribué à une clé primaire ou
unique n’est pas aisée.

Ce qui suit présente les ordres de création (« CREATE SEQUENCE »), de

214
SGBD relationnels – Tome 1, État de l’art

modification (« ALTER SEQUENCE ») et de suppression (« DROP


SEQUENCE ») d’une séquence ainsi que le mode de fonctionnement de cet
objet.

a) Création d’une séquence

La création d’une séquence se fait à l’aide de l’ordre CREATE SEQUENCE.


La syntaxe de cet ordre est la suivante :
CREATE SEQUENCE [Nom_schéma.]Nom_séquence
[INCREMENT BY n]
[START WITH n]
[{MAXVALUE n | NOMAXVALUE}]
[{MINVALUE n | NOMINVALUE}]
[{CYCLE | NOCYCLE}]
[{CACHE n | NOCACH}]
[{ORDER | NOORDER}]

Exemple 4.4.4.i : L’ordre qui suit crée un générateur des numéros d’avions,
qui commence par 1, incrémente les numéros générés par pas de 1, arrête la
génération des numéros à 100 sans la recommencer à 1 et n’effectue pas de
génération par anticipation dans la mémoire cache.
CREATE SEQUENCE Avions_NoAvion_Seq
INCREMENT BY 1
START WITH 1
MAXVALUE 100
NOCYCLE
NOCACHE;

b) Modification d’une séquence

La modification d’une séquence se fait à l’aide de l’ordre ALTER


SEQUENCE dont la syntaxe est la suivante :
ALTER SEQUENCE [Nom_schéma.]Nom_séquence
[INCREMENT BY n]
[{MAXVALUE n | NOMAXVALUE}]
[{MINVALUE n | NOMINVALUE}]

215
Joachim TANKOANO

[{CYCLE | NOCYCLE}]
[{CACHE n | NOCACH}]
[{ORDER | NOORDER}] ;

c) Suppression d’une séquence

La suppression d’une séquence se fait à l’aide de l’ordre DROP


SEQUENCE dont la syntaxe est :
DROP SEQUENCE [Nom_schéma.]Nom_séquence ;

d) Fonctionnement d’une séquence

Chaque séquence est dotée de deux pseudo-colonnes définies chacune


comme une fonction que le développeur peut appeler :
• [Nom_schéma.]Nom_séquence.NEXTVAL : génère une nouvelle
valeur non partageable et retourne cette valeur à la transaction
appelante ;
• [Nom_schéma.]Nom_séquence.CURRVAL : retourne la dernière
valeur générée pour la transaction appelante, sans incrémenter la
séquence.
Exemple 4.4.4.ii : L’exécution répétée de l’ordre INSERT ci-dessous, par la
même transaction, permet d’insérer dans la table « Personnel » plusieurs
lignes ayant une valeur différente et unique dans la colonne « NoEmp » et
une valeur identique dans la colonne « DptEmpl ». En d’autres termes,
l’exécution répétée de cet ordre à l’intérieur d’une boucle permet d’insérer
plusieurs employés du même département dans la table « Personnel ».
INSERT INTO Personnel (NoEmpl, DptEmpl, ….)
VALUES (NoEmpl_Seq.NEXTVAL, Dpt_Seq.CURRVAL, …) ;

4.4.5. La gestion des droits d’accès et de la confidentialité

Comme nous l’avons vu dans ce qui précède, SQL est le seul langage
d’interface que l’utilisateur peut utiliser pour ses interactions avec un
SGBD relationnel. À travers ces interactions, l’utilisateur peut agir sur les

216
SGBD relationnels – Tome 1, État de l’art

métadonnées à l’aide des ordres du LDD (le langage de définition des


données), manipuler ses données à l’aide des ordres du LMD (le langage
de manipulation des données) ou en exécutant des procédures et des
fonctions stockées définissant des opérations métier (voir la section 5.3), et
contrôler l’exécution de ses transactions notamment par leur démarcation
à l’aide du LCD (le langage de contrôle des données) (voir la section 9.5).

La finalité de la gestion des droits d’accès est de fournir une garantie sur la
confidentialité des données en garantissant que les utilisateurs qui
exécutent ces opérations sont des utilisateurs légitimes, munis des
autorisations requises pour le faire. La gestion des droits d’accès rend aussi
possible la traçabilité des accès à une base de données.

Pour gérer les droits d’accès, les éditeurs des SGBD relationnels s’appuient
principalement sur quatre concepts :
• Le concept d’UTILISATEUR, pour désigner tout acteur pouvant
interagir avec le SGBD. Cet utilisateur peut être une application, un
acteur de l’entreprise, un objet connecté, ou tout autre acteur
• Le concept de SCHEMA, utilisé pour regrouper l’ensemble des
objets gérés par le SGBD qui appartiennent à un utilisateur donné
(tables, vues, index, procédures stockées, etc.)
• Le concept de PRIVILEGE, utilisé pour désigner un droit :
o Soit pour l’exécution d’un ordre SQL
o Soit pour l’exécution d’une opération métier implémentée
par une procédure ou par une fonction stockée
• Le concept de ROLE, utilisé pour définir un groupe nommé de
privilèges.

Les principes généraux qui gouvernent l’utilisation de ces concepts pour


contrôler les accès des utilisateurs afin de fournir une garantie sur la
confidentialité des données et la traçabilité des accès se résument comme
suit :

(1) Pour interagir avec le SGBD, l’utilisateur qui le fait doit avoir été créé
préalablement. Il doit donc être connu du SGBD et doit avoir un nom

217
Joachim TANKOANO

d’utilisateur et une clé d’accès au SGBD. Il doit aussi et surtout avoir tous
les privilèges dont il a besoin pour ses interactions avec le SGBD.

(2) Par défaut, chaque utilisateur possède tous les privilèges sur chacun
des objets qu’il a créés dans son schéma et peut donc exécuter toute
opération (ordre SQL ou appel d’une procédure ou d’une fonction stockée)
qui concerne ces objets. En revanche, un utilisateur « u1 » ne peut exécuter
une opération qui concerne un objet qui appartient à un autre utilisateur
« u2 » que si l’utilisateur « u2 », en fonction de ses exigences en matière de
confidentialité, a octroyé à cet utilisateur « u1 » le privilège dont il a besoin
pour exécuter cette opération.

(3) Pour faciliter l’octroi de privilèges aux autres utilisateurs, il est


possible pour un utilisateur de créer des rôles, d’octroyer à chacun de ces
rôles un ensemble de privilèges et d’octroyer ensuite ces rôles à des
utilisateurs. L’utilisateur à qui un rôle est octroyé se voit octroyé l’ensemble
des privilèges octroyés à ce rôle.

Ce qui suit présente la mise en œuvre sous ORACLE de ces principes


généraux pour le contrôle des accès afin de fournir une garantie sur la
confidentialité et la traçabilité.

a) La gestion des utilisateurs et de leur mécanisme d’authentification

L’authentification d’un utilisateur qui cherche à accéder à une base de


données a pour finalité de vérifier l’identité de cet utilisateur afin de
s’assurer qu’il s’agit d’un utilisateur légitime. L’authentification est donc un
processus de vérification clé du contrôle des accès, étant entendu que
l’efficacité globale de ce contrôle dépend de l’efficacité de cette vérification.

La gestion des utilisateurs et des techniques mises en œuvre pour leur


authentification se fait à l’aide des ordres CREATE USER, ALTER USER
et DROP USER.

L’ordre CREATE USER permet de créer un utilisateur en spécifiant, en plus


de son nom, le type de technique qu’il doit utiliser pour son
authentification. La syntaxe simplifiée de l’ordre CREATE USER est la
suivante :

218
SGBD relationnels – Tome 1, État de l’art

CREATE USER Nom_utilisateur_de_connexion IDENTIFIED


{{BY Mot_de_passe}
| {EXTERNALLY [AS 'Nom_utilisateur_certifié']}
| {GLOBALLY [AS 'Nom_utilisateur_dans_annuaire']}}
[PASSWORD EXPIRE] [ACCOUNT {LOCK | UNLOCK}];

(1) Pour l’authentification, trois types de techniques sont mentionnées


dans cette syntaxe, à savoir :
• L’authentification par mot de passe (« BY Mot_de_passe »). Un
utilisateur créé en spécifiant cette option a automatiquement
un statut d’utilisateur local dont l’authentification est
entièrement gérée localement par le SGBD.
• L’authentification externe (« EXTERNALLY »). Un utilisateur
créé en spécifiant cette option a automatiquement un statut
d’utilisateur externe dont l’authentification est gérée par un
système tiers. Le système tiers peut être le système
d’exploitation hôte, un service réseau, un système de
certification par clé publique géré par l’entreprise ou par un
tiers de confiance, un service distant.
• L’authentification globale (« GLOBALLY »). Un utilisateur
créé en spécifiant cette option a automatiquement un statut
d’utilisateur global dont l’authentification est gérée par un
service d’annuaire d’entreprise.

(2) La clause « PASSWORD EXPIRE » permet de spécifier que le mot


de passe de l’utilisateur a un délai d’expiration. La clause « ACCOUNT »
rend l’utilisateur actif (option « UNLOCK ») ou inactif (option « LOCK »).

L’ordre ALTER USER permet de modifier les spécifications relatives à un


utilisateur. Sa syntaxe simplifiée est la suivante :
ALTER USER Nom_utilisateur_de_connexion IDENTIFIED
{{BY Nouveau_mot_de_passe [REPLACE Ancien_mot_de_passe]}
| {EXTERNALLY [AS 'Nom_utilisateur_certifié']}
| {GLOBALLY [AS 'Nom_utilisateur_dans_annuaire']}}
[DEFAULT ROLE {{Nom_rôle}[,..n]
| ALL [EXCEPT {Nom_rôle}[,..n]] | NONE}
[PASSWORD EXPIRE] [ACCOUNT {LOCK | UNLOCK}];

219
Joachim TANKOANO

(1) L’ordre ALTER USER permet d’activer ou de désactiver un


utilisateur (clause « ACCOUNT »), mais aussi de modifier le type de
technique utilisé pour son authentification (clause « IDENTIFIED ») et
l’option choisie pour l’expiration du mot de passe (clause
« PASSWORD »).

(2) L’ordre ALTER USER permet en outre de spécifier, parmi les rôles
octroyés à un utilisateur, ceux qui doivent être activés lors de sa connexion
à la base de données (clause « DEFAULT ROLE »).

L’ordre DROP USER permet de supprimer un utilisateur ainsi que tous les
objets de son schéma. Sa syntaxe simplifiée est la suivante :
DROP USER Nom_utilisateur [CASCADE] ;

Lorsque le schéma de l’utilisateur concerné contient des objets, l’option


« CASCADE » doit obligatoirement être spécifiée. Cette option conduit à
une invalidation des objets (vues, procédures et fonctions stockées,
package) qui sont dans d’autres schémas et qui font référence aux objets
supprimés.

b) La gestion des autorisations d’accès

Chaque fois qu’un utilisateur légitime soumet une opération au SGBD


(ordre SQL ou appel d’une procédure ou d’une fonction stockée), avant
d’exécuter cette opération, le SGBD vérifie que cet utilisateur a
l’autorisation requise, c'est-à-dire, le privilège requis, pour exécuter cette
opération. De ce fait, la gestion des autorisations d’accès doit avoir pour
finalité de faire en sorte que, au regard des exigences en matière de
confidentialité, les privilèges accordés à chaque utilisateur ne soient que
ceux dont il a besoin pour mener à bien ses activités. Chaque utilisateur doit
avoir tous les privilèges dont il a besoin et ne doit avoir que ces privilèges.
Le respect de cette règle permet de confier le contrôle de l’application des
règles de confidentialité de l’entreprise au SGBD plutôt qu’au développeur,
ce qui a l’avantage de simplifier grandement la programmation.

ORACLE distingue deux catégories de privilèges :

220
SGBD relationnels – Tome 1, État de l’art

• Les privilèges systèmes (ou globaux), associés aux ordres SQL qui ne
concernent pas un objet en particulier comme, « CREATE USER »,
« CREATE TABLE », « DROP ANY TABLE », etc. Il en existe une
centaine
• Les privilèges objets, associés aux opérations qui concernent un objet
en particulier d’un schéma. Le tableau ci-dessous liste quelques
exemples de privilèges objets relatifs aux tables, aux vues, aux
séquences et aux procédures et fonctions stockées.

Privilège objet Table Vue Séquence Procédure

ALTER X X
SELECT X X
INSERT X X
UPDATE X X
DELETE X X
REFRENCES X
INDEX X
EXECUTE X

Les ordres SQL qui servent à la gestion des autorisations d’accès à l’aide de
ces privilèges sont : CREATE ROLE, ALTER ROLE, DROP ROLE, SET
ROLE, GRANT et REVOKE. Ce qui suit présente ces ordres SQL.

L’ordre CREATE ROLE permet de créer un rôle, c'est-à-dire, un groupe


nommé de privilèges (vide au départ) et de spécifier les conditions qu’un
utilisateur doit respecter pour l’activation des privilèges accordées à ce rôle.
La syntaxe simplifiée de l’ordre CREATE ROLE est la suivante :
CREATE ROLE Nom_rôle
[{{NOT IDENTIFIED}
|{IDENTIFIED {{BY Mot_de_passe}
|EXTERNALLY
| GLOBALLY}}}] ;

(1) L’activation d’un rôle créé avec la clause « NOT IDENTIFIED »


s’effectue sans fournir de mot de passe.

(2) La clause « IDENTIFIED » comporte dans cette syntaxe simplifiée


trois options qui permettent la création de rôles de types différents ayant

221
Joachim TANKOANO

des conditions d’activation différentes :


• L’option « BY Mot_de_passe » permet de créer un rôle local. Pour
activer ce rôle, l’utilisateur doit être un utilisateur local et doit
fournir le mot de passe spécifié.
• L’option « EXTERNALLY » permet de créer un rôle externe. Ce rôle
ne peut être activé que par un utilisateur externe après avoir reçu
l’autorisation d’un système tiers.
• L’option « GLOBALLY » permet de créer un rôle global. Ce rôle ne
peut être activé que par un utilisateur global après avoir été autorisé
par un service d’annuaire d’entreprise.

L’ordre ALTER ROLE permet de modifier un rôle. Sa syntaxe simplifiée est


la suivante :
ALTER ROLE Nom_rôle
{{NOT IDENTIFIED}
|{IDENTIFIED {{BY Mot_de_passe}
|EXTERNALLY
| GLOBALLY}}} ;

L’ordre « DROP ROLE Nom_rôle » permet de supprimer un rôle.

L’ordre GRANT permet d’octroyer des privilèges et/ou des rôles, à des
rôles ou à des utilisateurs. La syntaxe simplifiée de cet ordre est la suivante
lorsqu’il s’agit d’octroyer des privilèges systèmes ou des rôles :
GRANT {Privilège_système |Nom_rôle | ALL PRIVILEGES}[,..n]
TO {Nom_utilisateur |Nom_rôle |PUBLIC}[,..n] [WITH ADMIN OPTION] ;

Lorsque l’option « WITH ADMIN OPTION » est spécifiée, les utilisateurs


auxquels le privilège ou le rôle a été octroyé peuvent à leur tour octroyer
ou retirer ce privilège ou ce rôle à un autre utilisateur ou à un autre rôle.

Lorsqu’il s’agit de privilèges objets, la syntaxe simplifiée de l’ordre GRANT


est la suivante :
GRANT {{Privilège_objet |ALL [PRIVILEGES]}
[({Nom_colonne}[,..n])]
ON [Nom_schéma.]Nom_objet
TO {Nom_utilisateur |Nom_rôle |PUBLIC}[,..n] [WITH GRANT OPTION] ;

222
SGBD relationnels – Tome 1, État de l’art

Lorsque l’option « WITH ADMIN OPTION » est spécifiée, les utilisateurs


auxquels le privilège a été octroyé peuvent à leur tour octroyer ou retirer ce
privilège à un autre utilisateur ou à un autre rôle.

L’ordre REVOKE permet de retirer des privilèges à un utilisateur ou à un


rôle. Lorsqu’il s’agit de retirer des privilèges systèmes ou des rôles, sa
syntaxe simplifiée est la suivante :
REVOKE {Privilège_système |Nom_rôle | {ALL PRIVILEGES}}[,..n]
FROM {Nom_utilisateur |Nom_rôle |PUBLIC}[,..n] ;

Lorsqu’il s’agit de retirer des privilèges objets, la syntaxe simplifiée de


l’ordre REVOKE est la suivante :
REVOKE {Privilège_objet | {ALL [PRIVILEGES]}}[,..n]
ON [Nom_schéma.]Nom_objet
FROM {Nom_utilisateur |Nom_rôle |PUBLIC}[,..n] ;

L’ordre REVOKE ne peut pas être utilisé pour retirer à un utilisateur, un


privilège objet ou un rôle octroyé à travers le système d’exploitation hôte.
En outre l’ordre REVOKE ne peut pas être utilisé pour retirer à un
utilisateur, un privilège ou un rôle octroyé à travers un rôle.

Un utilisateur ne peut retirer un privilège système ou un rôle que si ce


privilège ou ce rôle lui a été octroyé avec l’option « WITH ADMIN
OPTION ». Toutefois, un utilisateur auquel le privilège système « GRANT
ANY ROLE » a été octroyé peut retirer tout rôle octroyé à l’aide de l’ordre
GRANT.

Un utilisateur ne peut retirer un privilège objet à un autre utilisateur ou à


un rôle que s’il a octroyé ce privilège objet à cet autre utilisateur ou à ce rôle.
Toutefois, un utilisateur auquel le privilège système « GRANT ANY
OBJECT » a été octroyé peut retirer tout privilège objet octroyé à l’aide de
l’ordre GRANT, à l’exception des privilèges objets octroyés avec l’option
« WITH GRANT OPTION ».

Lorsqu’un utilisateur se connecte, le SGBD active automatiquement tous


ses privilèges octroyés explicitement et tous les privilèges de ses rôles par
défaut (clause « DEFAULT ROLE » de l’ordre ALTER USER). Au cours de
la session d’un utilisateur, le développeur peut utiliser l’ordre SET ROLE

223
Joachim TANKOANO

pour activer de nouveaux rôles ou pour désactiver des rôles associés à cet
utilisateur. La syntaxe de cet ordre est la suivante :
SET ROLE {{Nom_rôle IDENTIFIED BY Mot_de_passe}[,..n]
| {ALL [EXCEPT {Nom_rôle}[,..n]
| NONE} ;

(1) L’option « NONE » permet de désactiver tous les rôles accordés


pour une session.

(2) Ne peuvent être activés ou réactivés à l’aide de cet ordre que les rôles
activables par mots de passe.

(3) L’option « ALL » permet d’activer les rôles accordés pour une
session.

4.5. L’architecture fonctionnelle du moteur SQL


Dans ce chapitre, nous nous sommes intéressés à ce que font les ordres SQL,
sans dire comment l’exécution de ces ordres SQL se déroule concrètement.
Ce qui suit aborde succinctement cet aspect qui est traité de façon plus
détaillée dans d’autres chapitres.

Le déroulement de l’exécution d’un ordre SQL est coordonné dans un


SGBD relationnel par un moteur SQL. L’architecture fonctionnelle de ce
moteur SQL peut se schématiser comme ci-dessous.

Dans cette architecture, l’utilisateur interagit avec le moteur SQL en mode


interactif à l’aide d’un client SQL comme « SQL Plus » ou « SQL
Developer », auquel il peut soumettre une requête de demande de
connexion (conduisant à son authentification) ou de déconnexion au SGBD,
une requête SQL de définition des données, une requête SQL de
manipulation ou de contrôle des données, ou un appel de procédure ou de
fonction stockée. Le rôle de ce client SQL est de transmettre au moteur SQL
du SGBD la requête soumise par l’utilisateur et de délivrer en retour à
l’utilisateur le résultat de l’exécution de cette requête. Les chapitres 5 et 6
expliquent comment ce client SQL peut être remplacé dans cette
architecture par une application cliente qui est un outil spécifique
développé spécialement par l’entreprise, pour permettre aux acteurs

224
SGBD relationnels – Tome 1, État de l’art

autorisés d’interagir avec le SGBD pour des services spécifiques


prédéterminés à l’avance en fonction des besoins.

Client SQL ou Application cliente

Analyseur des requêtes SQL

Vérificateur des contraintes


d’intégrité et de confidentialité

Optimiseur des requêtes SQL


Catalogue
Générateur du code exécutable
des requêtes SQL

Gestionnaire de l’exécution des


transactions

BD utilisateurs

Chaque requête SQL de manipulation de données stockées dans une base


de données utilisateur ou dans le catalogue du SGBD, soumise au SGBD à
travers un client SQL ou à travers une application cliente, est traitée en
premier par un analyseur syntaxique avant sa prise en charge par le
vérificateur des contraintes d’intégrité et de confidentialité.

Le rôle du vérificateur des contraintes d’intégrité et de confidentialité est de


faire en sorte que le traitement d’une requête qui respecte les règles
syntaxiques ne puisse se poursuivre que si l’utilisateur concerné a les droits
requis pour l’exécution de cette requête et que si cette exécution ne violera
pas une contrainte d’intégrité prédéfinie. Comme l’indique le diagramme
de transition de l’état d’une transaction regroupant une séquence de
requêtes interdépendantes, présenté dans le paragraphe 9.4.2.a, en fonction
des besoins, des contrôles d’intégrité peuvent se faire en plus en fin de
transaction, avant la validation de celle-ci, afin de s’assurer qu’en
s’exécutant cette transaction n’a pas violé d’autres contraintes d’intégrité

225
Joachim TANKOANO

prédéfinies.

Pour faire simple, dans la suite de l’ouvrage, le terme « analyseur


syntaxique et sémantique » sera utilisé lorsqu’il s’agira d’un processeur qui
combine les fonctionnalités de l’analyseur syntaxique et du vérificateur des
contraintes d’intégrité et de confidentialité.

Le rôle de l’optimiseur des requêtes, du générateur du code exécutable et


du gestionnaire de l’exécution des transactions est d’assurer l’exécution
d’une requête après l’avoir transformée en code exécutable. Le
fonctionnement de ces composants est traité en détail dans les chapitres 7,
8 et 9.

Dans son fonctionnement, chacun de ces composants puise les informations


dont il a besoin du catalogue de la base de données.

226
SGBD relationnels – Tome 1, État de l’art

BIBLIOGRAPHIE
ANSI-ISO: American National Standard for Information Systems: Database
Language SQL - American National Standards Institute (1986).

ANSI-ISO: ISO/IEC JTC 1 Information technology: ISO/IEC 9075:1989


Information processing systems — Database Language SQL with integrity
enhancement - April 1989

ANSI-ISO: ISO/IEC JTC 1/SC 32 Data management and interchange: ISO/IEC


9075:1992 Information technology — Database languages — SQL - Nov.
1992

Bancilhon F. & Spyratos N.: Update Semantics and Relational Views - ACM
TODS, V4, N6, Dec. 1981, p. 557-575.

Chamberlin D.D. & Al.: SEQUEL 2: A Unified Approach to Data Definition,


Manipulation and Control - IBM Journal of Research and Development,
vol. 20, n° 6, novembre 1976.

Date C.J.: A Critique of the SQL Database Language - ACM SIGMOD Record,
vol. 14, n° 3, novembre 1984.

Date C.J. & Darwen G.: A Guide to the SQL Standard, 4th edition - Addison
Wesley (1997).

Codd E. F.: The relational model for database management, Second Edition -
Addison-Wesley Publishing Company, Inc., 1990

Griffiths P.P. & Wade B.W.: An Authorization Mechanism for a Relational


Database System - ACM Transactions on Database Systems, vol. 1, n° 3,
p. 242-255, Sept. 1996.

Melton J. & Simon A. R.: Understanding the New SQL: A Complete Guide -
Morgan Kaufmann (1993).

MIRANDA S. : L'art des bases de données, Tome 3, Comprendre et évaluer SQL


- Eyrolles, 1990

ORACLE: Oracle® Database SQL Language Reference 12c Release 1 (12.1) - July
2017

227
Joachim TANKOANO

Shaw Ph.: Database Language Standards: Past, Present, and Future - Lecture
Notes in Computer Science, n° 466, Database Systems of the 90s, A.
Blaser Ed., Springer Verlag, novembre 1990.

Silberschatz A., Korth H.F. & Sudarshan S.: Database system concepts, sixth
edition - McGraw-Hill, 2011

Stonebraker M.R.: A Functional View of Data Independance - ACM SIGMOD


Workshop on Data Description, Access and Control, ACM Ed., mai
1974.

228
Chapitre 5. : Extension de SQL en langage
complet de programmation (PL/SQL)

5.1 Introduction
Le développement de l’Internet a fortement influencé les traits
caractéristiques des applications d’entreprise qui manipulent une base de
données ainsi que leurs modèles de conception et de développement. Ces
applications sont devenues pour l’essentiel des applications réparties,
déployées dans des architectures multi-tiers constituées :
• De tiers clients où s’exécutent, éventuellement dans des
navigateurs, une partie plus ou moins importante du code de
l’application
• De tiers serveurs d’application où est réparti le code du cœur de
l’application
• De tiers serveurs de ressources où sont réparties les autres ressources
du système d’information de l’entreprise (serveurs de bases de
données, autres serveurs d’application, serveur de messagerie
d’entreprise, etc.).

La définition de l’architecture de ces applications, qui a été l’aspect le plus


impacté par cette évolution, combine l’utilisation :
• De concepts de la programmation orientée objet, de la
programmation orientée aspect et de la programmation déclarative
(ou métaprogrammation par annotations ou à l’aide d’un langage de
balisage descriptif comme XML)
• Du motif de conception architectural MVC (Modèle-Vue-
Contrôleur) et de ses dérivés pour la gestion des interactions via le
web
• De connecteurs pour l’accès aux bases de données, l’interconnexion
synchrone à d’autres applications et l’intégration dans des modèles
architecturaux faiblement couplés, orientés processus collaboratifs
(workflow) impliquant plusieurs applications.
Joachim TANKOANO

Ce paradigme architectural, a été au centre des travaux conduits autour de


« JEE (Java Entreprise Edition) ». Il capitalise les bonnes pratiques
consensuelles, recommandées par la communauté des concepteurs et des
développeurs d’applications.

Pour faciliter le développement, ce paradigme architectural conduit à une


répartition des fonctionnalités d’une application, relatives aux exigences
fonctionnelles, dans trois types de composants, en fonction de leur nature (à
savoir : présentation des interfaces homme-machine, traitements métier,
traitements relatifs à l‘accès aux bases de données via des connecteurs de
bases de données, traitements relatifs à l’intégration via des connecteurs
d’applications ou de serveurs de messagerie d’entreprise).

Ces trois types de composants fonctionnels sont :


• Les contrôleurs : ils ont pour rôle de rendre l’application accessible
via le web. Ils redéfinissent à cet effet le fonctionnement du protocole
« HTTP ». Lorsque le serveur d’application reçoit une requête d’un
client, ces composants se chargent de déterminer le comportement
que l’application doit adopter. Ils identifient l’opération métier
définie au niveau des modèles qui doit être exécutée pour le
traitement de la requête et sélectionnent une vue pour la
construction de la réponse à l’aide des données calculées par
l‘opération identifiée. En d’autres termes, ces composants
fonctionnels constituent la porte d’entrée d’une application sur le
web et jouent le rôle d’aiguillonneurs vers les bons traitements
métier encapsulés par les modèles et vers la bonne vue pour la
construction d’une réponse.
• Les modèles : ils ont pour rôle d’encapsuler l’état de la session de
chaque client et d’offrir une interface d’accès aux opérations métier
que les contrôleurs peuvent appeler pour le traitement d’une
requête. En outre, ils offrent aux opérations métiers une interface
d’accès aux opérations dont elles ont besoin pour accéder aux autres
ressources du système d’information de l’entreprise (bases de
données, serveur de messagerie d’entreprise et autres applications
de l’entreprise), en faisant abstraction de l’API utilisée. L’utilisation
des modèles évite de combiner dans le même code : les traitements

230
SGBD relationnels – Tome 1, État de l’art

relatifs à la présentation, les traitements métier, les traitements de


manipulation de la base de données, les traitements relatifs à l’accès
au serveur de messagerie d’entreprise et les traitements relatifs à
l’accès aux autres applications de l’entreprise.
• Les vues : elles ont pour rôle de modéliser les interfaces homme-
machine, afin de permettre, à la demande du contrôleur, de
construire dynamiquement la réponse à transmettre en retour à un
client, en utilisant les données calculées par les opérations des
modèles.

Des standards ont été définis pour ces trois types de composants mais aussi
pour leur environnement d’exécution. Ces standards amènent le
développeur à se concentrer sur les exigences fonctionnelles de
l’application et à laisser à l’environnement d’exécution la prise en charge
des exigences techniques (contrôle des autorisations d’accès, de l’exécution
des transactions, etc.).

Ces composants fonctionnels sont réutilisables et peuvent être combinés


pour former des applications indépendantes de l’environnement
d’exécution où elles doivent être déployées.

Ce nouveau paradigme architectural amène à structurer les applications


d’entreprises qui manipulent une base de données en trois couches en
s’appuyant sur ces composants logiciels à responsabilité distincte,
distribuables sur une plateforme multi-tiers :
• La couche présentation, constituée de vues (chargées de la
génération des interfaces utilisateur) et de contrôleurs (chargés de la
gestion de la logique d’enchainement de ces interfaces utilisateur et
du déclenchement des opérations métier en fonction des requêtes
soumises par le client)
• La couche métier, constituée de modèles (chargés de la logique des
opérations métier à exécuter pour le traitement des requêtes
soumises par le client)
• La couche d’accès aux ressources de l’entreprise, constituée
également de modèles (chargés d’offrir à la couche métier une

231
Joachim TANKOANO

interface pouvant permettre d’accéder simplement à ces ressources,


indépendamment de l’API utilisé : base de données, serveur de
messagerie d’entreprise, autres applications de l’entreprise).

Ce paradigme architectural constitue la norme de fait, recommandée pour


la décomposition des applications d’entreprises orientées bases de données
en composants à faible couplage et à forte cohésion (car à responsabilité
unique). Son adoption dans un projet induit une démarche méthodologique
qui facilite le dialogue au sein des équipes de développement ainsi que les
maintenances ultérieures.

Un nouvel écosystème d’offres technologiques a vu le jour et continue de


s’enrichir, principalement autour de Java, des technologies Microsoft, de
PHP et de JavaScript, dans le but de faciliter le développement et le
déploiement d’applications qui respectent ce paradigme architectural.

Les extensions apportées au langage SQL pour en faire un langage complet


de programmation s’inscrivent dans cette dynamique.
Ces extensions ont pour finalité première d’intégrer nativement dans un
nouveau langage de type procédural les fonctionnalités pouvant permettre
l’exécution des ordres SQL de manipulation d’une base de données et de
démarcation des transactions (voir chapitre 9) à partir d’un programme,
tout en évitant tout « CONFLIT D’IMPEDANCE » lié à une incompatibilité
entre les types de ce nouveau langage et les types du SGBD.
Les autres principaux objectifs de ces extensions sont :
• Garantir la productivité des développeurs et le développement
d’unités de traitement flexibles, sûres et performantes en simplifiant
la programmation
• Permettre l’écriture d’unités de traitement qui peuvent s’intègrer
dans une base de données afin de pouvoir interagir et coopèrer avec
le moteur SQL de la façon la plus naturelle, la plus simple, la plus
directe et la plus efficace possible
• Permettre ainsi le développement d’API et de fonctions qui étendent
les fonctionnalités du SGBD et du modèle relationnel

232
SGBD relationnels – Tome 1, État de l’art

• Permettre l’écrire d’unités de traitement qui assurent le contrôle des


contraintes d’intégrité des données qui ne peuvent pas être garanties
autrement par le SGBD notamment en garantissant l’intégrité de
relation, l’intégrité d’unicité et l’intégrité référentielle
• Permettre l’écriture d’unités de traitement qui assurent le suivi des
accès à la base de données afin de garantir la traçabilité de ces accès
• Permettre l’écriture des unités de traitement des applications
développées à l’aide d’outils d’aide au développement
• Offrir le support nécessaire pour une évolution vers des modèles de
données autre que le modèle relationnel, en permettant notamment
la création de types abstraits utilisateur SQL dans le schéma d’une
base de données (voir dans le tome 2 le chapitre 12 pour le modèle
objet-relationnel et le chapitre 13 pour XML)
• Offrir une alternative pour le développement des composants
fonctionnels des applications d’entreprise qui respectent
l’architecture MVC (contrôleurs, vues, composants de la couche
métier, composants de la couche d’accès aux autres systèmes de
l’entreprise), compatible aux solutions offertes par les frameworks
qui supportent le développement d’applications qui respectent ce
paradigme architectural.

Les principes généraux de l’extension de SQL en langage complet de


programmation ont été intégrés dans la norme SQL. Ils ont été implémentés
par plusieurs éditeurs de SGBD. Ce chapitre présente la solution proposée
par le SGBD ORACLE à travers PL/SQL (Procedural Language SQL).

Les scripts écrits en PL/SQL sont compilés et exécutés par un moteur


PL/SQL. L’intégration de ce moteur est effective au niveau des serveurs de
bases de données, des environnements d’exécution des applications 2-tiers
et multi-tiers développées à l’aide d’outils comme « Oracle FORMS » et
« Oracle REPORT », des outils d’aide au développement et des clients SQL
comme « SQL Developper ». Pour ce qui concerne plus spécifiquement
l’exécution des instructions de manipulation d’une base de données
intégrées dans PL/SQL, ce moteur coopère avec le moteur SQL du SGBD
en soumettant l’exécution des ordres SQL concernés au moteur SQL et en

233
Joachim TANKOANO

récupérant le résultat pour traitement en se conformant à la sémantique de


ces instructions.

Ce qui suit présente les principales unités de code que le développeur ou


l’administrateur d’une base de données peut créer à l’aide de PL/SQL, à
savoir : les blocs anonymes, les procédures et fonctions stockées, les
packages et les déclencheurs.

5.2. Les blocs


Les unités de code de base en PL/SQL (à savoir : les blocs anonymes non-
stockés, les blocs imbriqués, les procédures, les fonctions et les
déclencheurs) se définissent à l’aide de blocs.

Ces blocs ont une structure générale identique qui est la suivante :
[DECLARE
Déclarations]
BEGIN
CorpsBloc
[EXCEPTION
CodeGestionDesErreurs]
END ;

(1) Un bloc est ainsi constitué de trois parties : une partie déclarative
(facultative) précédée dans la plupart des cas du mot clé « DECLARE », une
partie corps (obligatoire) précédée du mot clé « BEGIN » et une partie
gestion des erreurs (facultative) précédée du mot clé « EXCEPTION », le
tout se terminant par le mot clé « END ».

(2) La partie déclarative d’un bloc est réservée aux définitions et aux
déclarations de types, de constantes, de variables, de curseurs, de
procédures et de fonctions. La portée de ces définitions et déclarations est
le bloc. Elles sont visibles dans les procédures et fonctions du bloc, dans sa
partie corps et dans sa partie gestion des erreurs.

(3) La partie corps d’un bloc est réservée aux traitements assignés à ce
bloc. Ces traitements se définissent comme dans les autres langages de
programmation à l’aide d’instructions de contrôle de la logique

234
SGBD relationnels – Tome 1, État de l’art

d’enchainement de ces traitements, d’instructions d’affection,


d’instructions d’appel de procédures et de fonctions, et d’instructions de
déclenchement des traitements d’erreurs. À ce niveau, ce qui constitue la
spécificité du langage PL/SQL en tant que langage d’extension de SQL en
langage complet de programmation réside dans le fait que dans un bloc les
traitements peuvent aussi se définir à l’aide d’instructions d’exécution des
ordres SQL du langage de manipulation des données (« SELECT »,
« INSERT », « UPDATE » et « DELETE »), d’instructions qui permettent le
traitement ligne par ligne du résultat retourné par un ordre « SELECT » et
d’instructions relatives à la démarcation des transactions (« COMMIT »,
« ROLLBACK » et « SET TRANSACTION »).

(4) La partie corps d’un bloc peut contenir d’autres blocs appelés « blocs
imbriqués ». Dans un bloc, un bloc imbriqué est traité comme une macro-
instruction et peut apparaître partout où une instruction est autorisée. Les
déclarations d’un bloc sont visibles dans ses blocs imbriqués. En revanche,
les déclarations d’un bloc imbriqué sont invisibles dans son bloc parent et
dans les blocs imbriqués adjacents.

(5) La partie gestion des erreurs d’un bloc définit, quant à elle, le
traitement à effectuer pour chaque type d’erreur pouvant survenir au cours
de l’exécution des traitements définis dans sa partie corps.

Ce qui suit présente successivement :


• Les instructions de déclaration des constantes et des variables
• Les instructions de définition de types de valeurs composites
• Les variables hôtes
• Les instructions d’affectation
• Les instructions de contrôle de la logique d’enchaînement des
actions
• Les instructions d’exécution des ordres SQL du langage de
manipulation des données
• Les curseurs et les variables curseurs
• Le traitement des erreurs à l’aide d’exceptions.

235
Joachim TANKOANO

5.2.1. Les constantes et les variables

La syntaxe relative à la déclaration des constantes et des variables est :


Identificateur [{Identificateur}[,..n]]
[CONSTANT] Type [NOT NULL] [{:= | DEFAULT} Expression]

(1) La déclaration d’une constante se distingue de celle d’une variable


par la présence du mot clé « CONSTANT » et par le fait que la spécification
de la valeur d’initialisation est obligatoire.

(2) Tous les types PL/SQL sont autorisés dans la déclaration d’une
constante ou d’une variable, à savoir, les types prédéfinis de SQL, les types
« BOOLEAN » et « BINARY_INTEGER » (aussi appelé
« PLS_INTEGER ») pour les entiers compris entre -2 147 483 648 et + 2 147
483 647, et les types définis par l’utilisateur stockés ou non dans le schéma
de la base de données, ce qui permet d’éviter les conflits d’impédance.

(3) On peut aussi rattacher à une variable, le type d’une colonne ou


d’une autre variable précédemment déclarée, en préfixant « %TYPE » par
le nom de la colonne ou de la variable. De même, on peut rattacher aussi à
une variable, le type qui définit la structure des lignes d’une table ou d’une
vue, y compris d’une table ou d’une vue objet-relationnelle ou XML (voir
chapitre 12 et 13), en préfixant « %ROWTYPE » par le nom de la table ou
de la vue. Ces possibilités permettent de garantir l’indépendance des
données en garantissant que la modification, dans le schéma d’une base de
données, du type d’une colonne ou de la structure d’une table ou d’une vue
n’entrainera pas de modification dans le code PL/SQL concerné.

Exemple 5.2.1.i : Convenons pour plus de clarté que les noms de variables
commencent par le caractère « V », les noms de constantes par le caractère
« C » et les noms de tables par « Tab ». Dans ces exemples de déclarations,
la variable « V_Empl_ID » a le même type que la colonne « matricule » de
la table « Tab_Employes ». De même, la variable « V_Montant_min » a le
même type que la variable « V_Montant ».
DECLARE
V_Genre CHAR;
V_Compteur BINARY_INTEGER DEFAULT 0;

236
SGBD relationnels – Tome 1, État de l’art

V_Empl_ID Tab_Employes.matricule%TYPE ;
V_Montant NUMBER (12,2) ;
V_Montant_min V_Montant%TYPE;
C_TauxRemise CONSTANT NUMBER (3,2) := 0.06 ;
BEGIN
NULL;
END;

5.2.2. Les types relatifs aux valeurs composites

PL/SQL supporte plusieurs types de structures de données composites,


pouvant être définis dans un programme et pouvant servir à typer des
variables ou des constantes :
• Le type « RECORD », pour les données structurées constituées d’un
agrégat de champs de valeurs de types différents
• Les types « TABLE OF/INDEX BY », « VARRAY » et « NESTED
TABLE », pour les données structurées constituées d’une collection
d’éléments de même type.

Le type d’un champ dans un « RECORD » peut être une collection.


Inversement, le type des éléments d’une collection peut être un type
« RECORD ».

Pour la manipulation des valeurs de type collection, PL/SQL offre un


ensemble commun de méthodes qui sont les suivantes :
• « COUNT » : retourne le nombre d’éléments dans la collection en
ignorant les éléments supprimés
• « EXISTS (i) » : retourne la valeur vraie si l’élément de rang « i »
existe dans la collection
• « FIRST » : retourne l’index du premier élément de la collection s’il
existe, sinon retourne « NULL »
• « LAST » : retourne l’index du dernier élément de la collection s’il
existe, sinon retourne « NULL »

237
Joachim TANKOANO

• « PRIOR (i) » : retourne l’index de l’élément qui précède l’élément


de rang « i » de la collection s’il existe, sinon retourne « NULL »
• « NEXT (i) » : retourne l’index de l’élément qui suit l’élément de rang
« i » de la collection s’il existe, sinon retourne « NULL »
• « DELETE (i) » : supprime dans la collection l’élément de rang « i »
s’il existe
• Le constructeur du type lorsqu’il s’agit des types « VARRAY » et
« NESTED TABLE » : ce constructeur a le même nom que le nom du
type et peut être utilisé pour initialiser une variable en spécifiant en
paramètre une liste de valeurs, séparées par des virgules.

a) Le type « RECORD »

Ce type s’utilise pour la définition des structures d’enregistrements.

Sa syntaxe est la suivante :


TYPE NomType IS RECORD ({NomDeChamp TypeDeChamp
[NOT NULL] [{:=|DEFAULT} Expression]}[,..n]);

Exemple 5.2.2.i : Dans cet exemple, le type « RECORD » « T_PERSONNE »


est défini comme étant constitué du champ « Nom » de type « RECORD »
« T_NOM », du champ « Adessse » de type « VARCHAR2 (50) » et du
champ « Sexe » de type « CHAR ». La variable « V_Personne » est définie
comme étant de type « T_PERSONNE » et la variable « V_Employe »
comme ayant le même type que les lignes de la table « Tab_Employes ».
DECLARE
TYPE T_NOM IS RECORD (
Prenom VARCHAR2 (15),
Patronyme VARCHAR2 (15)
);
TYPE T_PERSONNE IS RECORD (
Nom T_NOM,
Adresse VARCHAR2 (50),
Sexe CHAR
);
V_Personne T_PERSONNE ;

238
SGBD relationnels – Tome 1, État de l’art

V_Employe Tab_Employes%ROWTYPE ;
BEGIN
NULL ;
END ;

b) Le type « TABLE OF/INDEX BY »

Ce type s’utilise pour définir un type utilisateur tableau associatif


dynamique à une dimension.

La syntaxe pour la définition de ce type de tableau est la suivante :


TYPE NomType IS TABLE OF TypeElements [NOT NULL]
INDEX BY {BINARY_INTEGER | VARCHAR2 () | STRING};

(1) Cette syntaxe ne permet pas de spécifier le nombre d’éléments que


peut contenir un tableau associatif, car ce nombre est indéterminé.

(2) L’index pour l’accès à un élément d’un tableau associatif dynamique


peut être un entier ou une chaîne de caractères.

(3) Lorsqu’une variable de ce type n’a pas encore été initialisée, elle a
comme valeur « EMPTY ».

Exemple 5.2.2.ii : Dans cet exemple, nous avons ajouté dans le type
« T_PERSONNE » un champ « Couleurs_preferees » de type tableau
associatif dynamique indexé par un entier.
DECLARE
TYPE T_COULEURS IS TABLE OF VARCHAR2 (15)
INDEX BY BINARY_INTEGER;
TYPE T_NOM IS RECORD (
Prenom VARCHAR2 (15),
Patronyme VARCHAR2 (15)
);
TYPE T_PERSONNE IS RECORD (
Nom T_NOM,
Adresse VARCHAR2 (50),
Sexe CHAR,
Couleurs_preferees T_COULEURS

239
Joachim TANKOANO

);
V_Personne T_PERSONNE ;
BEGIN
NULL ;
END ;

c) Le type « VARRAY »

Ce type s’utilise pour définir un type utilisateur tableau dynamique à une


dimension, dont le nombre d’éléments peut varier de zéro au nombre
maximum spécifié dans la définition.

La syntaxe pour la définition de ce type de tableau est la suivante :


TYPE NomType [IS] VARRAY (TailleMax) OF TypeElements [NOT NULL] ;

(1) Il est aussi possible de créer un type « VARRAY » dans le schéma


d’une base de données objet-relationnelle et de l’utiliser pour typer
l’attribut d’un objet ou la colonne d’une table (voir chapitre 12).

(2) L’accès à un élément d’un tableau de type « VARRAY » se fait à


l’aide d’un index qui commence par 1.

(3) Lorsqu’une variable de ce type n’a pas encore été initialisée, elle a
comme valeur « EMPTY ».

d) Le type « NESTED TABLE » ou « table imbriquée »

Ce type permet de définir un type collection dont chaque valeur peut


correspondre soit à un « SET », soit à un « BAG » de taille indéterminée.

La syntaxe pour la définition d’un type « NESTED TABLE » est la


suivante :
TYPE NomType [IS] TABLE OF TypeElements [NOT NULL];

(1) Tout comme pour le type « VARRAY », il est aussi possible de créer
un type « NESTED TABLE » dans le schéma d’une base de données objet-
relationnelle et de l’utiliser pour typer l’attribut d’un objet ou la colonne
d’une table qui devient une colonne à valeurs relationnelles. Dans une ligne

240
SGBD relationnels – Tome 1, État de l’art

d’une table d’une base de données objet-relationnelle, le contenu de chaque


colonne à valeurs relationnelles est stocké sur le disque comme étant
constitué d’une collection de lignes non-ordonnées imbriquées dans cette
colonne (voir chapitre 12). Lorsque le contenu d’une colonne à valeurs
relationnelles de cette ligne est transféré dans une variable de type
« NESTED TABLE », PL/SQL range les lignes imbriquées concernées dans
un tableau et les rend accessibles à l’aide d’un index qui commence par 1.

(2) Lorsqu’une variable de ce type n’a pas encore été initialisée, sa


valeur est « NULL ».

5.2.3. Les variables hôtes

PL/SQL donne au développeur la possibilité de faire référence dans une


instruction PL/SQL à des variables hôtes, déclarées et transmises par des
programmes tiers. Il peut s’agir par exemple de :
• Variables déclarées dans des programmes Pro*
• Variables déclarées dans des formulaires FORMS
• Variables de substitution SQL*Plus.

Ces variables doivent être préfixées par « : » (Ex. : v_salaire :=


:g_salaire_annuel).

5.2.4. L’instruction d’affectation de la valeur d’une expression


à une variable

La syntaxe pour affecter le résultat de l’évaluation d’une expression à une


variable, à un champ d’un « RECORD » d’une variable ou à un élément
d’une collection d’une variable est la suivante :
• Pour ce qui concerne une variable :
NomVariable := Expression ;
• Pour ce qui concerne le champ d’un « record » d’une variable :
NomVariable.NomChamp := Expression ;

241
Joachim TANKOANO

• Pour ce qui concerne l’élément d’une collection d’une variable :


NomVariable (ValeurIndex) := Expression ;

Exemple 5.2.4.i : La partie corps de ce cet exemple contient cinq instructions


d’affectation.
DECLARE
TYPE T_COULEURS IS TABLE OF VARCHAR2 (15)
INDEX BY BINARY_INTEGER;
TYPE T_AMIS IS VARRAY (5) OF VARCHAR2 (15);
TYPE T_NOM IS RECORD (
Prenom VARCHAR2 (15),
Patronyme VARCHAR2 (15)
);
TYPE T_PERSONNE IS RECORD (
Nom T_NOM,
Adresse VARCHAR2 (50),
Sexe CHAR,
Couleurs_preferees T_COULEURS,
Amis T_AMIS
);
V_Personne T_PERSONNE ;
V_Employe Tab_Employes%ROWTYPE;
Idx BINARY_INTEGER;
BEGIN
V_Personne.NOM.Patronyme := V_Employe.Patronyme ;
V_Personne.Couleurs_Preferees (1) := 'vert' ;
V_Personne.Amis := T_AMIS ('Jean', 'Élise', 'Jacques', 'Pauline') ;
Idx := V_Personne.Amis.FIRST ;
V_Personne.Amis (V_Personne.Amis.NEXT (Idx)) := 'André' ;
END ;

La 1ère instruction affecte dans le champ « NOM » de la variable


« V_Personne », le champ « Patronyme » de la variable « V_Employe ».

La 2ème instruction affecte la valeur « 'vert' » dans l’élément de rang « 1 » du


champ « Couleurs_Preferees » (qui est un tableau associatif) de la variable
« V_Personne ».

La 3ème instruction initialise, en utilisant le constructeur du type

242
SGBD relationnels – Tome 1, État de l’art

« T_AMIS », les 4 premiers éléments du champ « Amis » (qui est un tableau


dynamique) de la variable « V_Personne », avec les valeurs « 'Jean' »,
« 'Élise' », « 'Jacques' » et « 'Pauline' ».

La 4ème instruction affecte dans la variable « Idx », l’index du 1er élément


dans le champ « Amis » (qui est un tableau dynamique) de la variable
« V_Personne ».

La 5ème instruction affecte la valeur « 'André' » dans l’élément du champ


« Amis » de la variable « V_Personne » qui suit l’élément dont l’index est
dans « Idx ».

5.2.5. Les instructions de contrôle de la logique


d’enchainement des actions

Comme les autres langages de programmation orientés actions, PL/SQL


offre des instructions de contrôle pour les enchainements conditionnels
(« IF » et « CASE ») et les enchaînements itératifs (« LOOP »,
« WHILE/LOOP » et « FOR/LOOP ») des instructions de traitement. Ces
instructions peuvent s’imbriquer les unes dans les autres.

a) L’instruction « IF »

La syntaxe de l’instruction « IF » combine les trois variantes ci-après :


1ère variante
IF ExpressionBooléenne THEN
SéquenceInstructions
END IF;

2ème variante
IF ExpressionBooléenne THEN
SéquenceInstructions
ELSE
SéquenceInstructions
END IF;

243
Joachim TANKOANO

3ème variante
IF ExpressionBooléenne THEN
SéquenceInstructions
ELSIF ExpressionBooléenne THEN
SéquenceInstructions
[ELSIF ExpressionBooléenne THEN
SéquenceInstructions] …
ELSE
SéquenceInstructions
END IF;

b) L’instruction « CASE »

La syntaxe de l’instruction « CASE » combine quant à elle les deux


variantes ci-après :
1ère variante
CASE Sélecteur
WHEN Valeur1Sélecteur THEN SéquenceInstructions1
WHEN Valeur2Sélecteur THEN SéquenceInstructions2
……..
WHEN ValeurNSélecteur THEN SéquenceInstructionsN
[ELSE
SéquenceInstructions]
END CASE;

2ème variante
CASE
WHEN ExpressionBooléenne1 THEN SéquenceInstructions1
WHEN ExpressionBooléenne2 THEN SéquenceInstructions2
……..
WHEN ExpressionBooléenneN THEN SéquenceInstructionsN
[ELSE
SéquenceInstructions]
END CASE ;

Ces deux variantes lèvent l’exception prédéfinie « CASE_NOT_FOUND »,


lorsqu’aucune des conditions spécifiées par les clauses « WHEN » ne peut
être évaluée à vraie et que la clause « ELSE » est absente.

244
SGBD relationnels – Tome 1, État de l’art

c) L’instruction « LOOP »

La syntaxe de l’instruction « LOOP » est la suivante :


LOOP
SéquenceInstructions
END LOOP ;

Pour éviter une boucle infinie, l’arrêt et la sortie de la boucle peut être
provoquée par une instruction « EXIT [WHEN ExpressionBooléennen] » ou
« RAISE Exception », contenue dans « SéquenceInstructions ».

d) L’instruction « WHILE/LOOP »

La syntaxe de l’instruction « WHILE/LOOP » est la suivante :


WHILE ExpressionBooléenne LOOP
SéquenceInstructions
END LOOP;

e) L’instruction « FOR/LOOP »

La syntaxe de l’instruction « FOR/LOOP » est la suivante :


FOR VariableIndex IN [REVERSE] ValIndexMin .. ValIndexMax LOOP
SéquenceInstructions
END LOOP ;

Cette instruction a comme effet la déclaration implicite de la variable


« VariableIndex » et son incrémentation ou décrémentation
automatiquement de 1 à chaque itération, suivant que l’option
« REVERSE » est spécifiée ou non. En outre, une sortie prématurée de la
boucle peut être provoquée par une instruction « EXIT [WHEN
ExpressionBooléenne] » ou « RAISE Exception », contenue dans
« SéquenceInstructions ».

5.2.6. Les instructions de manipulation d’une base de données

Ces instructions font partie de celles qui sont spécifiques aux langages qui
étendent SQL en langage complet de programmation.

245
Joachim TANKOANO

Pour exécuter une instruction de manipulation d’une base de données, le


moteur PL/SQL soumet l’exécution de l’ordre SQL concerné au moteur
SQL et récupère le résultat qu’il traite en se conformant à la sémantique de
cette instruction.

Les instructions de manipulation d’une base de données intègrent


nativement dans PL/SQL les ordres SQL « SELECT », « INSERT »,
« UPDATE » et « DELETE » de manipulation des tables et des vues
relationnelles, objet-relationnelles (voir chapitre 12) et XML (voir chapitre
13). Ces instructions PL/SQL apportent quelques extensions à la syntaxe et
aux fonctionnalités des ordres SQL concernés afin d’intégrer leurs
possibilités et celles de PL/SQL et de rendre ainsi possible leur exécution à
l’intérieur d’un programme.

De façon générale, grâce à ces extensions, toute expression pouvant


apparaître dans un ordre SQL intégré dans PL/SQL sous forme
d’instruction peut se formuler en utilisant les variables du programme qui
exécute cette instruction, ce qui amène les ordres SQL concernés à
s’exécuter sous le contrôle du moteur SQL en ayant accès au contenu des
variables de ce programme pour l’évaluation de ces expressions.

Ce qui suit présente les extensions apportées de façon plus spécifique aux
ordres « SELECT », « INSERT » et « UPDATE » intégrés dans PL/SQL.
Des extensions additionnelles relatives aux ordres « UPDATE » et
« DELETE » sont présentées dans le paragraphe 5.2.7.b.

a) L’instruction « SELECT/INTO/FROM/WHERE »

L’extension apportée à l’ordre SQL « SELECT/FROM/WHERE » intégrée


dans PL/SQL sous forme d’instruction a pour finalité de permettre au
moteur SQL de pouvoir affecter le résultat de l’exécution de cet ordre dans
des variables déclarées à l’intérieur du programme qui exécute cette
instruction. La syntaxe simplifiée de cette instruction est :
SELECT {NomColonne} [,..n]
{INTO {NomVariable}[,..n]
| BULK COLLECT INTO {NomVariableCollection}[,..n]}
FROM {NomTable}[,..n] WHERE Condition;

246
SGBD relationnels – Tome 1, État de l’art

(1) La clause « INTO {NomVariableScalaire}[,..n] » ne doit être utilisée


que lorsque le résultat de l’ordre « SELECT » ne peut contenir au plus
qu’une seule ligne. Elle permet d’affecter chaque colonne du résultat dans
la variable de même rang spécifiée explicitement dans cette clause. En
spécifiant dans cette clause une seule variable de type « RECORD » ou de
type « OBJECT », l’affectation peut aussi se faire dans les champs ou
attributs qui composent cette variable.

(2) La clause « BULK COLLECT INTO {NomVariableCollection}[,..n] » ne


doit être utilisée que lorsque que le résultat de l’ordre « SELECT » peut être
constitué de plusieurs lignes. Elle permet d’affecter l’ensemble de valeurs
relatif à chaque colonne du résultat de l’ordre « SELECT » dans la variable
de type collection de même rang spécifiée explicitement dans cette clause.
Ceci permet ensuite, dans une boucle, de traiter ce résultat ligne par ligne,
en parcourant les collections affectées dans les variables spécifiées à l’aide
des méthodes présentées dans le paragraphe 5.2.2. Les curseurs explicites
offrent une alternative pour le traitement du résultat d’un ordre
« SELECT » ligne par ligne (voir paragraphe 5.2.7).

Exemple 5.2.6.i : Dans cet exemple, les données de l’employé dont le


matricule est « 105 » sont récupérées dans la table « Tab_Empoyes » et sont
ensuitr affectées à la variable « V_Employe ».
DECLARE
V_Employe Tab_Employes%ROWTYPE ;
BEGIN
………..
SELECT *
INTO V-Employe
FROM Tab_Employes e
WHERE e.Matricule = 105;
………
END;

Exemple 5.2.6.ii : Dans cet exemple, l’ordre « SELECT » recherche dans la


table « Tab_Employes » l’employé dont le matricule est « 105 » et affecte le
matricule et l’adresse électronique de cet employé dans les variables

247
Joachim TANKOANO

« V_Matricule » et « V_Email ».
DECLARE
V_Matricule Tab_Employes.Matricule%TYPE;
V_Email Tab_Employes.Email%TYPE;
BEGIN
………..
SELECT e.Matricule, e.Email
INTO V_Matricule, V_Email
FROM T_Employes e
WHERE e.Matricule = 105 ;
………
END ;

Exemple 5.2.6.iii : Dans cet exemple, les matricules et les adresses


électroniques des employés sont récupérés dans la table « Tab_Employes »
et sont ensuite affectés dans les variables « V_LISTE_Matricules » et
« V_LISTE_ Emails » de type table imbriquée.
DECLARE
TYPE T_LISTE_Matr IS TABLE OF Tab_Employes.Matricule%TYPE ;
TYPE T_LISTE_Emails IS TABLE OF T_Employes.Email%TYPE ;
V_LISTE_Matricules T_LISTE_Matr ;
V_LISTE_Emails T_LISTE_Emails ;
BEGIN
………..
SELECT e.Matricule, e. Email
BULK COLLECT INTO V_LISTE_Matricules, V_LISTE_Emails
FROM Tab_Employes e ;
………
END;

b) L’instruction « INSERT »

L’extension apportée à l’ordre SQL « INSERT » par cette instruction


PL/SQL permet de spécifier dans la clause « VALUES » une variable de
type « RECORD » ou de type « OBJECT ».

248
SGBD relationnels – Tome 1, État de l’art

Exemple 5.2.6.iv :
DECLARE
V_Employe Tab_Employes%ROWTYPE;
BEGIN
………..
INSERT INTO Tab_Employes VALUES V_Employe;
………
END;

c) L’instruction « UPDATE/SET ROW/WHERE »

Cette instruction PL/SQL apporte à l’ordre SQL « UPDATE » une


extension qui permet de spécifier à l’aide de la clause « SET ROW » le
remplacement du contenu de chaque ligne sélectionnée en fonction de la
clause « WHERE » par le contenu d’une variable de type « RECORD » ou
de type « OBJECT ».

Exemple 5.2.6.v:
DECLARE
V_Employe Tab_Employes%ROWTYPE;
BEGIN
………..
UPDATE Tab_Employes e
SET ROW = V_Employe
WHERE e.Matricule = 105;
………
END;

5.2.7. Les curseurs

Les curseurs font également partie des fonctionnalités spécifiques aux


langages qui étendent SQL en langage complet de programmation.

Un curseur est une zone privée utilisée sous forme de ressource par
PL/SQL pour stocker les informations relatives à l’exécution d’un ordre
SQL.

249
Joachim TANKOANO

On distingue deux types de curseurs :


• Les curseurs implicites définis et utilisés implicitement par PL/SQL
• Les curseurs explicites définis et utilisés explicitement par le
développeur.

a) Les curseurs implicites

Pour chaque session, PL/SQL créé automatiquement un curseur implicite


de session. Chaque fois que PL/SQL traite dans une session une instruction
d’exécution d’un ordre SQL de manipulation de la base de données
présentée dans le paragraphe 5.2.6 (« SELECT », « INSERT », « UPDATE »
ou « DELETE »), il le fait en utilisant le curseur implicite créé pour cette
session. Le développeur ne peut pas manipuler directement ce curseur
implicite mais peut accéder à ses attributs, afin d’obtenir le rapport
d’exécution de la dernière instruction traitée à l’aide de ce curseur. Ces
attributs sont les suivants :
• « SQL%ISOPEN » : est vrai si le curseur de la session est dans l’état
« open ». Retourne toujours faux parce que la fin de l’exécution
d’une instruction de manipulation de la base de données provoque
toujours la fermeture automatique du curseur implicite utilisé pour
cette exécution
• « SQL%FOUND » : est vrai si dans la session, l’exécution de la
dernière instruction de manipulation de la base de données a traité
au moins une ligne et faux sinon. Retourne NULL si aucune
instruction de manipulation de la base de données n’a encore été
exécutée au cours de la session concernée
• « SQL%NOTFOUND » : est vrai si dans la session, l’exécution de la
dernière instruction de manipulation de la base de données n’a traité
aucune ligne et faux sinon. Retourne NULL si aucune requête n’a
encore été exécutée au cours de la session concernée
• « SQL%ROWCOUNT » : retourne le nombre de lignes traitées au
cours de l’exécution de la dernière instruction de manipulation de la
base de données de la session. Retourne NULL si aucune instruction

250
SGBD relationnels – Tome 1, État de l’art

n’a encore été exécutée au cours de la session ;


• « SQL%BULK_ROWCOUNT » : retourne le nombre de lignes
traitées au cours de l’exécution de la dernière instruction
« FORALL » de la session. Retourne NULL si aucune instruction
« FORALL » n’a encore été exécutée au cours de la session ;
• « SQL%BULK_EXCEPTIONS » : pour la gestion des anomalies
d’exécution produites au cours de l’exécution de la dernière
instruction « FORALL » de la session.

Exemple 5.2.7.i : Pour exécuter l’ordre « SELECT » de l’instruction de cet


exemple, le moteur PL/SQL le fait en utilisant le curseur implicite de la
session en cours. Cet ordre recherche dans la table « Tab_Employes » la
ligne où le matricule est « 105 » et l’affecte dans la variable « V_Employe ».
L’instruction « IF » qui suit ne procède au traitement de cette ligne que si le
rapport d’exécution, contenu dans le curseur implicite de la session en
cours, indique qu’elle a été trouvée.
DECLARE
V_Employe Tab_Employes%ROWTYPE;
BEGIN
………..
SELECT *
INTO V_Employe
FROM T_Employes e
WHERE e.Matricule = 105;
IF SQL%FOUND THEN
… Traiter la ligne lue …
END IF ;
………
END ;

b) Les curseurs explicites

Tout comme les curseurs implicites, un curseur explicite est un curseur de


session. Il doit être défini explicitement par le développeur qui peut
l’utiliser explicitement à plusieurs reprises au cours d’une session pour

251
Joachim TANKOANO

exécuter et pour traiter le résultat de l’ordre « SELECT » spécifié dans sa


définition. Par rapport à l’utilisation de la clause « BULK COLLECT
INTO » de l’instruction « SELECT/INTO/FROM/WHERE », les curseurs
explicites simplifient le traitement ligne par ligne du résultat de l’ordre
« SELECT » concerné. En plus de cet avantage, afin d’éviter tout conflit
d’accès, le développeur peut demander le verrouillage des lignes
sélectionnées par l’ordre « SELECT » spécifié lorsqu’il a l’intention de
mettre à jour ou de supprimer ces lignes.

Pour chaque session, PL/SQL crée tous les curseurs définis explicitement
par le développeur. Chaque fois que PL/SQL exécute dans une session une
requête du développeur (« OPEN », « CLOSE », « FETCH » ou « FETCH
BULK COLLECT ») relative à un curseur explicite, il le fait en utilisant la
zone privée créée pour ce curseur explicite pour le compte de cette session.

Les attributs associés à un curseur explicite relatif à une session, permettent


au développeur d’accéder au rapport d’exécution des derniers traitements
effectués à l’aide de ce curseur explicite pour le compte de cette session. Ces
attributs sont les suivants :
• « NomDuCurseur%ISOPEN » : est vrai si pour la session en cours
le curseur spécifié est dans l’état « open ». Permet d’éviter de
demander l’ouverture d’un curseur déjà ouvert
• « NomDuCurseur%FOUND » : est vrai si pour la session en cours
l’exécution du dernier « FETCH » concernant le curseur spécifié a
traité au moins une ligne et faux sinon. Retourne NULL si aucun
« FETCH » n’a été exécutée après l’ouverture du curseur
• « NomDuCurseur%NOTFOUND » : est vrai si pour la session en
cours l’exécution du dernier « FETCH » concernant le curseur
spécifié n’a traité aucune ligne et faux sinon. Retourne NULL si
aucun « FETCH » n’a été exécutée après l’ouverture du curseur
• « NomDuCurseur%ROWCOUNT » : retourne pour la session en
cours le nombre de lignes traitées au cours de l’exécution du dernier
« FETCH » concernant le curseur spécifié ou ZERO si aucun
« FETCH » n’a été exécutée après l’ouverture du curseur.

252
SGBD relationnels – Tome 1, État de l’art

La séquence des principales étapes qui jalonnent l’utilisation d’un curseur


explicite par le développeur peut être schématisée comme suit :

NON

DECLARE OPEN FETCH Vide ?

Déclare le curseur Ouvre le curseur, Récupère la ligne


et le définit en lui exécute l’ordre suivante, affecte les
associant un SELECT et colonnes à des CLOSE
ordre SELECT positionne le variables et avance
curseur avant la 1ère le curseur Ferme le curseur
ligne du résultat

La syntaxe des instructions que le développeur peut utiliser au cours de ces


étapes est la suivante :
DECLARE
CURSOR NomCurseur [({NomParamètre}[,..n])]
[RETURN TypeLignesRésultat]
IS OrdreSELECT [FOR UPDATE];
…….
BEGIN
……..
OPEN NomCurseur [({ValeurParamètre}[,..n])];
………
FETCH NomCurseur INTO {NomVariableScalaire}[,..n];
……..
FETCH BULK COLLECT NomCurseur INTO {NomVariableCollection}[,..n];
……..
UPDATE NomTable SET ……. WHERE CURRENT OF NomCurseur;
………
DELETE [FROM] Nom_table WHERE CURRENT OF NomCurseur;
………
CLOSE NomCurseur ;
………
END ;

1) Il est possible de déclarer un curseur dans une 1ère étape et de le


définir dans une 2ème étape.

253
Joachim TANKOANO

1ère étape :
CURSOR NomCurseur [({NomParamètre}[,..n])]
RETURN TypeLignesRésultat ;
2ème étape :
CURSOR NomCurseur [({NomParamètre}[,..n])]
[RETURN TypeLignesRésultat]
IS OrdreSELECT [FOR UPDATE] ;

Il est aussi possible de déclarer et de définir un curseur en une seule étape :


CURSOR NomCurseur [({NomParamètre}[,..n])]
[RETURN TypeLignesRésultat]
IS OrdreSELECT [FOR UPDATE] ;

Exemple 5.2.7.ii : Cet exemple explicite les différentes approches qui en


découlent pour la déclaration et la définition d’un curseur « c1 » qui sert à
l’exécution d’un ordre « SELECT » dont le type des lignes du résultat est
« T_Employes%ROWTYPE ».
1ère approche : Déclarer d’abord le curseur et le définir ensuite en spécifiant
la clause « RETURN » dans la déclaration et dans la définition
DECLARE
CURSOR c1 RETURN Tab_Employes%ROWTYPE;
CURSOR c1 RETURN Tab_Employes%ROWTYPE
IS SELECT * FROM Tab_Employes e;
BEGIN
NULL ;
END ;

2ème approche : Déclarer d’abord le curseur et le définir ensuite en ne


spécifiant la clause « RETURN » que dans la déclaration
DECLARE
CURSOR c1 RETURN Tab_Employes%ROWTYPE;
CURSOR c1
IS SELECT * FROM Tab_Employes e;
BEGIN
NULL ;
END ;

254
SGBD relationnels – Tome 1, État de l’art

3ème approche : Déclarer et définir en même temps le curseur en spécifiant


la clause « RETURN »
DECLARE
CURSOR c1 RETURN Tab_Employes%ROWTYPE
IS SELECT * FROM Tab_Employes e;
BEGIN
NULL ;
END ;

4ème approche : Déclarer et définir en même temps le curseur sans la clause


« RETURN »
DECLARE
CURSOR c1
IS SELECT * FROM Tab_Employes e;
BEGIN
NULL;
END;

2) Un curseur peut être paramétré. Ces paramètres servent à indiquer


dans la formulation de l’ordre « SELECT » du curseur les valeurs
spécifiques qui doivent être fournies à chaque ouverture de ce curseur.

3) La clause « FOR UPDATE » de l’ordre « SELECT » entraine, lors de


l’ouverture du curseur, un verrouillage des lignes sélectionnées afin de
permettre leur modification ou suppression ultérieure sans que cela
n’engendre de conflit d’accès avec d’autres utilisateurs.

4) Dans un programme, l’exécution d’une instruction « OPEN » qui


spécifie le nom d’un curseur et passe éventuellement les paramètres requis,
se traduit par la soumission au moteur SQL de l’exécution de l’ordre
« SELECT » associé à ce curseur et le verrouillage des lignes sélectionnées
par le moteur SQL si le curseur contient dans sa définition la clause « FOR
UPDATE ». Après l’exécution par le moteur SQL de l’ordre « SELECT »
associé au curseur, PL/SQL procède au positionnement d’un curseur avant
la 1ère ligne retournée. La fermeture d’un curseur ouvert s’effectue en
exécutant une instruction « CLOSE » qui spécifie son nom. Un curseur
ouvert et fermé à l’aide de ces deux instructions peut être ouvert et fermé à

255
Joachim TANKOANO

nouveau ;

5) Après l’ouverture d’un curseur, le traitement du résultat de l’ordre


« SELECT » associé à ce curseur s’effectue ligne par ligne en exécutant dans
une boucle une instruction « FETCH/INTO » qui spécifie ce curseur.
L’exécution de cette instruction amène PL/SQL à récupérer la ligne
suivante du résultat, à affecter la valeur contenue dans chaque colonne de
cette ligne dans la variable de même rang spécifiée par la clause « INTO »
et à déplacer le curseur sur la ligne suivante du résultat.

6) Après l’ouverture d’un curseur, l’exécution de l’instruction


« FETCH BULK COLLECT/INTO » amène quant à elle PL/SQL à affecter
l’ensemble des valeurs relatif à chaque colonne du résultat dans la variable
de type collection de même rang spécifiée par le développeur dans la clause
« INTO ». Ceci permet ensuite, dans une boucle, de traiter le résultat
retourné par l’ordre « SELECT » ligne par ligne en parcourant les
collections affectées dans les variables spécifiées à l’aide des méthodes
présentées dans le paragraphe 5.2.2. Procéder de cette façon peut s’avérer
plus efficace que l’utilisation de l’instruction « FETCH/INTO » qui oblige
le moteur PL/SQL à appeler le moteur SQL pour le traitement de chaque
ligne du résultat de l’ordre « SELECT ».

7) Lorsqu’un curseur contient dans sa définition la clause « FOR


UPDATE », les ordres « UPDATE/WHERE CURRENT OF » et
« DELETE/WHERE CURRENT OF » sont des extensions des ordres SQL
« UPDATE » et « DELETE » que le développeur peut utiliser pour mettre à
jour ou pour supprimer la ligne courante retournée par l’instruction
« FETCH/INTO ».

Exemple 5.2.7.iii : Cet exemple utilise un curseur pour la mise à jour du


salaire des employés pour lesquels la colonne « job_id » contient la valeur
« 'INFORMATICIEN' ».
DECLARE
CURSOR c1
IS SELECT e.matricule, e.job_id, e.salaire
FROM Tab_Employes e
FOR UPDATE;
my_emp_id Tab_Employes.matricule%TYPE ;

256
SGBD relationnels – Tome 1, État de l’art

my_job_id Tab_Employes.job_id%TYPE;
my_sal Tab_Employes.salaire%TYPE;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO my_emp_id, my_job_id, my_sal ;
EXIT WHEN c1%NOTFOUND;
IF my_job_id = 'INFORMATICIEN' THEN
UPDATE Tab_employes e SET e.salaire = my_sal * 1.02
WHERE CURRENT OF c1;
END IF;
END LOOP;
END;

Exemple 5.2.7.iv : Cet exemple fait la même chose que l’exemple précédent
en utilisant un curseur paramétré.

DECLARE
CURSOR c1 (job Tab_Employes.job_id%TYPE)
IS SELECT e.matricule, e.job_id, e.salaire
FROM Tab_Employes e
WHERE e.job_id = job
FOR UPDATE;
my_emp_id Tab_Employes.matricule%TYPE;
my_job_id Tab_Employes.job_id%TYPE;
my_sal Tab_Employes.salaire%TYPE;
BEGIN
OPEN c1 ('INFORMATICIEN');
LOOP
FETCH c1 INTO my_emp_id, my_job_id, my_sal;
EXIT WHEN c1%NOTFOUND;
UPDATE Tab_employes e
SET e.salaire = my_sal * 1.02
WHERE CURRENT OF c1;
END LOOP;
END;

257
Joachim TANKOANO

c) L’instruction « FOR LOOP » des curseurs

Sa syntaxe est la suivante :


FOR NomVariableDeParcourt
IN {{NomCurseur [(ParamètresEffectifs)]} |(OrdreSelect)}
LOOP SéquenceInstructions END LOOP ;

Cette instruction dont l’objectif est de simplifier le traitement ligne par ligne
du résultat d’un ordre « SELECT », basé sur l’utilisation d’un curseur, a
comme effet la déclaration implicite de la variable
« NomVariableDeParcourt » qui doit avoir le même type que les lignes
retournées par l’ordre « SELECT » concernée, l’ouverture du curseur
spécifié, l’exécution à chaque itération d’un ordre « FETCH » qui place la
ligne suivante du résultat de l’ordre « SELECT » dans la variable
« NomVariableDeParcourt » et la fermeture du curseur à la fin de l’itération.

Cette instruction s’applique aussi bien aux curseurs explicites (cas où le


nom d’un curseur explicite est spécifié) qu’aux curseurs implicites (cas où
un ordre « SELECT » est spécifié).

5.2.8. Les variables curseurs

Une variable curseur est une variable qu’on peut faire pointer vers un
curseur explicite défini dynamiquement.

Les variables curseurs diversifient les possibilités offertes par les curseurs
explicites :
• Une variable curseur ne sert pas, comme dans le cas d’un curseur
explicite, qu’à l’exécution d’un seul ordre « SELECT ». Elle peut
servir à différents endroits et à différentes étapes d’un traitement, à
l’exécution d’ordres « SELECT » différents.
• Comme toute autre variable et contrairement à un curseur explicite,
une variable curseur peut faire l’objet d’une affectation de valeur.
Elle peut être utilisée dans une expression ou comme paramètre de
sous-programme. Elle peut être aussi une variable hôte.

Cependant, contrairement aux curseurs explicites, une variable curseur ne

258
SGBD relationnels – Tome 1, État de l’art

peut pas être paramétrée et ne permet pas l’exécution des ordres


« UPDATE/WHERE CURRENT OF » et « DELETE/WHERE CURRENT
OF » lorsque l’ordre « SELECT » concerné spécifie la clause « FOR
UPDATE ».

En revanche, comme dans le cas d’un curseur explicite, le résultat de l’ordre


« SELECT » exécuté au cours de l’ouverture d’une variable curseur peut
être traité à l’aide des ordres « FETCH/INTO » et « FETCH BULK
COLLECT/INTO ».

Ce qui suit présente les instructions qui servent à déclarer, à ouvrir et à


fermer une variable curseur et illustre à l’aide d’un exemple quelques
possibilités d’utilisation de ces variables.

a) L’instruction de déclaration d’une variable curseur

Une variable curseur faible est une variable curseur qui peut être utilisée
pour traiter des requêtes « SELECT » dont le type des lignes retournées
n’est pas fixé à l’avance et peut de ce fait être quelconque.

Une variable curseur faible peut être déclarée soit en utilisant le type
prédéfini « SYS_REFCURSOR », soit en utilisant un type « REF
CURSOR » faible, défini par le développeur.

La définition d’un type « REF CURSOR » faible définit son nom. La syntaxe
pour définir un type « REF CURSOR » faible est la suivante :
TYPE NomTypeCurseurFaible [IS] REF CURSOR ;

La syntaxe pour déclarer une variable curseur faible est la suivante :


NomVariableCurseurFaible {NomTypeCurseurFaible | SYS_REFCURSOR}

Exemple 5.2.8.i : Cet exemple explicite les deux approches qui en découlent
pour la déclaration d’une variable curseur faible « V_c1 » qui peut être
utilisée pour traiter des requêtes « SELECT » dont le type des lignes
retournées peut être quelconque.

259
Joachim TANKOANO

1ère approche :
DECLARE
V_c1 SYS_REFCURSOR;
BEGIN
NULL;
END;

2ème approche :
DECLARE
TYPE T_vc1 IS REF CURSOR;
V_c1 T_vc1 ;
BEGIN
NULL ;
END ;

Une variable curseur forte est une variable curseur qui ne peut être utilisée
que pour traiter des requêtes « SELECT » dont le type des lignes retournées
correspond au type spécifié dans sa déclaration.

Une variable curseur forte ne peut être déclarée qu’en utilisant un type
« REF CURSOR » fort, défini par le développeur.

La définition d’un type « REF CURSOR » fort définit son nom ainsi que le
type des lignes qu’un ordre « SELECT » associé à une variable de ce type
doit retourner. La syntaxe pour définir un type « REF CURSOR » fort est
la suivante :
TYPE NomTypeCurseurFort
[IS] REF CURSOR RETURN NomTypeLignesRetournées ;

La syntaxe pour déclarer une variable curseur forte est la suivante :


NomVariableCurseurForte NomTypeCurseurFort ;

Exemple 5.2.8.ii : Cet exemple déclare une variable curseur « V_c1 » forte
qu’on ne peut utiliser que pour traiter les requêtes « SELECT » qui
retournent des lignes de type « Tab_Employes%ROWTYPE ».

260
SGBD relationnels – Tome 1, État de l’art

DECLARE
TYPE T_vc1 IS REF CURSOR RETURN Tab_Employes%ROWTYPE;
V_c1 T_vc1;
BEGIN
NULL ;
END;

b) Les instructions d’ouverture et de fermeture d’une variable curseur

L’ouverture d’une variable curseur permet d’associer dynamiquement à


cette variable un ordre « SELECT ». Elle se poursuit par l’exécution de
l’ordre « SELECT » associé et le positionnement du curseur avant la 1ère
ligne retournée.

La syntaxe simplifiée de l’instruction d’ouverture d’une variable curseur


est la suivante :
OPEN NomVariableCurseur FOR RequêteSQL ;

La fermeture d’une variable curseur ouverte s’effectue en exécutant une


instruction « CLOSE » qui spécifie son nom. Une variable curseur ouverte
et fermée à l’aide de ces deux instructions peut à nouveau être réouverte et
fermée.

La syntaxe de l’instruction de fermeture d’une variable curseur est la


suivante :
CLOSE NomVariableCurseur ;

c) Exemple d’utilisation des variables curseurs

Exemple 5.2.8.iii : Dans un premier temps, la variable curseur


« V_CurseurEmp » est utilisée dans cet exemple pour mettre à jour le
salaire des employés dont la colonne « job_id » contient la valeur
« 'INFORMATICIEN' », en l’associant dans l’ordre « OPEN/FOR » à un
ordre « SELECT » qui retourne l’ensemble des employés.

Dans un deuxième temps, la variable curseur « V_CurseurEmp » est à


nouveau utilisée, mais pour mettre à jour le salaire des employés dont la

261
Joachim TANKOANO

colonne « job_id » contient la valeur « 'COMPTABLE' », en l’associant


dans l’ordre « OPEN/FOR » à une variable qui contient un ordre
« SELECT » qui ne retourne que l’ensemble des employés ciblés.

On notera que dans les deux cas, les mises à jour sont effectuées à l’aide
d’un ordre « UPDATE » qui n’utilise pas la clause « WHERE CURRENT
OF » qui requiert la spécification d’un curseur et non d’une variable
curseur.
DECLARE
TYPE T_CurseurEmp IS REF CURSOR
RETURN Tab_Employes%ROWTYPE;
V_CurseurEmp T_CurseurEmp;
V_Employe Tab_Employes%ROWTYPE;
V_Req VARCHAR2 (100) :=
'SELECT *
FROM Tab_Employes e
WHERE e.job_id = "COMPTABLE"
FOR UPDATE';
BEGIN
OPEN V_CurseurEmp FOR
SELECT * FROM Tab_Employes FOR UPDATE;
LOOP
FETCH V_CurseurEmp INTO V_Employe;
EXIT WHEN V_CurseurEmp %NOTFOUND;
IF V_Employe. job_id = 'INFORMATICIEN' THEN
UPDATE Tab_employes e
SET e.salaire = V_Employe.salaire * 1.02
WHERE e.matricule = V_Employe.matricule;
END IF;
END LOOP;
CLOSE V_CurseurEmp;
OPEN V_CurseurEmp FOR V_Req;
LOOP
FETCH V_CurseurEmp INTO V_Employe;
EXIT WHEN V_CurseurEmp %NOTFOUND;
UPDATE Tab_employes e
SET e.salaire = V_Employe.salaire * 1.03
WHERE e.matricule = V_Employe.matricule;

262
SGBD relationnels – Tome 1, État de l’art

END LOOP;
CLOSE V_CurseurEmp;
COMMIT;
END;

5.2.9. Le mécanisme des exceptions

L’arrêt du déroulement normal de l’exécution du code défini dans le corps


d’un bloc PL/SQL peut être provoqué par plusieurs types d’anomalies. Ces
anomalies peuvent être d’origines diverses : incidents au niveau matériel,
erreurs de conception, erreurs de programmation, erreurs dans les données
saisies par l’utilisateur, violation d’une contrainte d’intégrité, violation des
autorisations d’accès aux données, conflit d’accès à une donnée, etc. Ces
anomalies peuvent en général se produire à n’importe quel endroit du code
et à n’importe quel moment. Lorsqu’une anomalie est détectable par le
moteur d’exécution de PL/SQL, on parle d’anomalie interne. Lorsqu’une
anomalie n’est détectable que par le développeur, on parle d’anomalie
applicative.

Comme dans les autres langages de programmation, le mécanisme des


exceptions de PL/SQL a pour objectif de rendre possible la poursuite de
l’exécution du programme concerné lorsque ces anomalies se produisent.
On parle d’exception système lorsqu’il s’agit d’une anomalie interne et
d’exception applicative lorsqu’il s’agit d’une anomalie applicative.

Ce qui suit présente le fonctionnement de ce mécanisme et les instructions


proposées pour sa mise en œuvre, pour ce qui concerne PL/SQL.

a) Les principes du mécanisme des exceptions de PL/SQL

Une exception est un événement système ou applicatif déclenché par


PL/SQL ou par le développeur, en réaction à la survenue d’une anomalie
au cours du déroulement normal de l’exécution du code défini dans le corps
d’un bloc PL/SQL. Sa finalité est de provoquer le transfert de l’exécution
du code de ce bloc vers un code de traitement de cette anomalie défini dans
sa partie « EXCEPTION ».

263
Joachim TANKOANO

S’il s’agit d’un bloc imbriqué, à la fin de l’exécution du code de traitement


de l’anomalie, l’exécution se termine dans ce bloc imbriqué et se poursuit
dans son bloc parent avec l’exécution de l’instruction qui suit ce bloc
imbriqué. De ce fait, pour éviter que l’exécution d’une séquence
d’instructions dans un bloc entraine en cas d’anomalie la fin de l’exécution
de ce bloc, il suffit d’encapsuler cette séquence d’instructions dans un bloc
imbriqué.

S’il ne s’agit pas d’un bloc imbriqué, à la fin de l’exécution du code de


traitement de l’anomalie, l’exécution se termine dans ce bloc et se poursuit
dans l’unité de traitement appelant.

Si par exemple une anomalie se produit dans un sous-programme SP2


appelé par un autre sous-programme SP1, à la fin de l’exécution du code de
traitement de l’anomalie, l’exécution se termine dans SP2 pour se
poursuivre dans SP1 avec l’exécution de l’instruction qui suit l’appel de
SP2.

Lorsqu’aucun code de traitement n’a été prévu dans la partie


« EXCEPTION » du bloc où s’est produite une anomalie, l’exception se
propage successivement dans les blocs parents et dans des unités de
traitement appelant, à la recherche de ce code. Lorsqu’aucun des blocs
parents et de ces unités de traitement ne contient ce code, l’exception se
termine par une erreur dans l’environnement hôte.

Nom d’exception Code erreur Cause


prédéfini prédéfini
NO_DATA_FOUND ORA-01403 L’ordre SELECT n’a pas retourné
de ligne
TOO_MANY_ROWS ORA-01422 L’ordre SELECT a retourné plus
d’une ligne
INVALID_CURSOR ORA-01001 Une opération est effectuée sur un
curseur invalide
ZERO_DIVIDE ORA-01476 Une division par zéro a été détectée
DUP_VAL_ON_INDEX ORA-00001 Une tentative d’insertion d’une
ligne dont la clé existe déjà dans la
table concernée a été détectée
Le tableau ci-dessus donne à titre d’illustration pour quelques exemples
d’exceptions, le nom de l’exception, le code de l’erreur qui provoque

264
SGBD relationnels – Tome 1, État de l’art

l’exception et la cause de l’erreur.

b) Les instructions de déclaration d’une exception

Pour qu’un code spécifique de traitement d’anomalie puisse être défini


pour une exception dans la partie « EXCEPTION » d’un bloc, cette
exception doit avoir un nom. Chaque type d’anomalie interne, c'est-à-dire
détectable par le moteur d’exécution PL/SQL, a un code erreur attribué par
PL/SQL, mais n’a pas obligatoirement un nom d’exception prédéfini qui
lui correspond.

Le développeur peut associer un nom à une exception qui concerne une


anomalie interne (sans nom prédéfini) ou qui concerne une anomalie
applicative (c'est-à-dire, qu’il a la responsabilité de détecter), en déclarant
ce nom dans la partie déclarative du bloc concerné à l’aide de l’instruction
ci-après :
NomException EXCEPTION ;

Lorsqu’il s’agit d’une exception sans nom concernant une anomalie interne,
le développeur doit en plus, dans la partie déclarative du bloc, associer ce
nom au numéro d’erreur de l’exception, attribué par PL/SQL, à l’aide de
l’instruction ci-après :
PRAGMA EXCEPTION_INIT (NomException, CodeErreur) ;

Il est aussi possible pour le développeur d’utiliser cette instruction pour


associer au nom d’une exception applicative un code erreur compris entre
-20000 et -20999.

c) L’instruction de déclenchement d’une exception

La survenue de toute anomalie interne amène le moteur d’exécution de


PL/SQL à déclencher automatiquement une exception.

Le développeur peut néanmoins décider lui aussi de déclencher une


exception qui concerne une anomalie interne.

Pour ce qui concerne les exceptions relatives aux anomalies applicatives,


elles ne peuvent être détectées et déclenchées que par le développeur.

265
Joachim TANKOANO

L’instruction qui permet au développeur, dans tous les cas, de déclencher


une exception est la suivante :
RAISE NomException ;

Les exceptions relatives aux anomalies applicatives peuvent aussi être


déclenchées à l’aide de la procédure « RAISE_APPLICATION_ERROR »
définie dans le package « DBMS_STANDARD ». La signature de cette
procédure est la suivante :
RAISE_APPLICATION_ERROR (CodeErreur, MessageErreur
[, {TRUE | FALSE}]) ;

Cette procédure ne peut être appelée qu’à l’intérieur d’un sous-programme


stocké. Elle permet de transmettre au code PL/SQL chargé de la gestion
d’une anomalie applicative, un code d’erreur et un message d’erreur. La
spécification du paramètre « TRUE » lors d’un appel permet de demander
à PL/SQL de placer le code de l’erreur au sommet de la pile des erreurs, au
lieu de remplacer la pile des erreurs par le code de l’erreur.

d) Le code PL/SQL de traitement d’une anomalie

Le code de traitement d’une anomalie dont la survenue dans un bloc peut


amener le moteur d’exécution de PL/SQL ou le développeur à déclencher
une exception doit être défini dans la partie « EXCEPTION » de ce bloc à
l’aide de l’instruction « WHEN » dont la syntaxe est définie comme suit :
WHEN {NomException}[OR..n] | OTHERS}
THEN CodeDeTraitementAnomalie;

1) Comme l’indique cette syntaxe, le code de traitement défini par une


instruction « WHEN » peut être commun à des exceptions de noms
différents.

Dans la partie « EXCEPTION » d’un bloc, on peut avoir plusieurs


instructions « WHEN » qui se suivent. Parmi ces instructions « WHEN » il
ne doit exister qu’une seule avec l’option « OTHERS ». Cette instruction
qui doit être la dernière a pour rôle de définir le code de traitement des
anomalies relatives à toutes les autres exceptions qui ne sont pas prises en
compte de façon spécifique dans une instruction « WHEN ».

266
SGBD relationnels – Tome 1, État de l’art

e) La classification des types d’exceptions

Au regard de ce qui précède, les exceptions PL/SQL peuvent être classées


en 3 types d’exceptions, traités différemment par le développeur :
• Les exceptions qui correspondent aux anomalies internes qualifiées
de standards et fréquentes. Ce type d’exceptions ont par défaut un
code erreur et un nom prédéfini et sont déclenchables
automatiquement dans tout bloc par PL/SQL. Pour traiter une
exception de ce type, le développeur doit tout simplement prévoir le
code de gestion de l’anomalie concernée dans la partie
« EXCEPTION » du bloc où l’exception peut être déclenchée :
[DECLARE
…….
BEGIN
……..
EXCEPTION
…….
WHEN NomException THEN CodeDeTraitementAnomalie ;
……..
END;

• Les exceptions qui correspondent aux anomalies internes qualifiées


de standards et non fréquentes. Ce type d’exceptions ont aussi par
défaut un code erreur et sont déclenchables automatiquement dans
tout bloc par PL/SQL, mais n’ont pas obligatoirement un nom
prédéfini. Lorsqu’une exception de ce type a un nom prédéfini, son
traitement par le développeur se fait comme dans le cas précédent.
Lorsqu’une exception de ce type n’a pas un nom prédéfini, son
traitement par le développeur nécessite de lui associer un nom dans
la partie « DECLARE » du bloc où l’exception peut être déclenchée :
DECLARE
…..
NomException EXCEPTION ;
PRAGMA EXCEPTION_INIT (NomException, CodeErreur);
BEGIN
……..

267
Joachim TANKOANO

EXCEPTION
……
WHEN NomException THEN CodeDeGestionAnomalie;
……
END;

• Les exceptions qui correspondent aux anomalies applicatives


détectables et déclenchables par le développeur. Pour une exception
de ce type, en plus de prévoir le code de gestion de l’anomalie
concernée dans la partie « EXCEPTION » du bloc où elle peut être
déclenchée, le développeur a la responsabilité de la déclarer et de la
définir dans la partie déclarative et de la déclencher explicitement
dans la partie corps du bloc, soit en utilisant l’instruction « RAISE »,
soit en utilisant la procédure « RAISE_APPLICATION_ERROR » :
DECLARE
……
NomException EXCEPTION;
……
BEGIN
……
RAISE NomException;
…….
EXCEPTION
……
WHEN NomException THEN CodeDeGestionAnomalie;
……..
END;

DECLARE
……..
NomException EXCEPTION;
PRAGMA EXCEPTION_INIT (NomException, CodeErreur);
…….
BEGIN
…….
RAISE_APPLICATION_ERROR (CodeErreur, Message [,{TRUE
| FALSE}]);
……..

268
SGBD relationnels – Tome 1, État de l’art

EXCEPTION
……
WHEN NomException THEN CodeDeGestionAnomalie;
……
END;

5.3. Les procédures et fonctions


Lorsque le traitement assigné à un bloc est complexe, le développeur peut
décomposer ce traitement en opérations plus élémentaires, réutilisables au
cours de l’exécution à plusieurs endroits et étapes. Comme dans les autres
langages de programmation, ces opérations peuvent être implémentées à
l’aide de procédures et fonctions imbriquées, déclarées et définies dans la
partie déclarative de ce bloc et appelées dans son corps et dans le corps de
ses blocs imbriqués. Ceci peut amener le développeur à imbriquer des
procédures et fonctions dans des blocs anonymes, des blocs imbriqués, des
déclencheurs, mais aussi dans d’autres procédures et fonctions.

En plus des procédures et fonctions imbriquées, Oracle permet de créer des


procédures et fonctions stockées PL/SQL (dites aussi cataloguées),
incorporées dans le catalogue de la base de données et éventuellement
encapsulées dans des packages.

Les procédures et fonctions stockées font partie des fonctionnalités


spécifiques aux langages qui étendent SQL en langage complet de
programmation. Il s’agit d’unités de traitement :
• Qui sont compilées et stockées au niveau de la base de données
• Qui sont de ce fait visibles et accessibles de façon sécurisée comme
les autres entités stockées au niveau de la base de données (tables,
vues, index, etc.)
• Qui peuvent être appelées à l’intérieur d’un ordre SQL ou d’autres
procédures et fonctions stockées
• Qui peuvent être appelées à l’intérieur de programmes écrits à l’aide
de langages de programmation quelconques, s’exécutant au niveau
des postes clients ou au niveau du serveur d’application comme

269
Joachim TANKOANO

expliqué dans le chapitre 6


• Qui peuvent aussi être appelées à partir d’un navigateur via une
passerelle PL/SQL.

C’est la fonctionnalité de PL/SQL qui, combiné avec les fonctionnalités


offertes par les packages, rend possible l’utilisation de ce langage pour le
développement d’applications d’entreprise modulaires, performantes,
sécurisées, faciles à maintenir, qui respectent notamment l’architecture
MVC :
• Les procédures et fonctions stockées peuvent être encapsulées avec
d’autres objets (constantes, types, variables, curseurs, exceptions)
dans des packages correspondant soient à des modules qui
implémentent des APIs, soient à des composants ou à des modules
fonctionnels d’une application.
• Cette possibilité est utilisée d’une part, par les fournisseurs d’offres
technologiques pour étendre les fonctionnalités du langage PL/SQL
et du SGBD à l’aide d’APIs et d’autre part, par le développeur pour
implémenter, en s’appuyant sur ces APIs, toute ou partie des
couches d’une application d’entreprise (à savoir, la couche métier et
la couche d’accès aux données principalement et éventuellement la
couche présentation).
• Ceci induit comme avantage, l’amélioration des performances des
applications d’entreprise :
o Grâce au fait qu’une procédure ou fonction stockée peut
traiter une requête web en effectuant des accès à la base de
données et en générant la réponse à transmettre en retour,
sans effectuer au cours de ce traitement des accès distants à
un serveur, ce qui réduit le trafic sur le réseau
o Grâce aussi au fait que, ce faisant, on évite de répéter les
analyses et les compilations de code nécessaires avant
l’exécution d’une procédure ou d’une fonction
• Ceci induit aussi comme avantage, l’amélioration de la sécurité des
données lorsque les droits d’accès accordés à la couche présentation

270
SGBD relationnels – Tome 1, État de l’art

et à l’utilisateur final ne concernent que les procédures et fonctions


de la couche métier et de la couche d’accès aux données et non les
opérations de base de manipulation de la base de données
(« SELECT », « INSERT », « UPDATE » et « DELETE ») ;
• Enfin, ceci induit comme avantage, la simplification de la
maintenance des applications d’entreprise :
o En rendant possible le partage de code par des programmes
écrits dans des langages différents ;
o En évitant ainsi la redondance au niveau du code.

Une procédure ou fonction stockée peut aussi être une procédure ou


fonction stockée Java, qui correspond à une méthode Java. Une procédure
ou fonction stockée PL/SQL peut appeler directement une procédure ou
fonction stockée Java comme s’il s’agissait d’une procédure ou fonction
stockée PL/SQL et inversement. Cette forte intégration de Java dans le
SGBD Oracle est utilisée pour le développement des parties des
applications Java qui ont besoin de tirer parti au mieux des avantages
offerts par les technologies Java et la technologie des bases de données.
Pour l’exécution des ordres SQL, les procédures et fonctions stockées Java,
peuvent s’appuyer, soit sur « SQLJ », l’offre « Embedded SQL » de Java,
soit sur le connecteur « JDBC (Java Data Base Connector) » qui offre une
API de bas niveau (voir la section 6.3). Oracle offre plusieurs outils et
utilitaires (l’IDE « JDeveloper » et l’utilitaire « loadjava » notamment) qui
permettent au développeur d’écrire, de compiler et de déployer des classes
Java dans une base de données Oracle. Après le déploiement d’une classe
Java, pour appeler une de ses méthodes, cette méthode doit être
préalablement publiée comme étant une procédure ou fonction stockée
Java. L’exécution d’une procédure ou d’une fonction stockée Java est
assurée par une machine virtuelle Java dédiée, activable dans chaque
session Oracle.

Par ailleurs, les procédures externes PL/SQL permettent de faire


correspondre, une procédure ou fonction stockée, à un fichier « DLL
(Dynamic Link Library) » contenant un sous-programme C (ou à un sous-
programme écrit dans tout autre langage qui peut être appelé par un

271
Joachim TANKOANO

programme C) afin de permettre son appel dans du code PL/SQL. L’appel


d’une procédure externe a comme effet, le chargement dynamique par le
moteur PL/SQL du fichier DLL spécifié, suivi de l’appel du sous-
programme qu’il contient.

Ce qui suit aborde dans les grandes lignes quelques aspects relatifs à la
déclaration, à la définition et à l’appel des procédures et fonctions.

5.3.1. La déclaration et la définition des procédures et fonctions

a) Les déclarations et définitions de procédures

La syntaxe simplifiée de l’instruction PL/SQL qui permet de déclarer et de


définir une procédure imbriquée dans un bloc anonyme, dans un bloc
imbriqué, dans un déclencheur, dans une autre procédure et fonction
(imbriquée ou stockée) ou de déclarer et de définir une procédure stockée
dans un package est la suivante :
PROCEDURE CodePL_SQLProcédure ;

Quant à l’ordre SQL qui permet de créer une procédure stockée dans le
catalogue d’une base de données, sa syntaxe simplifiée est la suivante :
CREATE [OR REPLACE] PROCEDURE [NomSchéma.]CodePL_SQLProcédure ;

Dans les deux cas, la syntaxe simplifiée du code de la procédure est la même
et peut être définie comme suit :
CodePL_SQLProcédure ::= DéclarationProcédure | DéfinitionProcédure ;
DéclarationProcédure ::= NomProcédure
[({DéclarationParamètreProcédure}[,..n])]
[ACCESSIBLE BY
({[TypeUnitéTrait] NomUnitéTrait}[,..n])]
[AUTHID {{CURRENT USER} | DEFINER}]

DéclarationParamètreProcédure ::= NomParamètre [IN | OUT | IN OUT]


TypeParamètre [:= Expression]
TypeUnitéTrait ::= {FUNCTION | PROCEDURE | PACKAGE | TRIGGER}
DéfinitionProcédure ::= DéclarationProcédure
{IS | AS} {{[DéclarationsBloc] CorpsBloc} | SpécifAppel}

272
SGBD relationnels – Tome 1, État de l’art

CorpsBloc ::= BEGIN SéquenceInstr [EXCEPTION CodeTraitAnomalies] END


SpécifAppel ::= {{LANGUAGE JAVA NAME NomMéthode}
|{LANGUAGE C LIBRARY NomLib NAME NomSousPgme}}

1) Il est possible de déclarer, dans une 1ère étape, une procédure en


spécifiant sa signature et ses propriétés et de définir, dans une 2ème étape,
cette procédure en rappelant sa signature et ses propriétés et en spécifiant
le contenu de son bloc. Le recours à cette possibilité est obligatoire lorsqu’il
s’agit d’une procédure stockée incorporée dans un package. Il est aussi
possible de déclarer et de définir une procédure en une seule étape.

2) Le mode « IN » est le mode par défaut des paramètres formels. Il


permet, lors d’un appel, de passer une valeur à la procédure. Le mode
« OUT » permet quant à lui de retourner une valeur à l’appelant. Le mode
« IN OUT » combine ces deux possibilités.

3) En outre, PL/SQL autorise la surcharge des procédures et fonctions.

4) Lorsqu’il s’agit d’une procédure stockée, la propriété définie par la


clause « ACCESSIBLE BY » permet de spécifier, sous forme de liste
blanche, le nom des unités de traitement autorisées à appeler cette
procédure.

5) Lorsqu’il s’agit d’une procédure stockée, la propriété définie par la


clause « AUTHID » permet quant à elle de spécifier le schéma et
l’utilisateur qui doivent être considérés comme étant le schéma courant et
l’utilisateur courant lors de l’exécution de la procédure. L’option
« DEFINER » permet d’indiquer que l’utilisateur courant doit être
considéré comme étant le propriétaire de la procédure et le schéma courant
comme étant le schéma du propriétaire de la procédure. L’option
« CURRENT USER » permet quant à elle d’indiquer que l’utilisateur
courant doit être considéré comme étant l’appelant et le schéma courant
comme étant le schéma de l’appelant.

6) Comme pour les blocs anonymes, le bloc qui définit une procédure
est constitué d’une partie déclarative (facultative), d’une partie corps
(obligatoire) et d’une partie code de traitement des anomalies (facultative).
Alternativement, la définition du bloc d’une procédure stockée peut être

273
Joachim TANKOANO

constituée d’une spécification qui publie une procédure stockée Java ou une
procédure externe C, en fournissant les informations requises pour l’appel.

Exemple 5.3.1.i : Cet exemple déclare et définit dans un bloc anonyme une
procédure « augmenter_salaire () » qui permet d’augmenter le salaire de
l’employé dont le matricule est passé en paramètre d’un montant et d’une
prime passés également en paramètres. L’appel de cette procédure ne peut
se faire que dans le corps du bloc anonyme.
DECLARE
PROCEDURE augmenter_salaire (
P_matricule IN Tab_Employes.matricule%TYPE,
P_montant IN Tab_Employes.salaire%TYPE,
P_prime IN Tab_Employes.salaire%TYPE
)
IS
BEGIN
UPDATE Tab_employes e
SET e.salaire = e.salaire + P_montant + P_prime
WHERE e.matricule = P_matricule ;
END ;
BEGIN
NULL ;
END ;

Exemple 5.3.1.ii : Cet exemple crée la procédure « augmenter_salaire () » de
l’exemple précédant sous forme de procédure stockée dans le schéma de
l’utilisateur courant. Cette procédure peut être appelée par des applications
qui s’exécutent au niveau d’un poste client, au niveau du serveur
d’application ou au niveau du serveur de la base de données, auxquelles le
privilège « EXECUTE » de la procédure a été accordé. Au cours d’une
exécution de cette procédure, le propriétaire sera considéré comme étant
l’utilisateur courant et son schéma comme étant le schéma courant.

274
SGBD relationnels – Tome 1, État de l’art

CREATE OR REPLACE PROCEDURE augmenter_salaire (


P_matricule IN Tab_Employes.matricule%TYPE,
P_montant IN Tab_Employes.salaire%TYPE,
P_prime IN Tab_Employes.salaire%TYPE
) AUTHID DEFINER
IS BEGIN
UPDATE Tab_employes e
SET e.salaire = e.salaire + P_montant + P_prime
WHERE e.matricule = P_matricule ;
END;

b) Les déclarations et définitions de fonctions

La syntaxe simplifiée de l’instruction PL/SQL qui permet de déclarer et de


définir une fonction imbriquée dans un bloc anonyme, dans un bloc
imbriqué, dans un déclencheur, dans une autre procédure et fonction
(imbriquée ou stockée) ou de déclarer et de définir une fonction stockée
dans un package est la suivante :
FUNCTION CodePL_SQLFonction ;

Quant à l’ordre SQL qui permet de créer une fonction stockée dans le
catalogue d’une base de données, sa syntaxe simplifiée est la suivante :
CREATE [OR REPLACE] FUNCTION [NomSchéma.]CodePL_SQLFonction ;

Dans les deux cas, la syntaxe simplifiée du code de la fonction est la même
et peut être définie comme suit :
CodePL_SQLFonction ::= DéclarationFonction | DéfinitionFonction;
DéclarationFonction ::= NomFonction
[({DéclarationParamètreFonction}[,..n])]
RETURN TypeRésutatFonction
[ACCESSIBLE BY
({[TypeUnitéTrait] NomUnitéTrait}[,..n])]
[AUTHID {{CURRENT USER} | DEFINER}]
DéclarationParamètreFonction ::= NomParamètre [IN | OUT | IN OUT]
TypeParamètre [ := Expression]
TypeUnitéTrait ::= {FUNCTION | PROCEDURE | PACKAGE | TRIGGER}

275
Joachim TANKOANO

DéfinitionFonction ::= DéclarationFonction


IS | AS} {{[DéclarationsBloc] CorpsBloc} | SpécifAppel}
CorpsBloc ::= BEGIN SéquenceInstr [EXCEPTION CodeTraitAnomalies] END
SpécifAppel ::= {{LANGUAGE JAVA NAME NomMéthode}
|{LANGUAGE C LIBRARY NomLib NAME NomSousPgme}}

1) La principale différence qui existe entre une fonction et une


procédure réside dans le fait qu’une fonction doit toujours retourner une
valeur correspondant au résultat du calcul qu’elle a effectué. Dans la
déclaration d’une fonction, la clause « RETURN » est donc obligatoire. Elle
permet de spécifier le type de cette valeur. En outre, l’exécution du code
défini dans le corps de cette fonction doit obligatoirement se terminer par
l’exécution d’une instruction « RETURN Expression » où le type de
« Expression » doit correspondre au type déclaré.

2) Dans le code exécutable d’une unité de traitement, l’appel d’une


fonction est traité comme une expression évaluée à la valeur retournée par
l’appel. Le développeur peut définir des fonctions de composition qui
implémentent ses propres symboles de fonctions qu’il peut ensuite utiliser
dans ses requêtes SQL.

5.3.2. Les appels de procédures et fonctions dans du code


PL/SQL

Les appels de procédures et de fonctions en PL/SQL se font de la même


manière que dans les autres langages de programmation en citant le nom
de la procédure ou de la fonction et en passant des paramètres effectifs en
remplacement des paramètres formels.

Comme dans les autres langages de programmation, un appel de procédure


est traité comme une action à exécuter et un appel de fonction comme une
expression à évaluer.

Exemple 5.3.2.i : Cet exemple déclare et définit dans un bloc anonyme une
procédure « augmenter_salaire () » qui, en rappel, permet d’augmenter le
salaire de l’employé dont le matricule est passé en paramètre d’un montant
et d’une prime passés également en paramètres. Cette procédure est ensuite

276
SGBD relationnels – Tome 1, État de l’art

appelée dans le corps du bloc anonyme pour augmenter le salaire de


l’employé dont le matricule est « 120 » d’un montant de « 10000 » et d’une
prime de « 3000 ».
DECLARE
PROCEDURE augmenter_salaire (
P_matricule IN Tab_Employes.matricule%TYPE,
P_montant IN Tab_Employes.salaire%TYPE,
P_prime IN Tab_Employes.salaire%TYPE
)
IS
BEGIN
UPDATE Tab_employes e
SET e.salaire = e.salaire + P_montant + P_prime
WHERE e.matricule = P_matricule;
END;
BEGIN
augmenter_salaire (120,10000, 3000);
END;

Exemple 5.3.2.ii : Nous supposons ici que la procédure « augmenter_salaire


() » de l’exemple précédant a été créée sous forme de procédure stockée
dans le schéma de l’utilisateur courant comme dans l’exemple 5.3.1.ii. Cet
exemple appelle dans le corps d’un bloc anonyme cette procédure pour
augmenter le salaire de l’employé dont le matricule est « 120 » d’un montant
de « 10000 » et d’une prime de « 3000 ».
BEGIN
augmenter_salaire (120, 10000, 3000) ;
END ;

Exemple 5.3.2.iii : Cet exemple crée une fonction stockée « nb_agents_chef


() » qui calcule le nombre d’employés qui ont comme chef l’employé dont
le matricule est passé en paramètre. Les appels de cette fonction sont
effectués dans un bloc anonyme, d’abord pour afficher le nombre
d’employés ayant comme chef l’employé dont le matricule est « 100 »,
ensuite pour afficher le numéro et le nom de chaque employé ayant sous

277
Joachim TANKOANO

ses ordres plus de 5 employés.


CREATE OR REPLACE FUNCTION nb_agents_chef (
P_matricule IN Tab_Employes.matricule%TYPE
) RETURN BINARY_INTEGER AUTHID DEFINER
IS
V_nb_agents BINARY_INTEGER;
BEGIN
SELECT COUNT (*)
INTO V_nb_agents
FROM Tab_employes e
WHERE e.matricule_chef = P_matricule ;

RETURN V_nb_agents ;
END;

BEGIN
DBMS_OUTPUT.PUT_LINE
('Nombre agents chef 100 = ' || nb_agents_chef (100));
SELECT e.matricule, e.nom
FROM Tab_employes e
WHERE nb_agents_chef (e.matricule) > 5
END ;

Exemple 5.3.2.iv : Cet exemple crée la procédure stockée


« calculer_Primes_Encadrement () » qui calcule dans la table
« Tab_primes_encadrement (matricule, mt_prime) » le montant de la
prime d’encadrement accordée à chaque employé en fonction du budget
alloué passé en paramètre et du nombre d’employés qu’il encadre, sachant
que le directeur général ne dépend d’aucun employé. Cette procédure
stockée est ensuite appelée dans un bloc anonyme en fournissant un budget
alloué de « 2 000 000 ».
CREATE OR REPLACE PROCEDURE calculer_Primes_Encadrement (
P_budget_prime NUMBER
)
IS
v_nb_total_employes BINARY_INTEGER;

278
SGBD relationnels – Tome 1, État de l’art

BEGIN
-- Calcul du nombre total d’employés
SELECT COUNT (*) INTO v_nb_total_employes
FROM Tab_employes e ;
-- Calcul des primes dans la table Tab_primes_encadrement
INSERT INTO Tab_primes_encadrement
SELECT e.matricule_chef,
(P_budget_prime / (v_nb_total_employes – 1)) * COUNT (*)
FROM Tab_employes e
WHERE e.matricule_chef IS NOT NULL
GROUP BY e.matricule_chef;
END;

BEGIN calculer_Primes_Encadrement (2000000) ; END ;


Exemple 5.3.2.v : Cet exemple crée la procédure stockée


« calculer_Top_Salaires () » qui calcule dans la table « Tab_top_salaires
(matricule, salaire) », en fonction de la valeur « n » passée en paramètre, la
liste des « n » employés qui ont le salaire le plus élevé. Cette procédure
stockée est ensuite appelée dans un bloc anonyme pour calculer la liste des
10 employés qui ont le salaire le plus élevé.
CREATE OR REPLACE PROCEDURE calculer_Top_Salaires (
P_n BINARY_INTEGER)
IS
CURSOR c1
IS SELECT e.matricule, e.salaire FROM Tab_employes e
ORDER BY e.salaire DESC;
V_enr_c1 c1%ROWTYPE;
BEGIN
OPEN c1;
FOR i IN 1 .. P_n
LOOP
FETCH c1 INTO V_enr_c1;
EXIT WHEN c1%NOTFOUND;
INSERT INTO Tab_top_salaires
VALUES (V_enr_c1.matricule, V_enr_c1.salaire);
END LOOP;
CLOSE c1;

279
Joachim TANKOANO

END;

BEGIN
calculer_Top_Salaire (10);
END;

5.3.3. L’appel d’un sous-programme stocké à partir d’un


navigateur

Oracle offre la possibilité d’appeler une procédure ou une fonction stockée


via le web à partir d’un navigateur, ce qui permet le développement et le
déploiement d’applications web (constituées que de procédures et
fonctions stockées) entièrement embarquées dans la base de données, où
elles s’exécutent en minimisant les échanges via le réseau. Au besoin, ces
applications peuvent respecter l’architecture logicielle MVC. En d’autres
termes, ces procédures et fonctions stockées peuvent être des contrôleurs,
des modèles ou des vues.

L’écriture d’une procédure ou d’une fonction stockée devant être appelée


via le web s’appuie sur le kit PL/SQL pour le web et ses appels sur une
passerelle PL/SQL.

La passerelle PL/SQL est un « listener HTTP ». Son rôle est de recevoir les
requêtes du navigateur, de transformer l’URL de chaque requête reçue en
instruction d’appel de la procédure ou de la fonction stockée spécifiée,
d’appeler cette procédure ou cette fonction et de retourner au navigateur le
résultat produit par cet appel.

La syntaxe d’une URL qui concerne un appel de procédure ou de fonction


stockée est la suivante :
protocole://nomHote[:port]/CheminVirtuel
/[[schema.][package.]nomProc[?paramètres]]

La passerelle PL/SQL peut être intégrée dans un « serveur HTTP Oracle »


à l’aide du module « mod_plsql », ou embarquée dans un SGBD Oracle via
un « listener HTTP de XML DB » associé à ce SGBD.

Quant au kit PL/SQL pour le web, il est constitué d’un ensemble de

280
SGBD relationnels – Tome 1, État de l’art

packages qui offrent des APIs que le développeur peut utiliser dans une
procédure ou fonction stockée pour traiter une requête « HTTP ».

Tout comme les autres frameworks d’aide au développement des


applications web, Oracle propose en outre « PSP (PL/SQL Server Pages) »,
un langage de balisage de même type que « JSP », pour la génération
dynamique côté serveur, des pages « HTML » à transmettre en retour au
navigateur. Comme les pages « JSP », ces pages peuvent être transformées
à l’aide d’un utilitaire en procédures stockées (équivalentes aux
« Servlets ») que le développeur peut compiler et déployer dans la base de
données.

Ce qui suit présente succinctement :


• L’intégration de la passerelle PL/SQL au niveau du « serveur HTTP
Oracle » et du serveur Oracle
• Le kit PL/SQL pour le web
• « PSP ».

a) L’intégration de la passerelle PL/SQL au niveau du « serveur HTTP


Oracle »

Le « serveur HTTP Oracle » (c'est-à-dire « OHS (Oracle HTTP Server) »)


est le serveur web de « Oracle Fusion Middleware » qui fournit, entre
autres, un listener « HTTP » à « Oracle WebLogic Server », le serveur
d’application « Java EE » d’Oracle. Ce serveur « HTTP » est un serveur
Apache auquel Oracle a ajouté des modules Apache additionnels. Parmi ces
modules additionnels on peut citer :
• Le module « mod_wl_ohs » qui permet d’intégrer au niveau d’un
« serveur HTTP Oracle » une passerelle pour rediriger les requêtes
« HTTP » vers un serveur d’application « Oracle WebLogic »
• Le module « mod_plsql » qui permet d’intégrer au niveau d’un
« serveur HTTP Oracle » une passerelle PL/SQL vers une base de
données Oracle, afin de permettre aux navigateurs d’appeler les
procédures et fonctions stockées de cette base de données

281
Joachim TANKOANO

• Le module « mod_ossl » qui implémente la couche « SSL (Secure


Sockets Layer) » d’Oracle requise pour le protocole « HTTPS ».

Pour router une requête vers un serveur de base de données Oracle, le


module « mod_plsql » utilise les données de configuration d’un
« descripteur d’accès à la base de données » ou « DAD (Database Access
Descriptor) » contenu dans un élément « <Location> » du fichier de
configuration « dads.conf ». Ces données de configuration concernent
notamment :
• Le chemin virtuel qui doit être utilisé pour appeler une procédure
ou fonction stockée
• La directive « SetHandler pls_handler » qui lie le descripteur au
module « mod_plsql »
• Les directives de sécurité « Order deny, allow » et « Allow from
all »
• La directive « PlsqlDatabaseUsername » qui spécifie le nom de
l’utilisateur devant être utilisé pour la connexion à la base de
données
• La directive « PlsqlDatabasePassword » qui spécifie le mot de
passe devant être utilisé pour la connexion à la base de données
• La directive « PlsqlDatabaseConnectString » qui spécifie la chaîne
de connexion devant être utilisée pour la connexion à la base de
données
• La directive « PlsqlAuthenticationMode » qui spécifie le type
d’authentification devant être utilisé pour la connexion à la base de
données.

N.B. : Le module « mod_plsql » n’a pas été inclus par Oracle dans la version
12.1.3 et les versions suivantes du « serveur HTTP Oracle ». Pour ces
versions, Oracle recommande l’utilisation des services « Oracle REST
Data ».

282
SGBD relationnels – Tome 1, État de l’art

b) L’intégration de la passerelle PL/SQL dans le SGBD Oracle

L’accès à une passerelle PL/SQL embarquée dans un SGBD Oracle


nécessite d’associer à ce SGBD un « listener HTTP de XML DB » dont le
rôle est de router les requêtes des navigateurs qui spécifient un appel de
procédure ou de fonction stockée vers cette passerelle. Cette passerelle
PL/SQL offre les mêmes fonctionnalités que le module « mod_plsql » sans
nécessiter de « serveur HTTP Oracle ». Elle se configure à l’aide du package
« DBMS_EPG » inclut dans le kit PL/SQL pour le web.

L’installation de « Oracle XML DB » installe et préconfigure une passerelle


PL/SQL embarquée dans le SGBD Oracle.

L’intégration de la passerelle PL/SQL dans un SGBD n’est intéressante que


pour les applications web déployées dans un Intranet.

c) Le kit PL/SQL pour le web

Le kit PL/SQL pour le web est un ensemble de packages fournis en


standard. Les APIs de ces packages offent les fonctionnalités requises pour
le traitement d’une requête « HTTP » dans une procédure ou dans une
fonction stockée. Elles permettent notamment de récupérer les informations
relatives à une requête « HTTP » contenues dans son URL et de générer la
réponse à transmettre en retour au navigateur.

Exemple 5.3.3.i : La procédure stockée « afficher_employes » définie dans


cet exemple génère dynamiquement le code « HTML » qui permet à un
navigateur d’afficher la liste des employés de la table « Tab_Employes ».
Cette génération est faite en utilisant la fonction « PRINT () » du package
« HTP » du kit PL/SQL pour le web.
CREATE OR REPLACE PROCEDURE afficher_employes IS
CURSOR c1 IS
SELECT e.patronyme, e.prenom FROM jt.Tab_Employes e
ORDER BY e.patronyme;
BEGIN
HTP.PRINT('<html>');
HTP.PRINT('<head>');
HTP.PRINT('<meta http-equiv="Content-Type" content="text/html">');

283
Joachim TANKOANO

HTP.PRINT('<title>Liste des employés</title>');


HTP.PRINT('</head>');
HTP.PRINT('<body TEXT="#000000" BGCOLOR="#FFFFFF">');
HTP.PRINT('<h1> Liste des employés </h1>');
HTP.PRINT('<table width="40%" border="1">');
HTP.PRINT('<tr>');
HTP.PRINT('<th align="left">Nom de famille</th>');
HTP.PRINT('<th align="left">Prénom</th>');
HTP.PRINT('</tr>');
FOR V_Employe IN c1 LOOP
HTP.PRINT('<tr>');
HTP.PRINT('<td>' || V_Employe.patronyme || '</td>');
HTP.PRINT('<td>' || V_Employe.prenom|| '</td>');
END LOOP;
HTP.PRINT('</table>');
HTP.PRINT('</body>');
HTP.PRINT('</html>');
END ;

d) Le langage de script coté serveur « PSP (PL/SQL Server Pages) »

« PSP » est un langage de script que le développeur peut utiliser pour coder
la génération dynamique, côté serveur, d’une page web en format
« HTML » à transmettre en retour à un navigateur. Comme pour les autres
langages de scripts côté serveur, les pages « PSP » peuvent s’écrire en
combinant « PL/SQL » (pour les parties dynamiques de la page) et d’autres
langages de script côté client comme « HTML », « CSS » et « JavaScript »
(pour les parties statiques de la page). Les pages « PSP » sont comparables
aux pages « JSP » où les scripts de génération dynamique de code sont
écrits en Java.

Une page « PSP » peut être transformée à l’aide de l’utilitaire « loadpsp »


en une procédure stockée PL/SQL dont l’exécution a comme effet la
génération dynamique du code « HTML » qui doit être transmis à un
navigateur en réponse à une requête. Cette génération de code s’effectue en
s’appuyant sur les APIs du kit PL/SQL pour le web.

284
SGBD relationnels – Tome 1, État de l’art

Exemple 5.3.3.ii : Nous faisons l’hypothèse que la page « PSP » ci-dessous


est contenue dans le fichier « afficher_employes.psp ». Cette page, qui
contient du code statique « HTML » et du code dynamique PL/SQL,
permet de générer un code identique à celui de la procédure stockée
« afficher_employes » de l’exemple précédent 5.3.3.i. La génération
s’effectue en exécutant la commande : « loadpsp -replace -user jt/jt
afficher_employes.psp ».

Comme dans « JSP » :


• La balise de début « <%@ » et la balise de fin « %> » servent à
délimiter une directive
• La balise de début « <%-- » et la balise de fin « -%> » servent à
délimiter un commentaire
• La balise de début « <% ! » et la balise de fin « %> » servent à
délimiter des déclarations qui doivent dans le cas des pages « PSP »
être des déclarations PL/SQL
• La balise de début « <% » et la balise de fin « %> » servent à délimiter
du code exécutable qui doit dans le cas des pages « PSP » être du
code PL/SQL
• La balise de début « <%= » et la balise de fin « %> » servent à
délimiter une expression qui doit dans le cas des pages « PSP » être
une expression PL/SQL.

<%@ page language="PL/SQL" %>


<%@ page contentType="text/html" %>
<%@ plsql procedure=" afficher_employes" %>
<%-- Cet exemple permet de générer le code de la procédure stockée --%>
<%-- afficher_employes de l’exemple précédent. --%>
<%!
CURSOR c1 IS
SELECT e.patronyme, e.prenom
FROM jt.T_Employes e
ORDER BY e.patronyme;
%>
<html>

285
Joachim TANKOANO

<head>
<meta http-equiv="Content-Type" content="text/html">
<title>Liste des employés</title>
</head>
<body TEXT="#000000" BGCOLOR="#FFFFFF">
<h1>Liste des employés</h1>
<table width="40%" border="1">
<tr>
<th align="left">Nom de famille</th>
<th align="left">Prénom</th>
</tr>
<% FOR V_Employe IN c1 LOOP %>
<tr>
<td> <%=V_Employe.patronyme%> </td>
<td> <%= V_Employe.prenom%> </td>
</tr>
<% END LOOP; %>
</table>
</body>
</html>

5.4. Les packages


Tout comme les tables, les index, etc., les packages font partie des objets qui
se stockent dans une base de données.

On utilise en général un package pour encapsuler des procédures et des


fonctions stockées avec d’autres objets (types, constantes, variables,
curseurs, exceptions) logiquement liés. Toutefois, un package peut ne pas
contenir de procédures et de fonctions stockées ou ne contenir que des
procédures et des fonctions stockées.

Les packages sont compilés avant leur stockage dans une base de données
d’où plusieurs applications peuvent partager leur contenu.

Les packages permettent de développer des unités de traitement qui


s’exécutent au niveau du serveur de base de données (donc de la façon la
plus efficace) tout en tirant parti des mécanismes offerts par le SGBD pour

286
SGBD relationnels – Tome 1, État de l’art

le contrôle des autorisations d’accès. De nombreux packages sont proposés


par Oracle afin d’offrir au développeur des APIs qui étendent les
fonctionnalités du langage PL/SQL et du modèle relationnel. Ces APIs sont
utilisés par le développeur pour créer à son tour des packages qui
implémentent des composants ou des modules fonctionnels applicatifs.

Un package est constitué de deux parties (dont chacune est facultative) :


• Une partie spécification qui contient les définitions et déclarations
publiques, visibles à l’extérieur du package (définitions de types,
déclarations de constantes, de variables, d’exceptions, de curseurs,
de procédures et de fonctions). Les applications qui utilisent un
package n’ont accès qu’aux items définis ou déclarés dans sa partie
spécification. Elles n’ont pas accès au contenu de sa partie corps.
• Une partie corps qui : (i) définit les curseurs et les procédures et
fonctions stockées déclarés dans la partie spécification, (ii) contient
les déclarations et définitions privées nécessaires pour les
traitements effectués mais non visibles à l’extérieur du package
(types, constantes, variables, exceptions, curseurs, procédures et
fonctions), (iii) contient aussi les instructions d’initialisation (qui
doivent être exécutées lors d’une instanciation du package) avec une
partie exception pour la définition des traitements des anomalies. La
partie corps d’un package est une boîte noire que le développeur du
package peut modifier sans modifier la partie spécification, sans
donc remettre en cause le fonctionnement des applications qui
partagent ce package.

Les instances d’un package sont des instances de session. Chaque session
qui utilise un package possède sa propre instance de ce package. Cette
instance est créée et initialisée au cours d’une session par le moteur PL/SQL
pour la durée de la session lors de la première rencontre d’une référence à
un item de ce package.

Dans chaque session, l’instance d‘un package inclut l’état de ce package


dans cette session. Cet état est défini par les valeurs des constantes,
variables et curseurs déclarés dans la partie spécification ou dans la partie
corps de ce package. Lorsqu’un package déclare au moins une constante,

287
Joachim TANKOANO

une variable ou un curseur, on dit que ce package est avec état


(« statefull »), sinon, on dit qu’il est sans état (« stateless »).

Ce qui suit présente la syntaxe des instructions proposées par Oracle pour
la création des packages ainsi qu’un exemple de création de package avant
d’aborder succinctement les packages offerts par Oracle.

5.4.1. Les instructions de création d’un package

a) La création et la modification de la partie spécification d’un package

La syntaxe simplifiée de l’ordre SQL qui permet de créer ou de modifier la


partie spécification d’un package est la suivante :
CREATE [OR REPLACE] PACKAGE CodeSpécificationPackage;
CodeSpécificationPackage ::= [NomSchéma.]NomPackage
[ACCESSIBLE BY ({[TypeUnitéTrait]
NomUnitéTrait}[,..n])]
[AUTHID {{CURRENT USER} | DEFINER}]
{IS | AS}
[PRAGMA SERIALLY_REUSABLE;]
{DéclarationItemPublic}[..n]
END [NomPackage]
TypeUnitéTrait ::= {FUNCTION | PROCEDURE | PACKAGE | TRIGGER}
DéclarationItemPublic ::= {DéfType | DéclConstante | DéclVariabe
|DéclException | DéclCurseur | DéclProcédure
| DéclFonction}

1) Tous les items définis ou déclarés dans la spécification d’un package


(types, constantes, variables, exceptions, curseurs, procédures et fonctions)
sont implicitement publics. Ces définitions et déclarations doivent respecter
les syntaxes déjà définies pour ces différents items.

2) Les privilèges accordés à un utilisateur sur un package à l’aide d’un


ordre « GRANT » s’appliquent implicitement sur tous ses items publics.

3) Les propriétés « ACCESSIBLE BY » et « AUTHID » définies dans


l’ordre de création d’un package concernent aussi implicitement toutes ses
procédures et fonctions publiques.

288
SGBD relationnels – Tome 1, État de l’art

4) « PRAGMA SERIALLY_REUSABLE » déclare un package comme


étant sériellement réutilisable. Lorsqu’un package est sériellement
réutilisable, dans chaque session, l’état de son instance est réinitialisé après
usage pour l’exécution d’un bloc avant que cette instance ne soit remise
dans un pool où elle peut être réutilisée pour une exécution d’un bloc dans
une autre session. Un package sériellement réutilisable fonctionne donc
comme un package sans état, afin d’éviter une mobilisation excessive de la
mémoire centrale pour la conservation de l’état des instances des packages
relatif à chaque session, lorsque cela n’est pas indispensable.

b) La création de la partie corps d’un package

La syntaxe simplifiée de l’ordre SQL qui permet de créer la partie corps


d’un package est la suivante :
CREATE [OR REPLACE] PACKAGE BODY CodeCorpsPackage;
CodeCorpsPackage ::= [NomSchéma.]NomPackage
{IS | AS}
[PRAGMA SERIALLY REUSABLE;]
[{DéclarationItemPrivé}[..n]]
[{DéfinitionItemPrivéPublic}[..n]]
[BEGIN InstructionsInitialisationPackage
[EXCEPTION CodeGestionDesErreurs]
]
END [NomPackage]
DéclarationItemPrivé ::= {DéfType | DéclConstante | DéclVariabe | DéclException
| DéclCurseur | DéclProcédure | DéclFonction}
DéfinitionItemPrivéPublic ::= {DéfCurseur | DéfProcédure | DéfFonction}

1) Pour créer un package sériellement réutilisable, « PRAGMA


SERIALLY_REUSABLE » doit être spécifié dans l’ordre de création de la
spécification de ce package et dans l’ordre de création de son corps, si ce
package possède un corps.

2) Par ailleurs, les définitions et déclarations (types, constantes,


variables, exceptions, curseurs, procédures et fonctions) doivent respecter
les syntaxes déjà définies pour ces différents items.

289
Joachim TANKOANO

5.4.2. Exemple de création de packages

Comme nous l’avons déjà vu, les spécifications relatives à la confidentialité


se définissent en dehors des programmes en n’accordant à chaque acteur
que les droits dont il a besoin dans le cadre de ses activités pour l’exécution
des opérations de base (ordres « SELECT », « INSERT », « UPDATE »,
« DELETE » sur les tables et sur les vues) et des opérations métier
(procédures et fonctions stockées) de manipulation de la base de données.

De ce fait, la décomposition d’une application web par regroupement de


ses fonctionnalités en fonction des restrictions à mettre en place pour l’accès
à ces fonctionnalités, est un critère de décomposition en composants ou
modules applicatifs qui fait partie des bonnes pratiques pouvant simplifier
grandement le management de la spécification des règles de confidentialité
de l’entreprise en dehors de tout programme comme dans cet exemple.

Supposons le schéma relationnel ci-dessous introduit dans le paragraphe


2.3.3 :
Avions (NoAvion, NomAvion, CapAvion, LocAvion)
Pilotes (NoPilote, NomPilote, AdrPilote)
Vols (NoVol, #NoPilote, #NoAvion, VD, VA, HD, HA)

Supposons par ailleurs que l’analyse des besoins des utilisateurs a fait
ressortir que l’application qui permettra l’exploitation de cette base de
données sera mise en œuvre par deux grands services : le service
exploitation et le service maintenance.

Pour faciliter la prise en compte de ce choix organisationnel dans la


définition des spécifications relatives à la confidentialité, le développeur
peut décider avantageusement de décomposer cette application comme
suit en trois grands modules, définis en termes de packages :
• Le package « pkg_service_expl », pour l’encapsulation des
procédures et fonctions métier de l’application qui implémentent les
fonctionnalités accessibles qu’au service exploitation
• Le package « pkg_service_maint », pour l’encapsulation des
procédures et fonctions métier de l’application qui implémentent les
fonctionnalités accessibles qu’au service maintenance

290
SGBD relationnels – Tome 1, État de l’art

• Le package « pkg_tout_service », pour l’encapsulation des


procédures et fonctions métier de l’application qui implémentent les
fonctionnalités accessibles aux deux services.

Cet exemple se focalise sur une définition partielle du package


« pkg_service_expl » et du package « pkg_service_maint », en supposant
que l’analyse des règles de gestion de l’entreprise, pour ce qui concerne la
confidentialité, a aussi fait ressortir ce qui suit :
• Le service exploitation doit être le seul à avoir le droit de créer un
vol, de supprimer un vol, de changer le pilote d’un vol, de modifier
le trajet d’un vol ou de modifier les horaires d’un vol
• Le service maintenance doit être le seul à avoir le droit de remplacer
l’avion utilisé dans un vol par un autre avion.

Nous supposons aussi que le développeur manipule la base de données de


l’application en s’appuyant sur un schéma externe défini en termes de vues
par l’administrateur de la base de données.

Les ordres SQL de création de la table « Vols », de la vue « V_Vols » dérivée


de cette table et de la séquence « SEQ_NoVol » qui permet de générer les
numéros de vols peuvent se définir comme suit :
CREATE TABLE Vols
(NoVol NUMBER (2) PRIMARY KEY,
NoPilote NUMBER (3) DEFAULT NULL,
NoAvion NUMBER (5) DEFAULT NULL,
VD VARCHAR2 (20) DEFAULT NULL,
VA VARCHAR2 (20) DEFAULT NULL,
HD DATE DEFAULT NULL,
HA DATE DEFAULT NULL,
CONSTRAINT fk_Pilote FOREIGN KEY (NoPilote)
REFERENCES Pilotes (NoPilote)
ON DELETE SET NULL,
CONSTRAINT fk_Avion FOREIGN KEY (NoAvion)
REFERENCES Avions (NoAvion)
ON DELETE SET NULL,
CHECK (HA  HD));

291
Joachim TANKOANO

CREATE SEQUENCE SEQ_NoVol


INCREMENT BY 1
START WITH 1
MAXVALUE 1000
NOCYCLE
NOCACHE;
CREATE OR REPLACE VIEW V_Vols AS SELECT * FROM Vols;

Si on ne tient pas compte des autres fonctionnalités accessibles qu’au


service exploitation, la création de la spécification et du corps du package
« pkg_service_expl » peut se faire comme suit :
-- Ordre SQL de création de la spécification du package « pkg_service_expl » qui
-- définit la signature des procédures et fonctions métier, accessibles que par les
-- employés du service exploitation
CREATE OR REPLACE PACKAGE pkg_service_expl
AS
PROCEDURE CreerVol (
P_NoP NUMBER,
P_NoAvion NUMBER,
P_VD VARCHAR2,
P_VA VARCHAR2,
P_HD DATE,
P_HA DATE);
PROCEDURE SupprimerVol (P_NoV NUMBER);
PROCEDURE ChangerPilote (P_NoV NUMBER, P_NoP NUMBER) ;
PROCEDURE ModifierTrajet (
P_NoV NUMBER,
P_VD VARCHAR2,
P_VA VARCHAR2) ;
PROCEDURE ModifierHoraire (
P_NoV NUMBER,
P_HD VARCHAR2,
P_HA VARCHAR2) ;
END pkg_service_expl;
-- Ordre SQL de création du corps du package « pkg_service_expl » qui
-- implémente les procédures et fonctions métier, accessibles que par les employés

292
SGBD relationnels – Tome 1, État de l’art

-- du service exploitation
CREATE OR REPLACE PACKAGE BODY pkg_service_expl
AS
PROCEDURE CreerVol (
P_NoP NUMBER,
P_NoAvion NUMBER,
P_VD VARCHAR2,
P_VA VARCHAR2,
P_HD DATE,
P_HA DATE)
IS
BEGIN
INSERT INTO V_Vols
VALUES (SEQ_NoVol.NEXTVAL, P_NoP, P_NoAvion, P_VD, P_VA,
P_HD, P_HA);
END;
PROCEDURE SupprimerVol (P_NoV NUMBER)
IS
BEGIN
DELETE FROM V_Vols v WHERE v.NoVol = P_NoV;
END;
PROCEDURE ChangerPilote (P_NoV NUMBER, P_NoP NUMBER)
IS
BEGIN
UPDATE V_Vols v SET v.NoPilote = P_NoP
WHERE v.NoVol = P_NoV;
END;
PROCEDURE ModifierTrajet (
P_NoV NUMBER,
P_VD VARCHAR2,
P_VA VARCHAR2)
IS
BEGIN
UPDATE V_Vols v SET v.VD = P_VD, v.VA = P_VA
WHERE v.NoVol = P_NoV;
END;

293
Joachim TANKOANO

PROCEDURE ModifierHoraire (
P_NoV NUMBER,
P_HD VARCHAR2,
P_HA VARCHAR2)
IS
BEGIN
UPDATE V_Vols v SET v.HD = P_HD, v.HA = P_HA
WHERE v.NoVol = P_NoV;
END;
END pkg_service_expl;

De même, si on ne tient pas compte des autres fonctionnalités accessibles


qu’au service maintenance, la création de la spécification et du corps du
package « pkg_service_maintence » peut se faire comme suit :
-- Ordre SQL de création de la spécification du package « pkg_service_maintenance »
-- qui définit la signature des procédures et fonctions métier, accessibles que par les
-- employés du service maintenance
CREATE OR REPLACE PACKAGE pkg_service_maintenance
AS
PROCEDURE RemplacerAvion (
P_NoV NUMBER,
P_NoAvion NUMBER);
END pkg_service_maintenance;
-- Ordre SQL de création du corps du package « pkg_service_maintenance » qui
-- implémente les procédures et fonctions accessibles métier, que par les employés
-- du service maintenance
CREATE OR REPLACE PACKAGE BODY pkg_service_maintenance
AS
PROCEDURE RemplacerAvion (
P_NoV NUMBER,
P_NoAvion NUMBER)
IS
BEGIN
UPDATE V_Vols v SET v.NoAvion = P_NoAvion
WHERE v.NoVol = P_NoV;
END;
END pkg_service_maintenance;

Sur la base de cette architecture applicative, la prise en compte des règles

294
SGBD relationnels – Tome 1, État de l’art

de gestion de l’entreprise relatives aux autorisations d’accès aux


fonctionnalités de l’application peut se faire simplement :
• En créant les rôles « EmplExploitation » et « EmplMaintenance »
• En accordent le privilège « EXECUTE » sur le package
« pkg_service_expl » au rôle « EmplExploitation » et le privilège
« EXECUTE » sur le package « pkg_service_maintenance » au rôle
« EmplMaintenance ».

Ceci rend les fonctionnalités encapsulées dans le package


« pkg_service_expl » accessibles qu’aux employés du service exploitation
attributaires du rôle « EmplExploitation » et les fonctionnalités
encapsulées dans le package « pkg_service_maintenance » accessibles
qu’aux employés du service maintenance attributaires du rôle
« EmplMaintenance ».

Les ordres SQL requis se définissent comme suit :


-- Création des rôles
CREATE ROLE EmplExploitation;
CREATE ROLE EmplMaintenance;
-- Attribution de privilèges aux rôles créés
GRANT EXECUTE ON jt.pkg_service_expl TO EmplExploitation;
GRANT EXECUTE ON jt.pkg_service_maintenance TO EmplMaintenance;

5.4.3. Les packages offerts par Oracle

Oracle offre au développeur de multiples packages PL/SQL. Ces packages


étendent le modèle relationnel et les fonctionnalités du SGBD Oracle afin
de répondre à divers besoins spécifiques : développement d’applications
web, gestion des bases de données répliquées, gestion des communications
asynchrones entre processus et serveurs faiblement couplés à l’aide de files
d’attente de messages, intégration SQL / XML, extension du modèle
relationnel aux bases de données spatiales, extension du modèle relationnel
aux bases de données décisionnelles, etc...

Ces packages PL/SQL sont livrés soit avec « Oracle Database Server », soit
avec « Oracle Developer », soit avec « Oracle Application server ».

295
Joachim TANKOANO

À titre d’exemples, on peut citer les quelques packages qui suivent qui
visent à faciliter le développement d’applications web :
• « APEX_CUSTOM_AUTH » : pour l’authentification et la gestion
des sessions ;
• « APEX_APPLICATION » : pour l’accès aux variables globales ;
• « APEX_ITEM » : pour la création de composants de formulaires ;
• « APEX_UTIL » : pour l’accès à l’état d’une session, aux
autorisations accordées à un utilisateur, …
• « HTF et HTP » : pour la génération d’un document HTML ;
• « DBMS_OUTPUT » : pour l’affichage de messages notamment lors
de la mise au point d’une unité de traitement.

5.5. Les déclencheurs (ou triggers)


Un déclencheur est une unité de traitement PL/SQL compilée et stockée
dans une base de données, comme les procédures et fonctions stockées,
mais rattaché à l’item sur lequel il est applicable. L’item sur lequel est
applicable un déclencheur peut être une table, une vue, un schéma ou la
base de données dans sa totalité. L’exécution d’un déclencheur ne peut être
provoquée que de façon automatique, lorsque survient un événement
concernant l’item auquel il rattaché.

Les déclencheurs rendent donc le SGBD réactif à l’occurrence d’évènements


prédéfinis. Cette fonctionnalité peut être exploitée pour :
• Répercuter l’effet d’une mise à jour d’une table sur d’autres tables
• Traiter des ordres SQL (« INSERT », « UPDATE » et « DELETE »)
formulés en utilisant des vues dérivées de plusieurs tables
• Vérifier qu’une mise à jour de table ne viole pas une contrainte
d’intégrité
• Implémenter un mécanisme d’archivage systématique
d’informations relatives aux accès à une base de données afin de
rendre possible la traçabilité et l’élaboration de statistiques

296
SGBD relationnels – Tome 1, État de l’art

• Contrôler les accès par rapport à des critères très spécifiques


comme, par exemple, interdire des accès à des heures données
• Générer des alertes
• Générer des informations sur un évènement relatif à un utilisateur,
à l’exécution d’un ordre SQL ou d’une commande de service,
notamment dans le cadre de la gestion des communications
asynchrones entre processus et serveurs faiblement couplés à l’aide
de files d’attente de messages.

Un déclencheur applicable à une table ou à une vue est un déclencheur du


langage de manipulation des données (« DML Triggers »). Son exécution
ne peut être provoquée que par des évènements d’insertion de l’ordre
« INSERT », de modification de l’ordre « UPDATE » ou de suppression de
l’ordre « DELETE » relatifs à cette table ou à cette vue.

Un déclencheur applicable à un schéma ou à une base de données est quant


à lui un déclencheur système. Son exécution ne peut être provoquée que
par un évènement relatif à l’exécution d’un ordre du langage de définition
de données (« CREATE », « ALTER », « DROP », « TRUNCATE »,
« RENAME », « GRANT », « REVOKE », etc.) ou d’une commande de
service (« STARTUP », « SHUTDOWN », « LOGON », « LOGOFF », etc.)
relatif à ce schéma ou à cette base de données.

À défaut de pouvoir appeler les déclencheurs qu’il a créés, le développeur


peut désactiver et réactiver un déclencheur.

L’ordre SQL de création d’un déclencheur précise notamment :


• Le nom de ce déclencheur
• L’item sur lequel il est applicable
• Le traitement qui doit être effectué
• Le ou les ordres SQL qui doivent provoquer l’exécution de ce
traitement (« CREATE », « INSERT », etc.)
• Le moment où l’exécution de ce traitement doit être déclenché :
avant (« BEFORE »), après (« AFTER ») ou en lieu et place
(« INSTEAD OF ») de l’ordre déclencheur

297
Joachim TANKOANO

• Le nombre de fois que le traitement doit être exécuté : une seule fois
pour chaque exécution de l’ordre déclencheur ou une fois pour
chaque ligne traitée par l’ordre déclencheur
• La condition qui doit éventuellement être vérifiée pour que le
déclenchement de l’exécution du traitement ait lieu.

Lorsque le traitement défini par un déclencheur doit être exécuté pour


chaque ligne traitée par l’ordre déclencheur, on peut faire référence dans ce
traitement aux valeurs des colonnes avant et après modification de la ligne
en cours de traitement. Pour ce faire, la colonne référencée doit être préfixée
de « :OLD. » lorsqu’il s’agit de l’ancienne valeur et de « :NEW. » lorsqu’il
s’agit de la nouvelle valeur. Si l’ordre déclencheur est « INSERT », la valeur
avant de chaque colonne est considérée comme étant NULL. De même si
l’ordre déclencheur est « DELETE », la valeur après de chaque colonne est
considérée également comme étant NULL. Ceci rend possible la gestion des
contraintes d’intégrité basées sur des règles comportementales relatives à
l’évolution de la valeur d’une colonne.

La syntaxe simplifiée de l’ordre SQL de création et de modification d’un


déclencheur peut être définie comme suit :
CREATE [OR REPLACE] TRIGGER CodeTrigger ;
CodeTrigger ::= [NomSchéma.]NomTrigger
{TriggerLMDSimpe |TriggerLMDInsteadOf |TriggerSystème}
TriggerLMDSimpe ::= {BEFORE | AFTER}
{INSERT | {UPDATE [OF ListeColonnes]}
| DELETE} [OR..n]
ON [NomSchéma.]{NomTable | NomVue}
[FOR EACH ROW]
[ENABLE | DISABLE]
[WHEN (Condition)]
TraitementTrigger
TriggerLMDInsteadOf ::= INSTEAD OF
{INSERT|UPDATE|DELETE} [OR..n]
ON [NomSchéma.]NomVue
[FOR EACH ROW]
[ENABLE | DISABLE]
[WHEN (Condition)]
TraitementTrigger

298
SGBD relationnels – Tome 1, État de l’art

TriggerSystème ::= {BEFORE | AFTER | {INSTEAD OF}}


{OrdresLDD | EvènementCommandesDeServiceBD}
ON {[NomSchéma.]SCHEMA | DATABASE}
TraitementTrigger
OrdresLDD ::= {ALTER|CREATE|DROP|GRANT|REVOKE
|TRUNCATE}
EvènementCommandesDeServiceBD ::= {STARTUP|SHUTDOWN|LOGON
|LOGOFF | SERVERERROR}
TraitementTrigger ::= [DECLARE DéclarationsBlocPL_SQL]
BEGIN CorpsBlocPL_SQL
[EXCEPTION CodeTraitementErreursBlocPL_SQL]
END ;

Contrairement aux spécifications de la norme, le paramètre « Condition » de


la clause « WHEN » ne peut pas contenir une sous-requête ou un appel de
fonction stockée. En outre, pour ce qui concerne les déclencheurs de
manipulation des données (« DML Triggers »), l’accès en lecture ou en
mise à jour à la table sur laquelle s’applique un déclencheur n’est pas
autorisée dans le corps de ce déclencheur. Ceci a comme conséquence une
limitation des possibilités offertes notamment pour le maintien d’une
contrainte d’intégrité liée à cette table.
Exemple 5.5.i : Cet exemple montre comment les déclencheurs peuvent être
utilisés pour la collecte systématique d’informations relatives aux accès à
une table. L’ordre SQL qui suit crée la table « Tab_log_maj_sal_empl »
dont le rôle est de servir à l‘enregistrement des informations ci-après,
relatives aux opérations qui modifient le salaire d’un employé : le matricule
de l’employé concerné, la date de l’opération, le montant du salaire avant
modification, le montant du salaire après modification, le type d’opération.
CREATE TABLE Tab_log_maj_sal_empl
(Matricule NUMBER (6),
Date_op DATE,
Sal_anc NUMBER (8,2),
Sal_nouv NUMBER (8,2),
Op VARCHAR2 (15),
CONSTRAINT pk PRIMARY KEY (Matricule, Date_op)) ;

L’ordre SQL qui suit crée le déclencheur « Tr_log_maj_sal_empl » qui


assure l’enregistrement de ces informations dans la table

299
Joachim TANKOANO

« Tab_log_maj_sal_empl » chaque fois qu’une modification est apportée


au salaire d’un employé. Ce déclencheur est exécuté pour chaque ligne
affectée lors de l’exécution de tout ordre « INSERT », « DELETE » ou
« UPDATE du salaire » portant sur la table « Tab_employes ».
CREATE OR REPLACE TRIGGER Tr_log_maj_sal_empl
AFTER INSERT OR DELETE OR UPDATE OF salaire
ON Tab_employes
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO T_log_maj_sal_empl (Matricule, Date_op, Sal_anc,
Sal_nouv, op)
VALUES (:NEW.matricule, SYSDATE, NULL, :NEW.salaire,
'INSERT');
ELSIF DELETING THEN
INSERT INTO T_log_maj_sal_empl (Matricule, Date_op, Sal_anc,
Sal_nouv, op)
VALUES (:OLD.matricule, SYSDATE, :OLD.salaire, NULL,
'DELETE');
ELSIF UPDATING THEN
INSERT INTO T_log_maj_sal_empl (Matricule, Date_op, Sal_anc,
Sal_nouv, op)
VALUES (:OLD.matricule, SYSDATE, :OLD.salaire, :NEW.salaire,
'UPDATE');
END IF ;
END;

300
SGBD relationnels – Tome 1, État de l’art

BIBLIOGRAPHIE
ANSI-ISO: ISO/IEC JTC 1/SC 32 Data management and interchange: ISO/IEC
9075:1992 Information technology — Database languages — SQL - Nov.
1992

Cocherane R., Pirahesh H. & Mattos N.: Integrating triggers and Declarative
Constraints in SQL Database Systems - Proc. 16th Intl. Conf. on Very
Large Data Bases, Brisbane, Australia, Morgan Kaufman Ed., p. 566-
577,

Date C.J. & Darwen G.: A Guide to the SQL Standard, 4th edition - Addison
Wesley (1997).

Melton J. & Simon A. R.: Understanding the New SQL: A Complete Guide -
Morgan Kaufmann (1993).

ORACLE: Oracle® Database Advanced Application Developer’s Guide 11g


Release 2 (11.2) -December 2014

ORACLE: Oracle® Database Database PL/SQL Language Reference 12c Release


2 (12.2) - December 2017

ORACLE: Oracle® Database Development Guide 12c Release 1 (12.1) - May


2016

ORACLE: Oracle® Database PL/SQL Packages and Types Reference 12c Release
1 (12.1) - September 2016

Silberschatz A., Korth H.F. & Sudarshan S.: Database system concepts, sixth
edition - McGraw-Hill, 2011

301
Chapitre 6. : Connecteurs des bases de
données

6.1. Introduction
Comme nous venons de le voir dans le chapitre 5, la principale finalité de
l’approche qui consiste à étendre le langage SQL pour en faire un langage
complet de programmation est : aboutir à un langage qui permet l’écriture
d’unités de traitement pouvant s’intégrer dans une base de données où le
développeur peut la manipuler de la façon la plus simple et directe possible
à l’aide d’instructions intégrées nativement dans ce langage (soumission de
l’exécution de requêtes SQL au moteur SQL et récupération du résultat
pour traitement). Comme nous l’avons vu, ceci permet de répondre à de
multiples besoins spécifiques essentiels.

Le présent chapitre aborde une approche complémentaire. La finalité de


cette approche est de permettre au développeur de manipuler une base de
données (c’est-à-dire, soumission de l’exécution de requêtes SQL au moteur
SQL et récupération du résultat pour traitement), à partir de programmes
écrits à l’aide de langages de programmation existants comme Cobol, C,
C++, Java, PHP, JavaScript, etc…, s’exécutant principalement au niveau des
postes clients ou des serveurs d’application.

Par rapport à celle que nous avons étudiée dans le chapitre 5, cette approche
à l’avantage d’offrir au développeur d’une application à la fois une liberté
dans le choix du langage de programmation, une indépendance de ses
programmes par rapport à l’offre technologique utilisée pour
l’implémentation de la base de données et une transparence par rapport à
la localisation de cette base de données qui peut être locale ou distante. En
somme, il s’agit d’une approche qui facilite la portabilité des applications.
En contrepartie, cette approche rend plus complexe la manipulation d’une
base de données à l’intérieur d’un programme.

Cette approche complémentaire repose sur les connecteurs de bases de


données.
SGBD relationnels – Tome 1, État de l’art

Un connecteur de bases de données est un middleware, c’est-à-dire, un


logiciel qui en s’intercalant entre les applications et les serveurs de base de
données offre aux développeurs qui écrivent des programmes applicatifs à
l’aide d’un langage existant (Cobol, C, Java, JSP, JSF, ASP, PHP, etc.) l’API
dont ils ont besoin pour :
• Se connecter à une base de données locale ou distante
• Soumettre à cette base de données l’exécution de requêtes SQL
• Récupérer en retour le résultat de l’exécution de ces requêtes pour
traitement
• Fermer la connexion à la base de données.

La base de données concernée peut être une base de données relationnelle,


objet-relationnelle (voir chapitre 12) ou XML (voir chapitre 13).

L’accès à ces fonctionnalités se fait par appel de fonctions ou de méthodes


de l’API, notamment en passant en paramètre à ces fonctions ou méthodes
une requête SQL à exécuter. Dans cette approche, les ordres SQL ne sont
donc pas compilés et intégrés dans le code exécutable du programme d’où
ils sont soumis pour exécution. Ces ordres SQL sont construits et soumis
pour exécution dynamiquement lors d’appels de fonctions ou de méthodes
de l’API en tant que paramètres sans avoir été préalablement compilés. On
parle d’ordres SQL dynamiques par opposition aux instructions PL/SQL
(« SELECT », « INSERT », « UPDATE » et « DELETE ») de manipulation
d’une base de données qui exécutent des ordres SQL statiques.

La très grande diversité des fonctions ou méthodes de l’API offert par les
connecteurs de base de données, induit une très grande complexité dans la
manipulation d’une base de données à l’intérieur d’un programme. Les
fournisseurs de technologies parviennent à réduire cette complexité grâce
aux approches ci-après :
• L’encapsulation de l’API du connecteur à l’aide d’une API de plus
haut niveau, offrant au développeur une interface plus simple pour
la manipulation d’une base de données, y compris la possibilité de
manipuler une base de données relationnelle comme s’il s’agissait
d’une base de données objets

303
Joachim TANKOANO

• L’encapsulation des ordres SQL dans des pseudo-instructions


intégrées dans le langage hôte comme une extension de celui-ci.
Cette approche qui a débuté avec des langages comme C (Pro*C),
Cobol (Pro*Cobol) et Fortran (Pro*Fortran) est aujourd’hui très
répandue. On parle d’ordres SQL intégrés (« embedded SQL »).
Dans cette approche, ces ordres doivent être traités par un pré-
compilateur chargé de les traduire en appels de fonctions ou de
méthodes fournis par un connecteur à travers son API. Du point de
vue du développeur, cette approche permet de lui offrir des ordres
de manipulation d’une base de données comparables, en termes de
complexité, à ceux que proposent les langages de programmation
qui étendent SQL en langage complet de programmation.

En outre, cette approche complémentaire à l’extension du langage SQL en


langage complet de programmation induit des difficultés
d’implémentation, liées principalement :
• Aux différences qui existent entre les types supportés par SQL et les
types supportés par les langages hôtes (c’est-à-dire aux
« CONFLITS D’IMPEDANCE ») qui font qu’il n’est pas toujours
possible de définir une correspondance simple, uniformisée et
directe entre ces types
• À l’absence du concept de valeur NULL dans plusieurs langages
hôtes.

Comme souligné dans la section 5.1, les connecteurs de bases de données


font partie des fondations du paradigme architectural des applications
modernes d’entreprises. De ce fait, bien que dans la pratique l’accès direct
aux fonctionnalités des connecteurs de bases de données par les
développeurs soit rare, il est essentiel que ces derniers comprennent leur
fonctionnement.

On distingue les deux grandes familles de connecteurs de bases de données


ci-après, qui s’appuient toutes les deux sur la spécification « CLI (Call Level
Interface) » de x/open SQL Group et qui sont de ce fait des standards
universels, ouverts et indépendants, permettant d’accéder à une gamme
variée de SGBD :

304
SGBD relationnels – Tome 1, État de l’art

• « ODBC (Open Database Connectivity) »


• « JDBC (Java Database Connectivity) ».

Ce qui suit présente succinctement ces deux familles de connecteurs.

6.2. Le connecteur ODBC


Il s’agit du connecteur le plus ancien et le plus répandu. Il est supporté par
un grand nombre de langages de programmation et de SGBD.

La principale particularité de ce connecteur est qu’il nécessite un


déploiement spécifique sur les postes clients et sur les serveurs de bases de
données, où le gestionnaire de pilotes et les pilotes doivent être
correctement installés et configurés à l’aide d’outils de configuration
intégrés au système d’exploitation hôte.

ODBC a été développé au départ par Microsoft comme étant une


technologie spécifique au monde WINDOWS, ce qui posait un problème de
portabilité des applications qui utilisaient cette technologie. Aujourd’hui,
cet inconvénient majeur a été corrigé par une offre ODBC mature pour les
environnements UNIX/LINUX.

La faiblesse actuelle du connecteur ODBC est la très grande diversité de ses


fonctionnalités de bas niveau. Pour pallier cette contrainte, le connecteur
orienté objet ADO (Active X Data Objects), développé au-dessus d’ODBC
pour l’environnement WINDOWS, offre une API plus simple d’utilisation.

Ce qui suit présente l’architecture du connecteur ODBC et quelques-unes


de ses fonctionnalités.

6.2.1. L’architecture du connecteur ODBC

L’architecture du connecteur ODBC est constituée de 4 grandes parties :


• L’API qui permet aux applications d’accéder aux fonctionnalités
offertes par le connecteur pour :
o Se connecter à une source

305
Joachim TANKOANO

o Soumettre l’exécution d’une requête SQL à cette source et


récupérer en retour le résultat pour traitement
o Se déconnecter de cette source
• Un ensemble de pilotes où chaque pilote assure une implémentation
de l’API du connecteur afin de permettre l’accès à des sources ayant
des caractéristiques particulières en offrant deux grandes
fonctionnalités : (1) la conversion lorsque cela est nécessaire de la
syntaxe des ordres SQL pour l’adapter à la syntaxe supportée par les
sources concernées, (2) le traitement des appels de fonctions de l’API
effectués par l’application en gérant les échanges avec la source
concernée
• Un gestionnaire des pilotes qui :
o Fournit à l’application des informations utiles comme la liste
des pilotes installés sur le poste où elle s’exécute
o Assure le chargement automatique du pilote requis par la
source concernée
o Transmet les requêtes de l’application à ce pilote et retourne
à l’application les résultats des traitements effectués par ce
pilote et la source concernée
• Un ensemble de sources de données où chaque source est constituée
d’un moteur SQL et d’une base de données.

Ce qui suit schématise cette architecture.

APPLICATION
API ODBC

GESTIONNAIRE DES PILOTES

Pilote ODBC ---- Pilote ODBC --- Pilote ODBC


-

Source de Source de Source de


données données données

306
SGBD relationnels – Tome 1, État de l’art

Cette architecture rend chaque application indépendante de la source avec


laquelle elle interagit. À tout moment, cette source peut être remplacée en
fonction des besoins par une autre située à un emplacement local ou distant
diffèrent dont le fournisseur peut aussi être diffèrent sans que ceci ne
nécessite une modification de l’application.

6.2.2. Quelques exemples de fonctionnalités de l’API ODBC


du langage C

a) Exemples d’équivalence entre les types du langage C et les types


ODBC

Le tableau qui suit donne à titre d’exemples quelques équivalences entre les
types ODBC et les types du langage C.

Constantes servant à Type ODBC Type en C


identifier les types
ODBC

SQL_C_CHAR SQLCHAR * unsigned char *


SQL_C_SSHORT SQLSMALLINT short int
SQL_C_USHORT SQLUSMALLINT unsigned short int
SQL_C_SLONG SQLINTEGER long int
SQL_C_FLOAT SQLREAL float
SQL_C_DOUBLE SQLDOUBLE, double
SQLFLOAT
SQL_C_BINARY SQLCHAR * unsigned char
SQL_C_TYPE_DATE SQL_DATE_STRUCT struct tagDATE_STRUCT {
SQLSMALLINT year;
SQLUSMALLINT month;
SQLUSMALLINT day;
}

b) Les structures de données internes du connecteur ODBC (les


« HANDLES »)

Pour fonctionner, le connecteur ODBC a besoin de ressources constituées


des zones de travail ci-après appelées « HANDLES » que le développeur
doit allouer dans son programme :

307
Joachim TANKOANO

• Un « HANDLE environnement » : cette zone de travail sert à la


mémorisation des informations relatives à la source et au pilote. Elle
a comme type prédéfini « SQLHENV » identifié par la constante
« SQL_HANDLE_ENV »
• Un « HANDLE connexion » : cette zone de travail sert à la
mémorisation des informations relatives à la connexion utilisée par
la session en cours pour accéder à la source. Elle a comme type
prédéfini « SQLHDBC » identifié par la constante
« SQL_HANDLE_DBC »
• Un « HANDLE ordre (statement) » : cette zone de travail est utilisée
pour l’exécution des ordres SQL. Elle a comme type prédéfini
« SQLHSTMT » identifié par la constante
« SQL_HANDLE_STMT »
• Un « HANDLE descripteur » : cette zone de travail a comme type
prédéfini « SQLHDESC » identifié par la constante
« SQL_HANDLE_DESC ».

Sur certains points, ces zones de travail sont comparables aux curseurs
PL/SQL.

c) Les fonctions pour l’allocation et la désallocation des HANDLES

La signature de la fonction qui permet au développeur d’allouer une zone


de travail, c’est-à-dire, un « HANDLE » est la suivante :
SQLRETURN SQLAllocHandle (SQLSMALLINT HandleType,
SQLHANDLE InputHandle,
SQLHANDLE *OutputHandlePtr);
Où :

• Le paramètre « HandleType » doit être la constante qui identifie le type


prédéfini du « HANDLE » dont la création est demandée

• Le paramètre « InputHandle » doit être une variable qui correspond à


une zone de travail contenant les informations requises pour la création
de ce « HANDLE »

308
SGBD relationnels – Tome 1, État de l’art

• Le paramètre « *OutputHandlePtr » doit être la variable qui pointera


vers la zone de travail, c’est-à-dire, le « HANDLE » après sa création.

Exemple 6.2.2.i : Ce qui suit donne des exemples d’allocation de différents


types de « HANDLES ».
• Allocation d’un « HANDLE environnement » dans la variable
« V_Env » de type « SQLHENV » :
V_err = SQLAllocHandle (SQL_HANDLE_ENV, SQL_NULL_HANDLE,
&V_Env);

• Allocation d’un « HANDLE connexion » dans la variable


« V_hdbc » de type « SQLHDBC » en utilisant un « HANDLE
environnement » contenu dans la variable « V_Env » :
V_err = SQLAllocHandle (SQL_HANDLE_DBC, V_Env, &V_hdbc);

• Allocation d’un « HANDLE ordre (statement) » dans la variable


« V_stmt » de type « SQLHSTMT » en utilisant un « HANDLE
connexion » contenu dans la variable « V_hdbc » :
V_err = SQLAllocHandle (SQL_HANDLE_STMT, V_hdbc, &V_stmt);

Pour désallouer une zone de travail, c’est-à-dire, un « HANDLE », le


développeur doit utiliser la fonction dont la signature est définie comme
suit :
SQLRETURN SQLFreeHandle (SQLSMALLINT HandleType,
SQLHANDLE InputHandle);
Où :

• Le paramètre « HandleType » doit être la constante qui identifie le type


prédéfini du « HANDLE » qui doit être désallouer

• Le paramètre « InputHandle » doit être une variable qui correspond à


la zone de travail à désallouer.

Exemple 6.2.2.ii : Cet exemple effectue la désallocation du « HANDLE


environnement » qui se trouve dans la variable « V_Env » de type
« SQLHENV » :

309
Joachim TANKOANO

V_err = SQLFreeHandle (SQL_HANDLE_ENV, V_Env);


d) La fonction pour la modification des attributs d’un HANDLE

La signature de la fonction qui permet de modifier les attributs d’un


« HANDLE » est la suivante :
SQLRETURN SQLSetEnvAttr (SQLHENV EnvironmentHandle,
SQLINTEGER Attribute,
SQLPOINTER ValuePtr,
SQLINTEGER StringLength);
Où :

• Le paramètre « EnvironmentHandle » doit être une variable de type


« SQLHENV »

• Le paramètre « Attribute » doit être une constante qui identifie l’attribut


à modifier

• Le paramètre « ValuePtr » doit être la nouvelle valeur de l’attribut à


modifier.

Exemple 6.1.3.iii : Cet exemple définit la version du pilote ODBC à utiliser.


V_err = SQLSetEnvAttr (V_Env, SQL_ATTR_ODBC_VERSION,
(void*) SQL_OV_ODBC3, 0) ;

e) Les fonctions simplifiées pour la connexion et la déconnexion à une


source de données

La signature de la fonction simplifiée qui permet de se connecter à une


source est la suivante :
SQLRETURN SQLConnect (SQLHDBC ConnectionHandle,
SQLCHAR *ServerName, SQLSMALLINT NameLength1,
SQLCHAR *UserName, SQLSMALLINT NameLength2,
SQLCHAR *Authentication, SQLSMALLINT NameLength3);

Exemple 6.2.2.iv : Dans cet exemple « SQL_NTS » indique que la longueur


des champs concernés doit être déterminé par « SQLConnect ».

310
SGBD relationnels – Tome 1, État de l’art

V_err = SQLConnect (V_hdbc,


(SQLCHAR*) "BD_GesPers", SQL_NTS,
(SQLCHAR*) "alain", SQL_NTS,
(SQLCHAR*) "", SQL_NTS);

Quant à la signature de la fonction simplifiée qui permet de se déconnecter


à une source, elle est la suivante :
SQLRETURN SQLDisconnect (SQLHDBC ConnectionHandle) ;

Exemple 6.2.2.v : L’exemple qui suit déconnecte l’utilisateur connecté.


V_err = SQLDisconnect (V_hdbc);

f) La Fonction pour la soumission de l’exécution d’un ordre SQL à une


source de données

La signature de la fonction que le développeur doit appeler pour soumettre


l’exécution d’un ordre SQL à une source est la suivante :
SQLRETURN SQLExecDirect (SQLHSTMT StatementHandle,
SQLCHAR *StatementText,
SQLINTEGER TextLength);
Où :

• Le paramètre « StatementHandle » doit être une variable de type


« SQLHSTMT » allouée en fournissant en paramètre la variable de type
« SQLHDBC » spécifiée lors de la connexion à la source

• Le paramètre « *StatementText » doit contenir le texte de l’ordre SQL à


exécuter

• Le paramètre « TextLength » doit indiquer la taille de la zone mémoire


qui contient l’ordre SQL à exécuter.

Lorsque l’ordre SQL passé en paramètre est un ordre « SELECT », le


résultat est retourné dans un « result set » que le développeur peut
parcourir à l’aide de la fonction « SQLFETCH ».

Exemple 6.2.2.vi : L’exemple qui suit exécute un ordre « SELECT » qui


retourne les noms des employés contenus dans la table « Tab_Employes ».

311
Joachim TANKOANO

V_err = SQLExecDirect (V_hstmt, "SELECT Nom FROM Tab_Employes",


SQL_NTS);

g) La fonction pour l’établissement d’un lien entre une colonne du


résultat d’une requête et une variable du programme

Cette fonction permet de désigner la variable du programme qui doit


recevoir le contenu d’une colonne du résultat de l’exécution d’un ordre
« SELECT » lors du parcours de ce résultat à l’aide de la fonction
« SQLFETCH ». La signature de cette fonction est la suivante :
SQLRETURN SQLBindCol (SQLHSTMT StatementHandle,
SQLUSMALLINT ColumnNumber,
SQLSMALLINT TargetType,
SQLPOINTER TargetValuePtr,
SQLINTEGER BufferLength,
SQLINTEGER *StrLen_or_IndPtr);

Exemple 6.2.2.vii : L’appel de fonction qui suit indique que lors d’un appel
de la fonction « SQLFETCH », la 1ère colonne de la ligne courante du
résultat est une chaîne de caractères qui doit être placée dans la variable
« V_buffer » de 20 caractères de long.
V_err = SQLBindCol (V_hstmt, 1, SQL_C_CHAR, &V_buffer, 20, &V_errx);

h) La fonction pour obtenir le nombre de lignes traitées par un ordre SQL

Cette fonction retourne le nombre de lignes traitées au cours de l’exécution


d’un ordre SQL. Sa signature est la suivante :
QLRETURN SQLRowCount (SQLHSTMT StatementHandle,
SQLLEN *RowCountPtr) ;

i) La fonction pour obtenir le nombre de colonnes dans le « result set »

Cette fonction retourne le nombre de colonnes qui existent dans le résultat


de l’exécution d’un ordre « SELECT ». Sa signature est la suivante :

312
SGBD relationnels – Tome 1, État de l’art

SQLRETURN SQLNumResultCols (SQLHSTMT StatementHandle,


SQLSMALLINT *ColumnCountPtr) ;

j) La Fonction pour parcourir un « result set »

Cette fonction permet de parcourir le résultat de l’exécution d’un ordre


« SELECT » afin de traiter ce résultat ligne par ligne. Chaque exécution de
cette fonction place le contenu de chaque colonne de la ligne suivante dans
la zone mémoire désignée à l’aide de la fonction « SQLBindCol ». Sa
signature est la suivante :
SQLRETURN SQLFetch (SQLHSTMT StatementHandle) ;

6.2.3. Un exemple de programme

Cet exemple illustre l’utilisation des fonctions présentées dans le


paragraphe précédent.
#include <stdlib.h>
#include <stdio.h>
#include <odbc/sql.h>
#include <odbc/sqlext.h>
#include <odbc/sqltypes.h>
SQLHENV V_Env;
SQLHDBC V_hdbc;
SQLHSTMT V_hstmt;
SQLINTEGER V_err, V_NbLignes ;
SQLSMALLINT V_NbColonnes, V_NoAvion ;
char V_ msg[200], V_ NomAvion[20] ;
int main (int argc, char *argv [])
{
// 1. Allocation du handle Environnement
SQLAllocHandle (SQL_HANDLE_ENV, SQL_NULL_HANDLE,
&V_Env);
SQLSetEnvAttr (V_Env, SQL_ATTR_ODBC_VERSION,
(void*) SQL_OV_ODBC3, 0);
// 2. Allocation du handle connexion
SQLAllocHandle (SQL_HANDLE_DBC, V_Env, &V_hdbc);

313
Joachim TANKOANO

// 3. Connection à la source de données « web »


SQLConnect (V_hdbc, (SQLCHAR*) "web", SQL_NTS,
(SQLCHAR*) "alain", SQL_NTS,
(SQLCHAR*) "", SQL_NTS) ;
Printf (« Connexion réussie !\n ») ;
// 4. Allocation du handle ordre
SQLAllocHandle (SQL_HANDLE_STMT, V_hdbc, &V_hstmt);
SQLBindCol (V_hstmt, 1, SQL_C_SSHORT, &V_NoAvion, 20, &V_err);
SQLBindCol (V_hstmt, 2, SQL_C_CHAR, &V_NomAvion, 20, &V_err);
SQLExecDirect (V_hstmt, "SELECT NoAvion, NomAvion FROM
Avions", SQL_NTS);
SQLNumResultCols (V_hstmt, &V_NbColonnes);
printf ("Number of Columns %d\n", V_ NbColonnes);
SQLRowCount (V_OD_hstmt, &V_NbLignes);
printf ("Number of Rows %d\n", V_ NbLignes);
V_err = SQLFetch (V_hstmt);
while (V_err != SQL_NO_DATA)
{
Printf ("Result: %d %s\n", V_NoAvion,V_NomAvion);
V_ err = SQLFetch (V_hstmt);
} ;
SQLFreeHandle (SQL_HANDLE_STMT, V_hstmt);
SQLDisconnect (V_hdbc);
SQLFreeHandle (SQL_HANDLE_DBC, V_hdbc);
SQLFreeHandle (SQL_HANDLE_ENV, V_Env);
return (0) ;
}

6.3. Le connecteur JDBC


Le connecteur JDBC ne fonctionne qu’avec le langage multi-plateformes
JAVA. Les applications développées à l’aide de ce langage et de ce
connecteur peuvent s’exécuter dans différents environnements
(UNIX/LINUX, WINDOWS, etc.) en s’appuyant indifféremment sur
différentes offres technologiques de SGBD grâce à une gamme très variée
de pilotes. Ce connecteur permet donc de développer des applications de
manipulation d’une base de données qui respectent le paradigme de Java
« WRITE ONCE, RUN ANYWHERE ».

314
SGBD relationnels – Tome 1, État de l’art

Le connecteur JDBC offre une API orientée objet définie par les packages
« java.sql » et « javax.sql ». Ce connecteur est facile d’utilisation dans un
programme. Contrairement à ODBC, il ne nécessite aucune installation
préalable sur les postes clients. Le code requis est téléchargé
dynamiquement au moment de l’exécution à partir d’une source
centralisée. Il ne nécessite en outre aucune configuration préalable des
postes clients.

Par ailleurs, sur les postes clients, ce connecteur garantit un fonctionnement


sécurisé même lorsque l’accès se fait grâce à une applet. En effet, une applet
ne peut pas télécharger des pilotes non écrits en Java et ne peut
communiquer qu’avec la machine d’où elle provient.

Dans sa conception, JDBC est fortement inspiré de ODBC.

Ce qui suit présente :


• L’architecture de ce connecteur
• Les principales caractéristiques des moyens qu’il offre pour :
o Se connecter ou se déconnecter à une source de données
o Soumettre l’exécution d’un ordre SQL non paramétré à cette
source et récupérer en retour le résultat pour traitement
o Soumettre l’exécution d’un ordre SQL paramétré à cette
source et récupérer en retour le résultat pour traitement
o Soumettre l’exécution d’un appel de fonction ou de procédure
stockée à cette source.

6.3.1. L’architecture du connecteur JDBC

L’architecture du connecteur JDBC se schématise comme ci-dessous.

Tout comme le connecteur ODBC, JDBC distingue dans son architecture 4


grandes parties :
• L’API JDBC
• Les pilotes qui implémentent cette API pour différents types de
sources

315
Joachim TANKOANO

• Le gestionnaire de ces pilotes


• Les sources de données.

Application

API JDBC

GESTIONNAIRE DES PILOTES JDBC

Pilote Java Pilote Java Pilote Java Pilote Java


type 1 type 2 type 3 type 4

API ODBC API native API serveur

PILOTE PILOTE natif SERVEUR


ODBC Source de MIDDLEWARE
données

Source de Source de Source de Source de


données données données données

Les pilotes JDBC implémentent l’interface « java.sql.Driver » et sont classés


en 4 catégories :
• Les pilotes de type 1 : ces pilotes sont des pilotes passerelles qui
permettent d’accéder à une source par l’intermédiaire d’un pilote
d’une autre technologie (ODBC par exemple) en convertissant les
appels effectués à l’aide de l’API JDBC en appels effectués à l’aide de
l’API de ce pilote. En d’autres termes, l’implémentation de l’API
JDBC par ce type de pilotes assure le mapping de l’API JDBC sur
l’API d’une autre technologie
• Les pilotes de type 2 : ces pilotes permettent l’accès à une source par
l’intermédiaire d’un pilote natif développé par l’éditeur du SGBD
concerné en convertissant les appels effectués à l’aide de l’API JDBC
en appels effectués à l’aide de l’API de ce pilote natif

316
SGBD relationnels – Tome 1, État de l’art

• Les pilotes de type 3 : ces pilotes permettent l’accès à une source par
l’intermédiaire d’un serveur middleware de pilotes qui implémente
différents types de connecteurs
• Les pilotes de type 4 : ces pilotes permettent l’accès à une source en
exécutant directement les appels de l’API JDBC.

Dans un programme JAVA, l’API JDBC permet au développeur :


• D’établir et de fermer une connexion à une source de données
• De soumettre à cette source l’exécution d’ordres SQL
• De récupérer en retour pour traitement le résultat de l’exécution de
ces ordres SQL
• D’assurer, au cours d’une session utilisateur, le contrôle de cette
session et des transactions qui s’y déroulent (voir la section 9.3).

6.3.2. La connexion et la déconnexion à une source de données

Dans un programme Java, la création d’un objet « Connection » chargé à


travers un pilote JDBC de la gestion d’une connexion physique à une source
de données et de la coordination des traitements effectués grâce à cette
connexion physique se fait en même temps que la création de cette
connexion physique.

Pour créer une connexion physique à une source et un objet « Connection »


pour la gestion de cette connexion et des traitements effectués grâce à cette
connexion, le développeur peut, soit utiliser la classe non instanciable
« DriverManager », soit utiliser des pilotes qui implémentent l’interface
« DataSource ».

La création d’une connexion physique à une source et d’un objet


« Connection » à l’aide de la classe non instanciable « DriverManager » se
fait par appel de sa méthode « getConnection () » dont la signature est
définie comme suit :
Connection DriverManager.getConnection (String URL_BD,
String NomUtilisateur,
String MotDePasse) ;

317
Joachim TANKOANO

Le paramètre « URL_BD » a comme syntaxe :


jdbc:NomduProtocole:Paramètres

Comme dans l’exemple « jdbc:mysql://miage.ibam.uo.bf/Bdtest », ceci


permet de fournir à la classe « DriverManager » les informations requises
sur le pilote, sur le serveur de base de données et sur la base de données
elle-même. Les informations fournies par ce paramètre permettent à la
classe « DriverManger » de rechercher parmi les pilotes qu’elle gère celui
qu’il peut utiliser pour établir la connexion demandée. La spécification de
ces informations au niveau d’un programme réduit la portabilité de ce
programme qui ne peut s’exécuter avec des spécifications différentes
qu’après une maintenance. En d’autres termes, ces spécifications ne
garantissent pas une indépendance du programme par rapport au pilote,
au serveur qui héberge la base de données et à la base de données elle-
même.

Le recours à un pilote qui implémente l’interface « DataSource » pour la


création dans un programme d’une connexion à une source et d’un objet
« Connection », rend ce programme indépendant des spécifications
relatives au pilote, au serveur de base de données et à la base de données.
Ceci permet en outre au serveur d’application de gérer des pools de
connexions physiques à différentes sources, partageables par plusieurs
sessions afin de réduire la taille de la mémoire centrale allouée aux
connexions. Dans cette deuxième approche, les informations qui spécifient
le pilote et le pool de connexions physiques relatifs à une source de données
sont fournies et gérées à l’extérieur du programme. Cette approche est
utilisée principalement dans les environnements multi-tiers comme « Java
EE ».

Les pilotes JDBC qui implémentent l’interface « DataSource » exposent de


ce fait un ensemble de propriétés qui servent à l’identification et à la
description de la source avec laquelle ils interagissent, à savoir :
• La propriété « databaseName » : pour la spécification du nom de la
base de données avec laquelle le pilote peut interagir

318
SGBD relationnels – Tome 1, État de l’art

• La propriété « dataSourceName » : pour la spécification


éventuellement du nom de l’objet qui gère le pool de connexions
physiques à cette source
• La propriété « description » : pour fournir une description de la
source
• La propriété « networkProtocol » : pour la spécification du
protocole réseau utilisé pour communiquer avec la source
• La propriété « portNumber » : pour la spécification du port d’écoute
du serveur de données de la source
• La propriété « serverName » : pour la spécification du nom du
serveur de données de la source
• La propriété « user » : pour la spécification du nom utilisateur qui
doit être utilisé pour se connecter à la source
• La propriété « password » : pour la spécification du mot de passe à
utiliser pour se connecter à la source.

L’instanciation d’un pilote qui implémente l’interface « DataSource »,


suivie de la modification de ses propriétés et de son enregistrement en le
liant à un nom logique à l’aide de l’API « JNDI (Java Naming and Directory
Interface) » se font en dehors du programme, notamment en utilisant les
outils fournis à cet effet par le serveur d’application ou en fournissant au
serveur d’application les informations requises dans un fichier de
configuration.

Exemple 6.3.2.i : Cet exemple crée un objet pilote de la classe


« VendorDataSource » qui implémente l’interface « DataSource ». Il
initialise ensuite quelques propriétés de cet objet pilote avant de
l’enregistrer à l’aide de l’API JNDI en le liant à un nom logique d’objet
pilote « jdbc/BDStock ».
// Création d’un objet pilote VendorDataSource qui implémente l’interface
// DataSource
VendorDataSource vds = new VendorDataSource () ;
// Initialisation des propriétés de l’objet pilote
vds.setServerName ("mon_serveur_de_BD ") ;

319
Joachim TANKOANO

vds.setDatabaseName ("ma_BD") ;
vds.setDescription (« Source de données stock produits ») ;
// Utilisation de l’API JNDI pour enregistrer l’objet pilote VendorDataSource
// en le liant au nom logique « jdbc/BDStock »
Context ctx = new InitialContext ();
ctx.bind ("jdbc/BDStock", vds);

Dans une plateforme comme « Java EE », un objet pilote qui implémente


l’interface « DataSource », créé et enregistré de cette façon, peut ensuite être
récupéré dans un programme Java en appelant la méthode « lookup () » de
la classe « InitialContext » et en passant en paramètre de cette méthode le
nom logique de cet objet pilote. L’objet pilote récupéré peut ensuite
s’utiliser pour la création de la connexion spécifiée en son sein et pour la
création d’un objet « Connection » par appel de sa méthode
« getConnection () ». L’objet « Connection » créé de cette façon est
identique à celui qui peut être créé à l’aide de la méthode « getConnection
() » de la classe « DriverManager ». Ceci permet de rendre le programme
indépendant de l’objet pilote et des informations qu’il détient, qui peuvent
être modifiées sans que cela ne nécessite une maintenance de ce
programme.

Exemple 6.3.2.ii : Cet exemple crée un objet « Connection » en utilisant


l’objet pilote créé et enregistré dans l’exemple précédent 6.3.2.i.
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDStock");
Connection con = ds.getConnection ("user", "pwd") ;

L’appel de la méthode « close () » d’un objet « Connection » a comme effet


la suppression de la connexion physique établie avec la source lors de la
création de cet objet « Connection ».

Les traitements spécifiques effectués dans chaque session pour ouvrir,


initialiser et fermer une connexion physique avec une source peut avoir
comme conséquence une dégradation significative des performances
globales de l’application concernée, due au temps de traitement engendré
par l’exécution répétée de ces opérations et surtout à l’espace mémoire

320
SGBD relationnels – Tome 1, État de l’art

mobilisé par les connexions au niveau des sessions en cours.

Lorsque les pilotes utilisés implémentent l’interface


« ConnectionPoolDataSource », le serveur d’application peut s’appuyer
sur cette implémentation pour obtenir un objet « PooledConnection » qui
lui permet de partager entre plusieurs clients un pool de connexions
physiques avec une source, ceci de façon transparente pour ces clients.
Lorsqu’un client appelle la méthode « DataSource.getConnection () », le
serveur d’application se sert de cet objet « PooledConnection » pour créer
une connexion logique qui utilise une connexion physique du pool. Lorsque
le client ferme la connexion en appelant la méthode « Connection.close () »,
le serveur d’application ferme la connexion logique créée et retourne la
connexion physique dans le pool.

L’utilisation d’un pool de connexions ne modifie en rien la manière dont le


développeur procède pour créer et utiliser un objet « Connection » et offre
l’avantage de permettre au serveur d’application d’améliorer les
performances de l’application concernée, lorsque le développeur ne
maintient une connexion que pour l’exécution d’une requête ou d’une
transaction.

En plus des propriétés du pilote, le développeur doit aussi définir et gérer


les propriétés du pool de connexion en dehors du programme, soit en
utilisant des outils fournis à cet effet, soit en fournissant au serveur
d’application les informations requises dans un fichier de configuration.

Pour la réalisation des traitements qui concourent à la soumission d’une


requête SQL à la source de données qu’elle gère, l’API de l’objet
« Connection » offre les méthodes ci-après :
• La méthode « createStatement () » : cette méthode sert à créer un
objet « Statement » qui offre à travers son API les méthodes requises
pour soumettre à la source l’exécution d’un ordre SQL non
paramétré
• La méthode « prepareStatement () » : cette méthode sert à créer un
objet « PreparedStatement » qui offre à travers son API les méthodes
requises pour soumettre à la source l’exécution d’un ordre SQL
paramétré

321
Joachim TANKOANO

• La méthode « prepareCall () » : cette méthode sert à créer un objet


« CallableStatement » qui offre à travers son API les méthodes
requises pour soumettre à la source un appel de fonction ou de
procédure stockée.

L’interface qu’implémente la classe « PreparedStatement » hérite de celle


qu’implémente la classe « Statement » et la complète avec les méthodes
spécifiques requises pour le traitement de la soumission d’une requête SQL
paramétrée. De même, l’interface qu’implémente la classe
« CallableStatement » hérite de celle qu’implémente la classe
« PreparedStatement » et la complète avec les méthodes spécifiques
requises pour le traitement de la soumission d’un appel de fonction ou de
procédure stockée.

L’API de l’objet « Connection » offre par ailleurs les méthodes requises


pour la gestion des transactions qui se déroulent dans une session (voir les
paragraphes 9.5.1 et 9.5.2.a) ainsi que la méthode « close () » pour la
fermeture de la connexion établie avec la source.

6.3.3. L’exécution d’un ordre SQL non paramétré

Dans un programme Java, la soumission de l’exécution d’un ordre SQL non


paramétré à une source se fait à l’aide de l’API d’un objet « Statement »
obtenu par appel de la méthode « createStatement () » de l’objet
« Connection » qui gère la connexion avec cette source. Quant à la
récupération du résultat de l’exécution d’un ordre « SELECT » non
paramétré et au traitement de ce résultat, ils s’effectuent à l’aide de l’API
d’un objet « ResulSet » créé à la suite de l’exécution de cet ordre
« SELECT ».

a) La creation d’un objet « Statement » pour l’exécution des ordres SQL


non paramétrés

L’appel de la méthode « createStatement () » d’un objet « Connection » qui


gère une connexion à une source retourne un objet « Statement » dont l’API
permet de soumettre à cette source l’exécution des ordres SQL de définition
des données (« CREATE TABLE », « ALTER TABLE », « DROP TABLE »,

322
SGBD relationnels – Tome 1, État de l’art

etc.) et l’exécution des ordres SQL non paramétrés de manipulation des


données (« SELECT », « INSERT », « UPDATE », et « DELETE »).

La signature de cette méthode « createStatement () » est la suivante :


Statement Connection.createStatement ([RST, RSC [, RSH]]);
RST ::= {ResultSet.TYPE_FORWARD_ONLY
|ResultSet.TYPE_SCROLL_INSENSITIVE
|ResultSet.TYPE_SCROLL_SENSITIVE
}
RSC ::= {ResultSet.CONCUR_READ_ONLY
|ResultSet.CONCUR_UPDATABLE
}
RSH ::= {ResultSet.HOLD_CURSORS_OVER_COMMIT
|ResultSet.CLOSE_CURSORS_AT_COMMIT
}
Où :

• Les paramètres « RST », « RSC », « RSH » ne sont requis que lorsque


l’objet « Statement » retourné par la méthode « createStatement () »
doit servir pour l’exécution d’un ordre « SELECT ». Ces paramètres
permettent dans ce cas de spécifier les caractéristiques que doit avoir
l’objet « ResultSet » qui doit être retourné après l’exécution d’un ordre
« SELECT » afin de permettre la récupération et le traitement du
résultat de cette exécution. Cet objet « ResultSet » est comparable sur
certains aspects aux curseurs PL/SQL.

• Le paramètre « RST » permet de spécifier les capacités que doit avoir


l’objet « ResultSet » retourné pour ce qui concerne le déplacement d’un
curseur sur les lignes du résultat et la prise en compte des mises à jour
effectuées sur ces lignes. L’option « TYPE_FORWARD_ONLY »
indique d’une part que le curseur ne doit être déplaçable que dans un
seul sens allant de la première à la dernière ligne et d’autre part que
l’état des lignes reflété par le SGBD doit être l’état après l’exécution de
l’ordre « SELECT » ou l’état au moment de la récupération de ces lignes.
L’option « TYPE_SCROLL_INSENSITIVE » indique d’une part que le
curseur doit être déplaçable dans les deux sens ou à une position
spécifiée et d’autre part que l’état des lignes reflété par le SGBD doit être
l’état après l’exécution de l’ordre « SELECT » ou l’état au moment de la

323
Joachim TANKOANO

récupération de ces lignes. L’option « TYPE_SCROLL_SENSITIVE »


indique d’une part que le curseur doit être déplaçable dans les deux
sens ou à une position spécifiée et d’autre part que les mises à jour
effectuées après l’exécution de l’ordre « SELECT » doivent être prises
en compte dans l’état des lignes reflété par le SGBD tant que l‘objet
« ResultSet » retourné reste ouvert.

• Le paramètre « RSC » permet de spécifier les capacités que doit avoir


l’objet « ResultSet » retourné par rapport aux accès concurrents.
L’option « CONCUR_READ_ONLY » permet au programme qui a
exécuté l’ordre « SELECT » de lire les lignes retournées mais ne lui
permet pas de modifier ces lignes en utilisant l’API de l‘objet
« ResultSet » retourné. L’option « CONCUR_UPDATABLE » permet
au programme qui a exécuté l’ordre « SELECT » de lire et de modifier
les lignes retournées en utilisant l’API de l‘objet « ResultSet » retourné.

• Le paramètre « RSH » permet de spécifier le comportement souhaité par


rapport à l‘objet « ResultSet » lors de l’appel de la méthode
« Connection.commit () ». L’option
« HOLD_CURSORS_OVER_COMMIT » indique que l‘objet
« ResultSet » retourné ne doit pas être fermé lors de l’appel de la
méthode « Connection.commit () ». L’option
« CLOSE_CURSORS_AT_COMMIT » indique que l‘objet
« ResultSet » retourné doit être fermé lors de l’appel de la méthode
« Connection.commit () ».

Un objet « Statement » retourné par appel de la méthode « createStatement


() » d’un objet « Connection » offre les méthodes ci-après qui permettent
d’interagir avec la source gérée par cet objet « Connection » pour
l’exécution d’un ordre SQL non paramétré :
• La méthode « executeQuery () » : cette méthode permet de
soumettre à cette source l’exécution d’un ordre « SELECT » non
paramétré passé en paramètre. Elle retourne un objet « ResultSet »
dont l’API permet la récupération et le traitement du résultat de cette
exécution
• La méthode « executeUpdate () » : cette méthode permet de
soumettre à cette source l’exécution des ordres SQL de définition des
données (« CREATE TABLE », « ALTER TABLE », « DROP

324
SGBD relationnels – Tome 1, État de l’art

TABLE », etc.) et des ordres SQL de manipulation des données


(« INSERT », « UPDATE », et « DELETE ») non paramétrés. Elle
retourne le nombre de mises à jour opérées
• La méthode « execute » : cette méthode est requise pour la
soumission à cette source de l’exécution d’ordres SQL pouvant
retourner plusieurs résultats éventuellement de types différents. Elle
retourne un booléen qui est vrai si le premier résultat retourné est un
objet « ResultSet » et faux si ce résultat est le nombre de mises à jour
effectuées. La méthode « getResultSet () » permet dans le premier
cas de récupérer l’objet « ResultSet » retourné et de mettre à jour ce
booléen en fonction du type du résultat suivant. De même, la
méthode « getUpdateCount () » permet dans le deuxième cas de
récupérer le nombre de mises à jour effectuées et de mettre à jour ce
booléen en fonction du type du résultat suivant
• La méthode « addBatch () » : cette méthode permet d’ajouter un
ordre SQL non paramétré qui ne retourne pas d’objet « ResultSet »
aux ordres SQL à exécuter en lot
• La méthode « executeBatch () » : cette méthode permet de soumettre
en différé à la source l’exécution du lot d’ordres SQL constitué par
appels de la méthode « addBatch () »
• La méthode « clearBatch () » : cette méthode permet de vider le lot
d’ordres SQL constitué par appels de la méthode « addBatch () ».

b) L’exécution des ordres SQL non paramétré de définition des données

La soumission à une source de l’exécution d’un ordre non paramétré de


définition des données s’effectue par appel de la méthode « executeUpdate
() » d’un objet « Statement » (retourné par appel de la méthode
« createStatement () » de l’objet « Connection » qui gère la connexion à
cette source). La méthode « executeUpdate () » prend en paramètre une
chaine de caractères correspondant à cet ordre non paramétré de définition
des données à exécuter.

Exemple 6.3.3.i : Le code Java qui suit crée un objet « Statement » dans la
variable « stmt » et appelle ensuite sa méthode « executeUpdate () » pour

325
Joachim TANKOANO

soumettre à la source de données concernée un ordre SQL de création de la


table « AirBurkina.Avions ».
// Création d’une connexion à une source de données et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « Statement » dans la variable « stmt » pour la soumission de
// l’exécution des ordres SQL non paramétrés à cette source de données
Statement stmt = maConn.createStatement () ;
// Soumission de l’exécution d’un ordre SQL de création de la table
// « AirBurkina.Avions »
stmt.executeUpdate ("CREATE TABLE AirBurkina.Avions ("
+ "NoAvion NUMBER (5) PRIMARY KEY, "
+ "NomAvion VARCHAR2(15) NOT NULL, "
+ "CapAvion NUMBER(4) DEFAULT NULL, "
+ "LocAvion VARCHAR2 (30) DEFAULT NULL) ");
stmt.close ();
maConn.commit ();
maConn.close () ;

c) L’exécution des ordres « INSERT », « UPDATE », et « DELETE » non


paramétrés

La soumission à une source de l’exécution d’un ordre non paramétré


« INSERT », « UPDATE », ou « DELETE » s’effectue également par appel
de la méthode « executeUpdate () » d’un objet « Statement » (retourné par
appel de la méthode « createStatement () » de l’objet « Connection » qui
gère la connexion à cette source) avec en paramètre une chaîne de caractères
correspondant à cet ordre. L’appel de cette méthode retourne un entier qui
indique le nombre de lignes traitées par cet ordre.

Exemple 6.3.3.ii : Le code Java qui suit crée un objet « Statement » dans la
variable « stmt » et appelle ensuite sa méthode « executeUpdate () » pour
soumettre à la source concernée l’exécution d’un ordre « UPDATE » qui
modifie à « 175 » la valeur de la colonne « CapAvion » de toutes les lignes

326
SGBD relationnels – Tome 1, État de l’art

de la table « AirBurkina.Avions » où la colonne « NomAvion » commence


par la chaîne de caractères « AIRBUS ».
// Création d’une connexion à une source de données et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « Statement » dans la variable « stmt » pour la soumission de
// l’exécution des ordres SQL non paramétrés à cette source
Statement stmt = maConn.createStatement () ;
// Soumission de l’exécution d’un ordre SQL de modification de la capacité des avions
// « AIRBUS » à 175
int nbLignes =stmt.executeUpdate ("UPDATE AirBurkina.Avions "
+ "SET CapAvion = 275"
+ "WHERE NomAvion LIKE 'AIRBUS%' ");
stmt.close () ;
maConn.commit ();
maConn.close () ;

d) L’exécution d’un ordre « SELECT » non paramétré

La soumission à une source de l’exécution d’un ordre « SELECT » non


paramétré s’effectue par appel de la méthode « executeQuery () » d’un
objet « Statement ». Cet objet « Statement » doit être créé par appel de la
méthode « createStatement () » de l’objet « Connection » qui gère la
connexion à cette source, en spécifiant les caractéristiques de l’objet
« ResultSet » qui doit être retourné pour permettre la récupération et le
traitement du résultat de l’exécution de cet ordre « SELECT ».

La méthode « executeQuery () » prend en paramètre une chaine de


caractères correspondant à l’ordre « SELECT » non paramétré à exécuter.

Un objet « ResultSet » retourné par appel de la méthode « executeQuery


() » d’un objet « Statement » offre l’API ci-après pour le déplacement d’un
curseur sur les lignes du résultat de l’exécution de l’ordre « SELECT » :

327
Joachim TANKOANO

• La méthode « first () » pour positionner le curseur sur la première


ligne du résultat
• La méthode « beforeFirst () » pour positionner le curseur avant la
première ligne du résultat
• La méthode « last () » pour positionner le curseur sur la dernière
ligne du résultat
• La méthode « AfterLast () » pour positionner le curseur après la
dernière ligne du résultat
• La méthode « next () » pour positionner le curseur sur la ligne
suivante du résultat
• La méthode « previous () » pour positionner le curseur sur la ligne
précédente du résultat
• La méthode relative (int n) pour positionner le curseur sur la nème
ligne du résultat par rapport à sa position courante
• La méthode absolute (int n) pour positionner le curseur sur la nème
ligne après la 1ère ligne du résultat.

Un objet « ResultSet » retourné par appel de la méthode « executeQuery


() » d’un objet « Statement » offre par ailleurs une API qui permet de
récupérer individuellement chaque colonne de la ligne courante du résultat
de l’exécution de l’ordre « SELECT » concerné. Chaque méthode de cette
API est un accesseur (c’est-à-dire, un « getter ») défini pour un type de
valeur Java (« Byte », « Short », « Integer », « Long », « Float », « Double »,
« Boolean », « Date », « Array », « Class », etc.). Cet accesseur peut prendre
en paramètre soit le nom de la colonne, soit le rang de la colonne en
considérant que les colonnes sont numérotées de la gauche vers la droite à
partir de 1 dans l’ordre où elles sont spécifiées dans l’ordre « SELECT ». À
titre d’exemple, on peut citer les accesseurs ci-après :
• L’accesseur « getByte () » qui peut effectuer une mise en
correspondance notamment avec le type JDBC « TINYINT »
• L’accesseur « getShort () » qui peut effectuer une mise en
correspondance notamment avec le type JDBC « SMALLINT »

328
SGBD relationnels – Tome 1, État de l’art

• L’accesseur « getInt () » qui peut effectuer une mise en


correspondance notamment avec le type JDBC « INTEGER »
• L’accesseur « getFloat () » qui peut effectuer une mise en
correspondance notamment avec le type JDBC « REAL »
• L’accesseur « getString () » qui peut effectuer une mise en
correspondance notamment avec les types JDBC « CHAR » et
« VARCHAR »
• L’accesseur « getBoolean () » qui peut effectuer une mise en
correspondance notamment avec les types JDBC « BIT » et
« BOOLEAN »
• L’accesseur « getDate () » qui peut effectuer une mise en
correspondance notamment avec le type JDBC « DATE »
• L’accesseur « getArray () » qui peut effectuer une mise en
correspondance notamment avec le type JDBC « ARRAY »
• L’accesseur « getObject () » qui peut effectuer une mise en
correspondance notamment avec les types JDBC « STRUCT » et
« JAVA_OBJECT ».

Exemple 6.3.3.iii : Le code Java qui suit crée un objet « Statement » dans la
variable « stmt » et appelle ensuite sa méthode « executeQuery () » pour
soumettre l’exécution d’un ordre « SELECT » qui retourne le numéro et le
nom de chaque avion contenu dans la table « AirBurkina.Avions ». L’objet
« ResultSet », produit par cet appel avec les caractéristiques par défaut
dans la variable « rs », est ensuite utilisé pour parcourir le résultat qu’il
contient afin de l’afficher sur la console système.
// Création d’une connexion à une source et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « Statement » dans la variable « stmt » pour la soumission de
// l’exécution des ordres SQL non paramétrés à cette source
Statement stmt = maConn.createStatement () ;
// Soumission de l’exécution d’un ordre « SELECT » qui retourne toutes les lignes

329
Joachim TANKOANO

// de la table « AirBurkina.Avions »
rs = stmt.executeQuery ("SELECT NoAvion, NomAvion "
+ "FROM AirBurkina.Avions") ;
// Récupération et affichage du résultat de l’exécution de l’ordre « SELECT » à l’aide
// de l’API de l’objet « ResultSet » retourné dans la variable « rs »
int NoAvion ;
String NomAvion ;
StringWriter sw = new StringWriter ();
PrintWriter writer = new PrintWriter (sw);
while (rs.next ()) {
NoAvion = rs.getInt ("NoAvion");
NomAvion = rs.getString ("NomAvion");
writer.println ();
writer.println ("No avion = " + NoAvion +" Nom = " + NomAvion );
writer.flush ();
System.out.println (sw.getBuffer().toString());
}
rs.close () ;
stmt.close ();
maConn.commit ();
maConn.close ();

e) L’utilisation d’un objet « Resulset » pour mettre à jour les lignes du


résultat de l’exécution d’un ordre « SELECT »

Il est possible, lors du parcours des lignes du résultat de l’exécution d’un


ordre « SELECT » d’utiliser l’API de l’objet « ResultSet » pour la mise à
jour de ces lignes.

L’API d’un objet « ResultSet » offre à cet effet des mutateurs qui permettent
de mettre à jour individuellement chaque colonne de la ligne courante du
résultat de l’exécution d’un ordre « SELECT ». Chacun de ces mutateurs est
préfixé par « update » et est défini, tout comme pour les accesseurs offerts
par cette API, pour un type de valeur Java (« Byte », « Short », « Integer »,
« Long », « Float », « Double », « Boolean », « Date », « Array », « Class »,
etc.). Chaque mutateur prend en paramètre d’une part, soit le nom de la
colonne, soit le rang de la colonne en considérant que les colonnes sont

330
SGBD relationnels – Tome 1, État de l’art

numérotées de la gauche vers la droite à partir de 1 dans l’ordre où elles


sont spécifiées dans l’ordre « SELECT » et d’autre part la nouvelle valeur à
affecter à cette colonne.

La mise à jour de la ligne courante du résultat de l’exécution d’un ordre


« SELECT » à l’aide de l’API de l’objet « ResultSet » se fait en deux phases :
• Une 1ère phase préparatoire où chaque colonne concernée de la ligne
courante doit être modifiée en mémoire centrale par appel d’un
mutateur
• Une 2ème phase de finalisation où les modifications effectuées dans la
1ère phase sont appliquées dans la base de données par appel de la
méthode « updateRow () » de l’objet « ResultSet ».

Toutefois, pour que la mise à jour des lignes du résultat de l’exécution d’un
ordre « SELECT » puisse se faire à l’aide de l’API de l’objet « ResultSet »,
l’objet « Statement » utilisé pour l’exécution de l’ordre « SELECT » par
appel de sa méthode « executeQuery () » doit être créé préalablement par
appel de la méthode « createStatement » d’un objet « Connection » en
passant le paramètre « CONCUR_UPDATABLE ».

Exemple 6.3.3.iv : Le code Java qui suit crée un objet « Connection » dans la
variable « maConn ». Un objet « Statement » est ensuite créé dans la
variable « stmt » par appel de la méthode « createStatement » de cet objet
« Connection » en fournissant « ResultSet.TYPE_FORWARD_ONLY » et
« ResultSet.CONCUR_UPDATABLE » comme paramètres. L’appel de la
méthode « executeQuery » de cet objet « Statement » exécute un ordre
« SELECT » qui retourne le numéro, le nom et la capacité de chaque avion
« AIRBUS » de la table « AirBurkina.Avions » et crée dans la variable
« rs » un objet « ResultSet ». Cet objet « ResultSet » est ensuite utilisé pour
modifier à « 175 » la valeur de la colonne « CapAvion » de toutes les lignes
de la table « AirBurkina.Avions » qui concerne un « AIRBUS ».
// Création d’une connexion à une source et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;

331
Joachim TANKOANO

// Création d’un objet « Statement » dans la variable « stmt » pour la soumission de


// l’exécution des ordres « SELECT » non paramétrés à cette source en
// spécifiant les caractéristiques de l’objet « ResultSet » qui doit être retourné
Statement stmt = maConn.createStatement
(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_UPDATABLE) ;
// Soumission de l’exécution d’un ordre « SELECT » qui retourne tous les avions
// « AIRBUS » contenus dans la table «AirBurkina.Avions »
ResultSet rs = stmt.executeQuery
("SELECT NoAvion, NomAvion, CapAvion "
+ "FROM AirBurkina.Avions "
+ "WHERE NomAvion LIKE 'AIRBUS%' ");
// Récupération et modification à 175 de la capacité de chaque avion retourné dans le
// résultat à l’aide de l’API de l’objet « ResultSet » retourné dans la variable « rs »
int CapAvion ;
while (rs.next ()) {
rs.updateInt ("CapAvion", 175) ;
rs.updateRow () ;
}
rs.close () ;
stmt.close ();
maConn.commit ();
maConn.close () ;

f) L’utilisation de l’objet « Resulset » pour la suppression d’une ligne


contenue dans le résultat de l’exécution d’un ordre « SELECT »

Comme le montre l’exemple qui suit, il est aussi possible, lors du parcours
des lignes du résultat de l’exécution d’un ordre « SELECT », de supprimer
une ligne par appel de la méthode « deleteRow () » de l’objet « ResultSet ».

Exemple 6.3.3.v : Le code Java qui suit exécute un ordre « SELECT » qui
retourne la ligne de chaque avion « AIRBUS » contenu dans la table
« AirBurkina.Avions » et utilise ensuite l’objet « ResultSet » pour
supprimer ces lignes de la base de données.
// Création d’une connexion à une source et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion

332
SGBD relationnels – Tome 1, État de l’art

Context ctx = new InitialContext ();


DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « Statement » dans la variable « stmt » pour la soumission de
// l’exécution des ordres « SELECT » non paramétrés à cette source en
// spécifiant les caractéristiques de l’objet « ResultSet » qui doit être retourné
Statement stmt = maConn.createStatement
(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE,
ResulSet.CLOSE_CURSORS_AT_COMMIT);
// Soumission de l’exécution d’un ordre « SELECT » qui retourne tous les avions
// « AIRBUS » contenus dans la table «AirBurkina.Avions »
ResultSet rs = stmt.executeQuery ("SELECT * FROM AirBurkina.Avions "
+ "WHERE NomAvion LIKE 'AIRBUS%'");
// Suppression des lignes du résultat à l’aide de l’objet « ResultSet » retourné dans la
// variable « rs »
while (rs.next ()) {
rs.deleteRow () ;
}
rs.close () ;
stmt.close ();
maConn.commit ();
maConn.close ();

g) L’utilisation de l’objet « Resulset » pour l’insertion d’une nouvelle


ligne dans la base de données

Enfin, lors du parcours des lignes du résultat de l’exécution d’un ordre


« SELECT », il est aussi possible d’insérer une nouvelle ligne dans la base
de données par appel de méthodes de l’API de l’objet « ResultSet ».

Cette opération s’effectue en trois étapes :


• L’appel de la méthode « moveToInsertRow() » de l’objet
« ResultSet »

333
Joachim TANKOANO

• L’affectation de valeurs dans les colonnes de la ligne à insérer par


appel des mutateurs de l’objet « ResultSet », présentés dans le
paragraphe f) ci-dessus
• La finalisation de l’opération par appel de la méthode « insertRow
() » de l’objet « ResultSet ».

Exemple 6.3.3.vi : Le code Java qui suit utilise l’objet « ResultSet » pour
insérer une nouvelle ligne dans la table « AirBurkina.Avions ».
// Création d’une connexion à une source et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « Statement » dans la variable « stmt » pour la soumission de
// l’exécution des ordres « SELECT » non paramétrés à cette source en
// spécifiant les caractéristiques de l’objet « ResultSet » qui doit être retourné
Statement stmt = maConn.createStatement
(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE,
ResulSet.CLOSE_CURSORS_AT_COMMIT) ;
// Soumission de l’exécution d’un ordre « SELECT » qui retourne tous les avions
// contenus dans la table «AirBurkina.Avions »
ResultSet rs = stmt.executeQuery ("SELECT * FROM AirBurkina.Avions "
+ "WHERE NomAvion LIKE 'AIRBUS%' ");
// Insertion d’un nouvel avion dans la table «AirBurkina.Avions » à l’aide de l’objet
// « ResultSet » retourné dans la variable « rs »
rs.moveToInsertRow() ;
rs.updateInt ("NoAvion", 100);
rs.updateString ("NomAvion", "BONING 747");
rs.updateInt ("CapAvion", 600) ;
rs.updateString ("LocAvion", "OUAGADOUGOU") ;
rs.insertRow () ;
rs.close () ;
stmt.close ();
maConn.commit ();
maConn.close () ;

334
SGBD relationnels – Tome 1, État de l’art

h) L’exécution en différé de lots d’ordres SQL

L’API d’un objet « Statement » (retourné par appel de la méthode


« createStatement () » de l’objet « Connection » qui gère une connexion à
une source) fournit les méthodes que le développeur peut utiliser pour
constituer un lot d’ordres SQL non paramétrés (qui ne retournent pas un
objet « ResultSet ») et pour ensuite exécuter en différé ce lot d’ordres SQL.

Il s’agit des méthodes ci-après :


• « addBatch () » qui permet d’ajouter, dans le lot d’ordres SQL associé
à un objet « Statement », un ordre SQL non paramétré ;
• « executeBatch () » qui permet d’exécuter le lot d’ordre SQL
constitué par appels de la méthode « addBatch () » ;
• « clearBatch () » qui permet de vider le lot d’ordres SQL associé à un
objet « Statement »

Exemple 6.3.3.vii : Le code Java qui suit crée un objet « Statement » dans la
variable « stmt » et appelle sa méthode « addBatch () » à plusieurs reprises
pour constituer un lot d’ordres « INSERT » dans la table « Avions ». La
méthode « executeBatch () » de cet objet « Statement » est ensuite appelée
pour l’exécution en différé de ce lot.
// Création d’une connexion à une source et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « Statement » dans la variable « stmt » pour la constitution et
// la soumission de l’exécution d’un lot d’ordres SQL non paramétrés à cette source
Statement stmt = maConn.createStatement () ;
// Constitution d’un lot d’ordres « INSERT » dans la table «Avions »
// par appesl de « stmt.addBatch () »
stmt.addBatch
("INSERT INTO Avions (NoAvion, NomAvion, CapAvion, LocAvion) "
+ "VALUES (10, 'AIRBUS A310', 200, 'BOBO-DIOULASSO') ");
stmt.addBatch
("INSERT INTO Avions (NoAvion, NomAvion, CapAvion, LocAvion) "
+ "VALUES (20, 'AIRBUS A340', 200, 'OUAGADOUGOU')");

335
Joachim TANKOANO

stmt.addBatch
("INSERT INTO Avions (NoAvion, NomAvion, CapAvion, LocAvion) "
+ "VALUES (20, 'BOING 707', 150, 'OUAGADOUGOU')") ;
// Soumission en différé de l’exécution du lot d’ordres constitué
int [] NbLignes = stmt.executeBatch ();
stmt.close ();
maConn.commit ();
maConn.close () ;

6.3.4. L’exécution d’un ordre SQL paramétré

Dans un programme Java, la soumission de l’exécution d’un ordre SQL


paramétré à une source se fait à l’aide de l’API d’un objet
« PreparedStatement » obtenu par appel de la méthode
« prepareStatement () » de l’objet « Connection » qui gère la connexion à
cette source.

L’appel de la méthode « prepareStatement () » de cet objet « Connection »


prend en paramètre un ordre SQL paramétré. Cet appel précompile l’ordre
SQL paramétré et retourne un objet « PreparedStatement » dont l’API
permet de soumettre à cette source de façon répétitive, avec des paramètres
effectifs différents, l’exécution de l’ordre SQL précompilé.

L’ordre SQL paramétré doit être un ordre SQL de manipulation des


données (« SELECT », « INSERT », « UPDATE », et « DELETE »).

La signature de la méthode « prepareStatement () » est la suivante :


PreparedStatement Connection.prepareStatement (OrdreSQLParamétré
[, RST, RS [, RSH]]);
RST ::= {ResultSet.TYPE_FORWARD_ONLY
|ResultSet.TYPE_SCROLL_INSENSITIVE
|ResultSet.TYPE_SCROLL_SENSITIVE
}
RSC ::= {ResultSet.CONCUR_READ_ONLY
|ResultSet.CONCUR_UPDATABLE
}

336
SGBD relationnels – Tome 1, État de l’art

RSH ::= {ResultSet.HOLD_CURSORS_OVER_COMMIT


|ResultSet.CLOSE_CURSORS_AT_COMMIT
}
Où :

• Le paramètre « OrdreSQLParamétré » désigne une chaîne de caractères


ou une variable contenant une chaîne de caractères, correspondant à un
ordre SQL paramétré

• Les paramètres « RST », « RSC », « RSH » ne sont requis que lorsque le


paramètre « OrdreSQLParamétré » désigne un ordre « SELECT ». Ces
paramètres ont la même signification et jouent le même rôle que les
paramètres de même nom présentés dans le paragraphe 6.3.3.a pour la
méthode « createStatement () » de l’objet « Connection ».

Dans un ordre SQL paramétré, chaque paramètre formel doit être


matérialisé par le caractère « ? ».

L’interface « PreparedStatement » étend l’interface « Statement » par ajout


des mutateurs qui permettent de spécifier dynamiquement le passage des
paramètres effectifs de l’ordre SQL précompilé.

Un objet « PreparedStatement » retourné par appel de la méthode


« prepareStatement () » d’un objet « Connection » fournit ainsi les
méthodes ci-après que le développeur peut utiliser pour interagir avec la
source gérée par cet objet « Connection » pour l’exécution de l’ordre SQL
paramétré précompilé :
• Les mutateurs « setters » : ces mutateurs permettent, avant la
soumission de l’ordre SQL précompilé, de réaliser dynamiquement
le passage des paramètres effectifs par affectation d’une valeur à
chaque paramètre formel. Chacun de ces mutateurs est préfixé par
« set » et est défini pour un type de valeur Java donné (« Byte »,
« Short », « Integer », « Long », « Float », « Double », « Boolean »,
« Date », « Array », « Class », etc.). Chaque mutateur prend en
paramètre le rang du paramètre concerné dans l’ordre SQL
précompilé et la valeur à affecter à ce paramètre. Le mutateur
« setNull () » permet d’affecter la valeur NULL à un paramètre. Il
prend en paramètre le rang du paramètre et son type Java.

337
Joachim TANKOANO

• La méthode « executeQuery () » : lorsqu’il s’agit d’un ordre


« SELECT », cette méthode permet de soumettre à la source
l’exécution de l’ordre SQL précompilé contenant des paramètres
effectifs. Elle retourne, comme pour la méthode de même nom de
l’interface « Statement », un objet « ResultSet » dont l’API permet la
récupération du résultat de l’exécution de l’ordre « SELECT »
concerné et l’exécution d’opérations de modification, de suppression
ou d’insertion de nouvelles lignes pendant le parcours de ce résultat.
• La méthode « executeUpdate () » : lorsqu’il s’agit d’un ordre SQL de
manipulation des données (« INSERT », « UPDATE », et
« DELETE »), cette méthode permet de soumettre à la source
l’exécution de l’ordre SQL précompilé contenant des paramètres
effectifs.
• La méthode « execute » : cette méthode est requise pour la
soumission à la source de l’exécution d’ordres SQL précompilés
pouvant retourner plusieurs résultats éventuellement de types
différents. Elle retourne un booléen qui est vrai si le premier résultat
retourné est un objet « ResultSet » et faux si ce résultat est le nombre
de mises à jour effectuées. La méthode « getResultSet () » permet
dans le premier cas de récupérer l’objet « ResultSet » retourné et de
mettre à jour ce booléen en fonction du type du résultat suivant. De
même, la méthode « getUpdateCount () » permet dans le deuxième
cas de récupérer le nombre de mises à jour effectuées et de mettre à
jour ce booléen en fonction du type du résultat suivant.
• La méthode « addBatch () » : après une initialisation des paramètres
d’appel de l’ordre précompilé, cette méthode permet d’ajouter
l’ordre SQL précompilé (contenant des paramètres effectifs) aux
ordres SQL à exécuter en lot associés à l’objet
« PreparedStatement ».
• La méthode « executeBatch () » : cette méthode permet de soumettre
en différé à la source l’exécution du lot d’ordres SQL précompilés
constitué par appels de la méthode « addBatch () »

338
SGBD relationnels – Tome 1, État de l’art

• La méthode « clearBatch () » : cette méthode permet de vider le lot


d’ordres SQL précompilés constitué par appels de la méthode
« addBatch () ».

Exemple 6.3.4.i : Le code Java qui suit crée un objet « PreparedStatement »


dans la variable « ps » pour un ordre « SELECT » dont l’exécution doit
retourner le numéro et le nom de chaque avion contenu dans la table
« AirBurkina.Avions » lorsque la capacité de l’avion est supérieure à une
valeur spécifiée par un paramètre formel. Cet objet est utilisé pour effectuer
dynamiquement le passage du paramètre effectif 150 et pour ensuite
appeler sa méthode « executeQuery () » afin de soumettre l’exécution de
l’ordre « SELECT » compilé contenant ce paramètre effectif à la source de
données concernée. L’objet « ResultSet » retourné dans la variable « rs »,
avec les caractéristiques par défaut est ensuite utilisé pour parcourir et
afficher sur la console système le résultat retourné par l’ordre « SELECT ».
// Création d’une connexion à une source et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « PreparedStatement » dans la variable « ps » pour la
// soumission de l’exécution d’un ordre « SELECT » paramétré à cette source
PreparedStatement ps = maConn.prepareStatement
("SELECT NoAvion, NomAvion "
+ "FROM AirBurkina.Avions "
+ "WHERE CapAvion  ? ") ;
// Passage du paramètre effectif 150 de façon dynamique
ps.setInt (1, 150) ;
// Soumission de l’exécution de l’ordre « SELECT » compilé pour retourner toutes les
// lignes de la table « AirBurkina.Avions » où la capacité de l’avion est supérieure
// à 150
rs = ps.executeQuery () ;
// Récupération et affichage du résultat de l’exécution de l’ordre « SELECT » à l’aide
// de l’API de l’objet « ResultSet » retourné dans la variable « rs »
int NoAvion ;
String NomAvion ;
StringWriter sw = new StringWriter ();
PrintWriter writer = new PrintWriter (sw);

339
Joachim TANKOANO

while (rs.next ()) {


NoAvion = rs.getInt ("NoAvion");
NomAvion = rs.getString ("NomAvion");
writer.println ();
writer.println ("No avion = " + NoAvion +" Nom = " + NomAvion );
writer.flush ();
System.out.println (sw.getBuffer().toString());
}
ps.close () ;
maConn.commit ();
maConn.close () ;

Exemple 6.3.4.ii : Le code Java qui suit crée un objet « PreparedStatement »


dans la variable « ps » pour un ordre « INSERT » où les valeurs des
colonnes de la ligne à insérer sont définies à l’aide de paramètres formels.
Pour chaque ligne à insérer, les mutateurs « settypeJava () » de cet objet sont
utilisés pour effectuer dynamiquement le passage des paramètres effectifs
de l’ordre « INSERT » et la méthode « addBatch () » de cet objet pour
ajouter l’ordre « INSERT » précompilé (où les valeurs des paramètres ont
ainsi été définies) aux ordres SQL à exécuter en lot, associés à l’objet
« PreparedStatement ». La méthode « executeBatch () » de l’objet
« PreparedStatement » est ensuite appellée pour soumettre l’exécution en
différé de ce lot d’ordres précompilés contenant des paramètres effectifs.
// Création d’une connexion à une source et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « PreparedStatement » dans la variable « ps » pour la
// soumission à cette source de l’exécution d’un ordre « INSERT » paramétré
PreparedStatement ps = maConn.prepareStatement
("INSERT INTO Avions (NoAvion, NomAvion, CapAvion, LocAvion) "
+ "VALUES ( ?, ?, ?, ?)") ;
// Passage des paramètres effectifs de façon dynamique pour obtenir
// une clause VALUES de l’ordre précompilé défini comme suit :
// VALUES (10, ‘AIRBUS A310’, 200, ‘BOBO-DIOULASSO’)
ps.setInt (1, 10);

340
SGBD relationnels – Tome 1, État de l’art

ps.setString (2, "AIRBUS A310");


ps.setInt (3, 200);
ps.setString (4, "BOBO-DIOULASSO");
// Ajout de l’ordre précompilé contenant des paramètres effectifs dans le lot
// d’ordres associé à l’objet « PreparedStatement »
ps.addBatch () ;
// Passage des paramètres effectifs de façon dynamique pour obtenir
// une clause VALUES de l’ordre précompilé défini comme suit :
// VALUES (20, ‘AIRBUS A340’, 600, ‘OUAGADOUGOU’)
ps.setInt (1, 20);
ps.setString (2, "AIRBUS A340");
ps.setInt (3, 600);
ps.setString (4, "OUAGADOUGOU");
// Ajout de l’ordre précompilé contenant des paramètres effectifs dans le lot
// d’ordres associé à l’objet « PreparedStatement »
ps.addBatch () ;
// Soumission à la source de l’exécution du lot d’ordres constitué
Int [] NbLignes = ps.executeBatch ();
ps.close () ;
maConn.commit ();
maConn.close () ;

6.3.5. L’exécution des procédures et fonctions stockées

Dans un programme Java, la soumission à une source de l’exécution d’un


appel de sous-programme (fonction ou procédure) stocké se fait à l’aide de
l’API d’un objet « CallableStatement » obtenu par appel de la méthode
« prepareCall () » de l’objet « Connection » qui gère la connexion à cette
source.

L’appel de la méthode « prepareCall () » de l’objet « Connection » qui gère


la connexion à cette source prend en paramètre une instruction paramétrée
d’appel d’un sous-programme stocké. Cet appel précompile l’instruction
paramétrée et retourne un objet « CallableStatement » dont l’API permet
de soumettre de façon répétitive à la source, avec des paramètres effectifs
différents, l’exécution de l’instruction précompilée.

341
Joachim TANKOANO

La signature de la méthode « prepareCall () » est la suivante :


CallableStatement Connection.prepareCall
(InstructionParamétréeAppelSousPgme) ;

Où le paramètre « InstructionParamétréeAppelSousPgme » désigne une chaîne


de caractères ou une variable contenant une chaîne de caractères
correspondant à une instruction paramétrée d’appel d’un sous-programme
stocké.

Dans une instruction paramétrée d’appel d’un sous-programme stocké,


chaque paramètre formel doit être matérialisé par le caractère « ? ». La
chaîne de caractères qui spécifie cette instruction doit respecter l’une des
deux syntaxes ci-après :
• « '{'CALL NomSousPgmeStocké [(ListeParamètres)]'}' »

• « '{' ? = CALL NomSousPgmeStocké [(ListeParamètres)]'}' ».

L’interface « CallableStatement » étend l’interface « PreparedStatement »


par ajout des méthodes ci-après :
• La méthode « registerOutParameter () » : avant la soumission de
l’exécution de l’instruction d’appel précompilée cette méthode doit
être appelée pour chaque paramètre en sortie défini dans le sous-
programme stocké afin d’indiquer qu’il s’agit d’un paramètre en
sortie. Cette méthode prend en paramètre le rang du paramètre
concerné dans l’instruction d’appel précompilée et le type Java de ce
paramètre
• Les accesseurs « gettypeJava () » : après l’exécution du sous-
programme stocké, ces accesseurs permettent de récupérer la valeur
de chaque paramètre en sortie défini dans l’instruction d’appel
précompilée. Ces accesseurs prennent en paramètre le rang du
paramètre concerné dans l’instruction d’appel précompilée.

Les autres méthodes de l’interface « CallableStatement » jouent le même


rôle que les méthodes de même nom de l’interface « PreparedStatement ».
On peut citer notamment :
• Les mutateurs « settypeJava () » pour initialiser avec une valeur,
avant la soumission de l’exécution de l’instruction d’appel

342
SGBD relationnels – Tome 1, État de l’art

précompilée, chaque paramètre en entrée défini dans le sous-


programme stocké
• Les méthodes « executeQuery () », « executeUpdate () » et « execute
() » pour soumettre à la source de données, en fonction de la nature
du résultat attendu en retour, l’exécution de l’instruction d’appel
précompilée.

L’objet « ResultSet » retourné par la méthode « executeQuery () » joue


également le même rôle que l’objet « ResultSet » retourné par la méthode
de même nom de l’interface « Statement ». Comme l’objet « ResultSet »
retourné par la méthode de même nom de l’interface « Statement », il offre
une API qui permet la récupération du résultat de l’exécution du sous-
programme stocké et l’exécution d’opérations de modification, de
suppression ou d’insertion de nouvelles lignes pendant le parcours de ce
résultat.

Exemple 6.3.5.i : Le code Java qui suit crée un objet « CallableStatement »


dans la variable « cs » pour une instruction d’appel de la procédure stockée
qui a pour signature « modifierTrajet (NoV NUMBER, VD VARCHAR2,
VA VARCHAR2, ERR OUT BOOLEAN) ». Cet objet
« CallableStatement » est utilisé ensuite pour déclarer le 4 paramètre de
ème

cette procédure stockée comme étant un paramètre en sortie de type


booléen, initialiser ses paramètres en entrée, soumettre l’instruction d’appel
à la source et récupérer la valeur retournée par le 4ème paramètre dans la
variable « err ».
// Création d’une connexion à une source et d’un objet « Connection »
// dans la variable « maConn » pour la gestion de cette connexion
Context ctx = new InitialContext ();
DataSource ds = (DataSource) ctx.lookup ("jdbc/BDAirBurkina");
Connection maConn = ds.getConnection ("user", "pwd") ;
// Création d’un objet « CallableStatement » dans la variable « cs » pour la soumission
// de l’exécution de l’appel de la procédure stockée dont la signature est :
// modifierTrajet (NoV NUMBER, VD VARCHAR2, VA VARCHAR2, ERR OUT
// BOOLEAN)
CallableStatement cs = maConn.prepareCall
("{CALL modifierTrajet ( ?, ?, ?, ?)}") ;
// Déclaration du 4 paramètre comme étant un paramètre en sortie de type booléen
ème

343
Joachim TANKOANO

cs.registerOutParameter (4, java.sql.Types.BOOLEAN) ;


// Initialisation des paramètres en entrée
cs.setInt (1, 10) ;
cs.setString (2, "OUAGADOUGOU");
cs.setString (3, "BOBO-DIOULASSO");
// Soumission de l’appel de la procédure stockée à la source de données
cs.execute () ;
// Récupération de la valeur retournée par le 4ème paramètre dans la variable « err »
Boolean err = getBoolean (4) ;
……
cs.close () ;
maConn.commit ();
maConn.close () ;

344
SGBD relationnels – Tome 1, État de l’art

BIBLIOGRAPHIE
ORACLE: Oracle® Database JDBC Developer's Guide, 12c Release 2 (12.2) –
May 2017

ORACLE: JDBC™ 4.3 Specification, JSR 221 - Lance Andersen, Specification


Lead, July 2017

Melton J. & Eisenberg A.: Understanding SQL and Java Together: A Guide to
SQLJ, JDBC, and Related Technologies - Morgan Kaufmann (2000).

Microsoft: Microsoft ODBC 3.0 Software Development Kit and Programmer’s


Reference - Microsoft Press (1997).

Silberschatz A., Korth H.F. & Sudarshan S.: Database system concepts, sixth
edition - McGraw-Hill, 2011

345
Chapitre 7. : Gestion de la mémoire
relationnelle

7.1. Introduction
Dans la section 1.2, nous avons vu que pour pouvoir servir de support pour
le stockage des données d’une entreprise, les performances constituent une
exigence essentielle que la technologie des bases de données doit satisfaire.

La satisfaction de cette exigence nécessite que le SGBD fournisse les


garanties ci-après :
• D’une part, la quantité de données qui peut être stockée sur les
supports physiques comme les disques ne doit pas être limitée pour
des raisons techniques
• D’autre part, le temps requis pour accéder à ces données doit être
raisonnable, au regard des attentes de l’entreprise en termes de délai
de réponse, lors des interactions avec une base de données, ceci
quelle que soit la volumétrie de celles-ci.

Dans les chapitres précédents, nous avons vu qu’au niveau logique le


modèle relationnel permet de manipuler une base de données à l’aide
d’opérateurs logiques qui font abstraction de l’implémentation des données
au niveau physique. Dans ce chapitre, nous allons nous intéresser à cette
implémentation des données au niveau physique, c’est-à-dire, à la manière
dont les SGBD relationnels organisent et stockent les données sur les
supports physiques et aux techniques et algorithmes qu’ils utilisent pour
accéder physiquement à ces données afin de satisfaire à l’exigence relative
aux performances.

Les techniques utilisées par les SGBD relationnels pour la gestion d’un
volume important de données et pour garantir un temps d’accès
raisonnable et indépendant de la volumétrie de ces données sont fortement
dictées par les caractéristiques intrinsèques des moyens qui servent de
support pour le stockage de ces données, à savoir, les disques. De ce fait,
nous allons commencer dans ce chapitre par un rappel sur l’architecture et
SGBD relationnels – Tome 1, État de l’art

sur les principes qui régissent le fonctionnement d’un disque magnétique.


Ensuite, nous examinerons les principes généraux qui en découlent pour
l’organisation physique du stockage d’une table sur un disque afin de
mettre en évidence les conséquences non tolérables de ces principes en
termes de temps moyen d’accès à une ligne de cette table. Les principales
techniques qui peuvent contribuer à réduire ce temps d’accès moyen, en
tenant compte de ces principes, seront ensuite examinées, à savoir :
l’utilisation de bonnes stratégies de placement des lignes sur le disque,
l’utilisation d’une mémoire tampon entre les applications et le disque et les
différentes techniques d’indexation d’une table. Pour terminer, nous
aborderons, à titre d’illustration, la prise en compte de ces techniques au
niveau du SGBD Oracle et de son langage SQL de définition des données.

7.2. La structure et le fonctionnement d’un disque


magnétique

7.2.1. Une vue schématique de l’architecture d’un disque


magnétique

Un disque magnétique est constitué de plusieurs plateaux. Chaque face


d’un plateau est constituée de pistes concentriques divisées en secteurs en
général de 512 octets. Les pistes qui sont à égale distance de l’axe centrale
forment un cylindre.

L’architecture d’un disque magnétique peut ainsi se schématiser comme


suit :
Piste Axe central de
rotation

Secteur Tête de
lecture/écriture
Bras
Contrôleur
Plateaux

Cylindre Unité centrale

347
Joachim TANKOANO

Dans un disque, ses plateaux sont entraînés dans un mouvement de


rotation régulier par rapport à son axe central. Quant à son bras, il se
déplace indépendamment de la rotation des plateaux dans un plan fixe qui
lui permet de positionner les têtes de lecture / écriture sur les pistes d’un
même cylindre.

Pour lire ou pour écrire une information sur un disque, l’unité centrale doit
soumettre une requête au contrôleur de ce disque dont la fonction
principale est de coordonner les opérations nécessaires pour l’exécution de
cette requête.

7.2.2. La lecture et l’écriture sur un disque magnétique

Pour être utilisable, un disque magnétique doit être formaté. L’une des
finalités du formatage d’un disque est de diviser l’espace disponible sur ce
disque en blocs de même taille, multiple de la taille des secteurs (512 octets,
1024 octets, 2048 octets, 4096 octets, etc.).

Après le formatage d’un disque, le bloc devient l’unité d’échange entre ce


disque et la mémoire centrale. En d’autres termes, pour lire ou pour écrire
même un seul bit sur ce disque, l’unité centrale doit lire ou écrire le bloc qui
contient ce bit.

Au regard de l’architecture des disques, l’adresse physique de chaque bloc


est composée des 3 éléments ci-après :
• Un numéro de face.
• Un numéro de piste sur la face.
• Un numéro de bloc dans la piste.

Chaque disque contient en générale une table qui fait le lien entre le numéro
séquentiel d’un bloc et son adresse physique.

La lecture ou l’écriture d’un bloc par l’unité centrale de l’ordinateur est


coordonnée par le contrôleur du disque et se déroule en 3 étapes :
• Le positionnement de la tête de lecture / écriture sur la piste qui
contient ce bloc, ce qui induit un délai de positionnement

348
SGBD relationnels – Tome 1, État de l’art

• L’attente pour que le mouvement de rotation des plateaux du disque


positionne le début de ce bloc sous une tête de lecture / écriture, ce
qui induit un délai de latence
• Le transfert de ce bloc, ce qui induit un temps de transfert.

La durée d’une opération de lecture ou d’écriture sur un disque est la


somme des temps consacrés à ces trois opérations qui reposent sur des
mouvements mécaniques.

7.2.3. Disques magnétiques versus mémoire centrale

Le tableau qui suit compare les caractéristiques des disques magnétiques à


celles de la mémoire centrale.

Du fait des contraintes mécaniques sur la vitesse de positionnement des


têtes de lecture / écriture et de rotation des plateaux, les disques
magnétiques offrent un temps d’accès relativement élevé qui est de l’ordre
de plusieurs millisecondes. Ce temps d’accès est largement au-dessus du
temps d’accès à la mémoire centrale, effectué électroniquement, qui n’est
que de l’ordre de quelques nanosecondes. En contrepartie, les disques
magnétiques sont des mémoires de masse non volatiles et peu coûteuses
qui offrent des capacités de stockage pouvant atteindre une dizaine de
téraoctets, alors que la mémoire centrale est volatile, coûteuse et n’offre
qu’une capacité de stockage de quelques gigaoctets.

Disque Mémoire centrale


Capacité de stockage (n * 100) Go à n To (n * 100 Ko) à n Go
Temps d’accès Plusieurs ms Quelques nano secs
Volatilité Non volatile (les Volatile (les données
données sont conservées sont perdues lorsque
lorsque l’ordinateur est l’ordinateur est mis
mis hors tension) hors tension)

De ce fait, dans l’état actuel de l’art, le rôle d’un disque dans un ordinateur
est de servir de support pour le stockage des données du système
d’information, tandis que le rôle de la mémoire centrale est de servir de
support pour l’exécution des programmes qui manipulent ces données.

349
Joachim TANKOANO

Le choix des disques pour le stockage des données et de la mémoire centrale


pour l’exécution des programmes est un choix qui s’impose à cause de la
volatilité de la mémoire centrale et de sa très faible capacité de stockage. Ce
choix a comme conséquence une différence trop importante entre la vitesse
à laquelle un programme peut s’exécuter en mémoire centrale et le temps
pendant lequel ce programme doit attendre chaque fois que le contrôleur
d’un disque doit exécuter à sa demande une opération d‘entrée/sortie
disque. La vitesse à laquelle un programme peut s’exécuter s’exprime en
nanosecondes alors que le temps pendant lequel ce programme doit
attendre s’exprime en millisecondes. Cette différence entre la rapidité
d’exécution de l’unité centrale et celle des contrôleurs de disques se traduit
par un rapport qui est de l’ordre de 1/1000000.

La technologie actuelle des bases de données vise principalement un


fonctionnement optimal des systèmes informatiques (programmes et
matériel) en dépit de ce très grand déséquilibre qui existe entre les
principaux composants qui concourent à ce fonctionnement en ce qui
concerne la rapidité avec laquelle ces composants peuvent exécuter les
tâches qui leur reviennent.

7.2.4. Un exemple de caractéristiques d’un disque magnétique

Ce qui suit donne des exemples de caractéristiques proches du réel d’un


disque imaginaire de petite capacité sur lequel nous nous appuierons dans
nos illustrations :
Capacité du disque : 9,1 Go
Taux de transfert : 80 Mo par seconde
Cache : 1 Mo
Nombre de faces : 6
Nombre de têtes de lecture / écriture : 6
Nombre total secteurs de 512 octets : 17 783 438
Nombre de cylindres : 9 772
Vitesse de rotation : 10 000 rpm (rotations par minute)
Temps de positionnement : en moyenne 5.2 ms

À partir de ces caractéristiques, d’autres caractéristiques peuvent être


déduites comme suit :

350
SGBD relationnels – Tome 1, État de l’art

Nombre de secteurs par face : 17 783 438 / 6 = 2 963 906


Nombre de secteurs par piste : 2 963 906 / 9 772 = 303
Durée d’une rotation : 1/(10 000 / 60) = 0,006 s = 6 ms
Délai de latence : en moyenne 3 ms
Temps de transfert d’un bloc de 1 Ko : (6 ms/303)*2 = 0,04 ms
Temps moyen d’accès à un bloc de 1 Ko : 5,2 ms + 3 ms + 0,04 ms = 8,24 ms

7.3. Les principes de base pour le stockage d’une table


sur un disque

7.3.1. Un rappel sur la structure logique d’une base de données


relationnelle

Comme nous l’avons vu dans le chapitre 4, d’un point de vue logique, une
base de données relationnelle est composée de tables qui contiennent des
extensions de relations.

Les lignes d’une table sont des enregistrements de même type dont les
champs correspondent aux colonnes de cette table.

Les champs des enregistrements peuvent être de différents types de valeurs


atomiques : nombres entiers ou réels, chaînes de caractères de longueur fixe
ou variable, dates, image, etc.

De ce fait, bien que les lignes d’une table soient de même type, elles ne sont
pas nécessairement de même longueur.

7.3.2. Le stockage d’une table dans des blocs de disques

Dans un disque, les lignes qui composent une table sont stockées à
l’intérieur de ses blocs avec comme corollaires que :
• Un bloc peut contenir une ou plusieurs lignes si la taille des blocs est
plus grande que la taille de ces lignes
• Une ligne peut aussi s’étendre sur plusieurs blocs si la taille des blocs
est plus petite que la taille de cette ligne.

351
Joachim TANKOANO

Par ailleurs, un bloc ne peut contenir que les lignes d’une même table (sauf
lorsque le mécanisme de clustérisassion est utilisé (voir les paragraphes
7.4.2 et 7.7.3.f)).

Les blocs qui sont alloués à une table ne sont pas nécessairement contigus.
De ce fait, ces blocs sont toujours chaînés entre eux, ce qui permet d’allouer
les blocs requis pour le stockage d’une table au fur et à mesure des besoins
sans contrainte particulière sur le nombre de blocs pouvant être alloués à
cette table.

L’accès direct à une ligne d’une table se fait au moyen de son adresse
physique définie par le couple :
n° du bloc, clé de recherche dans la ligne.

Cela nécessite de pouvoir accéder à chaque bloc par son numéro et à chaque
ligne dans un bloc par sa clé de recherche.

7.3.3. L’organisation des lignes dans un bloc

Les lignes sont en général stockées à l’intérieur d’un bloc l’une avant l’autre
en commençant par la fin du bloc.

Lorsque les lignes sont de taille variable, un tableau de pointeurs prévu au


début de chaque bloc appelée « catalogue » permet de conserver un
pointeur vers le début de chaque ligne. Ce catalogue peut avoir un nombre
fixe ou variable d’entrées. Lorsque le nombre d’entrées est variable, il est
possible soit d’indiquer le nombre d’entrées en début de catalogue, soit
d’utiliser un marqueur de fin en fin de catalogue.

Dans tous les cas, il reste très souvent de l’espace inutilisé dans chaque bloc,
situé entre le catalogue et la dernière ligne du bloc. Cet espace inutilisé peut
être volontairement prévu pour faciliter les insertions futures de lignes à
l’intérieur des blocs où sont stockées une table ou le remplacement de la
valeur d’une colonne par une autre de plus grande taille.

Ce principe général de l’organisation du stockage des lignes d’une table à


l’intérieur d’un bloc peut se schématiser comme suit :

352
SGBD relationnels – Tome 1, État de l’art

Pointeur
vers le
bloc
Catalogue suivant
Espace non-utilisé 3ème 2ème 1ère
ligne ligne ligne

Structure d’un bloc

7.3.4. L’organisation des colonnes dans une ligne

Chaque ligne stockée à l’intérieur d’un bloc est en général constituée dans
l’ordre des trois (3) parties ci-après :
• Un groupe de champs réservés pour la gestion de la ligne comme,
par exemple : un code indiquant si la ligne est logiquement
supprimée ou non, un code indiquant si la ligne est verrouillée ou
non (utilisé pour la gestion des accès concurrents), la taille de la ligne,
etc…
• Les colonnes de taille fixe
• Les colonnes de taille variable.

Pour ce qui concerne les colonnes de taille variable, deux options sont
possibles :
• La taille d’une colonne peut être indiquée au début de cette colonne,
ce qui ne permet pas un accès direct à chaque colonne
• L’adresse de début de chaque colonne peut être indiquée dans un
tableau inclus au début de la ligne dans le groupe des champs
réservés.

Cette organisation de la structure générale d’une ligne à l’intérieur d’un


bloc peut se schématiser comme suit :

353
Joachim TANKOANO

Taille
des
colonnes
de taille
Champs 5 colonnes de 3 colonnes de variable
réservés taille fixe taille variable

Structure d’une ligne de table stockée dans un bloc

7.3.5. Les conséquences sur le temps d’accès aux lignes d’une


table stockées en vrac dans des blocs

Si une table contient « n » lignes de taille fixe et si chaque bloc d’un disque
peut contenir « m » lignes de cette table, pour stocker cette table sur ce
disque de la façon la plus simple possible, en se conformant aux principes
définis dans le paragraphe 7.3.2, il suffit de stocker chaque groupe de « m »
lignes de cette table dans un bloc de ce disque (en respectant les principes
définis dans les paragraphes 7.3.3 et 7.3.4) et de chaîner ces blocs entre eux,
sans se préoccuper de l’ordre dans lequel ses lignes sont rangées.

En procédant de la sorte, le nombre de blocs requis sur un disque pour le


stockage d’une table est de « n/m », arrondi au nombre entier
immédiatement supérieur si la division donne comme résultat un nombre
fractionnaire.

Après avoir stocké en vrac les lignes d’une table sur un disque de cette
façon, pour y rechercher une ligne en particulier où la valeur du groupe de
colonnes qui constitue la clé de recherche dans cette table a une certaine
valeur donnée, il n’existe pas d’autre solution plus rapide que de transférer
en mémoire centrale, bloc après bloc, les blocs chaînés qui contiennent les
lignes de cette table et de procéder dans chaque bloc transféré en mémoire
centrale à une recherche de cette ligne.

Dans le meilleur des cas, la ligne recherchée peut être retrouvée après la
lecture en mémoire centrale du premier bloc de cette table et dans le pire
des cas, qu’après la lecture en mémoire centrale de son dernier bloc. En

354
SGBD relationnels – Tome 1, État de l’art

d’autres termes, l’accès à la ligne recherchée nécessitera au maximum la


lecture de « n/m » blocs et en moyenne la lecture de « n/2m » blocs.

Exemple 7.3.5.i : Si nous considérons que notre table contient « 1 000 000 »
de lignes de « 120 » octets, que la taille d’un bloc est de « 1024 » octets et que
le temps d’accès moyen à un bloc de « 8,25 ms », on peut calculer comme
suit le temps maximum et moyen requis pour la recherche d’une ligne dans
cette table, si on considère comme marginal le temps requis pour les
recherches effectuées en mémoire centrale :

Nombre de lignes par bloc = 1024 / 120


= 8,553
= 8 lignes entières par bloc
Nombre de blocs requis pour stocker la table = 1 000 000 / 8
= 125 000 blocs
Temps d’accès maximum = 125 000 * 8,25 ms
= 1 031 250 ms
= 1 031,25 s
= 17 mn 12s
Temps d’accès moyen = (17 mn 12s) / 2
= 8mn 43s

Sur la base de cet exemple, on ne peut que conclure que le stockage d’une
table en procédant comme décrit plus haut amène à violer l’exigence
imposée à la technologie des bases de données relative aux performances
qui veut d’une part, que la quantité de données qui peut être stockée sur les
supports physiques de mémorisation ne soit pas techniquement limitée et
d’autre part, que le temps d’accès à ces données soit raisonnable du point
de vue du temps de réponse attendu par l’entreprise et indépendant de
cette quantité. En procédant comme décrit plus haut, la quantité de données
que peut contenir une table n’est pas techniquement limitée, dès lors que
les blocs requis peuvent être alloués sur les disques disponibles. En
revanche, le temps moyen d’accès à une ligne qui est estimé à 8mm 43 est
très largement au-dessus du temps de réponse attendu qui doit être
inférieur à la seconde.

355
Joachim TANKOANO

Cet exemple met en relief la nécessité de techniques plus appropriées pour


le stockage d’une table sur un disque. Ces techniques doivent affiner
l’organisation physique des données définie dans les paragraphes 7.3.2,
7.3.3 et 7.3.4, imposés par les caractéristiques intrinsèques des disques et
proposer des algorithmes efficaces pour un accès rapide aux lignes d’une
table en fonction des critères de recherche définis par le développeur.

7.3.6. Les techniques pour réduire le temps d’accès à une ligne


d’une table

La réduction du temps d’accès aux lignes d’une table afin de satisfaire à


l’exigence relative aux performances s’effectue en combinant plusieurs
techniques parmi lesquelles on peut citer :
• Les techniques basées sur l’utilisation de stratégies de placement des
lignes
• Les techniques basées sur l’utilisation d’une zone tampon
• Les techniques d’indexation des tables.

Les techniques d’indexation des tables sont mises en œuvre distinctement


sur chaque table en fonction de ses caractéristiques et des besoins. Elles
permettent d’affiner au niveau d’un disque, l’organisation physique des
données contenues dans une table et de proposer des algorithmes efficients
pouvant permettre une réduction effective importante du temps d’accès
aux lignes recherchées lorsque l’emplacement sur ce disque des blocs qui
contiennent ces lignes n’est pas connu à l’avance. Il s’agit des moyens que
l’administrateur de la base de données peut utiliser explicitement ou que le
SGBD peut utiliser implicitement pour garantir le respect de l’exigence
relative aux performances.

Les techniques basées sur l’utilisation des stratégies de placement des


lignes et les techniques basées sur l’utilisation d’une zone tampon
permettent quant à elles une réduction du temps d’accès aux blocs qui
contiennent des lignes, peu importe la table concernée. Elles visent une
amélioration significative des performances globales du SGBD pouvant
avoir une incidence positive sur la réduction du temps d’accès aux lignes

356
SGBD relationnels – Tome 1, État de l’art

recherchées dans n’importe quelle table lorsque l’emplacement sur le


disque des blocs qui contiennent ces lignes n’est pas connu à l’avance.

Les sections qui suivent (7.4, 7.5 et 7.6) sont consacrées à l’étude de ces trois
techniques.

À ces techniques s’ajoutent celles mises en œuvre au niveau des disques


que nous n’aborderons pas ici, à savoir :
• Les techniques de séquencement des opérations de lecture et
d’écriture par le contrôleur du disque afin de réduire la durée du
délai de positionnement des têtes de lecture / écriture
• La technologie « RAID (Redundant Array of Independent Disks) »
utilisée pour améliorer les performances des disques mais aussi et
surtout pour garantir leur tolérance aux pannes.

7.3.7. L’impact des disques SSD sur le temps d’accès à une


ligne d’une table

Les disques « SSD (Solid State Drive) » sont des nouveaux types de disques
apparus ces dernières années. Ces disques n’ont ni plateaux, ni têtes de
lectures, ce qui élimine dans leur fonctionnement les délais de
positionnement et de latence. Comme les clés USB, les données sont
stockées dans des composants électroniques, ce qui permet des taux de
transfert de plusieurs milliers de mégaoctets par seconde.

De ce fait, il s’agit de disques plus rapides, plus silencieux, plus résistants


aux chocs et plus économiques en termes de consommation d’énergie.

En revanche, ces disques n’autorisent qu’un nombre déterminé d’écritures,


ce qui les exposent à une usure plus rapide. En outre leur coût est plus élevé.

Tout comme les techniques basées sur l’utilisation des stratégies de


placement des lignes et les techniques basées sur l’utilisation d’une zone
tampon, l’utilisation des disques SSD pour le stockage des tables permet
une amélioration significative des performances globales du SGBD pouvant
avoir un impact positif sur la réduction du temps d’accès aux lignes
recherchées dans n’importe quelle table.

357
Joachim TANKOANO

7.4. La réduction du temps d’accès à l’aide d’une


stratégie de placement des lignes de tables

7.4.1. Pourquoi gérer le placement des lignes à l’intérieur des


blocs

Soient 4 lignes de 300 octets chacune, à stocker sur le disque imaginaire du


paragraphe 7.2.4 où les blocs ont une capacité de 1024 octets. Les deux
figures ci-dessous schématisent deux stratégies extrêmes de placement de
ces lignes :

Dans le schéma de gauche, chaque ligne est placée dans un bloc différent et
les 4 blocs sont dans des pistes différentes. Le temps de lecture de ces 4 blocs
sera dans ce cas :
4 * (5,2 ms + 3 ms + 0,04 ms) = 33 ms.

Dans le schéma de droite, les 4 lignes sont placées dans 2 blocs contigus. Le
temps de lecture de ces 2 blocs sera dans ce cas :
5,2 ms + 3 ms + (2 * 0,04 ms) = 8,3 ms.

Comme on peut le constater, la stratégie utilisée dans le schéma de droite


privilégie le regroupement des lignes dans des blocs contigus et est de ce
fait plus avantageuse parce qu’elle permet d’éviter les déplacements des
têtes de lecture/écriture du disque et les délais de latence. Elle amène pour
cet exemple à diviser par 4 le temps d’accès qu’on peut obtenir avec la
stratégie mise en œuvre dans le schéma de gauche.

358
SGBD relationnels – Tome 1, État de l’art

7.4.2. Les leçons à tirer

Si « d1 » et « d2 » désignent deux données à placer sur un disque, ce qui suit


définit dans l’ordre décroissant les types de regroupements qui peuvent
conduire au gain le plus important lorsqu’il est nécessaire d’accéder en
même temps à ces données :
• Placement de « d1 » et « d2 » dans le même bloc : à chaque fois « d1 »
et « d2 » seront lues ensembles
• Placement de « d1 » et « d2 » dans deux blocs contigus : à chaque fois
les blocs qui contiennent « d1 » et « d2 » seront lus l’un à la suite de
l’autre
• Placement de « d1 » et « d2 » dans deux blocs situés sur la même
piste : à chaque fois les blocs qui contiennent « d1 » et « d2 » seront
lus par la même tête de lecture sans déplacement de cette dernière et
au plus en une seule rotation des plateaux
• Placement de « d1 » et « d2 » dans deux blocs situés dans le même
cylindre : à chaque fois les blocs qui contiennent « d1 » et « d2 »
seront lus par deux têtes de lecture / écriture différentes sans
déplacement de ces dernières et au plus en une seule rotation des
plateaux
• Placement de « d1 » et « d2 » dans deux blocs situés dans des
cylindres différents : à chaque fois, la lecture des deux blocs qui
contiennent « d1 » et « d2 » nécessitera à deux reprises le
déplacement des têtes de lecture / écriture et l’attente du
positionnement d’une de ces têtes sur le bloc concerné. Le gain sera
d’autant plus important que les deux cylindres sont proches l’un de
l’autre.

De façon plus générale, les stratégies de placement des lignes à l’intérieur


des blocs doivent éviter des éparpillements pouvant démultiplier le
nombre de blocs à lire dans des cylindres différents lorsque ces lignes sont
requises en même temps.

Pour que la gestion du placement des lignes d’une table à l’intérieur des
blocs puisse être assurée efficacement par le SGBD, l’espace requis pour le

359
Joachim TANKOANO

stockage de la base de données sur les disques doit être alloué au SGBD
pour qu’il en assure l’organisation et la gestion.

C’est l’option retenue par certains SGBD comme ORACLE qui stockent les
bases de données dans un ou plusieurs fichiers alloués par le système
d’exploitation hôte dont la gestion de l’espace est placée sous la seule
responsabilité du SGBD. Les concepts proposés notamment par le SGBD
Oracle pour permettre la maîtrise du regroupement et du placement des
données sont :
• Les « EXTENTS », qui sont des blocs contigus, alloués pour le
stockage d’une table et dont la taille peut être fixée par l’usager.
L’« EXTENT » constitue l’unité d’allocation à une table d’un espace
sur un disque
• Les « SEGMENTS », qui correspondent à des tables et qui sont
constitués d’un ou de plusieurs EXTENTS
• Les « CLUSTERS », où chaque bloc regroupe les lignes d’une table
ou de plusieurs tables qui possèdent la même valeur au niveau des
colonnes définies comme étant la clé du cluster. Ceci permet par
exemple de regrouper physiquement dans un même bloc chaque
ligne de la table des commandes avec les lignes de la table des
articles commandés qui lui correspondent
• Les « TABLESPACES », qui constituent l’espace logique pour le
stockage des segments et des clusters. Un « TABLESPACES » est
constitué d’un (au moins) ou de plusieurs fichiers du système hôte,
pouvant être répartis sur plusieurs disques.

7.5. La réduction du temps d’accès à l’aide d’une zone


tampon
Cette technique vise la réduction du temps d’accès aux blocs qui
contiennent une base de données. Elles conduisent à une amélioration
significative des performances globales du SGBD pouvant avoir une
incidence positive sur la réduction du temps d’accès aux lignes recherchées,
peu importe la table concernée.

360
SGBD relationnels – Tome 1, État de l’art

Cette technique est mise en œuvre par la plupart des fournisseurs de SGBD.
Elle consiste à réserver, en mémoire centrale, une zone tampon entre les
applications et la base de données où est copiée une partie des blocs qui
contiennent cette base de données.

Chaque fois que le SGBD veut accéder à un bloc d’une table, ce bloc est
d’abord recherché dans la zone tampon :
• Si ce bloc s’y trouve, l’accès au disque est évité
• Si ce bloc ne s’y trouve pas, le SGBD le récupère dans la base de
données et le recopie dans la zone tampon (éventuellement en
remplacement d’un autre bloc) avant d’exploiter son contenu.

Ce mécanisme doit s’accompagner d’un mécanisme de synchronisation


périodique du contenu de la zone tampon avec celui de la base de données
stocké sur le disque.

Ce mécanisme peut se schématiser comme suit :

Demande
d’accès à un
bloc d’une table Base de
Résultat de la données
demande
Zone tampon en
mémoire centrale

Plus la taille de la zone tampon est importante, plus les chances d’y
retrouver le bloc recherché sont grandes et plus le gain escompté est
significatif. De ce fait, il est essentiel que la mémoire centrale du serveur qui
héberge une base de données soit importante et qu’une bonne partie de
cette mémoire soit allouée au SGBD pour la création et la gestion de cette
zone tampon.

7.6. La réduction du temps d’accès à l’aide d’index


Les techniques d’indexation sont utilisées pour affiner l’organisation du
stockage du contenu d’une table sur le disque, telle que définie dans les

361
Joachim TANKOANO

paragraphes 7.3.2, 7.3.3 et 7.3.4, afin de permettre la mise en œuvre


d’algorithmes efficients pour l’accès à ses lignes.

Ces techniques constituent le principal moyen que la technologie des bases


de données fournit pour la garantie des performances. En fonction des
besoins, ces techniques sont mises en œuvre explicitement par les
administrateurs de base de données et implicitement par les SGBD. Cette
section présente l’objectif et le principe général de ces techniques et
quelques-unes d’entre elles.

7.6.1. L’objectif et le principe général

L’indexation d’une table vise la réduction du temps d’accès à ses lignes,


grâce à la création de chemins les plus directs possibles.

L’index d’une table est comparable à la table des matières d’un livre qui
permet de retrouver le numéro de la page où commence un chapitre, une
section ou un paragraphe, sans avoir à feuilleter ce livre page par page. Il
est aussi comparable à l’index des mots clés qui permet de retrouver en un
clin d’œil les numéros des pages où ces mots clés sont utilisés.

Dans la technologie des bases de données, l’indexation d’une table sur une
clé de recherche, constituée d’une colonne ou d’un groupe de colonnes, crée
une mise en correspondance qui permet, pour chaque valeur que cette clé
de recherche peut prendre, de déterminer les adresses des blocs où sont
stockées les lignes qui contiennent cette valeur.

L’indexation d’une table permet donc des recherches rapides dites


associatives, c’est-à-dire, basées sur le contenu d’une partie des colonnes de
cette table, comme par exemple, rechercher dans la table des employés
d’une entreprise toutes les lignes où la colonne correspondant au nom de
l’employé contient la valeur « OUEDRAOGO Ali ».

L’indexation d’une table entraine un affinement de l’organisation physique


des données, telle que définie dans les paragraphes 7.3.2, 7.3.3 et 7.3.4. Cet
affinement qui a pour finalité de créer des chemins d’accès rapide aux lignes
peut se réaliser de diverses manières, soit à l’aide de fichiers triés,
hiérarchisés, structurés sous forme d’arbre de recherche équilibré, soit à

362
SGBD relationnels – Tome 1, État de l’art

l’aide d’une fonction de hachage.

L’indexation d’une table peut se définir sur sa clé primaire, sur une clé
alternative, mais aussi sur n’importe quelle colonne ou groupe de colonnes.

L’indexation d’une table peut toutefois être sans intérêt, si la recherche doit
s’effectuer sur une clé de recherche à très faible cardinalité pour laquelle les
lignes qui contiennent chaque valeur qu’elle peut prendre existent dans la
presque totalité des blocs qui servent au stockage de cette table du fait de la
technique utilisée. Dans de telles situations, le parcourt séquentiel de ces
blocs reste la meilleure solution. C’est le cas, par exemple, s’il faut
rechercher dans une table des étudiants tous les étudiants originaires de la
région du centre lorsque du fait de la technique d’indexation utilisée les
lignes de cette table sont stockées en vrac sur le disque.

7.6.2. L’exemple utilisé pour les illustrations

Dans les paragraphes qui suivent, chaque fois qu’il sera nécessaire
d’évaluer les performances d’une technique d’indexation nous nous
appuierons sur le fichier présenté dans l’exemple 7.3.5.i en supposant qu’il
s’agit d’une table de films : « Films (Titre, Année, Producteur, Pays, …) ».
En rappel, ses caractéristiques sont les suivantes :
• Nombre de lignes dans la table : 1 000 000 ;
• Taille d’une ligne de la table : 120 octets ;
• Taille des blocs du disque où la table est stockée : 1024 octets ;
• Temps d’accès moyen à un bloc : 8,25 ms.

Par ailleurs, lorsqu’il sera nécessaire d’illustrer le fonctionnement d’un


algorithme, nous nous appuierons sur la version réduite ci-après de cette
table en supposant que chaque bloc ne peut contenir que 3 lignes :

363
Joachim TANKOANO

TITRE ANNEE …

TEZA 2009 …
EZRA 2007 …
DRUM 2005 …

HEREMAKONO 2001 …
ALI ZAOUA 2001 …
BUUD YAM 1997 …

GUIMBA 1995 …
AU NOM DU CHRIST 1991 …
TILAÏ 1991 …

SARRAOUNIA 1987 …

7.6.3. Les index non denses

Les techniques d’indexation dense et non dense ont été développées avant
l’avènement de la technologie des bases de données. Elles ont servi de base
pour le développement de techniques d’indexation plus récentes. Ce qui
suit présente le principe général qui régit l’organisation physique des
données d’une table indexée à l’aide d’un index non dense et les
implications de ce principe général sur l’opération de construction d’une
table indexée et sur les algorithmes de recherche, insertion et suppression
de lignes. Les implications sur l’opération de modification d’une ligne ne
sera pas abordée. Dans le pire des cas, elle peut se traduire par une
suppression suivie d’une insertion de ligne.

a) Le principe général de l’organisation physique des données d’une


table indexée à l’aide d’un index non dense

Une table indexée sur une clé de recherche à l’aide d’un index non dense
est stockée et organisée physiquement au niveau du disque dans deux
fichiers : un fichier dit principal (FP) qui contient toutes les lignes de la table
et un fichier index (FI).

L’indexation d’une table à l’aide d’un index non dense nécessite un tri

364
SGBD relationnels – Tome 1, État de l’art

préalable du fichier principal FP selon l’ordre croissant de la clé de


recherche et le maintien de ce tri après chaque insertion ou modification de
ligne.

Le fichier index non dense FI est quant à lui un fichier qui, pour chaque bloc
du fichier principal FP, contient une ligne constituée de deux champs : un
champ « adresse de bloc » qui contient l’adresse de ce bloc et un champ
« valeur de clé » qui contient la valeur de la clé de recherche dans la 1ère
ligne de ce bloc. Le fichier index non dense FI est donc trié par construction
selon l’ordre croissant de la clé de recherche et ce tri doit être maintenu
après chaque insertion ou modification de ligne. À tout moment, si « c1, c2,
…, cn » désigne la liste des valeurs triées de la clé de recherche dans le fichier
index non dense FI, et « c » la valeur de la clé de recherche dans une ligne
stockée dans un bloc « i » du fichier principal FP, on doit avoir de ce fait « ci
 c  ci+1 ».

Exemple 7.6.3.i : L’indexation de la table réduite des films à l’aide d’un


index non dense en prenant comme clé de recherche le titre des films peut
se schématiser comme suit :
Fichier principal trié (FP)

TITRE ANNEE …
ALI ZAOUA 2001 …
AU NOM DU CHRIST 1991 …
(FI) BUUD YAM 1997 …
ALI ZAOUA
DRUM DRUM 2005 …
HEREMAKONO EZRA 2007 …
TILAÏ GUIMBA 1995 …

HEREMAKONO 2001 …
SARRAOUNIA 1987 …
TEZA 2009 …

TILAÏ 1991 …

Ce schéma décrit l’organisation physique des données qui découle de cette


indexation.

365
Joachim TANKOANO

b) La construction d’une table indexée

Pour maintenir, après une insertion ou une modification de ligne, le tri du


fichier principal FP et du fichier index non dense FI d’une table indexée,
tout en garantissant un temps de traitement raisonnable de cette opération,
il est essentiel qu’elle puisse se faire sans engendrer dans ces deux fichiers
des décalages de lignes pouvant nécessiter plusieurs minutes de traitement.
Le non-respect de cette contrainte peut avoir comme conséquence la
violation de l’exigence relative aux performances imposée à la technologie
des bases de données.

Lors de la construction du fichier principal FP et du fichier index non dense


FI, les techniques d’indexation d’une table à l’aide d’un index non dense
prennent en compte cette contrainte majeure en stockant ces fichiers triés
dans des blocs chaînés où un certain pourcentage prédéterminé de l’espace
disponible de chaque bloc a été réservé pour les insertions futures.

Exemple 7.6.3.ii : Ainsi, si on suppose d’une part, qu’en prévision des


insertions futures, le taux de remplissage initial des blocs du fichier
principal FP et de son fichier index FI doit être respectivement de 75% et
95% et d’autre part, que la taille d’un enregistrement dans le fichier index
est de 29 octets, l’espace disque requis pour l’indexation de la table des films
se calcule comme suit :
Nombre de lignes dans 1 bloc de FP = (1024 / 120) *0,75 = 6 lignes
Nombre de blocs à allouer à FP = 1 000 000 / 6 = 166 667 blocs
Espace à allouer à FP = 166 667 * 1024 octets = 170 667 008 octets
Nombre de lignes dans 1 bloc de FI = (1024 / 29) * 0,95 = 33 lignes
Nombre de blocs à allouer à FI = 166 667/ 33 = 5 051 blocs
Espace à allouer à FI = 5 051 * 1024 = 5 172 224 octets.

c) La recherche d’une ligne dans une table indexée

Lorsqu’une table est indexée sur une clé de recherche « c » à l’aide d’un
index non dense, la recherche des lignes où « c » contient une certaine

366
SGBD relationnels – Tome 1, État de l’art

valeur ciblée s’effectue de la façon suivante :


• Dans une première étape, on doit rechercher dans le fichier index FI
(stockée disons dans « n » blocs), en commençant par la première
ligne, la dernière ligne où la valeur de la clé de recherche est
inférieure ou égale à la valeur ciblée.
Si la recherche dans le fichier index FI est séquentielle, cette étape
nécessitera en moyenne le transfert successif en mémoire centrale de
« n/2 » blocs de ce fichier index non dense FI et la recherche de cette
ligne, dans chaque bloc transféré en mémoire centrale.
Si en revanche la recherche s’effectue de façon dichotomique après
avoir chargé en mémoire centrale une image ne contenant que
l’adresse de chaque bloc de ce fichier index FI, ceci ne nécessitera au
plus que le transfert successif en mémoire centrale de log2 (n)
blocs, correspond à la profondeur de l’arbre binaire de recherche
sous-jacent.
• Dans une deuxième étape, on doit charger en mémoire centrale, le
bloc du fichier principal dont l’adresse est contenue dans la ligne
déterminée par la première étape et y rechercher les lignes dont la
clé de recherche contient la valeur ciblée.

N.B. : Lorsque « logb (n) » est un nombre fractionnaire, « logb (n) »


correspond au nombre entier immédiatement supérieur. Ainsi : « 4,2 =
4,8 = 5 = 5 ».

Exemple 7.6.3.iii : Ainsi, pour rechercher dans la table réduite des films
indexée dans l’exemple 7.6.3.i, le film dont la clé de recherche contient la
valeur « GUIMBIA », il faut dans un premier temps rechercher dans le
fichier index FI, en commençant par la première ligne, la dernière ligne où
la valeur de la clé de recherche est inférieure ou égale à « GUIMBIA ». Cette
première étape retourne comme résultat la ligne du fichier index où la
valeur de la clé de recherche est « DRUM ». Dans une deuxième étape, il
faut transférer en mémoire centrale le bloc du fichier principal dont
l’adresse est contenue dans la ligne déterminée par la première étape, c’est-
à-dire, le deuxième bloc du fichier principal de la table réduite des films. La
recherche en mémoire centrale dans le contenu de ce bloc permet de

367
Joachim TANKOANO

retrouver en troisième position la ligne recherchée, c’est-à-dire, la ligne


dont la clé de recherche contient la valeur « DRUM ».

Exemple 7.6.3.iv : En supposant que, pour ce qui concerne la table non


réduite des films, la clé de recherche a une longueur de 25 octets et que
l’adresse d’un bloc dans le fichier index FI est de 4 octets, on peut, en
s’appuyant sur les résultats des calculs effectués dans l’exemple 7.6.3.ii,
calculer comme suit le temps moyen pour accéder à une ligne de cette table,
en distinguant le cas où la recherche dans le fichier index FI est séquentielle
et le cas où cette recherche est dichotomique :
Nombre de blocs alloués à FI = 5 051 blocs
Temps moyen pour l’accès à un enregistrement dans le cas d’une
recherche séquentielle dans FI
= ((5 051 / 2) +1) * 8,25 ms = 20 844 ms = 20 sec 844 ms
Temps maximum pour l’accès à un enregistrement dans le cas d’une
recherche dichotomique dans FI
= (log2 (5 051) + 1) * 8,25 ms
= ( 12,30 + 1) * 8,25 ms
= 115,5 ms

Lorsque cette table n’est pas indexée, le temps moyen calculé dans
l’exemple 7.3.5.i est de 515 625 ms, soient 8mn 43s. Ce temps a été divisé par
plus de 4 464, grâce à la création d’un index non dense et au recours à une
recherche dichotomique dans le fichier index FI, ce qui montre très bien
l’intérêt de l’indexation d’une table à l’aide d’un index non dense

d) L’insertion d’une ligne dans une table indexée à l’aide d’un index non
dense

L’objectif de l’algorithme d’insertion d’une ligne dans une table indexée à


l’aide d’un index non dense doit être double : préserver le tri du fichier
principal FP et de son fichier index FI et n’effectuer les décalages de lignes
nécessaires que dans le bloc concerné et dans le pire des cas, en plus dans
un de ses blocs adjacents, afin de garantir un temps réduit de traitement

368
SGBD relationnels – Tome 1, État de l’art

pour l’insertion.

Lorsqu’une table est indexée sur une clé de recherche « c » à l’aide d’un
index non dense, l’insertion dans cette table d’une nouvelle ligne où la clé
de recherche « c » contient une certaine valeur s’effectue en procédant
comme suit :
• Dans un premier temps on doit, en parcourant le fichier index FI,
comme décrit dans le paragraphe c) ci-dessus, identifier le bloc « b »
du fichier principal FP où cette ligne devrait se trouver.
• Si ce bloc « b » n’est pas plein, on doit y effectuer les décalages
nécessaires pour préserver le tri du fichier principal FP et insérer la
nouvelle ligne dans ce bloc « b » à l’emplacement libéré.
• Si ce bloc « b » est plein et qu’il existe de la place dans le bloc
précédent ou suivant, on doit effectuer les décalages nécessaires
dans ce bloc « b » et dans un de ses blocs adjacents non pleins pour
préserver le tri du fichier principal FP, insérer la nouvelle ligne à
l’emplacement libéré et modifier en conséquence le fichier index FI
si dans un des blocs affectés par le décalage sa 1ère ligne n’est plus la
même.
• Si ce bloc « b » et ses blocs adjacents (précédant et suivant) sont
pleins, on doit allouer au fichier principal FP un nouveau bloc « b’ »,
répartir les lignes du bloc « b » et la ligne à insérer dans les deux
blocs « b » et « b’ » de manière à préserver le tri du fichier principal
FP et modifier en conséquence le fichier index FI en y insérant une
nouvelle ligne.

Exemple 7.6.3.v : Soit l’état ci-après de l’organisation physique de la table


réduite des films indexée à l’aide d’un index non dense.

Pour insérer le film « FINYE » de 1983 dans cette table, il faut d’abord
parcourir le fichier index FI, en commençant par la première ligne, pour
identifier sa dernière ligne où la valeur de la clé de recherche est inférieure
ou égale à « FINYE ». Cette première étape identifie la ligne du fichier index
FI où la valeur de la clé de recherche est « DRUM » et où la valeur du champ
« adresse de bloc » pointe sur le deuxième bloc du fichier principal FP où

369
Joachim TANKOANO

doit être inséré le film « FINYE ».


Fichier principal trié (FP)

TITRE ANNEE …
ALI ZAOUA 2001 …
AU NOM DU CHRIST 1991 …
(FI) BUUD YAM 1997 …
ALI ZAOUA
DRUM DRUM 2005 …
HEREMAKONO EZRA 2007 …
TEZA GUIMBA 1995 …

HEREMAKONO 2001 …
SARRAOUNIA 1987 …

TEZA 2009 …
TILAÏ 1991

Le transfert en mémoire centrale de ce deuxième bloc du fichier principal


FP et de ses blocs adjacents permet de constater qu’il est plein mais que le
bloc qui le suit n’est pas plein.

Ce constat doit amener à effectuer dans ces deux blocs les décalages
nécessaires pour une insertion qui préserve le tri du fichier principal FP.

L’insertion du film « FINYE » à l’emplacement libéré dans le deuxième bloc


du fichier principal et la mise à jour du fichier index FI pour tenir compte
du fait que la première ligne du troisième bloc du fichier principal FP a
maintenant comme valeur « GUIMBA » et non « HEREMAKONO »
conduit à l’état final qui doit résulter de l’insertion du film « FINYE ».

L’état intermédiaire qui découle du décalage et l’état final qui résulte de


l’insertion du film « FINYE » peuvent se schématiser comme suit :

370
SGBD relationnels – Tome 1, État de l’art

Fichier principal trié (FP)


TITRE ANNEE …
ALI ZAOUA 2001 …
AU NOM DU CHRIST 1991 …
(FI) BUUD YAM 1997 …
ALI ZAOUA
DRUM DRUM 2005 …
HEREMAKONO EZRA 2007 …
TEZA …

GUIMBA 1995 …
HEREMAKONO 2001 …
SARRAOUNIA 1987 …

TEZA 2009 …
TILAÏ 1991

Fichier principal trié (FP)


TITRE ANNEE …
ALI ZAOUA 2001 …
AU NOM DU CHRIST 1991 …
(FI) BUUD YAM 1997 …
ALI ZAOUA
DRUM DRUM 2005 …
GUIMBA EZRA 2007 …
TEZA FINYE 1983 …

GUIMBA 1995 …
HEREMAKONO 2001 …
SARRAOUNIA 1987 …

TEZA 2009 …
TILAÏ 1991

e) La suppression d’une ligne dans une table indexée à l’aide d’un index
non dense

Il s’agit de l’opération la plus simple. Elle se traduit par une suppression


logique de cette ligne au niveau du bloc concerné consistant en un
positionnement de flag dans un champ prévu dans la structure de cette
ligne.

371
Joachim TANKOANO

f) Les avantages et les inconvénients de l’index non dense

L’indexation d’une table à l’aide d’un index non dense présente les
avantages ci-après :
• Cette technique d’indexation est la plus simple à implémenter.
• Si un fichier index non dense est volumineux, il peut à son tour être
indexé, ce qui peut conduire à une hiérarchie d’index.

L’indexation d’une table à l’aide d’un index non dense présente également
des inconvénients :
• Le fichier principal et le fichier index de la table doivent être triés et
ce tri doit être préservé après chaque insertion ou modification de
ligne. Ceci amène à prévoir pour les modifications et les insertions
futures de lignes de l’espace libre dans chaque bloc concerné, ce qui
accroit l’espace disque requis pour le stockage de ces deux fichiers.
• L’index est dit plaçant, dans le sens que son contenu dépend de
l’ordre des lignes contenues dans le fichier principal. De ce fait, il est
impossible de créer sur une table plusieurs index non denses, basés
sur des clés de recherche différentes.
• L’espace occupé par un fichier index non dense peut être non
négligeable. En d’autres termes, ce qu’on gagne en temps d’accès, on
le perd en occupation de l’espace disque disponible.

7.6.4. Les index denses

Dans ce type d’indexation d’une table, le fichier index dense FID doit
contenir une ligne pour chaque ligne du fichier principal. Chaque ligne du
fichier index dense FID doit être constituée de deux champs : un champ
« adresse de ligne » qui contient l’adresse de la ligne qui lui correspond
dans le fichier principal et un champ « valeur de clé » qui contient la valeur
de la clé de recherche de cette ligne. Le champ « adresse de ligne » doit être
constitué d’un sous-champ « adresse de bloc » qui contient l’adresse du
bloc où se trouve la ligne et d’un sous-champ « adresse de ligne dans le
bloc » qui permet d’accéder à cette ligne dans ce bloc.

372
SGBD relationnels – Tome 1, État de l’art

Dans ce type d’organisation physique des données d’une table, le fichier


principal FP peut ne pas être trié par rapport à la clé de recherche. Seul le
fichier index dense FID doit l’être.

Les blocs du fichier principal, qui est le fichier le plus encombrant, peuvent
donc être remplis au maximum.

En outre, l’index dense est un index non-plaçant. Plusieurs fichiers index


denses peuvent être construits sur le même fichier principal FP pour des
clés de recherche différentes. Ainsi, il est possible par exemple de construire
sur le fichier principal un fichier index non dense FI à l’aide de la clé
primaire de la table concernée et plusieurs fichiers index denses FID à l’aide
des clés uniques de cette table ou de toute autre colonne ou groupe de
colonnes.

Exemple 7.6.4.i : L’indexation de la table réduite des films sur la date de


parution des films à l’aide d’un index dense conduit à une organisation
physique des données de cette table qui peut être schématisée comme ci-
dessous.
Fichier principal non trié (FP)

Fichier index TITRE ANNEE …


dense trié ALI ZAOUA 2001 … 0
(FID) AU NOM DU CHRIST 1991 … 1
1987 7 BUUD YAM 1997 … 2
1991 1
1991 9 DRUM 2005 … 3
1995 5 EZRA 2007 … 4
1997 2 GUIMBA 1995 … 5
2001 0
2001 6 HEREMAKONO 2001 … 6
2005 3 SARRAOUNIA 1987 … 7
2007 4 TEZA 2009 … 8
2009 8
TILAÏ 1991 … 9

373
Joachim TANKOANO

7.6.5. Les arbres B+

Cette technique d’indexation des tables est celle qui est la plus utilisée dans
la technologie des bases de données relationnelles. Elle combine
l’organisation et le fonctionnement des arbres B (ou arbres équilibrés) et les
techniques d’indexation à l’aide d’index denses et non denses. Ce qui suit
présente le principe général qui régit l’organisation physique des données
d’une table indexée à l’aide de cette technique et les implications de ce
principe général sur les algorithmes de recherche, d’insertion et de
suppression de lignes. Tout comme pour l’indexation à l’aide d’un index
non dense, les implications sur l’opération de modification d’une ligne ne
seront pas abordées.

a) Le principe général de l’organisation physique d’une table indexée à


l’aide d’un arbre B+

L’indexation d’une table à l’aide d’un arbre B+ conduit à une organisation


physique des données constituée d’une hiérarchie de fichiers (« FP », « FI1 »,
« FI2 », …, « Fik ») où :
• « FP » désigne le fichier principal, constitué de blocs doublement
chainés qui contiennent les lignes triées de cette table
• « FI1 » est un fichier index non dense construit sur ce fichier principal
« FP »
• « Fii+1 » est un fichier index non dense construit sur le fichier index
non dense « FIi »
• « Fik » est le fichier index non dense racine, constitué d’un seul bloc,
construit sur le fichier index non dense « FIk-1 »
• En prévision des insertions futures de lignes on considère de façon
arbitraire que :
o Le nombre de lignes dans chaque bloc du fichier principal
« FP » est compris entre « e » et « 2e – 1 » (où « 2e – 1 » désigne
le nombre maximum de lignes que peut contenir un bloc).
o Le nombre de lignes dans chaque bloc d’un fichier index non

374
SGBD relationnels – Tome 1, État de l’art

dense « FIi » est compris entre « d » et « 2d – 1 » (où « 2d – 1 »


désigne le nombre maximum de lignes que peut contenir un
bloc).
• La clé de recherche dans la 1ère ligne de chaque bloc d’un fichier
index non dense « FIi » est omise parce qu’inutilisée par les
algorithmes de manipulation de cette table.

Les blocs des fichiers index non denses « FI1 », « FI2 », … et « Fik »
constituent le fichier index de cette table et sont en général regroupés dans
un fichier unique où le premier bloc est celui du fichier index racine « Fik ».

Par ailleurs, il est possible d’intercaler entre le fichier principal « FP » et le


fichier index non dense « FI1 » un fichier index dense « FID » construit sur
le fichier principal « FP » et de construire le fichier index non dense « FI1 »
sur ce fichier index dense « FID » plutôt que sur le fichier principal.

Pour simplifier la présentation, nous considérons que chaque bloc du fichier


principal « FP » a une structure abstraite schématisée comme suit :

C1 R1 C2e-1 R2-1 S

Où :
• « Ci » désigne la clé de recherche dans la ligne « i »
• « Ri » désigne les autres colonnes restantes de la ligne « i »
• « S » est un pointeur vers le bloc suivant (N.B. : pour faciliter les
insertions, les blocs sont en général doublement chaînés)
• « » représente la zone non utilisée dans ce bloc.

De même, nous considérons que chaque bloc d’un fichier index non dense
« FIi » a une structure abstraite schématisée comme suit :

P1 C2 P2 Ci Pi C2d-1 P2d-1 S

C < C2 Ci ≤ C < Ci+1 C2d-1 ≤ C

375
Joachim TANKOANO

Où :
• Chaque ligne « i » est constituée de deux champs « Pi » et « Ci »
• « Pi » pointe sur le sous-arbre « i »
• « Ci » doit correspondre à la valeur de la clé de recherche dans la 1ère
ligne du 1er bloc de ce sous-arbre « i »
• « » désigne un sous-arbre (réduit à un bloc du fichier principal FP
s’il s’agit d’un bloc du fichier index non dense « FI1 »)
• « C » désigne une clé quelconque de ce sous-arbre
• « » représente la zone non utilisée dans ce bloc
• « S » est un pointeur vers le bloc suivant (N.B. : ici également, pour
faciliter les insertions les blocs sont en général doublement chaînés).

Exemple 7.6.5.i : Dans cet exemple, pour faire simple, nous posons « e = 2 »
et « d= 2 », ce qui veut dire que le nombre de lignes dans un bloc du fichier
principal « FP » varie entre 2 et 3 et que le nombre de lignes dans un bloc
d’un fichier index non dense « Fii » varie également entre 2 et 3. Nous
supposons aussi une table de 12 lignes qui contiennent les valeurs suivantes
pour ce qui concerne la clé de recherche : « 10, 20, 25, 30, 33, 40, 48, 50, 55,
60, 70 ».

Ce qui suit schématise l’organisation physique des données de cette table


lorsqu’elle résulte d’une indexation à l’aide d’un arbre B+.

5
FI2 (index racine) 0

2 4 6
FI1 5 0 0

1 2 2 3 3 4 4 5 5
0 0 5 0 3 0 8 0 5

6 7
0 0
FP
Comme on peut le constater, les nœuds de cet arbre qui matérialise

376
SGBD relationnels – Tome 1, État de l’art

l’organisation physique des données sur le disque sont des blocs et la


profondeur de cet arbre dans cet exemple est de 3.

Les lignes de cette table sont triées et réparties dans des blocs chaînés qui
constituent à la fois les feuilles de l’arbre et les blocs du fichier principal
« FP ». Conformément à nos hypothèses de base, ces blocs contiennent
chacun au minimum 2 lignes et au maximum 3 lignes.

Le fichier index non dense « FI1 » est construit sur le fichier principal « FP »
et le fichier index non dense « FI2 » sur le fichier index non dense « FI1 ». Le
fichier index non dense « FI2 » est constitué d’un seul bloc correspondant à
la racine de l’arbre B+. Conformément à nos hypothèses de base, chaque
bloc de ces deux fichiers index non denses « FI1 » et « FI2 » contient
également au minimum 2 lignes et au maximum 3 lignes. Dans chacun de
ces blocs, la clé de recherche de la 1ère ligne a été omise. La clé de recherche
contenue dans chacune des autres lignes possède la même valeur que la clé
de recherche de la 1ère ligne du bloc du sous-arbre pointé par cette ligne. Par
exemple, la valeur « 50 » de la clé de recherche dans la 2ème ligne du bloc
racine correspond à la valeur de la clé de recherche de la 1ère ligne du 1er
bloc du sous-arbre pointé par cette ligne.

b) La recherche d’une ligne dans une table indexée à l’aide d’un arbre B+

Lorsqu’une table est indexée sur une clé de recherche « C » à l’aide d’un
arbre B+, la recherche des lignes où « C » contient une valeur ciblée
s’effectue en parcourant l’arbre qui matérialise l’organisation physique des
données sur le disque.

Ce parcours doit se faire depuis le bloc racine qui est celui du fichier index
racine non dense « FIk » jusqu’au bloc du fichier principal « FP » qui doit
être celui qui contient les lignes recherchées. Au cours de ce parcours, dans
chaque bloc d’un fichier index non dense « FIi » atteint, on doit rechercher
la ligne qui contient la valeur de la clé de recherche la plus grande qui est
inférieure ou égale à la valeur ciblée et utiliser le pointeur de bloc contenu
dans cette ligne pour accéder au bloc suivant. En d’autres termes, ce
parcours doit se faire en lisant en mémoire centrale un bloc de chaque

377
Joachim TANKOANO

fichier index non dense et un bloc du fichier principal, c’est-à-dire, un


nombre de blocs égal à la profondeur de l’arbre qui matérialise
l’organisation physique des données sur le disque.

Exemple 7.6.5.ii : Si on considère l’arbre construit dans l’exemple précédent


7.5.5.i pour matérialiser l’organisation physique d’une table indexée à l’aide
d’un arbre B+, la recherche dans cette table de la ligne où la valeur de la clé
de recherche est « 48 », doit s’effectuer de la façon suivante :
• On doit rechercher, dans le bloc racine de l’arbre, la ligne qui contient
la valeur de la clé de recherche la plus grande qui est inférieure ou
égale à « 48 ». Cette recherche identifie la première ligne de ce bloc
où la valeur de la clé de recherche a été omise et où le pointeur pointe
sur le 1er bloc du fichier index non dense « FI1 » qui devient le bloc
où le parcours doit se poursuivre.
• On doit ensuite rechercher, dans ce 1er bloc du fichier index non
dense « FI1 », la ligne qui contient la valeur de la clé de recherche la
plus grande qui est inférieure ou égale à « 48 ». Cette deuxième
recherche identifie la 3ème ligne de ce bloc qui contient la valeur « 40 »
pour la clé de recherche et un pointeur sur le 3ème bloc du fichier
principal « FP » qui doit être le bloc qui contient la ligne où la valeur
de la clé de recherche est « 48 ».
• On doit enfin rechercher dans ce 3ème bloc du fichier principal « FP »
la ligne où la valeur de la clé de recherche est « 48 ». Cette dernière
recherche identifie la 2ème ligne de ce bloc.

c) L’insertion d’une ligne dans une table indexée à l’aide d’un arbre B+

Lorsqu’une table est indexée sur une clé de recherche « C » à l’aide d’un
arbre B+, l’insertion d’une nouvelle ligne où la clé de recherche « C »
contient une certaine valeur s’effectue en procédant comme suit :
• On doit rechercher, en procédant comme décrit dans le paragraphe
b) ci-dessus, le bloc « b » du fichier principal où cette ligne pourrait
s’insérer.

378
SGBD relationnels – Tome 1, État de l’art

• Si ce bloc « b » n’est pas plein, on doit y effectuer les décalages requis


pour une insertion qui préserve le tri du fichier principal « FP » et
insérer ensuite cette ligne à l’emplacement libéré.
• Sinon, si ce bloc « b » est plein, c’est-à-dire, s’il contient « 2e – 1 »
lignes, on doit allouer un nouveau bloc « b’ » au fichier principal
« FP », répartir les « 2e – 1 » lignes de « b » et la nouvelle ligne, dans
« b » et « b’ », à raison de « e » lignes par bloc en commençant par
« b » et insérer le bloc « b’ » après le bloc « b ». Ensuite, on doit
reconstruire le bloc du fichier index non dense qui contient la ligne
qui pointe sur « b » en y insérant de la même manière la ligne qui
doit pointer sur « b’ ». L’insertion d’une nouvelle ligne dans un bloc
d’un fichier index non dense peut se poursuivre jusqu’au bloc racine
de l’arbre qui constitue le fichier index non dense racine « FIk », si
chaque bloc concerné est plein.

Exemple 7.6.5.iii : Supposons une table vide indexée sur une clé de
recherche « C » à l’aide d’un arbre B+ dont l’organisation physique est
schématisée par l’arbre ci-dessous.

FI1
Bloc 0

FP
Bloc 1

L’insertion successive dans cette table des lignes où la valeur de la clé de


recherche est « 40 », « 20 » et « 60 » doit conduire à l’état ci-après de cet
arbre :

FI1
Bloc 0

2 4 6
0 0 0 FP
Bloc 1

379
Joachim TANKOANO

Le bloc 1 étant plein, l’insertion de la ligne où la valeur de la clé de recherche


est « 30 » doit conduire à l’état ci-après de cet arbre où :
• Le bloc 2 a été alloué au fichier principal « FP » parce que le bloc 1
est plein
• Les lignes « 20 », « 30 », « 40 » et « 60 » ont été réparties entre le bloc
1 et le bloc 2, à raison de 2 lignes par bloc, de manière à préserver le
tri du principal « FP »
• Une ligne contenant un pointeur vers le bloc 2 et la valeur « 40 »,
correspondant à la valeur de la clé de recherche de la 1ère ligne de ce
bloc, a été ajouté dans le bloc 0.

4
0 FI1
Bloc 0

2 3 4 6
0 0 0 0 FP
Bloc 1 Bloc 2

À partir de cet état, l’insertion de la ligne où la valeur de la clé de recherche


est « 25 » doit conduire à l’état ci-après où cette ligne a été insérée après
décalage dans le bloc 1 :
4
0 FI1
Bloc 0

2 2 3 4 6
0 5 0 0 0 FP
Bloc 1 Bloc 2

À partir de cet état, l’insertion dans cette table de la ligne où la valeur de la


clé de recherche est « 10 » doit conduire à l’état ci-après où :
• Le bloc 3 a été alloué au fichier principal « FP » parce que le bloc 1
est plein
• Les lignes « 10 », « 20 », « 25 », et « 30 » ont été réparties entre le bloc
1 et le bloc 3, de manière à préserver le tri du principal « FP »

380
SGBD relationnels – Tome 1, État de l’art

• Une ligne contenant un pointeur vers le bloc 3 et la valeur « 25 »,


correspondant à la clé de recherche dans la 1ère ligne de ce bloc, a été
ajouté dans le bloc 0.

2 4
5 0 FI1
Bloc 0

1 2 2 3 4 6
0 0 5 0 0 0 FP
Bloc 1 Bloc 3 Bloc 2

À partir de cet état, l’insertion de la ligne où la valeur de la clé de recherche


est « 48 » doit conduire à l’état ci-après où cette ligne a été insérée après
décalage dans le bloc 2 :
2 4
5 0 FI1
Bloc 0

1 2 2 3 4 4 6
0 0 5 0 0 8 0 FP
Bloc 1 Bloc 3 Bloc 2

À partir de cet état, l’insertion de la ligne où la valeur de la clé de recherche


est « 50 » doit conduire à l’état ci-après où :
• Le bloc 4 a été alloué au fichier principal « FP » parce que le bloc 2
est plein
• Les lignes « 40 », « 48 », « 50 » et « 60 » ont été réparties entre le bloc
2 et le bloc 4, de manière à préserver le tri du principal « FP »
• L’insertion, dans le bloc 0 qui est plein, d’une ligne contenant un
pointeur vers le bloc 4 et la valeur « 50 », correspondant à la clé de
recherche dans la 1ère ligne de ce bloc, a provoqué une allocation en
cascade du bloc 5 à « FI1 » et du bloc 6 à « FI2 » qui devient le
nouveau fichier index non dense racine.

381
Joachim TANKOANO

4
FI2 (index racine) 0
Bloc 6

2 5 FP
FI1
5 0
Bloc 0 Bloc 5

1 2 2 3 4 4 5 6
0 0 5 0 0 8 0 0
Bloc 1 Bloc 3 Bloc 2 Bloc 4

d) La suppression d’une ligne dans une table indexée à l’aide d’un arbre
B+

Lorsqu’une table est indexée sur une clé de recherche « C » à l’aide d’un
arbre B+, la suppression d’une ligne où la clé de recherche « C » contient
une certaine valeur s’effectue en procédant comme suit :
• On doit rechercher, en procédant comme décrit dans le paragraphe
b) ci-dessus, le bloc « b » du fichier principal qui contient cette ligne
à supprimer.
• Si après la suppression de cette ligne, ce bloc « b » contient au moins
« e » lignes, on doit s’arrêter là, après avoir recherché et reconstruit,
si c’est le cas, le bloc du fichier index non dense qui contient une ligne
qui pointe sur un sous-arbre où la ligne supprimée était la 1ère ligne
du 1er bloc de ce sous-arbre.
• Sinon, si après la suppression de cette ligne, le bloc « b » contient « e-
1 » lignes et si un de ses blocs voisins (disons « b’ ») contient plus de
« e » lignes, on doit transférer une ligne du bloc « b’ » vers le bloc
« b » et s’arrêter là, après avoir recherché et reconstruit, si c’est le cas,
les blocs des index non denses qui contiennent une ligne qui pointe
sur un sous-arbre contenant le bloc « b » ou le bloc « b’ », où la 1ère
ligne a changé.

• Sinon, si après la suppression de cette ligne, le bloc « b » contient « e-


1 » lignes et ses deux blocs adjacents « e » lignes chacun, on doit

382
SGBD relationnels – Tome 1, État de l’art

regrouper dans le bloc « b » ses lignes et les lignes de l’un de ses blocs
adjacents (disons « b’ ») pour former un bloc de « 2e-1 » lignes,
désallouer le bloc « b’ », reconstruire le bloc de l’index non dense
« FI1 » qui contient la ligne qui pointait vers « b’ » en supprimant
cette ligne. La suppression de cette ligne peut à son tour provoquer
un regroupement des lignes de deux blocs dans un seul bloc et la
désallocation d’un bloc, ce processus pouvant se poursuivre
jusqu’au niveau du bloc racine.

Exemple 7.6.5.iv : Soit une table indexée sur une clé de recherche « C » à
l’aide d’un arbre B+ dont l’organisation physique est schématisée comme
suit :
2 4
5 0 FI1
Bloc 0

1 2 2 3 4 4 6
0 0 5 0 0 8 0 FP
Bloc 1 Bloc 2 Bloc 3

La suppression de la ligne dont la clé de recherche contient la valeur « 60 »


conduirait à l’évolution ci-après du schéma précédent :
2 4
5 0 FI1
Bloc 0

1 2 2 3 4 4
0 0 5 0 0 8 FP
Bloc 1 Bloc 2 Bloc 3

Exemple 7.6.5.v : Soit une table indexée sur une clé de recherche « C » à
l’aide d’un arbre B+ dont l’organisation physique est schématisée comme
ci-dessous.

Après la suppression de la ligne dont la clé de recherche contient « 40 », la


ligne dont la clé de recherche contient « 48 » deviendra la 1ère ligne du bloc
5. Par ailleurs la ligne dont la clé de recherche contient « 55 » deviendra la
1ère ligne du bloc 6.

383
Joachim TANKOANO

4
FI2 (index racine) 0
Bloc 0

2 5 FP
FI1 5 0
Bloc 1 Bloc 2

1 2 2 3 4 4 5 5 6
0 0 5 0 0 8 0 5 0
Bloc 3 Bloc 4 Bloc 5 Bloc 6

De ce fait, la valeur de la clé de recherche dans la ligne du bloc 0 qui pointe


sur le sous-arbre qui a comme 1er bloc le bloc 5 et celle dans la ligne du bloc
2 qui pointe sur le sous-arbre qui a comme 1er bloc le bloc 6, deviendront
respectivement « 48 » et « 55 », comme indiqué ci-dessous.

4
FI2 (index racine) 8
Bloc 0

2 5
FI1 5 5
FP
Bloc 1 Bloc 2

1 2 2 3 4 5 5 6
0 0 5 0 8 0 5 0
Bloc 3 Bloc 4 Bloc 5 Bloc 6

Exemple 7.6.5.vi : Soit une table indexée sur une clé de recherche « C » à
l’aide d’un arbre B+ dont l’organisation physique est schématisée comme
ci-dessous.

Après la suppression de la ligne où la clé de recherche contient « 40 », le


nombre de lignes dans le bloc 5 devient inférieur à « e » qui vaut « 2 », ce
qui doit entrainer le regroupement de la ligne qui reste dans le bloc 5 avec
les lignes d’un de ses blocs adjacents, disons le bloc 4, dans le bloc 5 et la
désallocation du bloc 4.

La désallocation du bloc 4 doit entrainer la suppression de la ligne du bloc


1 qui pointe sur ce bloc, rendant le nombre de lignes dans le bloc 1 inférieur

384
SGBD relationnels – Tome 1, État de l’art

à « d » qui vaut également « 2 ». Ceci va à nouveau entrainer le


regroupement de la ligne qui reste dans le bloc 1 avec les lignes du bloc 2
dans le bloc 1 et la désallocation des blocs 2 et 0.

4
FI2 (index racine) 0
Bloc 0

2 5
FI1 5 0
FP
Bloc 1 Bloc 2

1 2 2 3 4 4 5 6
0 0 5 0 0 8 0 0
Bloc 3 Bloc 4 Bloc 5 Bloc 6

Ceci donne en définitive ce qui suit :


2 5
5 0 FI1
Bloc 1

1 2 2 3 4 5 6
0 0 5 0 8 0 0 FP
Bloc 3 Bloc 5 Bloc 6

e) L’efficacité de l’indexation d’une table à l’aide d’un arbre B+

Dès lors qu’une opération d’insertion, de suppression ou de modification


d’une ligne dans une table indexée n’engendre pas de coût prohibitif en
termes de temps de traitement du fait des décalages de lignes qui en
résultent, l’efficacité de la technique utilisée pour indexer cette table ne
dépend principalement que du coût de l’opération de recherche que
nécessite cette opération d’insertion, de suppression ou de modification de
cette ligne.

Lorsqu’une table est indexée sur une clé de recherche « C » à l’aide d’un
arbre B+, ce qui suit évalue le coût, en termes de nombre de blocs à lire,
d’une recherche de ligne contenant une certaine valeur ciblée de « C ».

Si « n » désigne le nombre de lignes dans le fichier principal « FP » de cette

385
Joachim TANKOANO

table et « p » le nombre de fichiers index non denses « FIi », ou en d’autres


termes le nombre de niveaux (ou la profondeur diminuée de 1) de l’arbre
qui matérialise l’organisation physique des données sur le disque, on peut
écrire les égalités ci-après :
Nombre maximum de blocs pour « FP » = n/e (car « e » lignes par bloc)
Nombre maximum de blocs pour « FI1 » = n/ed (car n/e lignes dans « FI1 » et
« d » lignes par bloc)
Nombre maximum de blocs pour « FI2 » = n/ed2 (car n/ed lignes dans « FI2 »
et « d » lignes par bloc)

Nombre maximum de blocs pour « FIi » = n/edi (par généralisation).

Pour le fichier index non dense racine « Fik », on a donc :


n/edk = n/edp = 1.

On en déduit que :
n/e = dp  p = logd (n/e)

En d’autres termes :
Nombre de fichiers index non denses « FIi » = p = logd (n/e).

En conclusion, on peut dire que dans une table indexée sur une clé de
recherche « C » à l’aide d’un arbre B+, la recherche de toute ligne dans cette
table nécessite au plus la lecture de :
logd (n/e) + 1 blocs (correspondant à la lecture d’un bloc dans chaque fichier
index non dense « FIi » et d’un bloc dans le fichier
principal « FP »).

Exemple 7.6.5.vii : Si on considère la table des films (1 000 000 de lignes, 8


lignes par bloc de 1 024 octets) on doit avoir pour les blocs du fichier
principal « FP » de cette table :
2e – 1 = 7  e = 4 (c’est-à-dire 7 lignes au maximum et 4 lignes au minimum
par bloc).

Si par ailleurs les fichiers index non denses de cette table sont composés de
lignes de 29 octets (25 octets pour la clé de recherche et 4 octets pour les
adresses de blocs) on aura pour ce qui concerne les blocs de ces fichiers

386
SGBD relationnels – Tome 1, État de l’art

index non denses :


Nombre de lignes par bloc = (1024/29) = 35,31 = 36 lignes dont 1 de 4 octets.

2d – 1 = 35  d= 18 (c’est-à-dire 35 lignes au maximum et 18 lignes au minimum


par bloc).

Ceci permet de déduire dans le tableau ci-après le nombre de blocs requis


pour le stockage des fichiers FP et FIi, en considérant d’une part, le cas où
chaque bloc du fichier principal et des fichiers index non denses contient
un nombre minimum de lignes et d’autre part, le cas où chaque bloc du
fichier principal et des fichiers index non denses contient un nombre
maximum de lignes :

Nombre minimum de lignes par Nombre maximum de lignes par bloc


bloc
FP 106 / 4 = 250 000 blocs 106 / 7 = 142 858 blocs
FI1 250 000 / 18 = 13 889 blocs 142 858 / 35 = 4 082 blocs
FI2 13 888 / 18 = 772 blocs 4 082 / 35 = 117 blocs
FI3 772 / 18 = 43 blocs 117 / 35 = 4 blocs
FI4 43 / 18 = 3 blocs 4 / 35 = 1 bloc
FI5 3 / 18 = 1 bloc
Nombre total de blocs requis : Nombre total de blocs requis :
250000 +13889+772+43+3+1 = 264 142858+4082+117+4+1 = 147 062 blocs
708 blocs

On en déduit que pour retrouver une ligne dans la table des films, il faut
lire en mémoire centrale 6 blocs dans le pire des cas et 5 blocs dans le
meilleur des cas (à raison de 1 bloc dans chaque fichier index non dense
« FIi » et 1 bloc dans le fichier principal « FP »). En rappel, dans l’exemple
7.3.5.i, nous avions calculé que lorsque la table n’est pas indexée, il faut lire
au maximum 125 000 blocs et en moyenne 62 500 blocs.

Pour stocker le fichier principal et ses fichiers index, il faut 264 708 blocs
dans le pire des cas et 147 062 blocs dans le meilleur des cas. Nous avions
calculé que lorsque cette table n’est pas indexée, il faut 125 000 blocs pour
la stocker.

Exemple 7.6.5.viii : Si dans le cas traité dans l’exemple précédent 7.6.5.vii

387
Joachim TANKOANO

on intercale un fichier index dense « FID » entre le fichier principal « FP »


et le fichier index non dense « FI1 », ce qui revient à construire l’arbre B+
sur le fichier index dense « FID » et non sur le fichier principal « FP », on
peut ne plus avoir à trier le fichier principal « FP » et on peut aussi de ce fait
remplir ses blocs au maximum.

En supposant que chaque ligne du fichier index dense « FID » contient 31


octets (25 octets pour la clé de recherche et 6 octets pour l’adresse d’une
ligne dans le fichier « FP »), on aura pour les blocs du fichier index dense
« FID » :
Nombre de lignes par bloc de « FID » = 1024/31 = 33,03 = 33 blocs

2e – 1 = 33  e = 17 (c’est-à-dire 33 lignes au maximum et 17 lignes au minimum


par bloc).

Si par ailleurs les fichiers index non denses de cette table sont composés
comme dans l’exemple précédent de lignes de 29 octets (25 octets pour la
clé de recherche et 4 octets pour les adresses de blocs), on aura pour ce qui
concerne les blocs de ces fichiers index non denses :
Nombre de lignes par bloc = (1024/29) = 35,31 = 36 lignes dont 1 de 4 octets.

2d – 1 = 35  d= 18 (c’est-à-dire 35 lignes au maximum et 18 lignes au minimum


par bloc).

Ceci permet de déduire le tableau ci-après :

Nombre minimum de lignes par Nombre maximum de lignes par


bloc bloc
FP 10 / 8 = 125 000 blocs
6 10 / 8 = 125 000 blocs
6

FID 106 / 17 = 58 824 blocs 106 / 33 = 30 304 blocs


FI1 58 824 / 18 = 3 268 blocs 30 304 / 35 = 866 blocs
FI2 3 268 /18 = 182 blocs 866 / 35 = 25 blocs
FI3 182 / 18 = 11 blocs 25 / 35 = 1 bloc
FI4 11 / 18 = 1 bloc
Nombre total de blocs requis : Nombre total de blocs requis :
125000+58824+3268+182+11+1 = 187 125000+30304+866+25+1 = 156 196
286 blocs blocs

On en déduit que pour retrouver une ligne dans la table des films, il faut
comme dans l’exemple précédent 7.6.5.vii lire 6 blocs dans le pire des cas et

388
SGBD relationnels – Tome 1, État de l’art

5 blocs dans le meilleur des cas (à raison de 1 bloc dans chaque fichier index
non dense « FIi », 1 bloc dans le fichier index dense « FID » et 1 bloc dans le
fichier principal « FP »).

Pour stocker le fichier principal et ses fichiers index, il faut 187 286 blocs
dans le pire des cas et 156 196 blocs dans le meilleur des cas.

f) Les avantages et inconvénients de l’indexation d’une table à l’aide


d’un arbre B+

L’indexation d’une table à l’aide d’un arbre B+ présente des avantages.


Lorsque la taille des blocs est suffisamment grande (8 à 16 ko), « p » la
profondeur de l’arbre qui matérialise l’organisation physique des données
sur le disque devient relativement petit, ce qui permet de retrouver tout
enregistrement après la lecture de 2 à 4 blocs. En outre, lorsqu’on intercale
un fichier index dense « FID » entre le fichier principal « FP » et le fichier
index non dense « FI1 », l’indexation devient non plaçant, ce qui permet :
(1) d’utiliser un fichier principal « FP » non trié, (2) de remplir ses blocs au
maximum (3) d’avoir la possibilité de construire plusieurs index B+ sur le
même fichier principal.

L’indexation d’une table à l’aide d’un arbre B+ présente également des


inconvénients. L’index nécessite un espace disque non négligeable et sa
gestion est relativement complexe. En outre, lorsque la cardinalité du
groupe de colonnes sur lequel la table est indexée est faible, la construction
d’un arbre B+ sur un fichier index dense « FID » peut conduire à des
performances moins bonnes que celles de la lecture séquentielle de cette
table.

7.6.6. Le hachage statique

a) Le principe général du hachage statique

Cette technique d’indexation amène à organiser le stockage physique d’une


table ayant « c » comme clé de recherche en se basant sur les valeurs
retournées par une fonction de hachage de cette clé de recherche, notée
« h(c) », pour répartir les lignes dans un nombre fixe de « p » paquets

389
Joachim TANKOANO

(constitués chacun d’un bloc ou de plusieurs blocs chaînés).

Pour chaque valeur possible de la clé de recherche « c », la fonction de


hachage « h(c) » doit retourner une valeur comprise entre « 0 » et « p-1 » et
pour plusieurs valeurs différentes de la clé de recherche, cette fonction de
hachage « h(c) » doit pouvoir retourner la même valeur. Pour chaque valeur
possible de la clé de recherche, la valeur retournée par la fonction de
hachage « h(c) » doit être dans l’entrée d’un répertoire qui pointe sur le
paquet où se trouvent (ou se trouveront) toutes les lignes où la clé de
recherche contient cette valeur.

L’organisation physique des blocs d’une table indexée à l’aide d’une


fonction de hachage statique peut ainsi se schématiser de la façon suivante :

Répertoire Blocs de débordement


0 Bloc 1 Bloc 20

1 Bloc 5 Bloc 13

p-1 Bloc 10 Bloc 25

Dans ce schéma, l’entrée du répertoire contenant la valeur « 1 » pointe sur


le paquet constitué des blocs chaînés « 5, 13, … » où se trouvent (ou se
trouveront) toutes les lignes contenant une valeur de la clé de recherche
pour laquelle la fonction de hachage « h(c) » retourne « 1 » comme valeur.

Si la fonction de hachage « h(c) » est telle que les paquets ainsi constitués
sont équilibrés, cette technique d’indexation divise par « p » le temps
d’accès à toute ligne de la table concernée en limitant la recherche au paquet
pointé par l’entrée du répertoire qui contient la valeur hachée de la clé de
recherche de cette ligne. L’objectif doit être de faire en sorte qu’en plus
chaque paquet ne soit constitué que d’un seul bloc pour que l’accès à toute
ligne de la table concernée puisse se faire qu’en transférant un seul bloc en
mémoire centrale.

390
SGBD relationnels – Tome 1, État de l’art

Plus « p » est grand, moins il y aura des débordements dans les paquets et
plus la diminution du temps d’accès sera importante.

Par ailleurs, une copie de la table de hachage (c’est-à-dire, le répertoire) peut


être conservée en mémoire centrale si « p » n’est pas excessivement grand.

b) La fonction de hachage dans l’indexation à l’aide du hachage statique

Dans la pratique, il n’est pas aisé de trouver une fonction de hachage d’une
clé de recherche pouvant conduire à une répartition équilibrée des lignes
entre les paquets alloués à une table.

Lorsque la clé de recherche est un nombre entier, la fonction « MODULO »


est très fréquemment utilisée avec une valeur de « p » correspondant à un
nombre premier afin de définir comme suit la fonction de hachage :
h (c) = c mod p.

Lorsque la clé de recherche est une chaine de caractères, pour permettre la


définition d’une fonction de hachage qui utilise la fonction « MODULO »,
cette chaine de caractères peut être convertie en un nombre entier de
diverses manières, par exemple en divisant la chaîne de caractères en
groupes de caractères (disons de 2), en considérant la représentation binaire
de chaque groupe de caractères comme étant la représentation binaire d’un
nombre entier et en sommant ces nombres.

Exemple 7.6.6.i : Dans cet exemple, nous nous intéressons à l’indexation de


la table réduite des films sur la colonne « titre » en utilisant la fonction de
hachage définie comme suit :
h (titre) = rang (titre[0]) mod 3

Où : « rang (titre [0]) » désigne le rang dans l’alphabet du 1er caractère du titre
du film, en supposant que le rang du caractère « A » est « 1 ».

Si nous supposons que chaque bloc du disque peut contenir trois lignes,
l’indexation de cette table à l’aide de cette fonction de hachage conduit à
une organisation physique des données sur le disque qui peut être
schématisée comme suit :

391
Joachim TANKOANO

0 ALI ZAOUA 2001 … GUIMBA 1995 …


1 AU NOM DU CHRIST 1991 … SARRAOUNIA 1987 …
2 DRUM 2005 …
Répertoire
BUUD YAM 1997 … TEZA 2009 …
EZRA 2007 … TILAÏ 1991 …
HEREMAKONO 2001 …

Cet exemple met en évidence le fait qu’il n’est pas aisé de trouver une
fonction de hachage capable de garantir une répartition équilibrée entre les
paquets, quelles que soient les données contenues dans une table. Dans cet
exemple, le 1er paquet n’est constitué que d’un seul bloc qui de surcroit est
vide, alors que les 2 autres paquets débordent.

c) Les avantages et inconvénients du hachage statique

Les principaux avantages de l’indexation d’une table à l’aide du hachage


statique résident dans sa simplicité et dans le fait que, lorsqu’il n’y a pas de
débordement dans les paquets, ce qui veut dire que chaque paquet n’est
constitué que d’un seul bloc, l’accès à une ligne se fait toujours en ne
transférant en mémoire centrale qu’un seul bloc, peu importe la valeur de
sa clé de recherche.

Les points faibles de cette technique d’indexation concernent ce qui suit :


• Tout comme pour les autres techniques d’indexation d’une table
basées sur le hachage, présentées dans le paragraphe 7.6.7, les lignes
n’étant pas ordonnées, le hachage statique ne permet pas une
recherche efficace des lignes où la valeur de la clé de recherche est
comprise dans un intervalle de valeurs.
• Par ailleurs, tout comme pour les autres techniques d’indexation
basées sur le hachage, le hachage statique est une technique plaçant,
ce qui ne permet pas de définir et d’appliquer à une même table

392
SGBD relationnels – Tome 1, État de l’art

plusieurs fonctions de hachage portant sur des clés de recherche


différentes.
• Pour ce qui concerne de façon plus spécifique le hachage statique,
lorsque « p » est trop petit ou lorsque les données conduisent à une
très grande déviation statistique de la fonction de hachage par
rapport aux attentes, le nombre de débordements dans les paquets
peut devenir très important et entraîner une dégradation du temps
d’accès aux lignes. À l’opposé, lorsque « p » est trop grand les
débordements deviennent rares au détriment du taux d’occupation
des blocs qui lui peut devenir très faible, ce qui peut avoir comme
conséquence une mauvaise utilisation de l’espace disponible sur le
disque. En outre, cette technique d’indexation requiert la gestion
d’un répertoire. Toutefois, l’encombrement de ce répertoire est en
général faible, ce qui permet de réduire l’importance de
l’inconvénient lié à cette gestion en conservant une copie de ce
répertoire en mémoire centrale.

La technique d’indexation des tables à l’aide du hachage statique est très


peu utilisée dans la technologie des bases de données lorsqu’il s’agit de
tables contenant des données persistantes.

7.6.7. Le hachage dynamique

Les techniques d’indexation d’une table à l’aide du hachage dynamique ont


été développées pour apporter une solution aux deux points faibles ci-après
de l’indexation à l’aide du hachage statique :
• Le premier point concerne le fait qu’avec le hachage statique, il ne
soit pas aisé de trouver une valeur de « p », c’est-à-dire du nombre
de paquets à prévoir à l’avance pour le stockage des données d’une
table : (1) qui prend en compte les prévisions de son évolution en
termes de nombre de lignes, et (2) qui évite les débordements à
l’intérieur des paquets tout en garantissant en même temps un bon
taux de remplissage de ces paquets afin de permettre une
exploitation optimale de l’espace alloué sur le disque.
• Le deuxième point concerne le fait qu’il ne soit pas aisé non plus de

393
Joachim TANKOANO

trouver une fonction de hachage qui évite, dans tous les cas de figure,
des déviations statiques, afin de garantir une répartition équilibrée
des lignes entre les paquets.

Dans les techniques d’indexation à l’aide du hachage dynamique, la valeur


de « p » n’est pas fixée à l’avance, mais varie en fonction de l’évolution du
nombre de lignes qui existent réellement dans la table concernée. En outre,
ces techniques d’indexation sont conçues pour assurer dynamiquement une
répartition équilibrée des lignes entre les paquets alloués à une table.

Il existe deux grandes variantes de techniques d’indexation d’une table à


l’aide du hachage dynamique : l’indexation à l’aide du hachage
dynamique extensible et l’indexation à l’aide du hachage dynamique
linéaire.

Ce qui suit présente les principes généraux ainsi que les avantages et
inconvénients de ces deux variantes.

a) Le principe général du hachage dynamique extensible

Dans cette variante, le nombre d’entrées « p » du répertoire augmente de


façon progressive au cours de l’existence de la table concernée, en fonction
de l’augmentation du nombre de lignes qu’elle contient, mais reste toujours
égal à une puissance de 2.

En outre, un paquet peut être pointé par plusieurs entrées du répertoire,


dans le but d’assurer une répartition équilibrée des lignes, entre les paquets
alloués, en regroupant dynamiquement, chaque fois que cela est possible,
plusieurs paquets dans un seul paquet.

Cette approche utilise une fonction de hachage qui, pour chaque valeur
possible de la clé de recherche, génère une valeur hachée sous la forme
d’une chaîne de « N » bits où « N » est suffisamment grand pour ne pas
constituer une contrainte sur le nombre de lignes que peut contenir une
table. À cet effet, on peut fixer par exemple à 32 la valeur de « N ».

Au cours de l’évolution de l’organisation physique des données d’une table


indexée à l’aide du hachage dynamique extensible, lorsque la taille du
répertoire est « 2M », toutes les lignes de la table pour lesquelles les « M »

394
SGBD relationnels – Tome 1, État de l’art

derniers bits de la valeur hachée de leur clé de recherche sont identiques,


sont regroupées dans le même paquet. Pour déterminer, à partir du
répertoire, le paquet où se trouve (ou devrait se trouver) une ligne, seuls les
« M » derniers bits de la valeur hachée de la clé de recherche de cette ligne
sont de ce fait utilisés.

Au départ, « M » peut par exemple être égal à « 1 », ce qui veut dire que la
taille du répertoire est égale à « 21 » c’est-à-dire « 2 ». À cette étape initiale
de l’évolution de l’organisation physique des données, la 1ère entrée du
répertoire doit contenir la valeur binaire « 0 » et pointer sur le paquet qui
doit contenir toutes les lignes pour lesquelles la valeur hachée de leur clé de
recherche se termine par cette valeur binaire, c’est-à-dire « 0 ». Quant à la
2ème entrée du répertoire, elle doit contenir la valeur binaire « 1 » et pointer
sur le paquet qui doit contenir toutes les lignes pour lesquelles la valeur
hachée de leur clé de recherche se termine par cette valeur binaire, c’est-à-
dire « 1 ». Pour retrouver une ligne où le dernier bit de la valeur hachée de
sa clé de recherche est par exemple « 1 », il suffit de transférer en mémoire
centrale le paquet pointé par l’entrée du répertoire qui contient « 1 ».

Quand un paquet pointé par une seule entrée du répertoire doit déborder
après une insertion de ligne, on doit :
• Étendre le nombre de paquets alloués à la table concernée par
duplication de ce paquet plein
• Doubler la taille du répertoire, qui devient « 2M+1 » en dupliquant
chaque entrée : (1) par ajout du bit 0 à gauche de la valeur contenue
dans l’ancienne entrée et du bit 1 à gauche de la valeur contenue dans
le duplicata de l’ancienne entrée, (2) en faisant pointer le paquet
plein par son ancien pointeur et le nouveau paquet par le duplicata
de ce pointeur d’une part, et d’autre part, en faisant pointer les autres
paquets non concernés par l’insertion, par leurs anciens pointeurs et
leurs duplicatas
• Redistribuer la nouvelle ligne et les lignes du paquet plein entre ce
dernier et le nouveau paquet pour que chaque ligne soit dans le
paquet pointé par l’entrée du répertoire dont la valeur correspond
aux « M+1 » derniers bits de la valeur hachée de sa clé de recherche.

395
Joachim TANKOANO

Quand un paquet pointé par plus d’une entrée du répertoire doit déborder
après une insertion de ligne, on doit :
• Se contenter de dupliquer ce paquet plein sans toucher au répertoire
qui contient déjà toutes les entrées requises
• Répartir, la ligne à insérer, les lignes du paquet plein et les pointeurs
du répertoire qui le concernent, entre le paquet plein et le nouveau
paquet. Lorsque le paquet plein est pointé par plus de 2 entrées du
répertoire, chacun de ces 2 paquets (le paquet plein et le nouveau
paquet) doit être pointé, après la répartition, par les entrées du
répertoire dont la terminaison binaire des valeurs qu’elles
contiennent indiquent qu’elles sont issues de la même opération de
duplication.

Exemple 7.6.7.i : Dans cet exemple, nous nous intéressons à l’indexation de


la table réduite des films sur la colonne « titre », à l’aide du hachage
dynamique extensible. Supposons que dans l’organisation physique de
cette table, le nombre maximum de lignes par paquet soit de « 2 ».
Supposons aussi que la chaîne de bits générée par la fonction de hachage
pour le titre de chaque film soit celle indiquée dans le tableau qui suit :

Titre Valeur hachée générée par la fonction de hachage


ALI ZAOUA 01001010
AU NOM DU CHRIST 00100101
BUUD YAM 00110001
DRUM 00001000
EZRA 00100110
GUIMBA 00010001
HEREMAKONO 00111010
SARRAOUNIA 00010011
TEZA 00010101
TILAÏ 00001010

Si au départ « M » est égal à « 1 », ce qui veut dire que la taille du répertoire


est égale à « 21 » c’est-à-dire à « 2 » et si la table réduite des films ne contient
aucune ligne, ce qui suit schématise à ce stade initiale l’organisation
physique de cette table sur le disque :

396
SGBD relationnels – Tome 1, État de l’art

Répertoire Paquet 1
0
1
Paquet 2

À ce stade, toutes les lignes pour lesquelles la valeur hachée de la clé de


recherche se termine par « 0 » doivent être regroupées dans le paquet 1. De
même, toutes les lignes pour lesquelles la valeur hachée de la clé de
recherche se termine par « 1 » doivent être regroupées dans le paquet 2.

À partir de cet état de l’organisation physique de la table, l’insertion des


films « BUUD YAM (00110001) », « HEREMAKONO (00111010) » et
« DRUM (00001000) » doit donc conduire à l’état suivant :
Répertoire DRUM (00001000) Paquet 1
0 HEREMAKONO (00111010)
1
BUUD YAM (00110001) Paquet 2

À partir de cet état, l’insertion du film « Tilaï (00001010) » doit conduire à


l’état suivant où :
• Le paquet 3 a été alloué à la table concernée par duplication du
paquet 1, parce ce paquet où le film « Tilaï (00001010) » devrait être
inséré est plein
• L’entrée du répertoire qui contenait « 0 » a été dupliquée pour
donner une entrée et son duplicata qui contiennent respectivement
« 00 » et « 10 ». L’entrée qui contient « 00 » contient en plus un
pointeur sur le paquet 1 où ne doivent être regroupées maintenant
que les lignes pour lesquelles la valeur hachée de la clé de recherche
se termine par « 00 ». L’entrée duplicata du répertoire qui contient
« 10 » contient en plus un pointeur sur le nouveau paquet 3 où
doivent être regroupées toutes les lignes pour lesquelles la valeur
hachée de la clé de recherche se termine par « 10 »

397
Joachim TANKOANO

• L’entrée du répertoire qui contenait « 1 » a été dupliquée pour


donner une entrée et son duplicata qui contiennent respectivement
« 01 » et « 11 ». Ces deux entrées du répertoire pointent toutes les
deux sur le paquet 2 parce qu’il n’a pas été dupliqué et doit de ce fait
regrouper maintenant toutes les lignes pour lesquelles la valeur
hachée de la clé de recherche se termine par « 01 » ou par « 11 »

• La ligne à insérer, c’est-à-dire la ligne qui concerne le film « Tilaï


(00001010) » et les lignes du paquet 1 qui était plein ont été
redistribuées entre le paquet 1 et le paquet 3 nouvellement alloué
pour que chaque ligne soit dans le paquet pointé par l’entrée du
répertoire dont la valeur correspond aux deux derniers bits de la
valeur hachée de sa clé de recherche.

Répertoire DRUM (00001000) Paquet 1


00
01
10 BUUD YAM (00110001) Paquet 2
11

HEREMAKONO (00111010) Paquet 3


Tilaï (00001010)

À partir de cet état, l’insertion du film « Sarraounia (00010011) » doit


conduire à l’état suivant où ce film a été inséré dans le paquet 2 qui doit
regrouper toutes les lignes pour lesquelles la valeur hachée de la clé de
recherche se termine par « 01 » ou par « 11 » :

Répertoire DRUM (00001000) Paquet 1


00
01
10 BUUD YAM (00110001) Paquet 2
11 Sarraounia (00010011)

HEREMAKONO (00111010) Paquet 3


Tilaï (00001010)

À partir de cet état, l’insertion du film « GUIMBIA (00010001) » doit


conduire à l’état suivant où :

398
SGBD relationnels – Tome 1, État de l’art

• Le paquet 4 a été alloué à la table concernée par duplication du


paquet 2, parce que ce paquet où le film « GUIMBIA (00010001) »
devrait être inséré est plein. Ceci n’a pas nécessité une duplication
du répertoire, parce le paquet 2 est pointé par deux entrées du
répertoire, ce qui veut dire que les entrées du répertoire qui sont
requises existent déjà
• L’entrée du répertoire qui contient « 01 » est devenue la seule à
pointer sur le paquet 2 qui ne doit regrouper maintenant que les
lignes pour lesquelles la valeur hachée de la clé de recherche se
termine par « 01 »
• L’entrée du répertoire qui contient « 11 » pointe à présent sur le
paquet 4, qui doit regrouper toutes les lignes pour lesquelles la
valeur hachée de la clé de recherche se termine par « 11 »
• La ligne à insérer, c’est-à-dire la ligne qui concerne le film
« GUIMBIA (00010001) » et les lignes du paquet 2 qui était plein ont
été redistribuées entre ce paquet 2 et le paquet 4 nouvellement alloué
pour que chaque ligne soit dans le paquet pointé par l’entrée du
répertoire dont la valeur correspond aux deux derniers bits de la
valeur hachée de sa clé de recherche.

Répertoire DRUM (00001000) Paquet 1


00
01
10 BUUD YAM (00110001) Paquet 2
11 GUIMBIA (00010001)

HEREMAKONO (00111010) Paquet 3


Tilaï (00001010)

Sarraounia (00010011) Paquet 4

Exemple 7.6.7.ii : Dans cet exemple, nous supposons l’état ci-après de


l’organisation physique de la table réduite des films, où le paquet 2 est
pointé par 4 entrées du répertoire parce que le répertoire a été dupliqué

399
Joachim TANKOANO

deux fois pour l’insertion de lignes dans des paquets pleins autres que ce
paquet 2.

Répertoire DRUM (01001100) Paquet 1


000
001
010 EZRA (00100101) Paquet 2
011 Sarraounia (11010011)
100
101 HEREMAKONO (10111010) Paquet 3
110 Tilaï (10001010)
111
AU NOM DU CHRIST (01100110) Paquet 4

Dans cet état, ce paquet 2 doit regrouper toutes les lignes pour lesquelles
les trois derniers bits de la valeur hachée de la clé de recherche sont « 001 »,
« 101 », « 011 » ou « 111 ».

À ce stade, l’insertion du film « BUUD YAM (10001001) » doit conduire à


l’état suivant où :
• Le paquet 5 a été alloué à la table concernée par duplication du
paquet 2, parce que ce paquet où le film « BUUD YAM (10001001) »
devrait être inséré est plein. Ceci n’a pas nécessité une duplication
du répertoire parce le paquet 2 est pointé par quatre entrées du
répertoire, ce qui veut dire les entrées du répertoire qui sont requises
existent déjà

• Les entrées du répertoire qui contiennent les valeurs « 001 » et


« 101 », issues de la même opération de duplication parce qu’elles
ont la même terminaison binaire « 01 », sont devenues les seules à
pointer sur le paquet 2 qui ne doit contenir maintenant que les lignes
pour lesquelles la valeur hachée de la clé de recherche se termine par
« 001 » ou par « 101 »

• Les entrées du répertoire qui contiennent les valeurs « 011 » et


« 111 », issues de la même opération de duplication parce qu’elles
ont la même terminaison binaire « 11 », sont les seules à pointer sur

400
SGBD relationnels – Tome 1, État de l’art

le paquet 5 nouvellement alloué qui doit regrouper toutes les lignes


pour lesquelles la valeur hachée de la clé de recherche se termine par
« 011 » ou par « 111 »
• La ligne à insérer, c’est-à-dire la ligne qui concerne le film « BUUD
YAM (10001001) » et les lignes du paquet 2 qui était plein ont été
redistribuées entre ce paquet 2 et le paquet 5 nouvellement alloué
pour que chaque ligne soit dans le paquet pointé par l’entrée du
répertoire dont la valeur correspond aux trois derniers bits de la
valeur hachée de sa clé de recherche.

Répertoire DRUM (01001100) Paquet 1


000
001
010 BUUD YAM (10001001) Paquet 2
011 EZRA (00100101)
100
101 HEREMAKONO (10111010) Paquet 3
110 Tilaï (10001010)
111
AU NOM DU CHRIST (01100110) Paquet 4

Sarraounia (11010011) Paquet 5

b) Le principe général du hachage dynamique linéaire

Dans cette variante du hachage dynamique, on utilise aussi comme dans


l’indexation d’une table à l’aide du hachage dynamique extensible, une
fonction de hachage de la clé de recherche pour générer une chaîne de « N »
bits où « N » est suffisamment grand pour ne pas constituer une contrainte
sur le nombre de lignes que peut contenir une table.

Dans l’organisation physique d’une table indexée à l’aide du hachage


dynamique linéaire, le fichier qui regroupe les blocs alloués à cette table est
conceptuellement constitué de deux zones : une zone primaire (où le
nombre de paquets doit à tout moment être une puissance de 2) et une zone

401
Joachim TANKOANO

d’extension de même taille.

Comme pour l’indexation d’une table à l’aide du hachage dynamique


extensible, ce fichier est étendu par duplication de paquet chaque fois
qu’une insertion doit se faire dans un paquet plein.

Toutefois, dans le cas de l’indexation d’une table à l’aide du hachage


dynamique linéaire, le paquet de la zone primaire qui doit être dupliqué
chaque fois qu’une insertion de ligne doit se faire dans un paquet plein n’est
pas nécessairement ce dernier, mais plutôt celui sur lequel est positionné
un curseur « p » qui parcourt les paquets de la zone primaire de façon
cyclique en se déplaçant sur le paquet suivant après chaque duplication.

Lorsque le paquet qui doit être dupliqué n’est pas celui où l’insertion doit
se faire, la ligne à insérer est mise dans une zone dite zone de débordement,
en attente du positionnement du curseur « p » sur ce paquet plein.

La fin du parcours de la zone primaire par le curseur « p » doit entrainer le


début d’un nouveau cycle avec une zone primaire 2 fois plus grande que la
précédente, constituée des anciennes zones, primaire et d’extension.

Au cours du « dème » cycle du parcours de la zone primaire par le curseur


« p », le rang de chaque paquet de cette zone primaire doit être compris
entre « 0 » et « 2d – 1 ». L’adresse de chaque paquet situé dans cette zone
primaire avant le curseur « p » et de son duplicata dans la zone d’extension,
est considérée comme étant constituée de « d+1 » bits correspondant aux
« d+1 » derniers bits de la valeur hachée de la clé de recherche de chaque
ligne qui se trouve (ou devrait se trouver) dans ce paquet. Quant à l’adresse
du paquet pointé par « p » ou de chaque paquet situé après « p », elle est
considérée comme étant constituée de « d » bits correspondant aux « d »
derniers bits de la valeur hachée de la clé de recherche de chaque ligne qui
se trouve (ou devrait se trouver) dans ce paquet.

Au début du « dème » cycle du parcours de la zone primaire par le curseur


« p », ce curseur doit pointer sur le 1er paquet de cette zone et l’adresse de
chaque paquet doit être constituée de « d » bits qui correspondant aux « d »
derniers bits de la valeur hachée de la clé de recherche de chaque ligne qui
se trouve (ou devrait se trouver) dans ce paquet.

402
SGBD relationnels – Tome 1, État de l’art

Chaque fois qu’un paquet pointé par le curseur « p » est dupliqué :


• L’adresse du bloc dupliqué doit passer à « d+1 » bits par ajout d’un
bit « 0 » à gauche de l’ancienne adresse
• L’adresse du duplicata dans la zone d’extension doit correspondre à
une adresse dérivée de cette nouvelle adresse du paquet dupliqué en
remplaçant le bit « 0 » ajouté à gauche par le bit « 1 »
• Les lignes qui étaient regroupées dans le paquet dupliqué, les lignes
mises dans la zone de débordement en attente d’une duplication de
ce paquet et éventuellement la ligne à insérer doivent être
redistribuées entre le paquet dupliqué et son duplicata pour que
chaque ligne soit dans le paquet dont la valeur de l’adresse
correspond aux « d+1 » derniers bits de la valeur hachée de sa clé de
recherche
• Le curseur « P » doit ensuite être déplacé sur le paquet suivant de la
zone primaire si le cycle en cours n’est pas terminé.

Ce qui suit schématise l’organisation abstraite sur le disque des données


d’une table indexée à l’aide du hachage dynamique linéaire.
P
ZONE DE DEBORDEMENT

Paquets Paquets restant à Paquets


dupliqués dupliquer duplicata
Adresse à Adresse à d bits Adresse à
d+1 bits d+1 bits

ZONE PRIMAIRE ZONE D’EXTENSION

Exemple 7.6.7.iii : Dans cet exemple, nous nous intéressons à l’indexation


de la table réduite des films sur la colonne « titre » à l’aide du hachage
dynamique linéaire. Supposons comme dans l’exemple 7.6.7.i précédent
que dans l’organisation physique de cette table, le nombre maximum de
lignes par paquet soit de « 2 ». Supposons aussi que la chaîne de bits
générée par la fonction de hachage pour le titre de chaque film soit celle
indiquée dans le tableau qui suit :

403
Joachim TANKOANO

Titre Valeur hachée générée par la fonction de


hachage
ALI ZAOUA 11110001
AU NOM DU CHRIST 01100110
BUUD YAM 10001001
DRUM 01001100
EZRA 00100101
GUIMBA 01110000
HEREMAKONO 10111010
SARRAOUNIA 11010011
TEZA 00010110
TILAÏ 10001010

Supposons que ce qui suit schématise l’organisation physique sur le disque


de cette table au début du 2ème cycle du parcours de la zone primaire par le
curseur « p ». Ceci veut dire qu’à ce stade on a « d = 2 » et que le rang des
paquets varie entre « 0 » et « (22-1) = 3 ».

00 DRUM (01001100)
p

01 BUUD YAM (10001001)


ALIZAOUA (11110001)

10 HEREMAKONO (10111010)
Tilaï (10001010)

11 Sarraounia (11010011)

Comme on peut le constater, dans cet état, le paquet d’adresse « 00 »


regroupe les lignes pour lesquelles les 2 derniers bits de la valeur hachée de
leur clé de recherche sont « 00 ». Le paquet d’adresse « 01 » regroupe les
lignes pour lesquelles les 2 derniers bits de la valeur hachée de leur clé de
recherche sont « 01 ». Le paquet d’adresse « 10 » regroupe les lignes pour
lesquelles les 2 derniers bits de la valeur hachée de leur clé de recherche
sont « 10 ». Le paquet d’adresse « 11 » regroupe les lignes pour lesquelles
les 2 derniers bits de la valeur hachée de leur clé de recherche sont « 11 ».

À partir de cet état, l’insertion du film « EZRA (00100101) » doit conduire à

404
SGBD relationnels – Tome 1, État de l’art

l’état suivant où :
• Le paquet d’adresse « 00 », correspondant au paquet pointé par le
curseur « p », a été dupliqué parce que le paquet d’adresse « 01 » où
devrait être inséré le film « EZRA (00100101) » est plein
• L’adresse du paquet dupliqué est devenue « 000 » par ajout du bit
« 0 » à gauche de son ancienne adresse
• L’adresse du paquet duplicata dans la zone d’extension a été dérivée
de la nouvelle adresse du paquet dupliqué en remplaçant le bit « 0 »
ajouté à gauche par le bit « 1 » pour donner « 100 »
• La seule ligne contenue dans le paquet dupliqué a été transférée dans
son paquet duplicata dans la zone d’extension, parce que l’adresse
de ce paquet duplicata est « 100 », ce qui correspond aux « 3 »
derniers bits de la valeur hachée de la clé de recherche de cette ligne
• Le film « EZRA (00100101) » a été placée dans la zone de
débordement en attente de la duplication du paquet d’adresse « 01 »
• Le curseur a été déplacé sur le paquet suivant de la zone primaire.

000

Zone de débordement
01 BUUD YAM (10001001) EZRA (00100101) p
ALIZAOUA (11110001)

10 HEREMAKONO (10111010)
Tilaï (10001010)

11 Sarraounia (11010011)

100 DRUM (01001100)

À partir de cet état, l’insertion du film « GUIMBIA (01110000) » doit


conduire à l’état suivant où, ce film dont les « 3 » derniers bits de la valeur
hachée de sa clé de recherche sont « 000 » a été de ce fait rangé dans le
paquet d’adresse « 000 ».

405
Joachim TANKOANO

000 GUIMBIA (01110000)

Zone de débordement
01 BUUD YAM (10001001) EZRA (00100101) p
ALIZAOUA (11110001)

10 HEREMAKONO (10111010)
Tilaï (10001010)

11 Sarraounia (11010011)

100 DRUM (01001100)

À partir de cet état, l’insertion du film « TEZA (00010110) » doit conduire à


l’état suivant où :
• Le paquet d’adresse « 01 » qui correspond au paquet pointé par le
curseur « p » a été dupliqué parce que le paquet d’adresse « 10 » où
devrait être inséré le film « TEZA (00010110) » est plein
• L’adresse du paquet dupliqué est devenue « 001 » par ajout du bit
« 0 » à gauche de son ancienne adresse.
• L’adresse du paquet duplicata dans la zone d’extension a été dérivée
de la nouvelle adresse du paquet dupliqué en remplaçant le bit « 0 »
ajouté à gauche par le bit « 1 » pour donner « 101 »
• Les lignes contenues dans le paquet dupliqué et la ligne contenue
dans la zone de débordement en attente d’une insertion dans ce
paquet ont été réparties entre ce paquet et son duplicata dans la zone
d’extension, en faisant en sorte que pour chaque ligne concernée les
« 3 » derniers bits de la valeur hachée de sa clé de recherche
correspondent à l’adresse du paquet où il se retrouve
• Le film « TEZA (00010110) » a été placé dans la zone de débordement
en attente de la duplication du paquet d’adresse « 10 ».
• Le curseur a été déplacé sur le paquet suivant de la zone primaire.

406
SGBD relationnels – Tome 1, État de l’art

000 GUIMBIA (01110000)

001 BUUD YAM (10001001)


ALIZAOUA (11110001)
Zone de débordement
10 HEREMAKONO (10111010) TEZA (00010110)
Tilaï (10001010)

11 Sarraounia (11010011)

100 DRUM (01001100)

101 EZRA (00100101)

À partir de cet état, l’insertion du film « AU NOM DU CHRIST


(01100110) » doit conduire à l’état suivant où :
• Le paquet d’adresse « 10 », qui correspond à la fois au paquet pointé
par le curseur « p » et au paquet où devrait être inséré le film « AU
NOM DU CHRIST (01100110) », a été dupliqué parce qu’il est plein
• L’adresse du paquet dupliqué est devenue « 010 » et l’adresse du
paquet duplicata dans la zone d’extension est « 110 »
• Les lignes contenues dans le paquet dupliqué, la ligne à insérer et la
ligne contenue dans la zone de débordement en attente d’une
duplication de ce paquet, ont été réparties entre ce paquet et son
duplicata dans la zone d’extension, en faisant en sorte que pour
chaque ligne concernée les « 3 » derniers bits de la valeur hachée de
sa clé de recherche correspondent à l’adresse du paquet où il se
retrouve
• Le curseur a été déplacé sur le paquet suivant de la zone primaire.

407
Joachim TANKOANO

000 GUIMBIA (01110000)

001 BUUD YAM (10001001)


ALIZAOUA (11110001)

010 HEREMAKONO (10111010)


Tilaï (10001010)

Sarraounia (11010011) p
11

100 DRUM (01001100)

101 EZRA (00100101)

110 AU NOM DU CHRIST (01100110)


TEZA (00010110)

c) Les avantages et inconvénients du hachage dynamique

La technique d’indexation d’une table à l’aide du hachage dynamique


extensible fait évoluer de façon progressive le nombre de blocs alloués à
une table en fonction du nombre de blocs requis pour stocker ses lignes et
résout en plus le problème lié aux débordements des paquets. Si chaque
paquet n’est constitué que d’un seul bloc, on peut accéder à toute ligne de
la table concernée en ne transférant qu’un seul bloc en mémoire centrale.
Toutefois, cette technique nécessite un répertoire.

Quant à la technique d’indexation d’une table à l’aide du hachage


dynamique linéaire, elle fait évoluer de façon progressive le nombre de
blocs alloués à une table en fonction du nombre de blocs requis pour stocker
ses lignes et ne nécessite pas la gestion d’un répertoire. Toutefois, cette
technique nécessite la gestion d’une zone de débordement.

Par ailleurs comme pour la technique d’indexation basée sur le hachage


statique, les techniques d’indexation basée sur le hachage dynamique

408
SGBD relationnels – Tome 1, État de l’art

souffrent de deux points faibles majeurs :


• Elles ne permettent pas des recherches efficaces des lignes qui
contiennent une valeur de la clé de recherche comprises dans un
intervalle de valeurs, les lignes de la table n’étant pas ordonnées sur
le disque.
• En outre, il s’agit de techniques d’indexation plaçant, ce qui ne
permet pas de les appliquer à une même table sur des clés de
recherche différentes.

De ce fait, les techniques d’indexation d’une table à l’aide d’un hachage


dynamique ne sont utilisées en général dans la technologie des bases de
données relationnelle par l’administrateur de la base de données que pour
l’organisation physique des données des tables clustérisées ou par le SGBD
que pour créer des tables temporaires dans le cadre de l’exécution de
requêtes SQL complexe.

7.6.8. Les Index bitmap

Cette technique d’indexation d’une table est utilisée principalement pour


les clés de recherche à faible cardinalité qui ne peuvent prendre qu’un
nombre limité de valeurs différentes.

a) Le principe général

Une table indexée sur une clé de recherche à l’aide d’un index bitmap est
stockée et organisée physiquement sur le disque dans deux fichiers : un
fichier principal FP qui contient toutes les lignes et un fichier index bitmap.

Dans le fichier index bitmap, chaque ligne est associée à la ligne de même
rang dans le fichier principal et chaque colonne représente une des valeurs
que la clé de recherche peut prendre. Un fichier bitmap contient donc autant
de lignes qu’il y a de lignes dans le fichier principal et autant de colonnes
que de valeurs différentes de la clé de recherche.

Dans chaque ligne d’un fichier index bitmap, chaque colonne doit contenir
comme valeur le bit « 0 », à l’exception de la colonne qui représente la

409
Joachim TANKOANO

valeur de la clé de recherche dans la ligne de même rang dans le fichier


principal, qui lui doit contenir le bit « 1 ». En d’autres termes, si on
considère une ligne d’un fichier index bitmap et la ligne de même rang dans
le fichier principal, seul une colonne dans la ligne du fichier index bitmap
doit contenir le bit « 1 » et cette colonne doit être celle qui représentr la
valeur de la clé de recherche dans la ligne du fichier principal.

Un fichier index bitmap est donc une matrice creuse, peu volumineuse,
constituée de « 0 » et de « 1 ».

Pour retrouver les adresses des lignes du fichier principal qui ont une
valeur donnée de la clé de recherche, il suffit de retrouver dans le fichier
index bitmap toutes les lignes où la colonne qui représente cette valeur
contient le bit « 1 ».

Exemple 7.5.8.i : Supposons que dans la table des films il existe une colonne
« Métrage » qui ne peut prendre que l’une des trois valeurs suivantes :
« court », « moyen », « long ». L’indexation de la table réduite des films à
l’aide d’un index bitmap en prenant comme clé de recherche la colonne
« Métrage » conduirait à une organisation physique des données qui peut
être schématisée comme suit :
Fichier principal Fichier index bitmap
TITRE ANNEE METRAGE … Court Moyen Long

0 TEZA 2009 Long …. 0 0 0 1


1 EZRA 2007 Court …. 1 1 0 0
2 DRUM 2005 Moyen …. 2 0 1 0
3 HEREMAKONO 2001 Long …. 3 0 0 1
4 ALI ZAOUA 2001 Moyen …. 4 0 1 0
5 BUUD YAM 1997 Court …. 5 1 0 0
6 GUIMBA 1995 Long …. 6 0 0 1
7 AU NOM DU CHRIST 1991 Long …. 7 0 0 1
8 TILAÏ 1991 Court …. 8 1 0 0
9 SARRAOUNIA 1987 Long …. 9 0 0 1

Les lignes « 0 », « 3 », « 6 », « 7 » et « 9 » du fichier index bitmap contiennent


dans la colonne « Long » le bit « 1 », parce que les lignes de même rang dans
le fichier principal contiennent dans la clé de recherche « METRAGE » la
valeur « Long ». Les lignes « 2 » et « 4 » du fichier index bitmap contiennent

410
SGBD relationnels – Tome 1, État de l’art

dans la colonne « Moyen » le bit « 1 », parce que les lignes de même rang
dans le fichier principal contiennent dans la clé de recherche
« METRAGE » la valeur « Moyen ». Les lignes « 1 », « 5 » et « 8 » du fichier
index bitmap contiennent dans la colonne « Court » le bit « 1 », parce que
les lignes de même rang dans le fichier principal contiennent dans la clé de
recherche « METRAGE » la valeur « Court ».

b) L’intérêt de l’index bitmap

Cette technique d’indexation est non-plaçant, ce qui permet d’indexer une


table en utilisant des clés de recherche différentes.

En outre, lorsque la cardinalité des clés de recherche est très faible, les
fichiers index bitmap qui en résultent sont très peu encombrants, ce qui
permet de charger leurs copies en mémoire centrale et de les utiliser pour
le traitement de requêtes complexes sur la table indexée sans avoir à accéder
au contenu du fichier principal stocké sur le disque, comme :
• Compter le nombre de lignes de cette table qui contiennent certaines
combinaisons de valeurs de ces clés de recherche, définies à l’aide
d’une expression booléenne.
• Rechercher les adresses des lignes qui contiennent certaines
combinaisons de valeurs de ces clés de recherche, définies également
à l’aide d’une expression booléenne.

7.6.9. Exercices

Exercice 7.6.9.i : On suppose que la table « PERSONNEL » et le disque sur


lequel est stockée cette table ont les caractéristiques ci-après :
• Taille d’une ligne : 80 octets
• Nombre de lignes : 50 000
• Taille d’un bloc : 1024 octets
• Temps moyen d’accès à un bloc : 10 ms.

411
Joachim TANKOANO

1) Si la table « PERSONNEL » doit être stockée en vrac dans un fichier


séquentiel, quel doit être l’espace disque requis et quel sera le temps
moyen d’accès à une ligne ?
2) Quel est l’espace disque requis pour l’indexation de la table
« PERSONNEL » si on fait les hypothèses suivantes :
• L’index est un index non dense.
• En prévision des insertions futures de lignes dans cette table, les
blocs du fichier principal doivent être rempli à 75% et les blocs
du fichier index non dense à 90 %.
• La clé de recherche sur laquelle l’indexation doit s’effectuer
occupe 8 octets et les pointeurs de blocs 4 octets.
3) Que devient le temps moyen d’accès à une ligne de la table
« PERSONNEL » si la recherche dans l’index non dense se fait (i) de
façon séquentielle, (ii) de façon dichotomique ?
4) Quels sont l’espace minimum et l’espace maximum requis pour indexer
la table « PERSONNEL » en utilisant un arbre B+ ? Que devient le
temps maximum pour accéder à une ligne de cette table ?
5) Si on suppose que le stockage d’un pointeur vers une ligne du fichier
principal nécessite 6 octets, quels sont l’espace minimum et l’espace
maximum requis si l’arbre B+ est construit sur un index dense ? Que
devient le temps maximum pour accéder à une ligne de la table
« PERSONNEL » ?

Exercice 7.6.9.ii : Soit le schéma ci-dessous de l’organisation physique d’une


table indexée à l’aide d’un arbre B+ où « e = d = 2 ».
1) À partir de cette situation, quel sera l’état de l’arbre B+ après l’insertion
de la ligne où la clé de recherche est « 05 » ?
2) Même question pour une ligne où la clé de recherche est « 71 » ?
3) À partir de cette situation, quel sera l’état de l’arbre B+ après la
suppression de la ligne où la clé de recherche est « 58 » ?
4) Même question pour une ligne où la clé de recherche est « 44 » ?

412
SGBD relationnels – Tome 1, État de l’art

FI2 (index racine) 1 5


5 4

FP
0 3 4 7
FI1 0 0 0
7

0 0 0 1 5 5 7 7 8
1 3 7 0 4 8 0 8 0

1 2 2 3 3 4 4
5 0 6 0 9 0 4

Exercice 7.6.9.iii : On suppose que la chaîne de bits générée par la fonction


de hachage pour le titre de chaque film de la table réduite des films est celle
indiquée dans le tableau qui suit :

Titre Valeur hachée générée par la fonction de hachage


ALI ZAOUA 11110001
AU NOM DU CHRIST 01100110
BUUD YAM 10001001
DRUM 01001100
EZRA 00100101
GUIMBA 01110000
HEREMAKONO 10111010
SARRAOUNIA 11010011
TEZA 00010110
TILAÏ 10001010

1) Quelle serait l’évolution de l’organisation physique des données si cette


table est indexée à l’aide du hachage dynamique extensible et si les
films sont insérés dans l’ordre ci-après : EZRA, TILAÏ,
HEREMAKONO, AU NOM DU CHRIST, SARRAOUNIA, DRUM,
BUUD YAM ?
2) Même question pour le hachage dynamique linéaire si les lignes sont
insérées dans l’ordre ci-après : EZRA, BUUD YAM, HEREMAKONO,

413
Joachim TANKOANO

SARRAOUNIA, ALI ZAOUA, GUIMBA, AU NOM DU CHRIST,


TEZA.

7.7. La gestion de la mémoire relationnelle par le


SGBD Oracle
Dans la 1ère partie ci-dessus de ce chapitre, nous avons rappelé qu’il est
essentiel qu’au niveau physique la technologie des bases de données puisse
gérer le tockage d’un volume important de données et qu’elle puisse
garantir un temps d’accès raisonnable, indépendant de la volumétrie de ces
données. Nous avons vu que pour satisfaire cette exigence les SGBD
relationnels combinent principalement trois techniques, à savoir :
• Les techniques basées sur l’utilisation de stratégies de placement des
lignes de chaque table
• Les techniques basées sur l’utilisation d’une zone tampon entre la
base de données et les applications
• Les techniques basées sur l’indexation des tables.

Cette 2ème partie du chapitre examine, à titre d’exemple, la manière dont le


SGBD Oracle prend en compte et met œuvre ces techniques, à travers :
• Son architecture physique en termes de fichiers du système hôte
• L’organisation logique des données à l’intérieur de ces fichiers
• Son langage de définition des données du schéma physique d’une
base de données
• L’architecture interne de son serveur de bases de données qui assure
l’implémentation du moteur SQL.

7.7.1. L’architecture physique d’un SGBD Oracle en termes de


fichiers

Une installation du SGBD Oracle est physiquement constituée


principalement de 5 types de fichiers alloués au SGBD par le système hôte :

414
SGBD relationnels – Tome 1, État de l’art

• Le fichier de contrôle : À chaque installation du SGBD Oracle est


associé un fichier de contrôle indispensable pour son
fonctionnement. Ce fichier contient la cartographie de cette
installation. Il permet de retrouver : le nom de la base de données,
l’emplacement des autres fichiers (fichier des paramètres
d’initialisation (« SPFile »), fichier des mots de passe des utilisateurs,
fichiers de données, fichier de restauration (REDO LOG), fichiers de
sauvegarde, …), les informations sur les points de reprise, etc. Pour
garantir la sécurité d’une installation, Oracle permet de gérer sur des
disques différents plusieurs copies identiques de ce fichier.
• Le fichier des paramètres d’initialisation « SPFile » : il contient les
informations de configuration du SGBD.
• Le fichier des mots de passe des utilisateurs gérés localement par le
SGBD.
• Les fichiers de données utilisés pour le stockage des métadonnées du
catalogue du SGBD et des objets qu’elles décrivent (tables, index,
etc.).
• Le fichier REDO LOG : ce fichier fait partie du journal des transactions
et est de ce fait indispensable pour la restauration, en cas d’incident,
des données dans leur état antérieur cohérent le plus récent. Du fait
de son importance pour la sécurité des données, il existe en plusieurs
groupes de copies identiques qui évoluent par rotation suivant un
cycle prédéfini géré par le SGBD (voir le paragraphe 9.5.3).

7.7.2. L’organisation logique des données à l’intérieur des


fichiers

Le contenu des fichiers de données allouées au SGBD par le système hôte


est structuré logiquement par ORACLE en conteneurs de données qui
s’emboîtent les uns dans les autres. Il s’agit notamment des
« TABLESPACES », des « SEGMENTS », des « EXTENTS », des « BLOCS
DE DONNEES » ou « PAGES » et des lignes de tables.

Ce qui suit présente chacun de ces conteneurs.

415
Joachim TANKOANO

a) Les « TABLESPACES »

L’espace dédié au stockage des métadonnées du catalogue du SGBD et des


objets qu’elles décrivent est divisé en un ou plusieurs « TABLSPACES », le
1er étant créé lors de l’installation.

Chaque « TABLESPACE » recouvre l’espace d’un ou de plusieurs fichiers


de données alloués au SGBD par le système hôte. Un « TABLESPACE »
peut de ce fait s’étendre sur plusieurs disques, si les fichiers qu’il recouvre
sont sur des disques différents.

Chaque « TABLESPACE » est logiquement structuré en « SEGMENTS »,


créés à l’intérieur des fichiers que ce « TABLESPACE » recouvre et ces
« SEGMENTS » en « EXTENTS », constitués de « BLOCS » ou « PAGES »
contigus à l’intérieur d’un disque.

L’administrateur d’une base de données Oracle peut utiliser les


« TABLESPACES » pour contrôler la distribution du stockage des
métadonnées du catalogue du SGBD et des objets qu’elles décrivent afin de
pouvoir contrôler leur mise en ligne ou hors ligne ou de pouvoir effectuer
des sauvegardes et des restaurations partielles.

La syntaxe simplifiée de l’ordre SQL qui permet à l’administrateur de la


base de données de créer un « TABLESPACE » est la suivante :
CREATE TABLESPACE Nom_tablespace
DATAFILE Spécif_fichier [,..n]
[MINIMUM EXTENT entier [K|M]]
[DEFAULT Clause_storage]
[PERMANENT | TEMPORARY]
[ONLINE | OFFLINE] ;
Spécif_fichier :: = 'Chemin_fichier'
[SIZE entier [K|M|G|T]]
[AUTOEXTEND ON [NEXT SIZE entier [K|M|G|T]]]
Clause_storage ::= ([INITIAL entier [K|M]]
[NEXT entier [K|M]]
[MINEXTENTS entier]
[MAXEXTENTS {entier | UNLIMITED}
[PCTINCREASE entier])

416
SGBD relationnels – Tome 1, État de l’art

(1) La clause « DATAFILE » permet de spécifier les fichiers qui doivent


être alloués à ce « TABLESPACE » par le système hôte, en précisant
notamment le chemin d’accès de chaque fichier, sa taille et la quantité
d’espace disque qui doit être allouée pour chacune de ses extensions
futures.

(2) La clause « MINIMUM EXTENT » permet de spécifier la taille


minimale qu’un « EXTENT » est autorisé à avoir dans ce
« TABLESPACE ». Cette clause permet notamment d’éviter l’émiettement
de l’espace disque alloué au SGBD et de contrôler le regroupement des
données qui appartiennent à chaque table de ce « TABLESPACE ».

(3) La clause « DEFAULT » sert à la définition des paramètres de


stockage par défaut des « SEGMENTS » de ce « TABLESPACE ». Ces
paramètres sont utilisés pour calculer la taille que doit avoir le 1er
« EXTENT » et les « EXTENTS » suivants alloués à un « SEGMENT ».

(4) Les clauses « PERMANENT » et « TEMPORARY » servent à définir


ce « TABLESPACE » comme devant contenir des données persistantes ou
temporaires.

(5) Les clauses « ONLINE » et « OFFLINE » permettent de monter et de


démonter ce « TABLESPACE ».

b) Les « SEGMENTS »

Chaque « TABLESPACE » est constitué de segments qui sont


principalement de quatre types :
• Les segments de données, correspondant à des tables, à des partitions
de tables ou à des clusters de tables
• Les segments d’index, contenant des index ou des partitions d’index
partitionnés
• Les segments temporaires, créés pour les besoins de l’exécution d’un
ordre SQL et supprimés à la fin de cette exécution.
• Les « ROLLBACK SEGMENTS », qui constituent la partie du journal
des transactions utilisée pour mémoriser l’image des données avant

417
Joachim TANKOANO

leur modification, d’une part, pour gérer les accès concurrents aux
données et d’autre part, pour permettre l’annulation des
modifications effectuées par une transaction en cas de reprise après
un incident (voir chapitre 9).

Chaque segment est stocké physiquement soit dans un seul fichier de


données, soit dans plusieurs fichiers de données (alloués éventuellement
sur des disques différents) d’un seul « TABLESPACE ».

c) Les « EXTENTS »

L’« EXTENT » constitue l’unité d’allocation de l’espace disque à un


segment.

Chaque « EXTENT » alloué à un segment est constitué physiquement de


BLOCS DE DONNEES contigus à l’intérieur d’un fichier de données.

Les « EXTENTS » sont alloués à un segment à la demande en fonction des


besoins et en tenant compte des paramètres de stockage définis au niveau
du « TABLESPACE » ou du segment. Les « EXTENTS » alloués à un
segment ne sont donc pas nécessairement contigus et peuvent être dans des
fichiers de données différents d’un « TABLESPACE » se trouvant sur des
disques différents.

Dans un fichier de données, de fréquentes allocations et libérations


d’« EXTENTS » peut entrainer une fragmentation importante de son
espace qui rend impossible de nouvelles allocations.

d) Les « BLOCS DE DONNEES » ou « PAGES »

Au niveau le plus fin, les données stockées dans un « TABLESPACE » sont


regroupées en BLOCS DE DONNEES ou « PAGES ».

Chaque BLOC DE DONNEES est composé d’un nombre déterminé de blocs


physiques contigus du système hôte. Par défaut, la taille d’un BLOC DE
DONNEES Oracle est de 8 ko.

Quel que soit le type de segment, chaque BLOC DE DONNEES a la


structure ci-après, où :

418
SGBD relationnels – Tome 1, État de l’art

• « Common and Variable Header » contient des informations


générales sur ce bloc (adresse, type de segment, …).
• « Table Directory » contient des informations sur la table dont les
lignes sont stockées dans ce bloc.
• « Row Directory » contient des informations sur les lignes stockées
dans ce bloc, y compris l’adresse de chaque ligne dans la partie
« Row Data ».
• « Row Data » contient les lignes stockées dans ce bloc.
• « Free Space » correspond à l’espace disponible pour la modification
des lignes de ce bloc, l’insertion de nouvelles lignes et le stockage des
informations sur les transactions concurrentes qui ont accès
simultanément au contenu de ce bloc. Les paramètres pour la gestion
de cet espace sont :
o « PCTFREE » et « PCTUSED », qui définissent
respectivement le pourcentage de remplissage pour bloquer
et pour reprendre les insertions de lignes dans un bloc.
o « INITRANS » qui définit le nombre minimum d’entrées à
prévoir pour la gestion des transactions concurrentes qui ont
accès simultanément au contenu d’un bloc.

419
Joachim TANKOANO

e) Les lignes de tables

Les lignes d’une table sont stockées dans la partie « Row Data » des BLOCS
DE DONNEES sous forme d’enregistrements de longueur variable.

Le « ROWID » de chaque ligne l’identifie de façon unique et permet de la


localiser sur le disque. Il est codé sur 10 octets et est affichable à l’aide de 18
caractères qui fournissent les informations ci-après sur cette ligne :
• « 000000 », le numéro de l’objet (table, index, …) qui la contient.
• « FFF », le numéro relatif du fichier de données dans le
« TABLESPACE » qui la contient.
• « BBBBBB », le numéro relatif du bloc dans le fichier de données qui
la contient.
• « RRR », sa position dans le répertoire de lignes de l’entête du BLOC
DE DONNEES qui la contient.

Le « ROWID » d’une ligne offre le moyen le plus rapide pour accéder à une
ligne et est utilisable dans les requêtes SQL sous forme de pseudo-colonne.

Chaque ligne d’un BLOC DE DONNEES comprend deux parties :


• Un entête de ligne qui contient le nombre de colonnes dans la ligne, le
« ROWID » de la suite de la ligne lorsque celle-ci s’étend sur
plusieurs blocs, les informations de verrouillage de la ligne
• Les données de cette ligne composées pour chaque colonne de la
longueur et de la valeur de cette colonne.

7.7.3. Le langage de définition du schéma physique d’une base


de données

Dans le chapitre 4, nous avons vu que le langage de définition des données


de SQL permet de définir et de modifier à la fois le schéma logique, les
schémas externes et le schéma physique d’une base de données. Nous y
avons présenté les ordres SQL du langage de définition des données du
SGBD Oracle (« CREATE TABLE », « ALTER TABLE », « CREATE
VIEW », « CREATE SEQUENCE » et « ALTER SEQUENCE ») permettant

420
SGBD relationnels – Tome 1, État de l’art

de définir et de modifier le schéma logique et les schémas externes d’une


base de données en faisant abstraction de son schéma physique.

Oracle propose une grande variété d’ordres, de clauses, d’options et


d’options par défaut pouvant servir pour la définition du schéma physique
d’une base de données. L’organisation physique des données, leur stockage
et leur gestion sur les supports physiques ainsi que les algorithmes utilisés
pour l’accès à ces données en tenant compte de l’organisation logique
présentée dans le paragraphe 7.7.2 ci-dessus sont définis principalement
par ces ordres, clauses et options. Ces ordres, clauses et options permettent
la mise en œuvre de techniques spécifiques d’allocation de l’espace disque
pouvant favoriser le regroupement des lignes et de techniques d’indexation
des tables pouvant créer des chemins d’accès rapide à ces lignes.

Ce paragraphe présente, à titre d’exemples, les clauses de l’ordre


« CREATE TABLE » et les ordres plus spécifiques du langage de définition
des données (notamment « CREATE INDEX », « CREATE BITMAP
INDEX » et « CREATE CLUSTER ») les plus couramment utilisés pour la
définition du schéma physique d’une base de données.

a) L’ordre « CREATE TABLE »

Sa syntaxe simplifiée qui suit explicite, à titre d’exemple, quelques clauses


relatives au schéma physique d’une table. En rappel, la syntaxe des clauses
relatives au schéma logique « [([{Définition_colonne} [,..n]] [{Contrainte_ligne}
[,..n]])] » est présentée dans le chapitre 4.
CREATE TABLE [Nom_schéma.]Nom_table
[([{Définition_colonne} [,..n]]
[{Contrainte_ligne} [,..n]])]
[TABLESPACE Nom_Tablespace]
[PCTFREE entier]
[PCTUSED entier]
[INITRANS entier]
[STORAGE Clause_storage]
[LOGGING | NOLOGGING]
[CACHE | NOCACHE]
[AS Sous_requête] ;

421
Joachim TANKOANO

(1) La clause « TABLESPACE » permet à l’administrateur de la base de


données de choisir un « TABLESPACE » pour le stockage d’une table.

(2) Les clauses « PCTFREE », « PCTUSED » et « INITRANS »


permettent de redéfinir les paramètres de gestion par défaut de l’espace non
utilisé dans les « PAGES » où seront stockées les lignes de cette table.

(3) La clause « STORAGE » permet de définir des paramètres


spécifiques de stockage de cette table, en remplacement des paramètres de
stockage par défaut, définis au niveau du « TABLESPACE ».

(4) Les clauses « LOGGING » et « NOLOGGING » permettent


respectivement d’activer et de désactiver la journalisation des
modifications apportées dans cette table pour les reprises en cas d’incident.

(5) Les clauses « CACHE » et « NOCACHE » ont un effet équivalent


respectivement à une activation et à une désactivation de l’accès aux
« PAGES » de cette table, via la zone tampon gérée par le SGBD pour la
réduction du temps d’accès aux « PAGES ». Cette possibilité permet de
donner la priorité dans l’utilisation de ce mécanisme aux tables les plus
critiques en termes de performances attendues.

b) Les index B-TREE standards

L’index B-TREE standard d’Oracle est une implémentation de l’indexation


d’une table sur une clé de recherche à l’aide d’un arbre B+ construit sur un
fichier index dense « FID » du fichier principal « FP ».

Dans cette technique d’indexation d’une table, le fichier index dense


« FID » dont les « PAGES » constituent les feuilles de l’arbre B+ est trié
dans l’ordre croissant de la clé de recherche et ses « PAGES » sont
doublement chaînées pour permettre un parcours dans les deux sens.

Chaque entrée dans une « PAGE » du fichier index dense « FID » pointe
sur une ligne distincte du fichier principal « FP » et est composée de :
• Un entête qui contient le nombre de colonnes qui compose la clé de
recherche et les informations de verrouillage de cette entrée pour la
gestion des accès concurrents à la ligne concernée dans le fichier

422
SGBD relationnels – Tome 1, État de l’art

principal « FP »
• Une valeur de la clé de recherche composée, pour chaque colonne, du
couple « longueur et valeur de la colonne »
• Un « ROWID » qui pointe sur la ligne concernée dans le fichier
principal « FP ».

Dans cette solution, l’arbre B+ constitué du fichier index dense « FID » et


des fichiers index non denses (« FI1 », « FI2 », …, « FIk ») sont stockés
ensemble dans un segment d’index.

Quant au fichier principal « FP », qui peut ne pas être trié, il est stocké dans
un segment de données.

Une table peut ainsi être indexée sur plusieurs clés de recherche à l’aide
d’index B-TREE standards.

La syntaxe simplifiée de l’ordre « CREATE INDEX » qui permet d’indexer


une table sur une clé de recherche à l’aide d’un index B-TREE standard est
la suivante :
CREATE [UNIQUE] INDEX [Nom_schéma.]Nom_index
ON [Nom_schéma.]Nom_table
({Nom_colonne_clé_de_recherche [ASC | DESC]} [,..n])
[TABLESPACE Nom_Tablespace]
[PCTFREE entier]
[INITRANS entier]
[STORAGE Clause_storage]
[LOGGING | NOLOGGING]
[SORT | NOSORT] ;

(1) Cet ordre permet de donner un nom à l’index B-TREE standard et de


spécifier le nom de la table concernée ainsi que le groupe de colonnes de
cette table qui doit constituer la clé de recherche.

(2) Les clauses communes à l’ordre « CREATE TABLE » et à l’ordre


« CREATE INDEX » ont la même signification.

(3) Comme nous l’avons déjà vu, dans ce type d’indexation d’une table,
l’index dense doit être trié dans l’ordre croissant de la clé de recherche. Pour
que ce fichier soit trié par construction, Oracle trie préalablement le fichier

423
Joachim TANKOANO

principal, à moins que la clause « NOSORT » n’ait été spécifiée pour


indiquer que le fichier principal est déjà trié selon ce critère.

c) Les index à clé inversée

La différence qui existe entre un index B-TREE standard et un index à clé


inversée est que, dans le cas d’un index à clé inversée, les octets qui
composent la valeur de chaque clé de recherche sont inversés par le SGBD.
Si la valeur d’une clé de recherche est par exemple « 739201 » cette valeur
sera codée dans l’index par « 102937 ».

Lorsque les valeurs d’une clé de recherche sont générées en séquence pour
des opérations d’insertion en masse, l’utilisation d’un index à clé inversée
permet de répartir ces insertions sur l’ensemble des branches de
l’arborescence de l’index. En contrepartie, les index à clé inversée ne
facilitent pas la recherche de lignes lorsque la valeur de la clé de recherche
doit être comprise dans un intervalle de valeurs.

Comme on peut le voir dans ce qui suit, à l’exception de la clause


« REVERSE » qui sert à spécifier qu’il s’agit d’un index à clé inversée, la
syntaxe simplifiée de l’ordre de création d’un index à clé inversée est la
même que celle qui concerne la création d’un index B-TREE standard.
CREATE [UNIQUE] INDEX [Nom_schéma.]Nom_index
ON [Nom_schéma.]Nom_table
({Nom_colonne_clé_de_recherche [ASC | DESC]} [,..n])
[TABLESPACE Nom_Tablespace]
[PCTFREE entier]
[INITRANS entier]
[STORAGE Clause_storage]
[LOGGING | NOLOGGING]
REVERSE ;

d) Les tables INDEX-ORGANIZED

Une table INDEX-ORGANIZED d’Oracle est une table indexée sur la clé
primaire à l’aide d’un arbre B+ construit directement sur le fichier principal
« FP ».

424
SGBD relationnels – Tome 1, État de l’art

Le fichier principal « FP » doit être trié sur la clé primaire et ses « PAGES »
doivent constituer les feuilles de l’arbre B+.

Le fichier principal « FP » et les fichiers index non denses (« FI1 », « FI2 »,


…, « FIk ») construits sur ce fichier principal « FP » sont stockés et conservés
ensemble dans un segment d’index.

Chaque entrée d’une feuille de l’arbre B+ concerne donc une ligne distincte
de la table indexée et est composée de :
• Un entête qui contient le nombre de colonnes qui compose la ligne, le
nombre de colonnes qui compose la clé primaire, ainsi que les
informations de verrouillage de la ligne pour la gestion des accès
concurrents.
• Une valeur de la clé primaire composée, pour chaque colonne, du
couple « longueur et valeur de la colonne ».
• Les valeurs des autres colonnes de la ligne, constituées chacune du
couple « longueur et valeur de la colonne ».

Les tables INDEX-ORGANIZED permettent d’économiser les accès à une


table, l’accès à l’index étant suffisant pour retrouver les lignes recherchées.

À l’exception des clauses « ORGANIZATION INDEX »


« PCTTHRESHOLD », et « OVERFLOW », la syntaxe simplifiée de l’ordre
de création d’une table INDEX-ORGANIZED est la même que celle qui
concerne la création d’une table.
CREATE TABLE [Nom_schéma.]Nom_table
[([{Définition_colonne} [,..n]] [{Contrainte_ligne} [,..n]])]
ORGANIZATION INDEX
[TABLESPACE Nom_Tablespace]
[PCTFREE entier]
[PCTTHRESHOLD entier]
[OVERFLOW Attributs-segment]
[INITRANS entier]
[STORAGE Clause_storage]
[LOGGING | NOLOGGING]
[CACHE | NOCACHE]
[AS Sous_requête] ;

425
Joachim TANKOANO

(1) La clause « ORGANIZATION INDEX » définit la table comme


étant une table INDEX-ORGANIZED.

(2) La clause « PCTTHRESHOLD » sert à la spécification d’un


pourcentage (compris entre 1 et 50) de remplissage de l’espace d’une
« PAGE ». Lorsque l’espace requis par une ligne entraine un dépassement
de ce pourcentage, la colonne qui a provoqué ce dépassement et toutes les
colonnes de cette ligne qui suivent (exceptée la clé primaire) sont déplacées
dans un segment de débordement défini par la clause « OVERFLOW ».

e) Les index bitmap

Oracle offre la possibilité d’indexer une table à l’aide d’un index bitmap
lorsque la clé de recherche a une faible cardinalité.

L’index bitmap d’Oracle ne diffère de l’index B-TREE standard que par le


fait que dans chaque entrée du fichier index dense « FID » construit sur le
fichier principal « FP », le ROWID correspondant à l’adresse d’une ligne
dans le fichier principal « FP » est remplacé par la spécification d’un
bitmap.

Un bitmap est une séquence de bits où chaque bit correspond à une ligne
distincte du fichier principal « FP » et vaut « 1 » si la valeur de la clé de
recherche dans cette ligne est égale à la valeur de la clé de recherche dans
l’entrée du fichier index dense « FID » où se trouve ce bitmat et « 0 » sinon.
Chaque bit d’un bitmap peut être transformé en « ROWID » de la ligne qui
lui correspond à l’aide d’une fonction.

Chaque entrée d’une « PAGE » du fichier index dense « FID » est de ce fait
composée comme suit de :
• Un entête qui contient le nombre de colonnes qui compose la clé de
recherche et les informations de verrouillage de cette entrée pour la
gestion des accès concurrents.
• Une valeur de la clé de recherche composée, pour chaque colonne, du
couple « longueur et valeur de la colonne ».
• Le « ROWID » de départ qui désigne le « ROWID » de la ligne du
fichier principal « FP » correspondant au 1er bit du bitmap.

426
SGBD relationnels – Tome 1, État de l’art

• Le « ROWID » de fin qui désigne le « ROWID » de la ligne du fichier


principal « FP » correspondant au dernier bit du bitmap.
• Un bitmap où les lignes correspondant à ses bits se suivent dans le
fichier principal « FP ».

Cette organisation a comme conséquence un coût de mise à jour élevé.

La syntaxe simplifiée de l’ordre « CREATE BITMAP INDEX » qui permet


d’indexer une table sur une clé de recherche à l’aide d’un index bitmap est
la suivante :
CREATE BITMAP INDEX [Nom_schéma.]Nom_index
ON [Nom_schéma.]Nom_table
({Nom_colonne_clé_de_recherche [ASC | DESC]} [,..n])
[TABLESPACE Nom_Tablespace]
[PCTFREE entier]
[INITRANS entier]
[STORAGE Clause_storage]
[LOGGING | NOLOGGING]
[SORT | NOSORT] ;

f) Les clusters de tables

Un cluster de tables permet de regrouper physiquement, dans chacune de


ses pages, les lignes d’une table ou de plusieurs tables qui possèdent la
même valeur au niveau des colonnes définies comme étant la clé du cluster.

Ce groupage physique est un mécanisme transparent pour les applications


qui continuent à accéder aux tables concernées comme si elles n’étaient pas
clustérisées.

L’utilisation d’un cluster de tables permet de réduire le temps d’accès à


l’ensemble des lignes qui concernent une valeur donnée de la clé du cluster.
En contrepartie, le parcours séquentiel de toutes les lignes d’une table
contenue dans un cluster est plus lent.

Oracle distingue :
• Les clusters indexés qui utilisent un index qui a la même structure
qu’un index normal (c’est-à-dire un arbre B+) pour accéder aux

427
Joachim TANKOANO

pages qui contiennent les données clustérisées, c’est-à-dire


regroupées, sur la base d’une valeur donnée de la clé du cluster.
• Les clusters hash qui utilisent une fonction de hachage pour
déterminer la page qui contient les données clustérisées sur la base
d’une valeur donnée de la clé du cluster.

La création d’un cluster de tables s’effectue en deux ou trois étapes :


• La 1ère étape concerne, dans tous les cas, la création de ce cluster
• L’étape suivante concerne l’indexation de ce cluster à l’aide d’un arbre
B+, s’il s’agit d’un cluster indexé
• La dernière étape concerne, dans tous les cas, la création de chacune
des tables qui composent ce cluster.

La syntaxe simplifiée de l’ordre de création d’un cluster est la suivante :


CREATE CLUSTER [Nom_schéma.]Nom_cluster
({Nom_colonne_clé_cluster Type_colonne [SORT]} [,..n])
{INDEX | HASHKEYS entier [HASH IS expression]}
[TABLESPACE Nom_Tablespace]
[SIZE entier [K | M | G | T]
[PCTFREE entier]
[PCTUSED entier]
[INITRANS entier]
[STORAGE Clause_storage]
[CACHE | NOCACHE] ;

(1) L’ordre « CREATE CLUSTER » permet de donner un nom au


cluster de tables, de définir sa clé et de spécifier la fonction de hachage s’il
s’agit d’un cluster hash.

(2) La clause « SIZE » permet de spécifier la taille en octets de l’espace


requis dans une page pour le stockage d’un groupe de lignes des tables
clustérisées qui ont une valeur identique de la clé du cluster, ce qui permet
de déduire le nombre de groupes de lignes qui peuvent être stockés dans
une page. Par défaut chaque groupe de ligne est stocké dans une seule page.

La syntaxe simplifiée de l’ordre SQL de création d’un index de cluster est


la suivante :

428
SGBD relationnels – Tome 1, État de l’art

CREATE INDEX [Nom_schéma.]Nom_index


ON CLUSTER [Nom_schéma.]Nom_cluster
[TABLESPACE Nom_Tablespace]
[PCTFREE entier]
[INITRANS entier
[STORAGE Clause_storage]
[LOGGING | NOLOGGING]
[SORT | NOSORT] ;

Quant à la syntaxe simplifiée de l’ordre SQL de création d’une table dans


un cluster, elle est la suivante :
CREATE TABLE [Nom_schéma.]Nom_table
[([{Définition_colonne} [,..n]]
[{Contrainte_ligne} [,..n]])]
CLUSTER [Nom_schéma.]Nom_cluster
({Nom_colonne_clé_cluster} [,..n]) ;

(1) La clause « CLUSTER » permet d’inclure la table dans le cluster


spécifié.

(2) La clause « CLUSTER » permet en outre de spécifier les colonnes de


la table qui correspondent aux colonnes de la clé de ce cluster. Ces colonnes
doivent être spécifiées dans le même ordre que dans l’ordre de création du
cluster.

7.7.4. L’architecture interne d’un serveur de bases de données


Oracle

Le montage d’une base de données Oracle se traduit par le lancement de


processus de services d’arrière-plan et par l’allocation d’un espace en
mémoire centrale appelé « SGA (System Global Area »).

Une instance Oracle est constituée de l’ensemble de ces processus de


services d’arrière-plan et de la SGA. Ses paramètres d’initialisation sont
définis dans le fichier système « SPFile ».

Un serveur de base de données Oracle est constitué d’une ou de plusieurs


instances et d’une base de données Oracle. Chacune de ces instances
implémente un moteur SQL.

429
Joachim TANKOANO

Le rôle d’un serveur de base de données Oracle est de permettre l’exécution


simultanée de plusieurs requêtes SQL en garantissant la confidentialité et
l’intégrité des données, en gérant la concurrence, en permettant les reprises
en cas d’incidents et en s’appuyant notamment sur une zone tampon
allouée dans la « SGA » pour améliorer les performances globales du
système.

Pour ce faire, on retrouve dans la « SGA » :


• La zone tampon de la base de données (« DATABASE BUFFER
CACHE ») qui sert à stocker comme dans une antémémoire le
dernier état des blocs de la base de données les plus récemment
utilisés. L’objectif est de faire en sorte qu’à tout moment un bon
pourcentage des blocs de la base de données les plus fréquemment
utilisés soient en mémoire centrale afin d’éviter le plus possible les
accès aux disques où est stockée cette base de données
• La zone tampon du fichier de journalisation REDO LOG (« REDO LOG
Buffer ») dont le contenu n’est transféré dans le fichier REDO LOG
par un processus d’arrière-plan dédié à cette tâche qu’à des instants
prédéfinis à l’avance afin de permettre une exécution efficiente des
transactions : i) lorsque cette zone tampon est presque pleine, ii)
avant la synchronisation du contenu de la base de données avec celui
de sa zone tampon, iii) lors de la validation d’une transaction
• La zone mémoire partagée (« SHARED POOL ») qui sert à stocker des
informations comme le code des ordres SQL les plus récemment
exécutés, les données du dictionnaire des données les plus
récemment utilisées.

Les processus de services d’arrière-plan sont chargés quant à eux,


d’apporter les services requis pour le bon fonctionnement du serveur de
base de données et pour l’amélioration de ses performances globales. Ces
processus apportent aussi un support à l’ensemble des requêtes utilisateur
qui s’exécutent simultanément. Ces processus sont entre autres :
• Les processus d’écriture des blocs (« DBWn ») qui assurent le
transfert sur le disque des blocs modifiés qui se trouvent dans la zone
tampon de la base de données

430
SGBD relationnels – Tome 1, État de l’art

• Le processus d’écriture dans le journal (« LGWR ») qui assure le


transfert sur le disque du contenu de la zone tampon du fichier de
journalisation REDO LOG
• Le processus de création des points de reprise (« CKPT »)
• Le processus système (« SMON ») qui assure la vérification de la
cohérence et la restauration si nécessaire de la base de données lors
de son montage.
• Le processus moniteur des processus (« PMON ») qui assure la
reprise des processus lorsqu’ils se plantent.
• Les processus d’archivage (« ARCn ») qui assurent l’archivage du
fichier log quand il est plein ou lorsqu’une rotation doit s’effectuer.

7.8. Exercice

Rédigez par groupe de deux ou trois, sur la base d’une revue de documents
techniques de référence, un rapport de présentation de la gestion de la
mémoire relationnelle par le SGBD MySQL et par le SGBD PostgreSQL.

Pour chacun de ces deux SGBD, ce rapport doit faire ressortir les
mécanismes mis en œuvre pour améliorer les performances globales du
système et pour permettre un accès rapide aux lignes d’une table,
notamment à travers une étude des aspects ci-après :
• L’architecture physique d’une base de données en termes de fichiers
du système hôte.
• L’organisation logique des données à l’intérieur des fichiers du
système hôte.
• Le langage de définition des données du schéma physique d’une
base de données.
• L’architecture interne du serveur de base de données.

431
Joachim TANKOANO

BIBLIOGRAPHIE
Antonio Albano, Dario Colazzo, Giorgio Ghelli & Renzo Orsini:
Relational DBMS Internals - Copyright © 2015 by A. Albano, D.
Colazzo, G. Ghelli, R. Orsini

Chan C-Y. & Ioannidis Y.E.: Bitmap index Design and evaluation - ACM
SIGMOD Intl. Conf., SIGMOD Record V° 27, n° 2, Seattle, USA, 1998.

Comer D.: The Ubiquitous B-Tree, Computing Surveys - vol. 11, n° 2, juin 1979.

Date C.J. : Introduction aux bases de données - 8è édition - Vuibert, Paris, 2004

Fagin R., Nivergelt J., Pippengar N. & Strong H.R.: Extendible Hashing – A
Fast Access Method for Dynamic Files - ACM TODS, vol. 4, n° 3,
septembre 1979, p. 315-344.

Gardarin G. : Bases de données, 5e tirage 2003 – EYROLLES

Held G. & Stonebraker M. R.: Storage structures and access methods in


relational data base management system, INGRES – Proc. 1975 ACM-
PACIFIC, San Francisco, Calif., pp. 26-33, April 1975

Knott G. D.: Hashing functions – Computer J. 18, pp. 265-278, Aug. 1975

Knuth D. E.: The Art of Computer Programming - Volume 3, Addison Wesley,


Sorting and Searching (1973).

Larson P.: Dynamic Hashing - Journal BIT, n° 18, 1978, p. 184-201.

Litwin W.: Linear Hashing – A New Tool for File and Table Addressing - 6th
Very Large Data Bases, Montreal, octobre 1980, p. 224-232.

O’Neil P. & Quass D.: Improved Query Performance with Variant Indexes -
Proc. of the ACM SIGMOD Conf. on Management of Data (1997).

ORACLE: Oracle® Database Concepts 12c Release 1 (12.1) - July 2017

Silberschatz A., Korth H.F. & Sudarshan S.: Database system concepts, sixth
edition - McGraw-Hill, 2011

Tomás L. Christopher W. & Zduardo B.: Databases Buffer Paging in Virtual


Storage Systems – ACM TODS, Vol 2, No 4, Dec 1977

432
Chapitre 8. : Transformation des requêtes SQL
en code exécutable

8.1. Introduction
Pour camper le décor, ce chapitre commence par une présentation des
principes généraux du processus de transformation d’une requête SQL en
code exécutable afin de mettre en relief les principaux problèmes que ce
processus soulève et les processeurs requis pour sa mise en œuvre.

Les techniques mises en œuvre par les SGBD relationnels au niveau des
deux étapes clés de ce processus sont ensuite présentées, à savoir,
l’optimisation du plan d’exécution logique d’une requête SQL et la
génération de son plan d’exécution physique.

Le plan d’exécution logique d’une requête se matérialise à l’aide d’un arbre


définissant un calcul algébrique relationnel qui est un calcul abstrait basé
sur des opérateurs logiques et sur l’organisation logique des données
stockées dans la base de données. Ce calcul est déductible dynamiquement
de cette requête par reformulation en se basant sur les principes généraux
présentés dans les paragraphes 2.3.3, 2.4.5 et 4.3.2.

Quant au plan d’exécution physique, il correspond au code exécutable de


cette requête. Il se déduit du plan d’exécution logique et des choix opérés
pour l’implémentation des données sur les supports physiques (voir
chapitre 7). Il s’obtient en substituant dynamiquement chaque opérateur
logique par une séquence d’opérateurs physiques qui assure le calcul du
résultat de cet opérateur logique en se basant sur l’implémentation sur les
supports physiques des données que cet opérateur logique prend en entrée.

Ce sont les mécanismes qui rendent réalisable ce processus de


transformation dynamique du plan d’exécution logique d’une requête SQL
en code exécutable, en fonction des choix opérés pour l’implémentation des
données sur les supports physiques, qui garantissent l’indépendance
physique des données. Ils rendent ainsi possible la manipulation d’une base
de données à l’aide d’opérateurs algébriques relationnels qui font
Joachim TANKOANO

abstraction de l’organisation des données sur les supports physiques et des


algorithmes mis en œuvre pour cette manipulation.

Pour terminer par une illustration de la mise en œuvre de ce processus,


l’ordre « EXPLAIN PLAN » d’Oracle, prévu dans le langage SQL pour
permettre aux SGBD d’expliciter le plan d’exécution physique d’une
requête, sera présenté.

8.2. Principes généraux


Dans le chapitre 4, nous avons vu que l’ordre « SELECT » du langage de
manipulation des données de SQL a été fortement inspiré des langages
prédicatifs en calcul relationnel à variables n-uplets et qu’il intègre aussi les
opérateurs de l’algèbre relationnelle de façon implicite (, ) et explicite (*,
, , , -).

Le langage d’interrogation de SQL est de ce fait un langage abstrait hybride,


mi-prédicatif et mi-ensembliste, qui peut permettre au développeur de
formuler des requêtes purement déclaratives, non procédurales et proches
des formulations informelles. Comme nous l’avons déjà vu, dans ces
requêtes, le développeur peut se contenter de dire, dans un formalisme
précis et non ambigu, ce qu’il veut comme résultat sans dire comment
calculer ce résultat.

Pour dire comment le résultat recherché doit être construit, cette requête
doit être transformée en une séquence d’opérateurs algébriques
relationnels (voir paragraphes 2.3.3, 2.4.5 et 4.3.2), c’est-à-dire, en un calcul
algébrique relationnel. Comme nous le savons déjà, les opérateurs de ce
calcul sont des opérateurs logiques abstraits qui manipulent des éléments
du niveau logique de représentation des données, c’est-à-dire des tables, en
faisant abstraction de la représentation de ces éléments au niveau physique
et des algorithmes utilisés. Ce calcul matérialise de ce fait le plan
d’exécution logique de cette requête.

Dans la suite, nous représenterons une expression algébrique relationnelle


qui définit ce plan d’exécution logique, de façon graphique, à l’aide d’un
arbre où :

434
SGBD relationnels – Tome 1, État de l’art

• Au niveau des feuilles, on retrouve les tables devant être utilisées


pour le calcul à effectuer
• Chaque nœud intermédiaire est un opérateur algébrique relationnel,
devant être évalué en utilisant les résultats de ses nœuds fils pour
produire un nouveau résultat intermédiaire pour son nœud père
• Le nœud racine correspond au dernier opérateur algébrique
relationnel devant être évalué pour l’obtention du résultat final.

Comme nous l’avons vu dans les exemples traités dans les paragraphes
4.3.1 et 4.3.2, pour répondre à un besoin, l’écriture d’une requête SQL peut
conduire à de multiples solutions, différentes dans leur formulation mais
équivalentes par rapport au résultat qu’elles produisent. En outre, la
transformation d’une de ces requêtes SQL en un calcul algébrique
relationnel peut également conduire à plusieurs arbres de requêtes
construits différemment, mais qui produisent le même résultat. Comme le
montre l’exemple qui suit, ces arbres de requêtes, structurellement
différents mais sémantiquement équivalents, peuvent avoir un coût en
termes d’entrées / sorties disque différent.

Exemple 8.2.i : Soit le schéma logique relationnel défini comme suit :


Avion (NoAvion, NomAvion, CapAvion, …)
Vol (NoVol, NoAvion, NoEquipage, VD, VA, HD, HA, …)

Pour la question « quels sont les noms des avions qui effectuent un vol au départ
de Bobo-Dioulasso ? » on peut formuler les deux requêtes ci-dessous,
exprimées différemment mais qui sont sémantiquement équivalentes :

SELECT NomAvion
FROM Avion, Vol
WHERE Avion.NoAvion = Vol.NoAvion
AND Vol.VD = 'Bobo-Dioulasso';

SELECT NomAvion
FROM Avion
NATURAL JOIN Vol
WHERE Vol.VD = 'Bobo-Dioulasso';

435
Joachim TANKOANO

En outre, à partir de n’importe laquelle de ces deux requêtes, on peut


déduire les deux arbres de requêtes ci-dessous, structurellement différents,
mais également sémantiquement équivalents, parce que produisant le
même résultat.

1er arbre de requête :

NomAvion(Vol.VD = 'Bobo-Dioulasso' (Avion Avion.NoAvion = Vol.NoAvion Vol))

π a.NomAvion

σ
v.VD = 'Bobo-Dioulasso'

∞ a.NoAvion = v.NoAvion

Avions a Vols v
2ème arbre de requête :

NomAvion((Avion Avion.NoAvion = Vol.NoAvion Vol.VD = 'Bobo-Dioulasso' (Vol)))

π a.NomAvion

∞ a.NoAvion = v.NoAvion

Avions a
σ
v.VD = 'Bobo-Dioulasso'

Vols v

Faisons à présent l’hypothèse que la table « Avions » contient 300 lignes


stockées dans 30 blocs, que la table « Vols » contient 1200 lignes stockées
dans 120 blocs, que 10% des vols sont au départ de Bobo-Dioulasso et que
pour effectuer la jointure de la table « Vols » et de la table « Avions », le
SGBD utilise un algorithme naïf, défini par le pseudo-code qui suit :

436
SGBD relationnels – Tome 1, État de l’art

Résultat   ;
Pour chaque bloc bv de Vols Faire
Pour chaque bloc ba de Avions Faire
Pour chaque ligne l_bv de bv Faire
Pour chaque ligne l_ba de ba Faire
si l_bv.NoAvion = l_ba.NoAvion
Alors ajouter {l_bv, l_ba} dans Résultat.

Sur la base de ces hypothèses on peut calculer le coût en termes d’entrées /


sorties disque des deux arbres de requêtes ci-dessus, comme suit :

Coût du 1er arbre de requête :


• La jointure de la table « Vols » et de la table « Avions » doit entrainer
la lecture de 3 720 blocs, c’est-à-dire (120 + 120*30) et l’écriture de 240
blocs, c’est-à-dire (120+((30/300)*1200)).
• La sélection des vols au départ de Bobo-Dioulasso à partir du
résultat intermédiaire qu’on obtient après le calcul précédent doit
entrainer la lecture de 240 blocs et l’écriture de 24 blocs.
• La projection et l’affichage à l’écran des noms des avions à partir des
lignes sélectionnées doit entrainer la lecture de 24 blocs.
• Soit un coût total de 4 248 E/S disque, à savoir (3 720 + 2*240 + 2*24).

Coût du 2ème arbre de requête :


• La sélection des vols au départ de Bobo-Dioulasso doit entrainer la
lecture de 120 blocs et l’écriture de 12 blocs.
• La jointure des vols au départ de Bobo-Dioulasso et de la table
« Avions » doit entrainer la lecture de 372 blocs, c’est-à-dire (12 +
12*30) et l’écriture de 24 blocs, c’est-à-dire (12+((30/300)*120)).
• La projection et l’affichage à l’écran des noms des avions à partir des
lignes sélectionnées doit entrainer la lecture de 24 blocs.
• Soit un coût total de 552 E/S disque (120 + 12 + 372 + 2*24), ce qui
équivaut à 7,7 fois moins d’E/S disque que dans le cas précédent.

Pour rendre exécutable le plan d’exécution logique d’une requête

437
Joachim TANKOANO

matérialisé par un arbre de requête, chaque nœud de cet arbre


correspondant à un opérateur algébrique relationnel doit être remplacé par
une séquence d’opérateurs qui calcule le résultat de cet opérateur
algébrique relationnel, à partir des éléments qui implémentent au niveau
physique les données en entrée de cet opérateur algébrique. Ces opérateurs
sont appelés opérateurs physiques ou itérateurs. Les éléments qui
implémentent au niveau physique les données en entrée d’un opérateur
algébrique que ces itérateurs peuvent manipuler sont constitués, comme
nous l’avons vu dans le chapitre 7, de fichiers, de pages ou blocs, de lignes,
de chemins d’accès, de pointeurs, d’algorithmes de recherche, etc…

On appelle plan d’exécution physique d’une requête SQL, l’arbre qu’on


obtient après avoir remplacé dans l’arbre de requête du plan d’exécution
logique, chaque opérateur algébrique relationnel par une séquence
d’itérateurs qui calcule le résultat de cet opérateur algébrique relationnel à
partir des éléments représentant au niveau physique les données en entrée
de cet opérateur algébrique relationnel.

Comme nous le verrons dans ce chapitre, pour chaque opérateur algébrique


relationnel, on peut définir des algorithmes différents qui produisent le
même résultat à des coûts différents en s’appuyant sur des itérateurs et des
éléments du niveau physique de représentation des données qui ne sont
pas nécessairement de même type. En d’autres termes, à partir d’un arbre
de requête, il est possible de construire, en fonction de l’organisation
physique des données et des algorithmes retenus pour le calcul du résultat
des opérateurs algébriques, des plans d’exécution physiques différents qui
ont des coûts d’exécution différents mais qui produisent le même résultat.

En résumé, comme synthétisé par le schéma ci-dessous :


• Pour répondre à un besoin d’informations calculables à partir d’une
base de données, il est possible d’écrire plusieurs requêtes SQL
équivalentes formulées différemment
• À partir de chacune de ces requêtes, il est possible de dériver
plusieurs plans d’exécution logique (ou arbres de requêtes)
équivalents, construits différemment, qui ont des coûts d’exécution
différents

438
SGBD relationnels – Tome 1, État de l’art

• À partir de chacun de ces plans d’exécution logique, il est possible


de dériver plusieurs plans d’exécution physique équivalents,
construits différemment, qui ont également des coûts d’exécution
différents.

Besoin

Plusieurs requêtes SQL équivalentes

Plusieurs plans d’exécution logique équivalents pour


chaque requête, ayant des coûts différents

Plusieurs plans d’exécution physique équivalents pour chaque


plan d’exécution logique, ayant des coûts différents

Le processus de transformation d’une requête SQL en code exécutable doit


avoir pour finalité d’aboutir au meilleur plan d’exécution physique de cette
requête, c’est-à-dire, au plan d’exécution physique le plus performant, c’est-
à-dire le plus efficient.

La recherche du meilleur plan d’exécution physique est un exercice qui


peut être très coûteux. Dans la pratique, les SGBD se contentent de
rechercher la solution pouvant conduire à un coût raisonnable, en
s’appuyant sur des heuristiques et sur une évaluation la plus systématique
possible du coût des solutions envisageables.

Pour ce faire, les processeurs qui interviennent dans la transformation


d’une requête SQL en code exécutable s’enchaînent comme indiqué dans le
schéma ci-dessous.

L’analyseur syntaxique et sémantique s’assure que la requête soumise


respecte les contraintes syntaxiques, d’intégrité et de confidentialité. Si
aucune erreur n’est détectée, il procède ensuite à la génération d’un plan
d’exécution logique brut matérialisé par un arbre de requête.

L’optimiseur des requêtes SQL transforme l’arbre de requête brut généré


par l’analyseur syntaxique et sémantique en un arbre de requête mis sous
sa forme canonique, équivalent à l’arbre brut et correspondant à l’arbre de
requête le plus efficace notamment en termes d’entrées / sorties disque.
Pour des requêtes SQL formulées différemment, l’arbre de requête auquel

439
Joachim TANKOANO

l’optimiseur de requêtes aboutit est en général le même.

Le générateur de code transforme l’arbre de requête optimisé en un plan


d’exécution physique le plus efficient possible, où les nœuds sont des
itérateurs opérant sur les éléments du schéma physique de la base de
données pour construire le résultat recherché.

REQUÊTE SQL

ANALYSEUR SYNTAXIQUE ET SÉMANTIQUE

PLAN D’EXECUTION LOGIQUE (OU ARBRE DE REQUÊTE) BRUT

OPTIMISEUR DE L’ARBRE DE
REQUÊTE

ARBRE DE REQUÊTE SOUS SA FORME


CANONIQUE

GENERATEUR DE CODE

PLAN D’EXECUTION
PHYSIQUE

8.3. Optimisation du plan d’exécution logique d’une


requête
Les restrictions syntaxiques comme celles explicitées à titre d’exemples
dans le paragraphe 4.3.1.i permettent de conférer à SQL un pouvoir
d’expression qui permet une reformulation des requêtes formulées dans ce
langage en expressions de l’algébre relationnelle. De ce fait, en se basant sur
des règles syntaxiques et sémantiques appropriées, il est possible pour
l’analyseur syntaxique de transformer une requête SQL en un arbre de
requête brut équivalent, construit selon la logique de cette requête, sans se
préoccuper de l’efficacité de l’arbre.

Le rôle de l’optimiseur des requêtes est d’optimiser cet arbre brut en le


mettant sous sa forme canonique, optimisée, correspondant à la
matérialisation du plan d’exécution logique le plus efficace.

440
SGBD relationnels – Tome 1, État de l’art

La stratégie utilisée par l’optimiseur des arbres de requêtes pour mettre un


arbre de requête brut sous sa forme canonique consiste à :
• Regrouper et ramener les opérateurs algébriques relationnels qui
allègent les tables, à savoir les sélections et les projections, le plus
près possible des feuilles afin de les évaluer le plus tôt possible, ce
qui permet de réduire le coût d’exécution des opérateurs binaires
(jointure, intersection, union, différence) qui suivent
• Ordonner les opérateurs binaires afin d’évaluer en premier les
opérateurs les plus sélectifs et de réduire ainsi le coût global
d’exécution.

Pour ce faire, l’optimiseur procède successivement comme suit :


• Regroupement en sélection unique des sélections de chaque sous-
arbre qui a la même table en entrée au niveau des feuilles
• Regroupement et descente des sélections le plus bas possibles vers
les feuilles
• Regroupement et descente des projections le plus bas possible vers
les feuilles sans les amener plus bas que les sélections de manière à
pouvoir effectuer les projections lors de la production des résultats
des sélections qui les précèdent
• Réordonnancement des jointures en faisant en sorte que celles qui
sont effectuées en premier soient celles qui retournent le moins de
lignes dans leur résultat et soient celles qui concernent des tables
indexées
• Suppression des projections redondantes (1) au niveau des feuilles
lorsque toutes les colonnes de la table sont concernées et (2) au
niveau des nœuds intermédiaires lorsqu’il existe plus bas une
projection qui concernent les mêmes colonnes.

Pour que l’arbre qui en résulte soit équivalent à l’arbre brut initial, ces
différentes transformations successives s’appuient, comme pour les autres
simplifications des expressions algébriques, sur des règles de réécriture
basées sur des équivalences algébriques et logiques qui découlent des
propriétés des opérateurs algébriques relationnels et des opérateurs
logiques.

441
Joachim TANKOANO

Ce qui suit présente ces règles de réécriture en distinguant celles qui se


fondent sur les propriétés des opérateurs algébriques relationnels et celles
qui se fondent sur les propriétés des opérateurs logiques.

8.3.1. Les règles de réécriture basées sur des équivalences


algébriques

Soient les relations R(), R1(1) et R2(2).

Les règles de réécriture, basées sur des équivalences algébriques, qui se


fondent sur les propriétés des opérateurs algébriques relationnels que
l’optimiseur de l’arbre de requête peut utiliser pour la simplification d’un
arbre sont les suivantes :

Règles du groupe GR1. Commutativité de la jointure, du produit cartésien,


de l’union et de l’intersection
(a) R1 c R2  R2 c R1
(b) R1 * R2  R2 * R1
(c) R1  R2  R2  R1
(d) R1  R2  R2  R1.

Règles du groupe GR2. Associativité de la jointure, du produit cartésien, de


l’union et de l’intersection
(a) R1 c1 (R2 c2 R3)  (R1 c1 R2) c2 R3 (si c1 est définie sur des
attributs communs à R1 et R2 et c2 sur des attributs communs à R2 et
R3)
(b) R1 * (R2 * R3)  (R1 * R2) * R3
(c) R1  (R2  R3)  (R1 R2)  R3
(d) R1  (R2  R3)  (R1  R2)  R3.

Règle du groupe GR3. Commutativité de la sélection


c1 (c2)  c2 (c1).

442
SGBD relationnels – Tome 1, État de l’art

Règle du groupe GR4. Élimination des cascades de sélections


c1 (c2)  c1  c2.

Règle du groupe GR5. Élimination des cascades de projections


A (B)  A.

Règles du groupe GR6. Commutation d’une sélection avec une projection


(a) c (A)  A (c) (l’inverse est vrai si les attributs dans c sont inclus
dans A)
(b) A (c(B) (A, B))  A (c(B)).

Règles du groupe GR7. Commutation d’une sélection avec une union (ou
intersection ou différence)
(a) c (R1  R2)  c (R1)  c (R2)
(b) c (R1  R2)  c (R1)  c (R2)
(c) c (R1 – R2)  c (R1) - c (R2).

Règles du groupe GR8. Commutation d’une sélection avec une jointure (ou
produit cartésien ou division)
Si A  1
(a) c1(A) (R1 c R2)  c1(A) (R1) c R2
(b) c1(A) (R1 * R2)  c1(A) (R1) * R2
(c) c1(A) (R1  R2)  c1(A) (R1)  R2
Si A  1 et B  2
(d) c1(A)  c2(B) (R1 c R2)  c1(A) (R1) c c2(B) (R2)
(e) c1(A)  c2(B) (R1 * R2)  c1(A) (R1) * c2(B) (R2)
(f) c1(A)  c2(B) (R1  R2)  c1(A) (R1)  c2(B) (R2)
Si A  1  ((B  1)  )  ((B  2)  )
(g) c1(A)  c2(B) (R1 c R2)  c2(B) (c1(A) (R1) c R2)
(h) c1(A)  c2(B) (R1 * R2)  c2(B) (c1(A) (R1) * R2)

443
Joachim TANKOANO

(i) c1(A)  c2(B) (R1  R2)  c2(B) (c1(A) (R1)  R2).

Règles du groupe GR9. Commutation d’une projection avec une union (ou
intersection)
(a) A (R1  R2)  A (R1)  A (R2)
(b) A (R1  R2)  A (R1)  A (R2).

Règles du groupe GR10. Commutation d’une projection avec une jointure


(ou produit cartésien ou division)
Si A  1, B  2, D = A  B et E  1  2
(a) D (R1 c R2)  A (R1) c B (R2) (si attributs condition de jointure
 (A  B))
(b) D (R1 c R2)  D (A, E (R1) c B, E (R2))
(c) D (R1 * R2)  A (R1) * B (R2)
(d) D (R1  R2)  A (R1)  B (R2).

Exemple 8.3.1.i : Soit le plan d’exécution logique d’une requête, matérialisé


par l’arbre de requête ci-après qui calcule la liste des numéros des pilotes
qui conduisent un avion AIRBUS au départ de OUAGA.

p.NoPilote 

a.NomAvion = 'AIRBUS' σ

p.AdrPilote = 'OUAGA' σ

p.NoPilote, p.AdrPilote, a.NomAvion 

v.NoAvion = a.NoAvion ∞

v.NoPilote = p.NoPilote ∞ Avions a

Vols v Pilotes p

Dans cet arbre de requête, il n’existe aucun sous-arbre dont toutes les
feuilles concernent la même table. Pour mettre cet arbre sous sa forme

444
SGBD relationnels – Tome 1, État de l’art

optimisée, on peut donc commencer directement par regrouper et


descendre ses deux sélections le plus bas possibles vers les feuilles. Le
regroupement de ces deux sélections par application de l’équivalence
algébrique définie par la règle « GR4 », c’est-à-dire « c1 (c2)  c1  c2 »,
conduit à l’arbre ci-après :

p.NoPilote 

a.NomAvion = 'AIRBUS'  p.AdrPilote = 'OUAGA' σ

p.NoPilote, p.AdrPilote, a.NomAvion 

v.NoAvion = a.NoAvion ∞

v.NoPilote = p.NoPilote ∞ Avions a

Vols v Pilotes p

À partir de cet arbre, pour descendre la sélection issue du regroupement


qui précède le plus bas possible, dans un premier temps, elle doit faire
l’objet d’une commutation avec la projection qu’elle suit par application de
l’équivalence algébrique définie par la règle « GR6.a », c’est-à-dire « c (A)
 A (c) », ce qui conduit à l’arbre ci-après :

p.NoPilote 

p.NoPilote, p.AdrPilote, a.NomAvion 

a.NomAvion = 'AIRBUS'  p.AdrPilote = 'OUAGA' σ

v.NoAvion = a.NoAvion ∞

v.NoPilote = p.NoPilote ∞ Avions a

Vols v Pilotes p

Pour poursuivre la descente de cette sélection, elle doit faire l’objet d’une
commutation avec la jointure qui la précède par application de
l’équivalence algébrique définie par la règle « GR8.d », c’est-à-dire « c1(A) 

445
Joachim TANKOANO

(R1 c R2)  c1(A) (R1) c c2(B) (R2) », ce qui conduit à l’arbre ci-après,
c2(B)

où à droite, la sélection est descendue le plus bas possible :

p.NoPilote 

p.NoPilote, p.AdrPilote, a.NomAvion 

v.NoAvion = a.NoAvion ∞

p.AdrPilote = 'OUAGA' σ σ a.NomAvion = 'AIRBUS'

v.NoPilote = p.NoPilote ∞ Avions a

Vols v Pilotes p

Pour poursuivre la descente de la sélection à gauche de l’arbre, elle doit


faire l’objet d’une commutation avec la jointure qui la précède par
application de l’équivalence algébrique définie par la règle « GR8.a », c’est-
à-dire « c1(A) (R1 c R2)  c1(A) (R1) c R2 », ce qui conduit à l’arbre
équivalent ci-après où les sélections sont maintenant le plus bas possible à
tous les niveaux de l’arbre :

p.NoPilote 

p.NoPilote, p.AdrPilote, a.NomAvion 

v.NoAvion = a.NoAvion ∞

v.NoPilote = p.NoPilote ∞ σ a.NomAvion = 'AIRBUS'

Vols v σ p.AdrPilote = 'OUAGA' Avions a

Pilotes p

L’étape suivante doit consister à regrouper et à descendre les deux


projections de cet arbre le plus bas possible vers ses feuilles sans les amener
en dessous des sélections. Le regroupement de ces deux projections par
application de l’équivalence algébrique définie par la règle « GR5 », c’est-
à-dire « A (B)  A », conduit à l’arbre ci-après :

446
SGBD relationnels – Tome 1, État de l’art

p.NoPilote 

v.NoAvion = a.NoAvion ∞

v.NoPilote = p.NoPilote ∞ σ a.NomAvion = 'AIRBUS'

Vols v σ p.AdrPilote = 'OUAGA' Avions a

Pilotes p

La descente de la projection issue du regroupement précédent doit


commencer par sa commutation avec la jointure qui la précède par
application de l’équivalence algébrique définie par la règle « GR10.b »,
c’est-à-dire « D (R1 c R2)  D (A, E (R1) c B, E (R2)) », ce qui conduit à
l’arbre ci-après, où la descente de la projection ne peut plus se poursuivre à
droite de l’arbre. Comme on peut le constater, la descente d’une projection
avant une jointure consiste, de façon intuitive, à présenter à la jointure les
attributs (ou colonnes) nécessaires pour la jointure (à savoir « v.NoAvion »
et « a.NoAvion » dans cet exemple) et pour la projection qui doit se faire
après la jointure (à savoir « p.NoPilote »).

p.NoPilote 

v.NoAvion = a.NoAvion ∞

v.NoAvion, p.NoPilote   a.NoAvion

v.NoPilote = p.NoPilote ∞ σ a.NomAvion = 'AIRBUS'

Vols v σ p.AdrPilote = 'OUAGA' Avions a

Pilotes p

À partir de cet état, la descente des projections dans la partie gauche de


l’arbre doit se poursuivre en commutant la projection et la jointure qui la
précède par application de l’équivalence algébrique définie par la règle
« GR10.b », c’est-à-dire « D (R1 c® R2)  D (A, E (R1) c® B, E (R2)) », ce
qui conduit à l’arbre ci-après où les projections sont descendues le plus bas

447
Joachim TANKOANO

possible à tous les niveaux de l’arbre.

p.NoPilote 

v.NoAvion = a.NoAvion ∞

v.NoAvion, p.NoPilote   a.NoAvion

v.NoPilote = p.NoPilote ∞ σ a.NomAvion = 'AIRBUS'

v.NoAvion,   p.NoPilote Avions a


p.NoPilote
Vols v σ p.AdrPilote = 'OUAGA'

Pilotes p

La dernière étape doit consister à supprimer la projection redondante sur


« v.NoAvion » et « p.NoPilote » prévue après la 1ère jointure, ce qui donne
l’arbre de requête final ci-après où les deux jointures qui constituent les
opérations les plus coûteuses de l’arbre de requête brut s’effectuent sur des
tables fortement allégées afin de réduire le coût d’exécution de la requête
en termes d’entrées / sorties disque.

p.NoPilote 

v.NoAvion = a.NoAvion ∞

 a.NoAvion

v.NoPilote = p.NoPilote ∞ σ a.NomAvion = 'AIRBUS'

v.NoAvion,   p.NoPilote Avions a


p.NoPilote
Vols v σ p.AdrPilote = 'OUAGA'

Pilotes p

448
SGBD relationnels – Tome 1, État de l’art

8.3.2. Les règles de réécriture basées sur des équivalences


logiques

Lorsque dans un sous-arbre d’un arbre de requête les feuilles concernent la


même table, pour optimiser le plan d’exécution logique matérialisé par ce
sous-arbre, ses sélections peuvent être regroupées en procédant comme
suit :
• Identifier le sous-arbre qui répond à cette caractéristique
• Déplacer les opérateurs de projection vers la racine de l’arbre, s’il en
existe
• Regrouper les sélections en s’appuyant sur les règles de récriture
basées sur les équivalences logiques du groupe « GR11 » ci-après,
qui se fondent sur les propriétés des opérateurs logiques.

Règles du groupe GR11. Regroupement de sélections portant sur la même


relation.
(a) c1  c2  c1  c2
(b) c1  c2  c1  c2
(c) c1 - c2  c1  c2
(d) c2 (c1)  c1  c2.

Exemple 8.3.2.i : Pour l’illustration de ces règles de réécriture basées sur des
équivalences logiques, considérons l’arbre de requête brut ci-après, où
toutes les feuilles concernent la table « Vols ».

Cet arbre de requête contient à plusieurs niveaux une projection sur


« v.NoVol, v.VD, v.VA » qu’il faut commencer par déplacer vers la racine
de l’arbre. Le déplacement vers la racine peut se faire :
• En commutant les projections et l’intersection qu’elles précèdent par
application de l’équivalence algébrique définie par la règle
« GR9.b », c’est-à-dire « A (R1  R2)  A (R1)  A (R2) ».
• En commutant ensuite la projection remontée au-dessus de
l’intersection avec la sélection qu’elle précède par application de

449
Joachim TANKOANO

l’équivalence algébrique définie par la règle « GR6.a », c’est-à-dire


« c (A)  A (c) ».
• En commutant dans la nouvelle situation les projections et l’union
qu’elles précèdent par application de l’équivalence algébrique
définie par la règle « GR9.a », c’est-à-dire « A (R1  R2)  A (R1) 
A (R2) ».

v.VD = 'OUAGA' σ  v.NoVol, v.VD, v.VA

 -

Vols v σ v.HA < 12


v.NoVol, v.VD, v.VA   v.NoVol, v.VD, v.VA
Vols v
v.NoPilote > 50 σ σ v.NoAvion [100, 200]

Vols v Vols v

Ceci conduit à l’arbre ci-après où la projection qui apparaissait à plusieurs


niveaux est remontée à la racine :

 v.NoVol, v.VD, v.VA

v.VD = 'OUAGA' σ -

 Vols v σ v.HA < 12

Vols v
v.NoPilote > 50 σ σ v.NoAvion [100, 200]

Vols v Vols v

À partir de cet état de l’arbre, le regroupement des deux sélections au bas


de l’arbre et de l’intersection qu’elles précèdent par application de

450
SGBD relationnels – Tome 1, État de l’art

l’équivalence logique définie par la règle « GR11.b », c’est-à-dire « c1  c2


 c1  c2 », conduit à l’arbre qui suit :

 v.NoVol, v.VD, v.VA

v.VD = 'OUAGA' σ -

v.NoPilote > 50  σ Vols v σ v.HA < 12


v.NoAvion [100, 200]
Vols v Vols v

À partir de cet état de l’arbre, le regroupement des deux sélections qui se


suivent à gauche de l’arbre, par application de l’équivalence logique définie
par la règle « GR11.d », c’est-à-dire « c2 (c1)  c1  c2 », conduit à l’arbre
ci-après :

 v.NoVol, v.VD, v.VA

v.VD = 'OUAGA' σ -
 v.NoPilote > 50
 v.NoAvion [100, 200] Vols v σ v.HA < 12

Vols v Vols v

À partir de cet état de l’arbre, le regroupement à droite de l’arbre, de la


sélection et de différence qu’elle précède par application de l’équivalence
logique définie par la règle « GR11.c », c’est-à-dire « c1 - c2  c1  c2 »,
conduit à l’arbre ci-après :

 v.NoVol, v.VD, v.VA


v.VD = 'OUAGA'
 v.NoPilote > 50 σ σ (v.HA < 12)
 v.NoAvion [100, 200]
Vols v Vols v

451
Joachim TANKOANO

Pour finir, le regroupement des deux sélections au bas de l’arbre et de


l’union qu’elles précèdent par application de l’équivalence logique définie
par la règle « GR11.a », c’est-à-dire « c1  c2  c1  c2 », conduit à l’arbre
final ci-après :

 v.NoVol, v.VD, v.VA

(v.VD = 'OUAGA'  v.NoPilote > 50 σ


 v.NoAvion [100, 200])  (v.HA < 12)
Vols v

8.4. Génération du plan d’exécution physique d’une


requête
Le plan d’exécution physique d’une requête SQL est matérialisé par l’arbre
qu’on obtient à partir de l’arbre qui matérialise le plan d’exécution logique
optimisé de cette requête, après avoir remplacé chaque opérateur
algébrique relationnel par une séquence d’itérateurs qui calcule le résultat
de cet opérateur algébrique relationnel à l’aide des éléments correspondant
à l’implémentation des données en entrée de cet opérateur relationnel.

Pour chaque opérateur algébrique relationnel, il est possible de définir des


séquences d’itérateurs correspondant à des algorithmes différents qui
produisent le même résultat mais à des coûts différents, en s’appuyant sur
des éléments relatifs à l’implémentation des données sur les supports
physiques qui ne sont pas nécessairement de même type.

Pour une requête SQL donnée, la génération d’un plan d’exécution


physique efficient est tributaire : (1) de l’espace disponible en mémoire
centrale pour les calculs à effectuer, (2) de la diversité, de l’adéquation et de
l’efficience des algorithmes alternatifs que le SGBD se donne pour pouvoir
prendre en compte, de la meilleure façon possible, les caractéristiques
spécifiques à chaque requête. Ces caractéristiques concernent notamment :
• L’organisation physique des tables concernées (types d’indexation
utilisés, ordonnancement dans les fichiers de données et d’index)

452
SGBD relationnels – Tome 1, État de l’art

• La volumétrie de ces tables


• La cardinalité de leurs colonnes
• La sélectivité des conditions spécifiées dans cette requête.

Ce qui suit présente :


• Quelques exemples d’algorithmes alternatifs pour chaque chaque
opérateur algébrique relationnel en mettant en évidence pour
chacun de ces algorithmes : (1) son coût en termes d’entrées / sorties
disque et d’occupation de la mémoire centrale (étant entendu que le
coût CPU des traitements est très marginal), (2) les éléments relatifs
à l’implémentation des données sur les supports physiques dont il a
besoin
• La stratégie générale, d’une part pour le choix d’un algorithme pour
l’évaluation d’un opérateur algébrique relationnel et d’autre part
pour la génération du plan d’exécution physique
• Les approches possibles pour l’exécution d’un plan d’exécution
physique.

Les notations spécifiques qui suivent sont utilisées dans cette présentation :
• « NL (T) » pour désigner le nombre de lignes dans la table « T »
• « NB (T) » pour désigner le nombre de blocs requis pour stocker la
table « T »
• « Cardc (T) » pour désigner le nombre de valeurs distinctes de la clé
de recherche « c » dans la table « T » (c’est-à-dire la cardinalité de la
clé de recherche « c »)
• « Hauteurc (T) » pour désigner le nombre de niveaux (c’est-à-dire la
profondeur) de l’index B+créé sur la clé de recherche « c » de la table
«T»
• « Ordrec (T) » pour désigner le nombre maximum de fils de chaque
nœud interne de l’index B+, créé sur la clé de recherche « c » de la
table « T »

453
Joachim TANKOANO

• « Sela » pour désigner la sélectivité de de la clé de recherche « c »


dans la table « T » (= NL(T)/Cardc (T)).

8.4.1. L’algorithme de tri fusion

Trier un fichier est l’une des opérations que les SGBD effectuent le plus
fréquemment.

Cette opération peut être requise :


• Lorsqu’il s’agit d’un ordre SQL qui contient une clause « ORDER
BY » que le développeur peut spécifier pour demander le tri du
résultat
• Lorsqu’il s’agit d’un ordre SQL qui contient une clause
« DISTINCT » que le développeur peut spécifier pour demander la
suppression des doublons qui pourraient exister dans le résultat
• Lorsqu’il s’agit d’un ordre SQL qui contient une clause « GROUP
BY » que le développeur peut spécifier pour demander l’agrégation
du résultat
• Lorsqu’il s’agit d’un ordre SQL qui contient une jointure de tables ou
toute autre opérateur algébrique binaire comme la différence ou
l’intersection.

Les fichiers peu volumineux pouvant être chargés intégralement en


mémoire centrale sont en général triés par les SGBD à l’aide d’algorithmes
définis pour un tri interne, qui s’exécutent à très faible coût.

Ce qui suit présente l’algorithme de tri-fusion qui est l’algorithme de tri


externe le plus utilisé par les SGBD pour trier les fichiers volumineux qui
ne peuvent pas faire l’objet d’un tri interne.

a) Le principe de l’algorithme du tri-fusion

Pour sa mise en œuvre, l’algorithme de tri-fusion nécessite une zone


tampon en mémoire centrale de « w » blocs.

Cet algorithme qui applique le principe « diviser pour régner » comporte

454
SGBD relationnels – Tome 1, État de l’art

deux grandes phases :


• Une phase de tri (où le fichier est trié en mémoire centrale par
fragments de « w » blocs stockés temporairement après leur tri sur
le disque)
• Une phase de fusion (où les fragments triés et stockés
temporairement sur le disque sont fusionnés récursivement par
groupes de « w-1 » fragments).

Au cours de la phase de tri :


• La zone tampon est remplie à chaque itération avec « w » nouveaux
blocs du fichier à trier
• À chaque itération, le contenu de la zone tampon est trié à l’aide d’un
algorithme de tri interne avant d’être réécrit sur le disque dans un
fragment trié de « w » blocs.

Cette phase produit à la fin sur le disque « NB (T) / w » fragments triés.

Au cours de la phase de fusion :


• Le 1er groupe constitué des « w-1 » premiers fragments triés et
stockés sur le disque est fusionné puis réécrit sur le disque dans un
seul fragment trié. Pour ce faire, pour chacun de ces « w-1 »
fragments triés, ses blocs sont lus successivement dans le bloc de la
zone tampon de même rang. Le « wéme » bloc restant de la zone
tampon est ensuite rempli successivement avec les plus petites lignes
ordonnées suivantes contenues dans les « w-1 » blocs de la zone
tampon, puis réécrit sur le disque. Au cours de ce processus, le
transfert, dans le « wéme » bloc de la zone tampon, d’une ligne d’un
des « w-1 » blocs de cette zone tampon, provenant des « w-1 »
fragments triés à fusionner, doit provoquer le déplacement d’un
curseur sur la ligne suivante de ce bloc ou la lecture du bloc suivant
du fragment concerné, si la ligne transférée est la dernière du bloc
courant.
• Les autres fragments triés restant sont ensuite fusionnés et réécrits
de la même façon par groupe de « w-1 » fragments dans un seul
fragment trié.

455
Joachim TANKOANO

• Ce processus de fusion des fragments triés est répété récursivement


jusqu’à l’obtention d’un seul fragment trié.

Exemple 8.4.1.i : Pour illustrer le processus de fusion de « w-1 » fragments


triés stockés temporairement sur le disque, supposons que « w » est égal à
4, que chaque bloc du fichier à trier contient 3 lignes et que la lecture du 1er
bloc des 3 premiers fragments triés « F1 », « F2 » et « F3 » dans les 3
premiers blocs de la zone tampon a conduit à l’état suivant, où la valeur
indiquée pour chaque ligne correspond à la valeur du critère de tri dans
cette ligne :

F F F
1 2 3
1er bloc 2ème bloc 3ème bloc
Zone tampon Zone tampon Zone tampon
→ 1 → 3 → 2
5 7 4
6 8 9

4ème bloc – Zone tampon


La fusion de ces trois 1ers blocs des fragments triés « F1 », « F2 » et « F3 »,
doit entrainer un premier remplissage ordonné du 4ème bloc avec les lignes
« 1 », « 2 » et « 3 », et l’écriture de ce bloc sur le disque. L’état des trois
premiers blocs de la zone tampon qui en résulte doit être le suivant :

F F F
1 2 3
1er bloc 2ème bloc 3ème bloc
Zone tampon Zone tampon Zone tampon
1 3 2
→ 5 → 7 → 4
6 8 9

Le deuxième remplissage ordonné du 4ème bloc avec les lignes « 4 », « 5 » et


« 6 » va entrainer l’écriture de ce bloc sur le disque après son remplissage

456
SGBD relationnels – Tome 1, État de l’art

et la lecture du 2ème bloc du fragment « F1 » dans le 1er bloc de la zone


tampon, ce qui va conduire à l’état suivant des trois premiers blocs de la
mémoire tampon :

F F F
1 2 3
1er bloc 2ème bloc 3ème bloc
Mémoire Mémoire Mémoire
tampon tampon tampon
→ 12 3 2
16 → 7 4
17 8 → 9

Le troisième remplissage ordonné du 4ème bloc avec les lignes « 7 », « 8 » et


« 9 » va entrainer l’écriture de ce bloc sur le disque après son remplissage,
la lecture du 2ème bloc du fragment « F2 » et du fragment « F3 »
respectivement dans le 2ème et 3ème bloc de la mémoire tampon et ainsi de
suite, jusqu’à la fusion complète des trois fragments triés.

b) La complexité de l’algorithme

La phase de tri de cet algorithme entraine une lecture complète et une


écriture complète du fichier à trier, soient « 2 * NB (T) » entrées / sorties
disque.

Par ailleurs, à titre d’exemple, si « NB (T) » vaut 1024 et si « w » vaut 5, on


peut calculer comme suit le nombre de fragments triés que la phase de tri
et que chacune des étapes successives de la phase de fusion doivent
produire :
• Phase de tri : 1024 / 5 =  204.8 = 205 fragments triés de 5 blocs
• 1ère étape de la fusion : 205 / 4  51.25 = 52 fragments triés de 20 blocs
• 2ème étape de la fusion : 52 / 4  13 fragments triés de 80 blocs
• 3ème étape de la fusion : 13 / 4  3.25 = 4 fragments triés de 320 blocs

457
Joachim TANKOANO

• 4ème étape de la fusion : 4 / 4  1 fragment trié de 1024 blocs.

De ce qui précède on en déduit que, pour fusionner « NB (T) / w »


fragments triés, le nombre d’étapes requises lors de la phase de fusion est
de « logw-1 (NB(T)/w) ».

Tout comme pour la phase de tri, chaque étape de la phase de fusion lit et
écrit une fois le fichier à trier, ce qui entraine « 2 * NB(T) * logw-1
(NB(T)/w) » entrées / sorties disque pour l’ensemble des étapes de la
phase de fusion.

De ce raisonnement, on en déduit que le coût de l’algorithme de tri fusion


est de l’ordre de « 2 * NB (T) (1 + logw-1 (NB(T)/w))) » entrées / sorties
disque, étant entendu que le coût CPU des traitements effectués en
mémoire centrale est très marginal. Ce coût diminue avec l’augmentation
de « w », c’est-à-dire du nombre de blocs alloués à la zone tampon.

Exemple 8.4.1.ii : Supposons que le fichier à trier est constitué de 12 blocs,


que « w » vaut 3 et que (pour faire simple) les lignes contenues dans chaque
bloc du fichier à trier ont la même valeur du critère utilisé pour le tri. Sur la
base de ces hypothèses, le tri de ce fichier à l’aide de l’algorithme de tri-
fusion peut être matérialisé de la façon suivante :

15 4 3 9 18 12 16 2 5 7 14 6
 Fichier à trier
 Phase de tri

3 4 15 9 12 18 2 5 16 6 7 14
 1ère étape de
la fusion
3 4 9 12 15 18 2 5 6 7 14 16
 2ème étape de
la fusion
2 3 4 5 6 7 9 12 14 15 16 18
 Fichier trié

L’utilisation de la formule définie plus haut permet de déduire comme suit


le nombre d’entrées / sorties disque que requiert ce tri :
Nombre d’entrées / sorties disque = 2 * 12 (1 + log2 (12/3))
= 24 (1 + 2) = 72

458
SGBD relationnels – Tome 1, État de l’art

8.4.2. Les algorithmes pour la sélection

Les aspects dont dépendent les algorithmes de sélection de lignes dans une
table concernent d’une part, la complexité de l’expression logique qui
définit la condition de sélection et d’autre part, l’organisation physique des
données.

Dans sa forme la plus simple, la condition de sélection peut être un simple


test d’égalité défini sur une colonne de sélection de la table (Ex. : « c='valeur'
(T) »).

Dans ses formes les plus complexes, cette condition peut être définie par :
• Des conjonctions et/ou disjonctions de tests d’égalité (Ex. :
« (c1='valeur1')  (c2='valeur2') (T) », « (c1='valeur1')  (c2='valeur2') (T) », etc…)
• Des tests d’inégalité
• Des tests d’appartenance à un intervalle de valeurs (Ex. : « (c≤'valeur1')
(T) », « (c>'valeur1')  (c<'valeur2') (T) », etc…)
• Une combinaison des formes précédentes.

Quant aux aspects liés à l’organisation physique des données, il s’agit


principalement de l’existence ou non d’index sur les colonnes de sélection
et de l’existence ou non de fichiers ou d’index ordonnés en fonction des
valeurs des colonnes de sélection.

Ce qui suit présente, à titre d’exemples, quelques algorithmes de sélection


définis en fonction de ces différents aspects.

a) L’algorithme de sélection sans index

Il s’agit de l’algorithme le plus simple. Il n’impose aucune condition


particulière aussi bien sur la forme de la condition de sélection que sur
l’organisation physique des données.

Il se définit par le pseudo-code ci-après, où « T » désigne le fichier où est


stockée la table de même nom où la sélection doit être effectuée :

459
Joachim TANKOANO

Résultat   ;
Pour chaque bloc b de T Faire
Pour chaque ligne l de b Faire
Si condition_de_sélection = vrai alors ajouter l dans Résultat ;

La complexité de cet algorithme est de l’ordre de « NB (T) » entrées / sorties


disque, ce qui correspond au coût le plus élevé qu’un algorithme de
sélection devrait engendrer.

b) L’algorithme de sélection par test d’égalité via un index défini par un


arbre B+ construit sur un fichier index dense

Cet algorithme n’est applicable que si les deux conditions suivantes sont
vérifiées :
• La condition de sélection est un test d’égalité sur une colonne de
sélection « c » (Ex. : « c='valeur' (T) »)
• Il existe un index, sur la colonne de sélection « c », défini à l’aide d’un
arbre B+ construit sur un fichier index dense « FID » du fichier
principal « FP ».

Dans cet algorithme, les adresses des blocs du fichier principal « FP »


contenant les lignes recherchées sont déterminées en parcourant l’arbre B+.

Cet algorithme est défini par le pseudo-code ci-après :


b  bloc racine de l’arbre B+ ;
Tantque b n’est pas une feuille de l’arbre B+ Faire
Déterminer l’adresse du nouveau bloc cible de l’arbre B+ en parcourant les lignes
de b ;
b  nouveau bloc cible ;
Résultat   ;
En parcourant à partir de b les lignes des blocs concernés du fichier FID, copier dans
LP la liste sans doublons des pointeurs sur les blocs de T qui contiennent les lignes
recherchées ;
Pour chaque pointeur dans LP Faire
b’  le bloc pointé dans T ;
Pour chaque ligne l de b’ Faire
Si condition_de_sélection=vrai Alors ajouter l dans Résultat ;

460
SGBD relationnels – Tome 1, État de l’art

La complexité de cet algorithme est de l’ordre de « Hauteurc (T) + Selc (T) »


entrées / sorties disque. Le coût de cet algorithme est fonction de la
sélectivité de la colonne de sélection. Il augmente lorsque la sélectivité de la
colonne de sélection diminue. Lorsque chaque bloc de « T » contient au
moins une ligne recherchée, ce coût devient supérieur au coût de
l’algorithme sélection sans index.

c) L’algorithme de sélection par test d’égalité via un index défini à l’aide


d’une fonction de hachage

Cet algorithme n’est applicable que si les deux conditions suivantes sont
vérifiées :
• La condition de sélection est un test d’égalité sur une colonne de
sélection « c » (Ex. : « c='valeur' (T) »)
• Il existe un index, sur la colonne de sélection « c », défini à l’aide
d’une fonction de hachage qui a permis de répartir les lignes de la
table concernée dans « p » paquets.

Cet algorithme détermine l’adresse du paquet qui contient les lignes


recherchées en appelant tout simplement cette fonction de hachage.

Sa complexité est de l’ordre de « NB (T) / p » entrées / sorties disque. Si


chaque paquet n’est constitué que d’un seul bloc, il permet de retrouver les
lignes recherchées en effectuant une seule entrée / sortie disque.

d) L’algorithme de sélection par disjonction de tests d’égalité via des


index

Cet algorithme n’est applicable que si les deux conditions suivantes sont
vérifiées :
• La condition de sélection est une disjonction de tests d’égalité sur des
colonnes de sélection « c1 », « c2 », … (Ex. : « (c1='valeur1')  (c2='valeur2')
(T) »)
• Il existe un index sur chacune des colonnes de sélection « c1 », « c2 »,

461
Joachim TANKOANO

Cet algorithme calcule la liste de lignes qui vérifient chaque test d’égalité
de la condition de sélection en utilisant l’index défini sur la colonne de
sélection concernée et procède ensuite à une concaténation de ces listes de
lignes.

La complexité de cet algorithme est de l’ordre de « (Nombre d’entrées /


sorties disque effectué pour chaque sélection par test d’égalité via un
index) ».

e) L’algorithme de sélection par conjonction de tests d’égalité via un


index

Cet algorithme n’est applicable que si les deux conditions suivantes sont
vérifiées :
• La condition de sélection est une conjonction de tests d’égalité sur
des colonnes de sélection « c1 », « c2 », … (Ex. : « (c1='valeur1') 
(c2='valeur2') (T) »)

• Il existe un index sur au moins une des colonnes de sélection « c1 »,


« c2 », …

Cet algorithme effectue une sélection par test d’égalité via un index qui
utilise le test d’égalité le plus sélectif pour lequel il existe un index, en ne
retenant dans le résultat final que les lignes qui satisfont également les
autres tests d’égalité.

Cet algorithme a comme complexité, la complexité de l’algorithme de


sélection par test d’égalité via un index, relatif au test d’égalité le plus
sélectif pour lequel il existe un index.

f) L’algorithme de sélection par conjonction de tests d’égalité via


plusieurs index

Cet algorithme n’est applicable que si les deux conditions suivantes sont
vérifiées :
• La condition de sélection est une conjonction de tests d’égalité sur
des colonnes de sélection « c1 », « c2 », … (Ex. : « (c1='valeur1') 

462
SGBD relationnels – Tome 1, État de l’art

(c2='valeur2') (T) »).


• Il existe un index sur chacune des colonnes de sélection « c1 », « c2 »,
…, défini à l’aide d’un arbre B+ construit sur un fichier index dense
« FID » du fichier principal « FP ».

Pour calculer les lignes recherchées, cet algorithme :


• Détermine, pour chaque test d’égalité, l’ensemble des adresses des
blocs du fichier principal « FP » qui contiennent les lignes qui
vérifient ce test d’égalité, en parcourant l’arbre B+ construit sur la
colonne de sélection concernée
• Détermine ensuite l’ensemble des adresses des blocs du fichier
principal « FP » qui contiennent les lignes qui vérifient la condition
de sélection, en calculant l’intersection des ensembles d’adresses de
blocs calculés dans l’étape précédente.

La complexité de cet algorithme est de l’ordre de « (Hauteurci (T)) + Selc »


entrées / sorties disque. Ce coût est fonction de la sélectivité de la
conjonction des tests d’égalité définie par « c ». Ce coût augmente lorsque
la sélectivité de « c » diminue.

8.4.3. Les algorithmes pour la projection

Dans un arbre de requête optimisé, constitué de plus d’un opérateur


algébrique, la projection apparaît en général à la suite d’un autre opérateur
(sélection, jointure, …).

Lorsqu’une projection apparaît à la suite d’un autre opérateur, son


évaluation s’intègre dans l’évaluation de cet opérateur et plus précisément,
dans la phase de production de son résultat.

La projection n’a donc pas de coût spécifique. Elle allège en plus le coût
d’évaluation de l’opérateur algébrique qui la précède en réduisant la taille
des lignes de son résultat, sauf lorsque les doublons doivent être éliminés
de ce résultat.

Aussi, dans ce qui suit, nous ne nous intéresserons qu’à la problématique


de l’élimination des doublons du résultat de l’exécution d’un ordre

463
Joachim TANKOANO

« SELECT », en présentant deux exemples d’algorithmes alternatifs


d’élimination des doublons :
• L’algorithme d’élimination des doublons à l’aide du tri fusion
• L’algorithme d’élimination des doublons à l’aide du hachage.

a) L’algorithme d’élimination des doublons à l’aide du tri fusion

Cet algorithme modifie tout simplement :


• La 1ère phase de l’algorithme de tri fusion, en y incluant l’élimination
des colonnes qui ne doivent pas apparaître dans le résultat, lors de
l’écriture d’une ligne dans un fragment trié
• Les étapes de la phase de fusion de cet algorithme, en y incluant
l’élimination des doublons qui se suivent, lors de la fusion d’un
groupe de fragments triés.

b) L’algorithme d’élimination des doublons à l’aide du hachage

Cet algorithme se déroule en deux phases. Pour s’exécuter, il a besoin d’une


zone tampon en mémoire centrale de « w » blocs et d’une fonction de
hachage statique « h(l) », qui retourne une valeur comprise entre « 0 » et
« w-1 », pour chaque ligne « l » du fichier à projeter où les colonnes qui ne
doivent pas apparaître dans le résultat ont été retirées.

La première phase de cet algorithme est une phase de partitionnement des


lignes où les colonnes qui ne doivent pas apparaître dans le résultat ont été
retirées. Elle consiste à :
• Lire successivement les lignes du fichier à projeter
• Retirer de chaque ligne les colonnes qui ne doivent pas apparaître
dans le résultat
• Stocker chaque nouvelle ligne ainsi allégée dans le bloc de la zone
tampon déterminé par la fonction de hachage
• Vider chaque bloc de la zone tampon dans le paquet sur le disque
qui lui correspond, dès qu’il se remplit.

464
SGBD relationnels – Tome 1, État de l’art

Le but de cette première phase est de répartir dans « w » paquets stockés


sur le disque, les lignes du fichier projeté, après avoir donc retiré les
colonnes qui ne doivent pas apparaître dans le résultat, en faisant en sorte
que deux lignes contenues dans deux paquets différents ne puissent pas
être des doublons.

La deuxième phase de cet algorithme est une phase d’élimination des


doublons qui consiste à :
• Lire successivement en mémoire centrale, chaque paquet construit
au cours de la première phase, ce qui suppose qu’aucun paquet ne
doit être constitué de plus de « w » blocs
• Éliminer les doublons présents dans chaque paquet en utilisant un
tri interne
• Réécrire, après élimination des doublons, chaque paquet sur le
disque.

8.4.4. Les algorithmes pour la jointure

Les aspects dont dépendent les algorithmes de jointure sont


principalement :
• L’organisation physique des données des tables à joindre, à savoir,
l’existence ou non d’un index sur les colonnes de jointure de l’une
d’entre elles et l’existence ou non de fichiers principaux ou d’index
ordonnés selon les colonnes de jointure
• La forme de l’expression logique qui définit la condition de jointure
(équijointure mono ou multicolonnes, jointure mono ou
multicolonnes basée sur des tests d’inégalité, …).

Quatre types d’approches sont envisageables pour la jointure de deux


tables :
• Les approches qui ne nécessitent aucune condition particulière sur
l’organisation physique des données des tables à joindre (Ex. : les
approches de jointure par boucles imbriquées sans index comme
« Nested Loop Join »)

465
Joachim TANKOANO

• Les approches qui nécessitent l’existence d’un index sur les colonnes
de jointure de l’une des deux tables (Ex. : l’approche de jointure par
boucles imbriquées avec index « Nested Loop index join »)
• Les approches qui nécessitent que pour chacune des deux tables, le
fichier principal ou l’index créé sur le fichier principal soit ordonné
selon les colonnes de jointure et qui effectuent si besoin, des tris dans
des fichiers temporaires afin de satisfaire cette condition (Ex. :
l’approche de jointure par tri-fusion « Sort Merge Join »)
• Les approches qui créent dynamiquement une organisation
physique temporaire de l’une ou des deux tables afin de faciliter la
jointure en utilisant des techniques d’indexation à l’aide de
fonctions de hachage (Ex. : « Hash Join », « Grace Hash Join »,
« Hybrid Hash Join »).

Ce qui suit présente, à titre d’exemples, ces quelques algorithmes de


jointure.

a) L’algorithme de jointure par boucles imbriquées simples « Nested


Loop Join »

Cet algorithme de jointure est le plus simple. En outre, il n’impose aucune


condition particulière sur l’organisation physique des données ou sur la
forme de l’expression logique qui définit la condition de jointure.

Il se définit comme dans le pseudo-code ci-après où « R » et « S » désignent


les fichiers qui contiennent les tables (de mêmes noms) à joindre et où on
suppose que « NB (R) < NB (S) » :
Résultat   ;
Pour chaque bloc br de R Faire
Pour chaque bloc bs de S Faire
/* Faire en mémoire centrale la jointure des lignes des blocs br et bs */
Pour chaque ligne r de br Faire
Pour chaque ligne s de bs Faire
si condition_de_jointure_r_s = vrai Alors ajouter {r, s} dans Résultat.

La complexité de cet algorithme est de l’ordre de « NB (R) + NB (R) * NB


(S) » entrées / sorties disque, ce qui correspond au coût le plus élevé qu’une

466
SGBD relationnels – Tome 1, État de l’art

jointure devrait engendrer.

En prévoyant en mémoire centrale une zone tampon de « w » blocs pour


permettre la lecture des blocs de « S » par groupe de « w » et la jointure en
mémoire centrale des lignes d’un bloc de « R » avec les lignes d’un groupe
de « w » blocs de « S », la complexité de cet algorithme devient de l’ordre
« NB (R) + NB (R) * (NB (S) / w) » entrées / sorties disque et diminue
lorsque qu’on augmente « w ». Si « w  NB (S) », la complexité de cet
algorithme devient de l’ordre « NB (R) + NB (S) » entrées / sorties disque,
ce qui correspond au coût le plus faible qu’une jointure puisse engendrer.

b) L’algorithme de jointure par boucles imbriquées avec index « Nested


Loop index join »

Cet algorithme n’est applicable que s’il existe un index sur les colonnes de
jointure de l’une des deux tables à joindre. Si la condition de jointure est
basée sur des tests d’inégalité, il doit faire l’objet d’une adaptation qui rend
son coût plus élevé.

Si « R » et « S » désignent deux fichiers contenant des tables (de mêmes


noms) à joindre et si « S » est indexée sur ses colonnes de jointure désignées
par « c » à l’aide d’un index « ISc », cet algorithme détermine pour chaque
ligne de « R » les lignes de « S » qui lui correspondent en utilisant l’index
« ISc ».

Cet algorithme peut se définir à l’aide du pseudo-code ci-après :


Résultat   ;
Pour chaque bloc br de R Faire
Pour chaque ligne r de br Faire
Rechercher dans S, à l’aide de l’index « ISc », les adresses des blocs qui
contiennent les lignes qui correspondent à r ;
Dans chacun de ces blocs de S, pour chaque ligne s qui correspond à r,
ajouter {r, s} dans Résultat.

Lorsque la condition de jointure définit une équijointure multicolonnes sur


des colonnes qui ont des index distincts, l’index utilisé doit être celui qui est
le plus sélectif et un test doit être effectué sur chaque ligne du résultat afin
de s’assurer qu’elle vérifie les autres conditions de la jointure.

467
Joachim TANKOANO

La complexité de cet algorithme est de l’ordre de « NB (R) + NL (R) *


(Hauteurc (S) + Selc (S)) » entrées / sorties disque. La complexité de de cet
algorithme est fonction de la sélectivité des colonnes de jointure dans « S ».
Elle augmente lorsque la sélectivité des colonnes de jointure dans « S »
diminue et devient plus élevé que celle de l’algorithme de jointure par
boucles imbriquées simples sans tampon, lorsque les lignes recherchées
existent dans chaque bloc qui sert à stocker la table « S ».

c) L’algorithme de jointure par tri fusion « Sort Merge Join »

Cet algorithme de jointure n’impose aucune condition particulière sur


l’organisation physique des données. Toutefois, si la condition de jointure
est basée sur des tests d’inégalité, il doit faire l’objet d’une adaptation qui
rend son coût plus élevé.

Pour effectuer la jointure de deux tables « R » et « S » :


• Il trie « R » et « S » par rapport à leurs colonnes de jointure en
utilisant l’algorithme de tri-fusion
• Il parcourt ensuite conjointement « R » et « S » triés en les alignant
l’un par rapport à l’autre, afin de joindre les lignes de « R » et « S »
qui correspondent.

La complexité de cet algorithme est de l’ordre de « (2 * NB (R) (1 + logw-1


(NB(R)/w))) + (2 * NB (S) (1 + logw-1 (NB(S)/w))) + NB (R) + NB (S) »
entrées / sorties disque. Si « R » et « S » sont déjà triés par rapport à leurs
colonnes de jointure, sa complexité n’est que de « NB (R) + NB (S) » entrées
/ sorties disque.

d) L’algorithme de jointure par hachage « Hash Join »

Cet algorithme de jointure par hachage n’impose aucune condition


particulière sur l’organisation physique des données. Cependant, il n’est
applicable pour la jointure de deux tables « R » et « S » que si (1) la
condition de jointure définit une équijointure et (2) la plus petite table,
disons « S », peut être hachée intégralement en mémoire centrale, à l’aide
d’une fonction de hachage sur les valeurs des colonnes de jointure.

468
SGBD relationnels – Tome 1, État de l’art

Dans cet algorithme, on distingue une phase de construction et une phase


de test.

La phase de construction consiste à hacher intégralement la plus petite table


« S » en mémoire centrale dans « p » paquets en utilisant une fonction de
hachage « h(c) » qui retourne pour chaque valeur des colonnes de jointure
une valeur comprise entre « 0 » et « p-1 ».

La phase de test consiste, pour chaque ligne de « R » à utiliser la même


fonction de hachage « h(c) » pour déterminer le paquet qui contient les
lignes de « S » qui lui correspondent afin d’effectuer la jointure.

La complexité de cet algorithme est de l’ordre de « NB (R) + NB (S) »


entrées / sorties disque.

e) L’algorithme de jointure par hachage « Grace Hash Join »

Comme le précédent, cet algorithme de jointure par hachage n’impose


aucune condition particulière sur l’organisation physique des données,
mais n’est applicable que si la condition de jointure définit une équijointure.
En outre, pour effectuer la jointure de deux tables « R » et « S », il a besoin
d’une zone tampon en mémoire centrale de « w » blocs et d’une fonction de
hachage « h(c) » qui retourne une valeur comprise entre « 0 » et « w-1 »
pour chaque valeur des colonnes de jointure.

Il se déroule également en deux phases.

La première phase est une phase de construction qui consiste à hacher « R »


et « S » sur le disque à l’aide de la même fonction de hachage « h(c) », en
procédant comme suit pour chaque table :
• Lire successivement les lignes du fichier où est stockée la table
• Copier chaque ligne lue dans le bloc de la zone tampon dont le
numéro est retourné par la fonction de hachage « h(c) » pour la
valeur de ses colonnes de jointure
• Vider chaque bloc de la zone tampon dans le paquet sur le disque
qui lui correspond, chaque fois qu’il se remplit.

Le but de cette première phase est de répartir les lignes de chacune des deux

469
Joachim TANKOANO

tables « R » et « S » dans « w » paquets stockés sur le disque, en faisant en


sorte que les lignes contenues dans le paquet « i » de « R » ne puissent être
jointes qu’aux lignes contenues dans le paquet « i » de « S » et inversement.

La deuxième phase de cet algorithme est une phase de test qui consiste
à effectuer la jointure des lignes des paquets de « R » et de « S » de même
rang en utilisant l’algorithme de jointure par hachage « Hash Join » :
• Pour chaque paire, un des paquets est haché en mémoire centrale
dans la zone tampon de « w » blocs, en utilisant une fonction « h’(c) »
différente de « h(c) »
• Ensuite, un parcours séquentiel est effectué sur le 2ème paquet de la
paire et pour chaque ligne de ce paquet, un test de jointure est
effectué à l’aide du paquet haché.

La complexité de cet algorithme est de l’ordre de « 2 * (NB (R) + NB (S)) »


entrées / sorties disque pour la phase de construction et de « NB (R) + NB
(S) » pour la phase de test.

f) L’algorithme de jointure par hachage « Hybrid Hash Join »

Cet algorithme n’est applicable que si la condition de jointure définit une


équijointure. Aucune condition particulière sur l’organisation physique des
données n’est requise pour cet algorithme qui combine l’approche « Hash
Join » et l’approche « Grace Hash Join », en se déroulant en trois phases.

Dans la première phase, cet algorithme démarre en entreprenant le hachage


en mémoire centrale de « S », la table la plus petite. Au cours de cette phase,
il transfère sur le disque, des paquets sélectionnés pour libérer de l’espace,
chaque fois que la mémoire centrale devient insuffisante. À la fin de cette
phase, les paquets construits pour « S » sont ainsi, soient intégralement en
mémoire centrale, soient répartis entre la mémoire centrale et le disque.

Dans la deuxième phase, les lignes de « R » qui correspondent à des lignes


de « S » contenues dans des paquets en mémoire centrale sont jointent à ces
lignes. Les autres lignes de « R » sont hachées dans des paquets sur le
disque.

470
SGBD relationnels – Tome 1, État de l’art

Dans la troisième phase, les paquets de « R » et de « S » qui sont sur le


disque sont traités comme dans la deuxième phase de l’algorithme de
jointure par hachage « Grace Hash Join ».

8.4.5. Les algorithmes pour l’intersection, l’union, la différence


et la division

Les algorithmes de ces opérateurs algébriques se définissent par adaptation


des algorithmes de jointure.

8.4.6. Le choix des algorithmes et la génération du plan


d’exécution physique

Comme le montre les algorithmes présentés ci-dessus, dans le plan


d’exécution logique d’une requête SQL, le calcul du résultat d’un opérateur
algébrique relationnel peut se faire en général à l’aide d’algorithmes
alternatifs ayant des exigences différentes par rapport aux caractéristiques
de la requête. Ces caractéristiques peuvent concerner :
• L’organisation physique des tables en entrée de la requête (index
existants, ordonnancement dans les fichiers principaux et index)
• La volumétrie de ces tables
• La cardinalité de leurs colonnes
• La sélectivité des conditions spécifiées dans cette requête.

Ces exigences peuvent aussi concerner l’espace mémoire requis pour le


calcul du résultat de l’opérateur algébrique relationnel.

Par ailleurs, ces algorithmes alternatifs engendrent en général des coûts


d’exécution différents en termes d’entrées / sorties disque.

De ce fait, pour transformer un plan d’exécution logique d’une requête SQL


en un plan d’exécution physique, pour chaque opérateur algébrique
relationnel du plan d’exécution logique, le SGBD doit :
• Déterminer, à l’aide d’heuristiques, les algorithmes alternatifs pour
lesquels les caractéristiques de la requête et la taille de la mémoire

471
Joachim TANKOANO

de travail disponible sont conformes aux exigences de ces


algorithmes
• Évaluer ensuite ces algorithmes alternatifs, éventuellement de façon
approximative, en s’appuyant sur les statistiques disponibles, afin de
sélectionner celui qui engendre en plus le coût le plus faible en
termes d’entrées / sorties disque.

Le plan d’exécution physique d’une requête s’obtient en remplaçant dans


l’arbre de requête optimisé chaque opérateur algébrique par la séquence
d’itérateurs qui implémente l’algorithme ainsi sélectionné. Ce plan
d’exécution physique peut se matérialiser à l’aide d’un arbre où chaque
nœud est un itérateur, agissant comme une boîte noire générique,
consommant un flux de données en entrée (ou deux s’il s’agit d’un itérateur
binaire) pour produire en sortie un nouveau flux de données.

Chacune de ces boîtes noires génériques :


• Reçoit en entrée, soient les éléments qui matérialisent
l’implémentation physique des tables en entrée de l’opérateur
algébrique (fichier principal ou fichier index de table), soient les flux
de données produits par les itérateurs qui constituent ses nœuds fils
• Effectue à la demande de l’itérateur qui constitue son nœud père un
traitement (prédéfini paramétré) des flux de données en entrées afin
de produire en sortie un flux de données pour ce nœud père.

Pour harmoniser les interactions avec les applications qui exécutent une
requête « SELECT » (notamment à l’aide d’un curseur ou son équivalent)
et entre un nœud père et ses nœuds fils, la tâche que chaque itérateur met
en œuvre doit être implémentée en implémentant une interface normalisée
de cette boîte noire, constituée, comme pour les curseurs PL/SQL, des trois
opérations ci-après :
• L’opération « OPEN () », que le père déclenche pour la création et
l’initialisation des variables de travail de cet itérateur
• L’opération « NEXT () », que le père appelle pour récupérer la ligne
suivante du résultat généré par l’exécution de cet itérateur

472
SGBD relationnels – Tome 1, État de l’art

• L’opération « CLOSE () », que le père déclenche après l’exécution de


cet itérateur, pour provoquer la libération des ressources allouées.

Exemple 8.4.6.i : Soit par exemple l’arbre de requête optimisé ci-après qui
calcule la liste des noms des avions conduits par le pilote numéro « 10 » :

 a.NomAvion

∞ a.NoAvion = v.NoAvion

a.NoAvion, a.NomAvion   v.NoAvion

Avions a σ v.NoPilote = 10

Vols v

Si nous faisons l’hypothèse que la table « Vols » est indexée sur la clé de
recherche « NoPilote » à l’aide d’un arbre B+ et que la table « Avions » est
aussi indexée sur la clé de recherche « NoAvion » à l’aide d’un arbre B+, il
est possible de dériver de cet arbre de requête le plan d’exécution physique
matérialisé par l’arbre ci-dessous, où :
• La table « Vols » a été remplacée par le fichier principal « Vols_FP »
où sont stockées ses lignes et par le fichier « Vols_NoPilote_idx » qui
contient son index sur la clé de recherche « NoPilote » construit à
l’aide d’un arbre B+
• La table « Avions » a été remplacée par le fichier principal
« Avions_FP » où sont stockées ses lignes et par le fichier
« Avions_NoAvion_idx » qui contient son index sur la clé de
recherche « NoAvion » construit à l’aide d’un arbre B+
• L’opérateur algébrique relationel de jointure a été remplacé par un
sous-arbre d’itérateurs (« NestedLoopJoinIndex », « ScanByAdr* »,
« ScanIndex* », « ScanByAdr** » et « ScanIndex** ») qui ensemble
implémentent l’algorithme de jointure par boucle imbriquée avec
index, où :
o Les itérateurs « ScanIndex* » et « ScanByAdr* »
implémentent le parcours des lignes de la 1ère table de la

473
Joachim TANKOANO

jointure (« Vols ») et l’opérateur algébrique de sélection de


lignes de cette table
o Les itérateurs « ScanByAdr** » et « ScanIndex** »
implémentent l’accès aux lignes de la 2ème table de la jointure
(« Avions ») via un index.

Curseur
application

NomAvion suivant

NestedLoopJoinIndex

ScanByAdr* ScanByAdr **

ScanIndex * Vols_FP Avions_FP ScanIndex


**

Vols_NoPilote_idx Avions_NoAvion_idx

Le tableau qui suit décrit à titre indicatif, pour des choix d’implémentation
que nous avons retenus de façon arbitraire, les tâches que les opérations
« OPEN () », « NEXT () » et « CLOSE () » de chaque itérateur devraient
effectuer dans le cadre du déroulement de l’algorithme de jointure
implémenté. Comme on peut le constater dans cet exemple, les itérateurs
génériques de même nom effectuent toujours les mêmes types de tâches.

Itérateur OPEN () NEXT () CLOSE ()


NestedLoop (1) Effectuer les allocations (1) Si toutes les Libérer les
JoinIndex et initialisations des lignes de la jointure ressources
variables requises de la dernière ligne allouées par
(2) Déclencher l’exécution de « Vols_FP » avec « OPEN () » et
de l’opération « OPEN () » les lignes qui lui appeler
de « ScanByAdr* » pour correspondent dans l’opération
initialiser la récupération « Avions_FP » « CLOSE () » de
des lignes du fichier n’ont pas encore été « ScanByAdr* »
« Vols_FP » qui retournées,
concernent le pilote retourner la ligne
numéro « 10 » suivante
(2) Sinon, si l’appel

474
SGBD relationnels – Tome 1, État de l’art

de « NEXT() » de
« ScanByAdr* »
retourne une ligne,
récupérer toutes les
lignes qui lui
correspondent dans
« Avions_FP » en
appelant « OPEN
() », « NEXT() »
autant de fois que
nécessaire et
« CLOSE() » de
« ScanByAdr** »,
joindre ces lignes et
la ligne de
« Vols_FP » dans
une zone tampon et
retourner la 1ère
ligne de cette zone
tampon
(3) Sinon retourner
« NULL »
ScanByAdr* Déclencher l’exécution de (1) Si l’appel de Libérer les
l’opération « OPEN () » de « NEXT() » de ressources
« ScanIndex* » « ScanIndex* » allouées par
retourne une « OPEN () » et
adresse de ligne appeler
dans « Vols_FP », l’opération
utiliser cette adresse « CLOSE () » de
pour récupérer et « ScanIndex* »
retourner la ligne
concernée de
« Vols_FP »
(2) Sinon, retourner
« NULL »
ScanIndex* Déclencher le parcours de 1) Si, dans la zone Libérer les
l’arbre B+ stocké dans tampon, toutes les ressources
« Vols_NoPilote_idx » adresses des lignes allouées par
pour récupérer dans une de « Vols_FP » qui « OPEN () »
zone tampon la liste des concernent le pilote
adresses des lignes de 10 n’ont pas encore
« Vols_FP » qui été traitées, traiter

475
Joachim TANKOANO

concernent le pilote l’adresse suivante


numéro « 10 » en la retournant
(2) Sinon retourner
« NULL »
ScanByAdr** Déclencher l’exécution de (1) Si l’appel de Libérer les
l’opération « OPEN () » de « NEXT() » de ressources
« ScanIndex** » « ScanIndex** » allouées par
retourne une « OPEN () » et
adresse de appeler
« Avions_FP », l’opération
utiliser cette adresse « CLOSE () » de
pour récupérer et « ScanIndex* »
retourner la ligne
concernée dans
« Avions_FP »
(2) Sinon retourner
« NULL »
ScanIndex** Déclencher le parcours de 1) Si, dans la zone Libérer les
l’arbre B+ stocké dans tampon, toutes les ressources
« Avions_NoAvion_idx » adresses des lignes allouées par
pour récupérer dans une de « Avions_FP » « OPEN () »
zone tampon la liste des qui concernent le
adresses des lignes de numéro d’avion
« Avions_FP » qui passé en paramètre,
concernent le numéro n’ont pas encore été
d’avion passé en traitées, traiter
paramètre. l’adresse suivante
en la retournant
(2) Sinon retourner
« NULL »

8.4.7. Les approches pour le déroulement d’un plan


d’exécution physique

En fonction de la logique des algorithmes sélectionnés, le déroulement du


plan d’exécution physique d’une requête SQL peut se traduire par une
exécution simultannée des itérateurs de plusieurs sous-arbres de l’arbre qui
matérialise ce plan d’exécution physique.

Exemple 8.4.7.i : Pour illustrer cette possibilité, considérons à nouveau

476
SGBD relationnels – Tome 1, État de l’art

l’arbre de requête de l’exemple 8.4.6.i ci-dessus. À partir de cet arbre, il est


aussi possible de dériver le plan d’exécution physique matérialisé par
l’arbre ci-après, où l’opérateur de jointure a été remplacé par le sous-arbre
constitué des itérateurs « HashJoinProbe », « HachJoinBuild »,
« ScanByAdr » et « ScanIndex », afin d’implémenter l’algorithme de
jointure par hachage « Hash Join » :

HashJoinProbe

HachJoinBuild ScanByAdr

Avions_FP Vols_FP ScanIndex

Vols_NoPilote_idx

Le tableau qui suit résume les tâches que les opérations « OPEN () »,
« NEXT () » et « CLOSE () » des itérateurs « HashJoinProbe » et
« HachJoinBuild » devraient effectuer dans le cadre des traitements qui
implémentent l’algorithme de jointure par hachage « Hash Join ».

Itérateur OPEN () NEXT () CLOSE ()


HashJoinProbe (1) Déclencher (1) Appeler « NEXT Libérer les
l’exécution de () » de ressources allouées
« OPEN () » de « ScanByAdr » pour par « OPEN () ».
« HachJoinBuild » récupérer la ligne Appeler « CLOSE
pour déclencher le suivante de la table () » de
hachage du fichier « Vols » qui « HachJoinBuild »
« Avions_FP ». concerne le pilote et de
(2) Déclencher numéro « 10 » « ScanByAdr ».
l’exécution de (2) Appeler
« OPEN () » de l’opération « NEXT
« ScanByAdr » pour () » de
déclencher le calcul « HachJoinBuild »
de la liste des pour récupérer la
adresses des lignes de ligne de
la table « Vols » qui « Avions_FP » qui
concernent le pilote contient la valeur
numéro « 10 ». contenue dans la
colonne
« NoAvion » de cette

477
Joachim TANKOANO

ligne de la table
« Vols »
(3) Retourner la
jointure de ces deux
lignes.
HachJoinBuild Déclencher le Retourner la ligne du Libérer les
hachage dans des fichier ressources allouées
paquets en mémoire « Avions_FP » dont par « OPEN () »
centrale des lignes du le numéro d’avion
fichier « Avions_FP » est passé en
en appliquant une paramètre après
fonction de hachage l’avoir récupérée
sur la valeur de la dans le paquet dont
colonne « NoAvion » le numéro
de chaque ligne de correspond à la
cette table valeur calculée pour
ce numéro d’avion à
l’aide de la même
fonction de hachage

Tel que définie dans ce tableau, l’exécution de l’opération « OPEN () » de


l’itérateur « HashJoinProbe » a comme effet le démarrage du déroulement
du plan d’exécution par le lancement en parallèle de l’exécution de
l’itérateur du sous-arbre de gauche (pour le hachage en mémoire centrale
des lignes du fichier « Avions_FP ») et de l’exécution des itérateurs du sous-
arbre de droite (pour le calcul de la liste des adresses des lignes de la table
« Vols » qui concernent le pilote numéro « 10 »).

Par ailleurs, le déroulement du plan d’exécution physique d’une requête


SQL peut se faire selon une approche dite par matérialisation ou selon une
approche dite par pipeline.

Dans l’approche par matérialisation, un itérateur ne peut démarrer son


exécution que si ses itérateurs fils ont fini de s’exécuter et que leurs résultats
sont disponibles dans des fichiers temporaires stockés sur le disque. Il s’agit
d’une approche applicable quel que soit le plan d’exécution, mais très
coûteuse : le stockage des résultats intermédiaires sur le disque dans des
fichiers temporaires induit des entrées / sorties supplémentaires et un

478
SGBD relationnels – Tome 1, État de l’art

besoin additionnel en espace disque.

Pour ce qui concerne le plan d’exécution physique de l’exemple 8.4.7.i, la


matérialisation des résultats intermédiaires sur le disque dans des fichiers
temporaires peut se visualiser comme suit :

HashJoinProbe

HachJoinBuild ScanByAdr

Avions_FP Vols_FP ScanIndex

Vols_NoPilote_idx

Dans l’approche par pipeline, les itérateurs s’exécutent en se passant


directement les lignes résultats, sans avoir à les écrire sur le disque dans des
fichiers temporaires. Pour accroître le parallélisme entre un itérateur
consommateur et un itérateur producteur, une zone tampon peut être
prévue entre les deux en mémoire centrale. Ceci permet de faire en sorte
que le consommateur ne soit bloqué que lorsque cette zone tampon est vide
et que le producteur ne soit bloqué que lorsque cette zone tampon est
pleine. Il s’agit toutefois d’une approche non applicable de façon
systématique à tous les itérateurs, certaines tâches effectuées par certains
itérateurs étant du fait de leur nature, bloquantes pour d’autres qui doivent
attendre qu’elles s’exécutent entièrement avant de pouvoir démarrer. C’est
le cas des tâches de hachage d’une table, de tri d’une table, de suppression
des doublons etc...

Dans la pratique, le déroulement du plan d’exécution physique d’une


requête SQL s’effectue en utilisant l’approche par pipeline chaque fois que
cela est possible et l’approche par matérialisation chaque fois qu’une tâche
est bloquante.

8.5. L’ordre EXPLAIN PLAN d’Oracle


Pour rendre les requêtes SQL exécutables, chaque éditeur de SGBD doit
choisir pour chaque opérateur algébrique les algorithmes alternatifs que
son SGBD doit mettre en œuvre en fonction des caractéristiques de ces

479
Joachim TANKOANO

requêtes et de la taille de la mémoire centrale disponible.

Comme nous l’avons déjà souligné dans la section 8.4, la génération pour
une requête SQL donnée d’un plan d’exécution physique efficient est
tributaire : (1) de la diversité, de l’adéquation et de l’efficience de ces
algorithmes alternatifs mis à la disposition du SGBD pour pouvoir prendre
en compte, de la meilleure façon possible, les caractéristiques spécifiques à
chaque requête et (2) de l’espace disponible en mémoire centrale.

En fonction de ses choix d’algorithmes, l’éditeur du SGBD doit ensuite


définir et implémenter une bibliothèque minimale d’itérateurs pouvant
permettre au SGBD de construire à la volée, pour chaque opérateur
algébrique, un sous-arbre d’itérateurs qui implémente l’algorithme
alternatif qu’il a sélectionné.

Dans ce qui suit, nous présentons à titre d’exemples, quelques itérateurs


retenus pour le SGBD Oracle et le fonctionnement de l’ordre « EXPLAIN
PLAN » de ce SGBD qui lui permet d’expliciter, pour chaque requête SQL
(« SELECT », « INSERT », « UPDATE » et « DELETE »), son plan
d’exécution physique construit à la volée en agençant ces itérateurs.

8.5.1. Le plan d’exécution physique d’Oracle

Un ordre « EXPLAIN PLAN » d’Oracle spécifie une requête SQL et le nom


d’une table créée pour le stockage de plans d’exécution physique. Son
exécution génère dynamiquement le plan d’exécution physique de cette
requête et le stocke dans cette table.

Les informations contenues dans chaque ligne de cette table concernent un


itérateur correspondant à un nœud d’un plan d’exécution physique. Ces
informations décrivent l’arbre qui matérialise chaque plan d’exécution
physique, en incluant les estimations des performances attendues qui ont
conduit au choix de chaque itérateur. Parmi les colonnes de cette table, qui
décrivent un itérateur correspondant à un nœud d’un plan d’exécution
physique, on peut citer :

480
SGBD relationnels – Tome 1, État de l’art

• « STATEMENT_ID », qui contient l’identifiant de ce plan


d’exécution physique spécifié dans l’ordre « EXPLAIN PLAN » par
l’utilisateur
• « PLAN_ID », qui contient l’identifiant de ce plan dans la base de
données
• « TIMESTAMP », qui contient la date et l’heure à laquelle ce plan a
été généré
• « ID », qui associe dans ce plan un numéro d’étape à cet itérateur
• « PARENT_ID », qui contient le numéro de la prochaine étape dans
ce plan
• « POSITION », qui contient le numéro d’ordre de cet itérateur par
rapport à ses frères
• « DEPTH », qui indique le niveau de l’arbre de requête où se
positionne cet itérateur
• « OPERATION », « OPTIONS » et « OBJECT_TYPE », qui
identifient l’itérateur
• « OBJECT_NAME », qui contient le nom de la table ou de l’index en
entrée de l’itérateur
• « ACCESS_PREDICATES », qui contient des prédicats servant à
sélectionner les lignes d’un flux en entrée de cet itérateur
• « FILTER_PREDICATES », qui contient les prédicats de sélection
des lignes d’un flux en fonction d’un autre flux en entrée de cet
itérateur
• « CARDINALITY », qui contient une estimation du nombre de
lignes qui seront traitées par cet itérateur
• « IO_COST », qui contient une estimation du nombre d’entrées /
sorties disque qui sera effectué par cet itérateur
• « COST », qui contient une estimation du coût associé à cet itérateur.

Le contenu de cette table peut être affiché en partie par l’utilisateur, à l’aide
d’une structure arborescente matérialisée par des indentations où :

481
Joachim TANKOANO

• La ligne la moins indentée spécifie le type de requête concerné


• Les lignes les plus indentées correspondent aux itérateurs d’accès
aux tables et aux index, c’est-à-dire aux feuilles de l’arbre
• Les lignes de même niveau correspondent à des itérateurs frères
devant être exécutés selon leur ordre d’apparition.

Exemple 8.5.1.i : Ce qui suit est un exemple d’affichage du contenu de cette


table pour la requête « SELECT * FROM Vols v, Avions a WHERE
v.NoAvion = a.NoAvion ; » :
ID OPERATION OPTION OBJECT_NAME
01 SELECT STATEMENT
02 NESTED LOOPS
03 TABLE ACCESS FULL VOLS
04 TABLE ACCESS FULL AVIONS

8.5.2. Les itérateurs du SGBD Oracle

Ce qui suit présente quelques itérateurs du SGBD Oracle regroupés en


fonction de la nature des traitements qu’ils effectuent.

a) Les itérateurs pour l’accès aux lignes d’une table

OPERATION OPTION TÂCHE EFFECTUEE


TABLE ACCESS FULL Accéder séquentiellement à toutes les lignes
de la table spécifiée dans la colonne
« OBJECT_NAME »
BY INDEX ROWID Accéder directement aux lignes de la table
spécifiée dans la colonne
« OBJECT_NAME » à l’aide de ROWID
calculés par un itérateur fils de parcours d’un
index B+
HASH Accéder à des lignes regroupées
physiquement dans un bloc du cluster de
tables spécifié dans la colonne
« OBJECT_NAME » en utilisant une
fonction de hachage
CLUSTER Accéder à des lignes regroupées

482
SGBD relationnels – Tome 1, État de l’art

physiquement dans un bloc du cluster de


tables spécifié dans la colonne
« OBJECT_NAME » en utilisant un index
B+

b) Les itérateurs pour le parcours d’un index B+

OPERATION OPTION TÂCHE EFFECTUEE


INDEX UNIQUE SCAN Parcourir l’index B+ spécifié dans la colonne
« OBJECT_NAME » pour retrouver un seul
ROWID
RANGE SCAN Parcourir l’index B+ spécifié dans la colonne
« OBJECT_NAME » pour retrouver
plusieurs ROWID
FULL SCAN Parcourir séquentiellement le fichier index
dense FID de l’index B+ spécifié dans la
colonne « OBJECT_NAME » pour
retourner tous les ROWID des lignes du
fichier principal, selon l’ordre de tri de
l’index FID

c) Les itérateurs pour la manipulation des index bitmap

OPERATION OPTION TÂCHE EFFECTUEE


BITMAP INDEX SINGLE Parcourir un index bitmap créé pour une clé
VALUE de recherche, spécifié dans la colonne
« OBJECT_NAME » et retourner toutes les
valeurs binaires d’une colonne de cet index
correspondant à une valeur donnée de cette
clé de recherche
INDEX RANGE Parcourir un index bitmap créé pour une clé
SCAN de recherche, spécifié dans la colonne
« OBJECT_NAME » et retourner toutes les
valeurs binaires de plusieurs colonnes de cet
index correspondant à des valeurs données de
cette clé de recherche
INDEX FULL Parcourir un index bitmap créé pour une clé
SCAN de recherche, spécifié dans la colonne
« OBJECT_NAME » et retourner toutes les
valeurs binaires de chaque colonne de cet
index correspondant à une valeur de cette clé
de recherche

483
Joachim TANKOANO

CONVERSION TO Convertir le bitmap du flux d’entrée en


ROWIDS ROWIDS
CONVERSION Convertir les ROWID du flux d’entrée en
FROM ROWIDS bitmap
CONVERSION Compter le nombre de bits à 1 dans le bitmap
COUNT en entrée
MERGE Fusionner les bitmaps retournés par
« RANGE SCAN » en un seul bitmap
OR Effectuer un OU logique entre 2 bitmaps en
entrée
AND Effectuer un AND logique entre 2 bitmaps en
entrée
MINUS Équivalent à « AND NOT »

d) Les itérateurs pour le tri

OPERATION OPTION TÂCHE EFFECTUEE


SORT ORDER BY Appliquer la clause ORDER BY au flux en
entrée
UNIQUE Supprimer les doublons dans le flux en entrée
GROUP BY Retourner une ligne pour chaque groupe de
lignes sans application de fonctions de
groupe
AGGREGATE Retourner une ligne pour chaque groupe de
lignes avec application de fonctions de
groupe
JOIN Effectuer l’étape de tri de la jointure par tri
fusion

e) Les itérateurs pour la jointure des tables

OPERATION OPTION TÂCHE EFFECTUEE


NESTED LOOPS Effectuer une jointure par boucles imbriquées
OUTER Effectuer une jointure externe par boucles
imbriquées
MERGE JOIN Effectuer l’étape de fusion de la jointure par
tri fusion
OUTER Effectuer l’étape de fusion de la jointure
externe par tri fusion
HASH JOIN Effectuer une jointure par hachage

484
SGBD relationnels – Tome 1, État de l’art

f) Les autres itérateurs

OPERATION OPTION TÂCHE EFFECTUEE


AND-EQUAL Effectuer l’intersection de 2 listes de
ROWIDS en entrée en éliminant les
doublons
CONCATENATION Effectuer la concaténation des deux
listes de lignes en entrée
FILTER Éliminer les lignes du flux d’entrée qui
ne vérifient pas la condition spécifiée

8.5.3. La mise en œuvre de l’ordre « EXPLAIN PLAN »

L’affichage du plan d’exécution d’une requête SQL à l’aide de l’ordre


« EXPLAIN PLAN » passe par les trois étapes ci-après :
• La création de la table de stockage des plans en exécutant le script
« utlxplan.sql »
• La génération du plan d’exécution de la requête dans la table de
stockage des plans en exécutant un ordre « EXPLAIN PLAN » dont
la syntaxe simplifiée se définit comme suit :
EXPLAIN PLAN [INTO Nom_table]
SET STATEMENT_ID = Identifiant_choisi
FOR requête_SQL ;

• L’affichage du contenu de la table où est stocké le plan d’exécution à


l’aide d’un ordre « SELECT » comme dans l’exemple 8.5.1.i.

Les outils graphiques d’aide au développement, comme « SQL


DEVELOPER », simplifient énormément ce processus en permettant
d’exécuter ou d’afficher le plan d’exécution de la requête courante par un
simple clic.

8.5.4. Exemples

Ce qui suit présente quelques exemples de plans d’exécution physique


élaborés en suivant la procédure en trois étapes décrites dans le paragraphe
précédent 8.5.3.

485
Joachim TANKOANO

Exemple 8.5.4.i : Soit la requête définie comme suit :


SELECT * FROM t
WHERE c1 = 2 AND c2 = 'a' OR c3 BETWEEN 10 AND 15 ;

Où « c1 », « c2 » et « c3 » sont des colonnes de la table « t » pour lesquelles les


index BITMAP « idx_t_c1 », « idx_t_c2 », « idx_t_c3 » ont été respectivement
créés.

Le plan d’exécution de cette requête peut se définir de la façon suivante :

0 SELECT STATEMENT
1 TABLE ACCES BY INDEX ROWID t
2 BITMAP CONVERSION TO ROWID
3 BITMAP OR
4 BITMAP AND
5 BITMAP INDEX SINGLE VALUE idx_t_c1
6 BITMAP INDEX SINGLE VALUE idx_t_c2
7 BITMAP MERGE
8 BITMAP INDEX RANGE SCAN idx_t_c3

Dans ce plan d’exécution, le bitmap (c’est-à-dire, la séquence de bits)


correspondant aux lignes de la table « t » qui vérifient la condition de
sélection définie dans la clause « WHERE », est déterminé en manipulant
les trois index bitmap (« idx_t_c1 », « idx_t_c2 », « idx_t_c3 ») à l’aide des
itérateurs prévus à cet effet. Les ROWID des lignes qui ont leur bit à 1 dans
le bitmap résultant de cette manipulation sont ensuite déterminés par
l’itérateur « BITMAP CONVERSION TO ROWID » et transmis à
l’itérateur « TABLE ACCES BY INDEX ROWID » pour un accès direct aux
lignes recherchées.
Cet exemple met en évidence l’intérêt de l’indexation d’une table, sur les
clés de recherche à faible cardinalité, à l’aide d’index bitmap. Ces index qui
occupent très peu de place peuvent être manipulés à très faible coût en
mémoire centrale pour la détermination des adresses des lignes du fichier
principal de cette table qui vérifient une condition complexe, sans avoir à
accéder à ce fichier principal stocké sur un disque.

Les autres exemples qui suivent supposent le schéma relationnel défini


comme suit :

486
SGBD relationnels – Tome 1, État de l’art

Cinemas (IdCinema*, Nom, Adresse)


Salles (IdSalle*, Nom, Capacite+, IdCinema+)
Films (IdFilm, Titre, Année, IdRealisateur+)
Seances (IdSeance, HD, HF, IdSalle+, IdFilm)
Où : (1) les attributs pour lesquels il existe un index unique sont marqués à
l’aide du caractère « * », (2) les attributs pour lesquels il existe un index non
unique sont marqués à l’aide du caractère « + », (3) le nom des fichiers index
ont le format général « idx_NomTable_NomColonneIndexée ».

Exemple 8.5.4.ii : Soit la requête définie comme suit :


SELECT * FROM Cinemas
WHERE Nom = 'Ciné Burkina';

Son plan d’exécution peut se définir de la façon suivante :


0 SELECT STATEMENT
1 TABLE ACCESS FULL Cinemas

Ce plan d’exécution utilise l’algorithme de sélection sans index parce que


la table « Cinemas » n’est pas indexée sur la colonne « Nom ». Pour réduire
le temps d’exécution de cette requête, il suffit donc de créer tout simplement
cet index.

Exemple 8.5.4.iii : Soit la requête définie comme suit :


SELECT *
FROM Cinemas
WHERE IdCinema = 12;

Le plan d’exécution de cette requête peut se définir de la façon suivante :


0 SELECT STATEMENT
1 TABLE ACCESS BY INDEX ROWID Cinemas
2 INDEX UNIQUE SCAN idx_Cinemas_IdCinema

La table « Cinemas » étant indexée sur la colonne de sélection


« IdCinema », ce plan d’exécution utilise des itérateurs qui mettent en
œuvre l’algorithme de sélection par test d’égalité via un index. L’itérateur
« INDEX UNIQUE SCAN » traverse l’arbre B+ contenu dans le fichier
« idx_Cinemas_IdCinema », récupère le ROWID de la ligne où « IdCinema
= 12 », transmet ce ROWID à l’itérateur « TABLE ACCESS BY INDEX

487
Joachim TANKOANO

ROWID » qui l’utilise pour accéder directement à cette ligne dans le fichier
principal de la table « Cinemas ».

Exemple 8.5.4.iv : Soit la requête définie comme suit :


SELECT Capacite
FROM Salles
WHERE IdCinema = 12 AND Nom ='Salle de Zogona' ;

Le plan d’exécution de cette requête peut se définir de la façon suivante :

0 SELECT STATEMENT
1 TABLE ACCESS BY INDEX ROWID Salles
2 INDEX RANGE SCAN idx_Salles_IdCinema

La table « Salles » étant indexée sur la colonne de sélection « IdCinema »,


ce plan d’exécution utilise des itérateurs qui mettent en œuvre l’algorithme
de sélection par conjonction de tests d’égalité via un index.

Exemple 8.5.4.iv : Soit la requête définie comme suit :


SELECT Nom FROM Salles
WHERE IdCinema = 12 AND Capacite = 150;

Le plan d’exécution de cette requête peut se définir de la façon suivante :


0 SELECT STATEMENT
1 TABLE ACCESS BY INDEX ROWID Salles
2 AND-EQUAL
3 INDEX RANGE SCAN idx_Salles_IdCinema
4 INDEX RANGE SCAN idx_Salles_Capacité

La table « Salles » étant indexée sur la colonne de sélection « IdCinema »


et sur la colonne de sélection « Capacité », ce plan d’exécution utilise des
itérateurs qui mettent en œuvre l’algorithme de sélection par conjonction
de tests d’égalité via plusieurs index. Ce plan calcule d’abord, à l’aide de
l’itérateur « INDEX RANGE SCAN » et des index
« idx_Salles_IdCinema » et « idx_Salles_Capacité », l’ensemble des
ROWID des lignes de « Salles » où « IdCinema = 12 » et l’ensemble des
ROWID des lignes de « Salles » où « Capacite = 150 », calcule ensuite

488
SGBD relationnels – Tome 1, État de l’art

l’intersection de ces deux ensembles à l’aide de l’itérateur « AND-


EQUAL », transmet les ROWID qui en résulte à l’itérateur « TABLE
ACCESS BY INDEX ROWID » qui les utilise pour accéder directement aux
lignes recherchées dans la table « Salles ».

Exemple 8.5.4.v : Soit la requête définie comme suit :


SELECT Nom FROM Salles
WHERE IdCinema = 12 OR Capacite = 150;

Le plan d’exécution de cette requête peut se définir de la façon suivante :


0 SELECT STATEMENT
1 CONCATENATION
2 TABLE ACCESS BY INDEX ROWID Salles
3 INDEX RANGE SCAN idx_Salles_IdCinema
4 TABLE ACCESS BY INDEX ROWID Salles
5 INDEX RANGE SCAN idx_Salles_Capacité

La table « Salles » étant indexée sur la colonne de sélection « IdCinema »


et sur la colonne de sélection « Capacité », ce plan d’exécution utilise des
itérateurs qui mettent en œuvre l’algorithme de sélection par disjonction de
tests d’égalité via plusieurs index. Ce plan calcule d’abord la liste des lignes
de « Salles » où « IdCinema = 12 » et la liste des lignes de « Salles » où
« Capacite = 150 » et concatène ensuite ces deux listes à l’aide de l’itérateur
« CONCATENATION ».

Exemple 8.5.4.vi : Soit la requête définie comme suit :


SELECT Nom
FROM Salles
WHERE IdCinema = 12 OR Nom = 'Salle de Zogona';

Le plan d’exécution de cette requête peut se définir de la façon suivante :


0 SELECT STATEMENT
1 TABLE ACCESS FULL Salles

La table « Salles » n’étant pas indexée sur la colonne « Nom », ce plan


d’exécution met en œuvre un algorithme de sélection sans index.

489
Joachim TANKOANO

Exemple 8.5.4.vii : Soit la requête définie comme suit :


SELECT C.Nom, S.Capacite FROM Salles S, Cinema C
WHERE S.IdCinema = C.IdCinema ;

Le plan d’exécution de cette requête peut se définir de la façon suivante :


0 SELECT STATEMENT
1 NESTED LOOPS
2 TABLE ACCESS FULL Salles
3 TABLE ACCESS BY INDEX ROWID Cinemas
4 INDEX UNIQUE SCAN idx_Cinemas_IdCinema

La table « Cinemas » étant indexée sur la colonne de jointure « IdCinema »,


ce plan d’exécution utilise des itérateurs qui mettent en œuvre l’algorithme
de jointure par boucles imbriquées avec index.

Exemple 8.5.4.viii : Soit la requête définie comme suit :


SELECT C.Nom, S.Capacite
FROM Salles S, Cinema C
WHERE S.IdCinema = C.IdCinema AND S.Capacite = 150 ;

Le plan d’exécution de cette requête peut se définir de la façon suivante :


0 SELECT STATEMENT
1 NESTED LOOPS
2 TABLE ACCESS BY INDEX ROWID Salles
3 INDEX RANGE SCAN idx_Salles_Capacite
4 TABLE ACCESS BY INDEX ROWID Cinemas
5 INDEX UNIQUE SCAN idx_Cinemas_IdCinema

Le prédicat de la clause « WHERE » de cette requête exprime une condition


de jointure et une condition de sélection. La table « Cinemas » étant indexée
sur la colonne de jointure « IdCinema », ce plan d’exécution utilise pour ce
qui concerne la jointure des itérateurs qui mettent en œuvre l’algorithme de
jointure par boucles imbriquées avec index. Par ailleurs, la table « Salles »
étant indexée sur la colonne de sélection « Capacité », ce plan d’exécution
utilise, pour ce qui concerne la sélection, des itérateurs qui mettent en
œuvre l’algorithme de sélection par test d’égalité avec index.

490
SGBD relationnels – Tome 1, État de l’art

Exemple 8.5.4.ix : Soit la requête définie comme suit :


SELECT Titre
FROM Films F, Seances S
WHERE F.IdFilm = S.IdFilm AND S.HD = '14:00:00' ;

Le plan d’exécution de cette requête peut se définir de la façon suivante :


0 SELECT STATEMENT
1 MERGE JOIN
2 SORT JOIN
3 TABLE ACCESS FULL Seances
4 SORT JOIN
5 TABLE ACCESS FULL Films

Le prédicat de la clause « WHERE » de la requête exprime une condition de


jointure et une condition de sélection définies sur des colonnes pour
lesquelles aucun index n’est défini. De ce fait, ce plan d’exécution utilise des
itérateurs qui mettent en œuvre l’algorithme de jointure par tri-fusion.

Exemple 8.5.4.x : Soit la requête définie comme suit :


SELECT Nom
FROM Cinemas
WHERE NOT EXISTS (SELECT *
FROM Seances, Salles
WHERE Salles.IdCinema = Cinemas.IdCinema
AND Salles.IdSalle = Seances.IdSalle
AND Seances.HD > '14 :00 :00') ;

Le plan d’exécution de cette requête peut se définir de la façon suivante :


0 SELECT STATEMENT
1 FILTER
2 TABLE ACCES FULL Cinemas
3 NESTED LOOPS
4 TABLE ACCESS BY INDEX ROWID Salles
5 INDEX RANGE SCAN idx_Salles_IdCinema
6 TABLE ACCESS BY INDEX ROWID Seances
7 INDEX RANGE SCAN idx_Seances_IdSalle

La requête SQL concernée utilise une requête imbriquée synchronisée. Ce

491
Joachim TANKOANO

plan d’exécution utilise des itérateurs qui mettent en œuvre l’algorithme de


jointure par boucles imbriquées avec index pour ce qui concerne cette
requête imbriquée synchronisée, et l’itérateur de filtrage « FILTER » pour
sélectionner les lignes de « Cinemas » pour lesquelles la requête
synchronisée retourne au moins une ligne.

8.6. Exercices
Exercice 8.6.i : Soit une base de données universitaire dont le schéma est
défini comme suit :
Etudiants (NoSS, Nom, Adresse, MoyGéné)
CoursSuivis (NoSS, NoCours, Moy)
Cours (NoCours, Titre, Prof)
1) Formulez une requête SQL qui permet d’obtenir le nom des étudiants
ayant suivi le cours BD et dont la moyenne générale est supérieure ou
égale à 12.
2) Représentez cette requête à l’aide d’un arbre de requête brut.
3) Optimisez cet arbre.
4) Quel pourrait être le plan d’exécution physique de cette requête sous
Oracle si on fait l’hypothèse que la table « Cours » est indexée sur la
colonne « NoCours » à l’aide d’un arbre B+ et que table « Etudiants »
est indexée quant à elle sur la colonne « NoSS » à l’aide également d’un
arbre B+ ?

Exercice 8.6.ii : Rédigez par groupe de deux ou trois, en s’appuyant sur une
revue de documents techniques de référence, un rapport de présentation du
processus de transformation d’une requête SQL en code exécutable par le
SGBD MySQL et par le SGBD PostgreSQL.

Pour chacun de ces deux SGBD, ce rapport doit faire ressortir :


• Les informations de description du plan d’exécution physique que le
SGBD donne à l’utilisateur à travers l’ordre « EXPLAIN »

492
SGBD relationnels – Tome 1, État de l’art

• Les différents types d’algorithmes que le SGBD met en œuvre pour


l’exécution des opérateurs algébriques
• Les itérateurs mis à la disposition du SGBD pour lui permettre de
construire à la volée des plans d’exécution physiques
• Quelques exemples commentés de plans d’exécution physique.

493
Joachim TANKOANO

BIBLIOGRAPHIE
Aho A.V., Sagiv Y. & Ullman J.D.: Equivalences among Relational Expressions
- SIAM Journal of Computing, vol. 8, n° 2, p. 218-246, Juin 1979.

Antonio Albano, Dario Colazzo, Giorgio Ghelli & Renzo Orsini:


Relational DBMS Internals - Copyright © 2015 by A. Albano, D.
Colazzo, G. Ghelli, R. Orsini

Date C.J. : Introduction aux bases de données - 8è édition - Vuibert, Paris, 2004

Davison D. L. & Graefe G.: Memory-Contention Responsive Hash Joins - Proc.


of the International Conf. on Very Large Databases (1994).

Gardarin G. : Bases de données, 5e tirage 2003 - EYROLLES

Graefe G.: Query Evaluation Techniques for Large Databases - ACM Computer
Surveys, vol. 25, n° 2, p. 73-170, Juin 1993.

Graefe G., Bunker R. & Cooper S.: Hash Joins and Hash Teams in Microsoft
SQL Server - Proc. of the International Conf. on Very Large Databases
(1998), pages 86–97.

Jarke M. & Koch J.: Query Optimization in Database Systems - ACM


Computing Surveys, vol. 16, n° 2, p. 111-152, Juin 1984

Knuth D.E.: The Art of Computer Programming, Volume 3: Sorting and


Searching - Addison-Wesley, Reading, Mass., 1973

ORACLE: Oracle® Database Concepts 12c Release 1 (12.1) - July 2017

Matthias Jarke and Jürgen Koch: Query Optimization in Database Systems.


ACM Computing Surveys. ACM Computing Surveys, Volume 16,
Issue 2, June 1984, pp 111–152.
https://doi.org/10.1145/356924.356928

MIRANDA S. & José-Maria BUSTA : L'art des bases de données, Tome 2, Les
bases de données relationnelles - 2è édition - Eyrolles, 1990

Shapiro L. D.: Join Processing in Database Systems with Large Main Memories
- ACM Transactions on Database Systems, Volume 11, Number 3
(1986), pages 239–264.

494
SGBD relationnels – Tome 1, État de l’art

Silberschatz A., Korth H.F. & Sudarshan S.: Database system concepts, sixth
edition - McGraw-Hill, 2011

Smith J.M. & Chang P.Y.: Optimizing the performance of a Relational Algebra
Database Interface - Comm. ACM, vol. 18, n° 10, p. 68-579, 1975

Yehoshua Sagiv and Mihalis Yannakakis. Equivalences Among Relational


Expressions with the Union and Difference Operators. Journal of the ACM,
Volume 27, Issue 4, Oct. 1980, pp 633–655.
https://doi.org/10.1145/322217.322221

Zeller H. & Gray J.: An Adaptive Hash Join Algorithm for Multiuser
Environments - Proc. of the International Conf. on Very Large
Databases (1990), pages 186–197.

495
Chapitre 9. : Gestion des accès concurrents et
des reprises en cas d’incident

9.1. Introduction
Nous avons vu dans la section 1.2 que, la gestion des accès simultanés aux
données par plusieurs utilisateurs, afin d’éviter les interférences pouvant
altérer l’intégrité des données, ainsi que la restauration des données dans
un état cohérent après la survenue d’un incident, sont des exigences
relatives à la sécurité des données, pour lesquelles la technologie des bases
de données doit fournir une garantie.

Les mécanismes mis en œuvre par les SGBD, pour fournir leurs services,
doivent permettre d’éviter la survenue des interférences pouvant altérer
l’intégrité des données en y introduisant des incohérences.

Par ailleurs, après la survenue d’un incident, les SGBD doivent fournir les
moyens pouvant permettre de remettre la base de données concernée dans
son état antérieur cohérent le plus récent, afin d’éviter ainsi, toute perte ou
altération accidentelle de données.

Ce chapitre :
• Présente dans un premier temps, (1) les caractéristiques des
anomalies qui peuvent survenir lorsque plusieurs utilisateurs ont
accès simultanément à une base de données, (2) les caractéristiques
des incidents qui peuvent survenir dans le cycle de vie d’une base
de données, (3) les propriétés attendues de l’environnement
d’exécution offert par les SGBD, pour que ces anomalies ne puissent
pas survenir et pour que la restauration des données dans un état
cohérent après la survenue d’un incident soit possible
• Examine ensuite de façon plus détaillée, comment les SGBD
procèdent pour offrir un environnement d’exécution, pouvant
apporter une solution satisfaisante aux problèmes que posent les
accès concurrents à une base de données et les reprises en cas

496
SGBD relationnels – Tome 1, État de l’art

d’incident
• Aborde, à titre d’illustration, la prise en compte des concepts et
techniques présentés, au niveau du SGBD Oracle et de son langage
SQL.

9.2. Problématique
Les applications qui manipulent une base de données sont par nature des
applications multiutilisateurs. Ces applications sont exploitées
simultanément par des centaines, voire des milliers d’utilisateurs, pour
l’exécution de transactions informatiques. Ces transactions effectuent des
traitements dont la finalité est de fournir aux utilisateurs de ces applications
les services dont ils ont besoin. On peut citer à titre d’exemple :
• Les applications bancaires, que chaque utilisateur peut utiliser pour
exécuter en ligne une transaction de transfert de crédit d’un compte
A vers un compte B
• Les applications de réservation de vols des compagnies aériennes,
que chaque utilisateur peut utiliser pour exécuter en ligne une
transaction qui effectue une réservation de « n » sièges sur le vol « x »
• Les applications de vente en ligne, que chaque utilisateur peut
utiliser pour exécuter une transaction qui effectue un achat de « n »
produits.

Chacune de ces transactions informatiques conduit à une séquence


d’opérations de lectures / écritures dans la base de données, générée par
l’exécution d’une portion de code d’un programme. Chacune de ces
séquences de lectures / écritures dans la base de données doit se terminer,
soit par une validation de la transaction concernée en exécutant
l’instruction « COMMIT » si tout s’est bien passé, soit par une annulation
de cette transaction dans le cas contraire, en exécutant l’instruction
« ROLLBACK ».

Exemple 9.2.i : Le pseudo code qui suit est un extrait du code d’un
programme imaginaire qui permet de réserver 5 sièges sur le vol 315 :

497
Joachim TANKOANO

SELECT v.VolNbPlaces INTO V_VolNbPlaces


FROM Vols v WHERE v.NoVol = 315 ;
If V_VolNbPlaces = 5
Then Begin
UPDATE Vols v
SET v.VolNbPlaces = v.VolNbPlaces – 5 WHERE v.NoVol = 315 ;
INSERT INTO Clients (NoClient, NoVol, Places_réservées)
VALUES (17, 315, 5);
End;
If échec Then ROLLBACK Else COMMIT;

9.2.1. Les anomalies liées aux accès simultanés à une base de


données

L’exécution simultanée de plusieurs transactions, engendre un


entrelacement des opérations de lectures / écritures que ces transactions
effectuent dans la base de données. Cet entrelacement peut avoir comme
conséquence, des interférences négatives entre ces transactions pouvant
engendrer quatre types d’anomalies :
• Des mises à jour perdues
• Des lectures impropres (ou sales) qui conduisent à des résultats
incohérents
• Des lectures non reproductibles
• L’apparition de tuples fantômes.

Par ailleurs, l’exécution simultanée de plusieurs transactions engendre


aussi un entrelacement des opérations de validation (« COMMIT ») et
d’annulation (« ROLLBACK ») de ces transactions avec les opérations de
lectures / écritures qu’elles effectuent dans la base de données. Cet
entrelacement peut provoquer des conséquences indésirables sur la
récouvrabilité de ces transactions, c’est-à-dire sur la possibilité offerte pour
l’annulation de ces transactions en cas d’incident. Cet entrelacement peut
rendre ces transactions :
• Non recouvrables (c’est-à-dire non annulables)

498
SGBD relationnels – Tome 1, État de l’art

• Recouvrables avec annulations en cascade


• Recouvrables sans annulations en cascade
• Strictes.

Ce qui suit explicite ces différentes anomalies et ces différentes


conséquences qui peuvent découler de l’entrelacement des opérations des
transactions, lorsque celles-ci s’exécutent simultanément.

a) L’anomalie des mises à jour perdues

Supposons que la transaction « T1 » qui crédite le compte « A » de 100 000


F et la transaction « T2 » qui débite ce compte de 200 000 F, s’exécutent
simultanément en s’entrelaçant comme indiqué dans le tableau ci-dessous.
N° T1 T2
1 Lire (Compte_A) ;
2 Compte_A  Compte_A + 100000 ;
3 Lire (Compte_A) ;
4 Compte_A  Compte_A – 200000 ;
5 Ecrire (Compte_A) ;
6 COMMIT ;
7 Ecrire (Compte_A) ;
8 COMMIT ;

À la fin de l’exécution de ces deux transactions, le nouvel état de la base de


données sera comme si la transaction « T1 » a été la seule à s’exécuter, la
mise à jour effectuée par « T2 » ayant été tout simplement écrasée par celle
de « T1 », donc perdue. Le solde du compte « A » augmentera de 100 000 F.

Pour éviter la mise à jour perdue du compte « A » par « T2 », il aurait fallu


que le SGBD retarde la lecture du compte « A » par « T2 », jusqu’à ce que
« T1 » ait fini ce qu’il a commencé, parce que « T1 » a lu avant « T2 » le
contenu du compte « A » en vue de le modifier.

b) L’anomalie des lectures impropres ou sales

Supposons que la transaction « T1 » qui transfert 100 000 F du compte « A »


d’un client vers le compte « B » du même client et la transaction « T2 » qui

499
Joachim TANKOANO

affiche la somme de ces 2 comptes, s’exécutent simultanément en


s’entrelaçant de la façon suivante :
N° T1 T2
1 Lire (Compte_A) ;
2 Compte_A  Compte_A -100000 ;
3 Ecrire (Compte_A) ;
4 Lire (Compte_A) ;
5 Lire (Compte_B) ;
6 Cumul_comptes  Compte_A +
Compte_B ;
7 Afficher (Cumul_comptes) ;
8 COMMIT ;
9 Lire (Compte_B) ;
10 Compte_B  Compte_B + 100000 ;
11 Ecrire (Compte_B) ;
12 COMMIT ;

À la fin de l’exécution de ces deux transactions, la variable


« Cumul_comptes », dans laquelle est calculé le cumul des deux comptes,
contiendra le cumul du nouveau solde du compte « A » et de l’ancien solde
du compte « B », ce qui correspond à un cumul incohérent. Pour que ce
cumul soit cohérent, il doit correspondre soit au cumul des anciens soldes
des comptes « A » et « B », soit au cumul des nouveaux soldes de ces deux
comptes. Le contenu de la variable « Cumul_comptes » est incohérent
parce que la transaction « T2 » a lu le compte « A » modifié par « T1 », sans
attendre que « T1 » fasse aussi la modification du solde du compte « B ».
Autrement dit, la transaction « T2 » a effectué une lecture sale, c’est-à-dire
une lecture d’une donnée modifiée par une autre transaction qui n’a pas
encore achevé le traitement qu’elle a entrepris.

Pour que le cumul des comptes « A » et « B » dans la variable


« Cumul_comptes » soit cohérent, il aurait fallu que le SGBD retarde la
lecture du compte « A » par « T2 », jusqu’à la fin de « T1 », parce que le
contenu du compte « A » a déjà été modifié par « T1 » alors que celui du
compte « B » n’a pas encore été modifié par « T1 ».

500
SGBD relationnels – Tome 1, État de l’art

c) L’anomalie des lectures non reproductibles

Supposons que la transaction « T1 » qui diminue de 4 le nombre de places


disponibles sur le vol n° 2 et la transaction « T2 » qui réserve toutes les
places disponibles sur ce vol s’il en existe 4 ou plus, s’exécutent
simultanément en s’entrelaçant de la façon suivante :
N° T1 T2
1 SELECT VolNbPlaces
INTO V_VolNbPlaces
FROM Vols WHERE NoVol = 2 ;
2 If V_VolNbPlaces = 4 Then
3 UPDATE Vols
SET VolNbPlaces = VolNbPlaces – 4
WHERE NoVol = 2;
4 COMMIT ;
5 Begin
INSERT INTO Clients
(NoClient, NoVol, VolPlacesPrises)
VALUES (17, 2,
(SELECT VolNbPlaces
FROM Vols
WHERE NoVol = 2));
6 UPDATE Vols
SET VolNbPlaces = 0
WHERE NoVol = 2;
End;
7 COMMIT ;

À la fin de l’exécution de ces deux transactions, si le nombre de places libres


dans le vol n° 2 était au départ de 6, le nombre de places réservées par le
client 17 sera 2 et non 6. Entre la ligne 2 et la ligne 5, le contenu de la colonne
« VolNbPlaces » dans la ligne qui concerne le vol n° 2 est passé de 6 à 2 à
cause de la mise à jour effectuée par « T1 » en ligne 3, ce qui rend le résultat
de la lecture de cette ligne par « T2 » non reproductible en ligne 5.

Pour que le résultat de la lecture effectuée en ligne 1 par « T2 » soit


reproductible en ligne 5, il aurait fallu que le SGBD retarde la mise à jour
effectuée en ligne 3 par « T1 », jusqu’à ce que « T2 » termine la réservation,
à cause de cette première lecture de « T2 ».

501
Joachim TANKOANO

d) L’anomalie des tuples fantômes

Supposons que la transaction « T1 » crée un nouveau vol de 150 places et


que la transaction « T2 » réserve 11 places pour un client qui doit faire
voyager une équipe de football sur un vol, seulement si ce vol est le seul à
avoir au moins 11 places de disponibles. Supposons que « T1 » et « T2 »
s’exécutent simultanément en s’entrelaçant de la façon suivante :
N° T1 T2
1 SELECT COUNTt (*)
INTO V_NbVols
FROM Vols
WHERE VolNbPlaces = 11 ;
2 If V_NbVols = 1 Then
3 INSERT INTO Vols
(NoVol, RefVol, VolNbPlaces)
VALUES (19, ‘VU 240’, 150) ;
4 COMMIT ;
5 Begin
UPDATE Vols
SET VolNbPlaces = VolNbPlaces – 11
WHERE VolNbPlaces = 11;
End;
6 COMMIT ;

À la fin de l’exécution de ces deux transactions, l’état de la base de données


reflètera une réservation de 11 places sur le seul vol qui avait au départ au
moins 11 places de disponible, mais aussi sur le vol N° 19 créé en ligne 3
par « T1 ». Tout se passera donc du point de vue de « T2 » comme si au
cours de son exécution, un fantôme qu’il ne peut pas voir a ajouté à son insu
une nouvelle ligne dans la base de données.

Pour éviter l’apparition d’un tuple fantôme au cours de l’exécution de


« T2 », il aurait fallu que l’insertion de la ligne effectuée en ligne 3 par « T1 »
soit retardée par le SGBD, jusqu’à ce que la réservation des 11 places par
« T2 » se termine, à cause de la première consultation de la table « Vols »
effectuée par « T2 ».

e) Les propriétés relatives à la récouvrabilité d’une transaction

Supposons que la transaction « T1 » qui crédite le compte « A » de 100 000

502
SGBD relationnels – Tome 1, État de l’art

F et la transaction « T2 » qui débite ce compte de 200 000 F, s’exécutent


simultanément en s’entrelaçant de la façon suivante :
N° T1 T2
1 Lire (Compte_A) ;
2 Compte_A  Compte_A + 100000 ;
3 Ecrire (Compte_A) ;
4 Lire (Compte_A) ;
5 Compte_A  Compte_A – 200000 ;
6 Ecrire (Compte_A) ;
7 COMMIT ;
8 ROLLBACK ;

Le « ROLLBACK » que « T1 » effectue en ligne 8 pour annuler les


opérations qu’elle a effectuées dans la base de données, doit entrainer aussi
le « ROLLBACK » de « T2 », parce qu’en ligne 4, « T2 » a lu et exploité la
valeur du compte « A » modifiée par « T1 ». La transaction « T2 » ne
pouvant plus faire de « ROLLBACK » parce qu’elle a déjà fait son
« COMMIT » en ligne 7, le « ROLLBACK » de « T1 » en ligne 8 ne pourra
pas non plus se faire. De ce fait, on dit que « T1 » est devenu non
recouvrable, ce qui est une propriété non souhaitable. Pour que « T1 » ne
soit pas dans un statut caractérisé par cette propriété, il aurait fallu que le
« COMMIT » de « T2 » en ligne 7 soit retardé par le SGBD, jusqu’à ce que
« T1 » fasse son « COMMIT » ou son « ROLLBACK », parce qu’en ligne 4,
« T2 » a lu la valeur du compte « A » modifiée par « T1 ».

Si le « COMMIT » de « T2 » en ligne 7 intervenait après le « COMMIT » ou


le « ROLLBACK » de « T1 » en ligne 8, « T1 » deviendrait recouvrable avec
annulations en cascade. Ceci veut dire que l’annulation des opérations
effectuées par « T1 » dans la base de données deviendrait possible mais
devrait aussi entrainer l’annulation en cascade de toutes les autres
transactions qui, comme « T2 », auraient utilisé directement ou
indirectement des données modifiées par « T1 ». Bien entendu, ce statut
non plus n’est pas souhaitable. Pour que « T1 » ne soit pas dans ce statut, il
aurait fallu que la lecture par « T2 » en ligne 4 d’une donnée salie par « T1 »
soit retardée par le SGBD, jusqu’à ce que « T1 » qui a sali cette donnée fasse
son « COMMIT » ou son « ROLLBACK ».

503
Joachim TANKOANO

Si la lecture par « T2 » en ligne 4 d’une donnée salie par « T1 » intervenait


après le « COMMIT » ou le « ROLLBACK » de « T1 », « T1 » deviendrait
recouvrable sans annulations en cascade, ce qui veut dire que l’annulation
des opérations effectuées par « T1 » sur la base de données deviendrait
possible sans que cela n’entraine l’annulation d’autres transactions.

Considérons à présent que la transaction « T1 » qui modifie « X » à 2 et la


transaction « T2 » qui modifie « X » à 3, s’exécutent simultanément en
s’entrelaçant de la façon suivante et que « X » contient au départ 1 :
N° T1 T2
1 X2;
2 Ecrire (X) ;
3 X3;
4 Ecrire (X) ;
5 ROLLBACK ;
6 ROLLBACK ;

Dans ce cas de figure, bien que « T1 » et « T2 » soient recouvrables sans


annulations en cascade, parce qu’aucune d’entre elles n’a effectué de lecture
sale, les « ROLLBACK » de « T1 » et de « T2 » en lignes 5 et 6 ne restaurent
pas « X » à sa valeur initiale :
• Le « ROLLBACK » de « T1 » en ligne 5 restaure « X » à 1 alors que
« X » a été modifié entre temps par « T2 » à 3
• Le « ROLLBACK » de « T2 » en ligne 6 restaure « X » à 2 alors que
« T1 » l’a déjà restaurée à 1.

Ceci revient à dire que, être recouvrable sans annulations en cascade, peut
ne pas être, comme dans cet exemple, un statut souhaitable pour une
transaction. Dans cet exemple, le « ROLLBACK » de « T2 » ne restaure pas
« X » dans un état cohérent parce que « T2 » a modifié « X » avant le
« COMMIT » de « T1 », alors que « X » avait déjà été modifié par « T1 ».
On dit que « T2 » a effectué une écriture sale. Pour que le « ROLLBACK »
de « T2 » restaure « X » dans un état cohérent, il aurait fallu que le SGBD
retarde l’écriture sale effectuée par « T2 » en ligne 4, jusqu’à ce que « T1 »
fasse son « COMMIT ».

Si la modification de « X » par « T2 » intervenait après le « COMMIT » ou

504
SGBD relationnels – Tome 1, État de l’art

le « ROLLBACK » de « T1 » en ligne 5, « T1 » et « T2 » deviendraient


strictes, ce qui veut dire que dans ce cas, chacune de ces deux transactions
pourrait être annulée sans entrainer des annulations en cascade et que
chacune de ces annulations restaurerait la base de données dans son état
initial, ce qui correspond au statut souhaitable pour toute transaction.

De façon générale, l’arbre binaire de décision qui suit permet de dire si une
transaction est non recouvrable, recouvrable avec annulations en cascade,
recouvrable sans annulations en cascade ou stricte.
Non recouvrable
OUI

Lectures sales et validation


avant la fin de la transaction Recouvrable avec
OUI à l’origine de la modification ? NON annulations en
cascade

Lectures sales ? Recouvrable sans


OUI annulations en
NON cascade
Écritures sales ?
Stricte
NON

9.2.2. Les caractéristiques des incidents

Les causes des incidents qui peuvent survenir dans le cycle de vie d’une
base de données sont multiples et variées. Ces incidents peuvent être la
conséquence d’erreurs de conception, de programmation ou de
paramétrage d’une application, de dysfonctionnements du SGBD, d’erreurs
dans les données fournies par l’utilisateur, de violation de contraintes
d’intégrité ou de confidentialité, d’actions malveillantes, de défaillances
matérielles, d’évènements externes (coupure de courant, inondation,
tremblement de terre, etc.), etc…

Au-delà de leurs causes, pour assurer la gestion de ces incidents, les SGBD
les regroupent en trois grandes catégories :
• Les incidents localisés au niveau de l’exécution d’une transaction

505
Joachim TANKOANO

• Les incidents qui entrainent une perte du contenu de la mémoire


centrale
• Les incidents qui entrainent une détérioration de toute ou partie du
contenu de la base de données sur le support physique.

Dans tous ces trois cas de figures, il doit être possible d’effectuer une
reprise. L’objectif de cette reprise doit être de remettre la base de données
dans son état antérieur cohérent le plus récent, afin d’éviter toute perte ou
toute altération des données. En d’autres termes, toutes les transactions en
cours au moment de l’incident doivent être strictes, c’est-à-dire
recouvrables sans annulations en cascade et leur annulation doit remettre
la base de données dans un état antérieur cohérent.

9.2.3. Les propriétés attendues de l’environnement d’exécution

Pour éviter la survenue d’anomalies liées à des accès concurrents et pour


que la reprise soit possible chaque fois qu’un incident survient, les SGBD
doivent offrir aux transactions un environnement d’exécution qui leur
fournit la garantie de quatre propriétés essentielles, regroupées sous
l’acronyme de propriétés « ACID », à savoir :
• L’Atomicité
• La Cohérence
• L’Isolation
• La Durabilité.

L’atomicité des transactions doit garantir que chaque transaction est


indivisible et est exécutée par le SGBD selon le principe « tout ou rien ».
Toute transaction qui démarre doit s’exécuter jusqu’à son terme. Si cela
n’est pas possible, le SGBD doit annuler les opérations qu’elle a effectuées
dans la base de données pour que tout se passe comme si elle n’a jamais
existé. L’atomicité requiert donc, du point de la récouvrabilité, des
transactions strictes.

La cohérence des traitements effectués par les transactions doit garantir que
chaque transaction que le SGBD exécute, démarre avec des données

506
SGBD relationnels – Tome 1, État de l’art

cohérentes dans la base de données et laisse après son exécution la base de


données dans un nouvel état cohérent, où aucune contrainte d’intégrité sur
les données n’a été violée.

L’isolation des transactions doit garantir que pendant l’exécution d’une


transaction par le SGBD, les modifications effectuées par les autres
transactions dans la base de données lui sont invisibles et que les
modifications qu’elle effectue dans la base de données sont aussi invisibles
aux autres transactions. L’état des données modifiées dans la base de
données par une transaction ne doit devenir visible par les autres
transactions qu’après la validation de celle-ci.

La durabilité des traitements effectués par les transactions doit garantir


qu’après la validation d’une transaction, les mises à jour effectuées par cette
transaction dans la base de données doivent devenir définitives et peuvent
être restaurées après tout incident.

Ce qui suit montre comment les SGBD procèdent pour fournir une garantie
d’ACIDité (c’est-à-dire, de ces propriétés) dans le cadre de la gestion des
accès concurrents et des reprises après incident, afin d’éviter toute
altération de l’intégrité des données liée à une interférence entre
transactions et toute perte accidentelle de données.

9.3. La gestion des accès concurrents


Les anomalies qui peuvent survenir lorsque des transactions accèdent
simultanément à une base de données ne dépendent que de la manière dont
les opérations de ces transactions (« COMMIT », « ROLLBACK » et
opérations de lectures / écritures dans la base de données) s’entrelacent
dynamiquement en s’exécutant.

Dans ce qui suit, nous commencerons par caractériser ces entrelacements


dynamiques appelés plans d’exécution de transactions concurrentes.

Ensuite, nous présenterons les approches et techniques que les SGBD


utilisent pour modifier à la volée, l’entrelacement des opérations d’un plan
d’exécution de transactions concurrentes, pour garantir l’isolation de
chaque transaction concernée et les possibilités de reprise après incident,

507
Joachim TANKOANO

afin de satisfaire à l’exigence liée à la sécurité des données.

9.3.1. Les caractéristiques d’un plan d’exécution de


transactions concurrentes

a) Quelques définitions

Les plans d’exécution sériels

Si le plan d’exécution de deux transactions « T1 » et « T2 » correspond à une


exécution complète des opérations de lectures / écritures de « T1 » suivie
de celle de « T2 » ou à une exécution complète des opérations de lectures /
écritures de « T2 » suivie de celle de « T1 », on dit que ce plan est sériel.

Un plan d’exécution sériel de « n » transactions est donc un plan


d’exécution où les opérations de lectures / écritures de ces « n »
transactions ne s’entrelacent pas, où les transactions ne s’exécutent pas
simultanément mais l’une après l’autre, ce qui en pratique n’est pas
tolérable pour les utilisateurs concernés par ces transactions. En revanche,
dans un plan d’exécution sériel, aucune interférence entre transactions n’est
possible, ce qui veut dire qu’il n’est pas possible de constater, à la fin de
l’exécution d’un plan sériel, une anomalie dans les résultats, due à une mise
à jour perdue, à une lecture sale, à une lecture non reproductible ou à des
tuples fantômes.

Étant donné « n » transactions, on peut dénombrer « n ! » plans d’exécution


sériels différents.

Les plans d’exécution sérialisables

Un plan d’exécution de « n » transactions, où les opérations de lectures /


écritures de ces transactions s’entrelacent, est dit sérialisable, si ce plan
d’exécution est équivalent à l’un des « n ! » plans d’exécution sériels de ces
transactions, c’est-à-dire, s’il produit le même effet que ce plan d’exécution
sériel.

Dans un plan d’exécution sérialisable de « n » transactions, ces « n »


transactions s’exécutent donc simultanément, mais sans interférences entre

508
SGBD relationnels – Tome 1, État de l’art

elles, parce que ce plan produit le même effet que celui d’un plan
d’exécution sériel. Ceci veut dire qu’il n’est pas possible de constater, à la
fin de l’exécution d’un tel plan, une anomalie dans les résultats, due à une
mise à jour perdue, à une lecture sale, à une lecture non reproductible ou à
des tuples fantômes.

Exemple 9.3.1.i : Soient deux transactions « T1 » et « T2 » définies comme


suit :
T1 : read (A) ; A  A – 10 ; write (A) ; read (B) ; B  B + 10 ; write (B) ; COMMIT
T2 : read (B) ; B  B – 20 ; write (B) ; read (C) ; C  C + 20 ; write (C) ; COMMIT

Pour ces deux transactions, on peut définir comme suit un plan d’exécution
sériel, où « T2 » s’exécute avant « T1 », un plan d’exécution sérialisable
équivalent, produisant le même effet que ce plan d’exécution sériel et un
plan d’exécution non-sérialisable, dont l’effet produit est différent de celui
d’un plan sériel :
Plan sériel (diminue A et B de
10 et augmente C de 20)
T1 T2
1 read (B)
2 B  B–20
3 write (B)
4 read (C)
5 C  C+20
6 write (C)
7 COMMIT
8 read (A)
9 A  A–10
10 write (A)
11 read (B)
12 B  B+10
13 write (B)
14 COMMIT

509
Joachim TANKOANO

Plan sérialisable (diminue A et Plan non sérialisable (diminue A de


B de 10 et augmente C de 20) 10 et augmente B de 10 et C de 20)
T1 T2 T1 T2
1 read (A) 1 read (A)
2 read (B) 2 A  A–10
3 A  A–10 3 read (B)
4 B  B–20 4 write (A)
5 write (A) 5 B  B–20
6 write (B) 6 read (B)
7 read (B) 7 write (B)
8 read (C) 8 B  B+10
9 B  B+10 9 read (C)
10 C  C+20 10 write (B)
11 write (B) 11 COMMIT
12 COMMIT 12 C  C+20
13 write (C) 13 write (C)
14 COMMIT 14 COMMIT

Les opérations commutables et non-commutables (ou en conflit) dans un


plan d’exécution de transactions concurrentes

Dans un plan d’exécution de transactions concurrentes, deux opérations de


lectures / écritures dans la base de données, notées « O1 » et « O2 », sont
commutables si et seulement si, l’exécution de « O1 » suivi de l’exécution
de « O2 » a le même effet que l’exécution de « O2 » suivie de l’exécution de
« O1 », c’est-à-dire, donne le même résultat au niveau des valeurs lues à
partir de la base de données et des valeurs modifiées dans la base de
données.

À l’intérieur de chaque transaction, l’ordre des opérations de lectures /


écritures dans la base de données ne dépend pas de l’entrelacement
dynamique des transactions, mais est imposé par le développeur. Cette
propriété ne concerne donc pas ce type d’opérations.

Lorsque deux opérations de deux transactions différentes concernent des


lignes différentes, elles sont toujours commutables, quelle que soit leur
nature (lecture, écriture).

De même, deux opérations de lecture de deux transactions différentes qui

510
SGBD relationnels – Tome 1, État de l’art

concernent la même ligne sont toujours commutables.

En revanche, les paires « lecture – écriture », « écriture – lecture » et


« écriture – écriture » de deux transactions différentes qui concernent la
même ligne ne sont pas commutables. Ces paires d’opérations peuvent
engendrer des interférences indésirables. On dit que les opérations de ces
paires sont en conflit.

Ce type de paires d’opérations sont rares dans les plans d’exécution de


transactions concurrentes. Lorsqu’elles s’y trouvent, elles peuvent entrainer
des effets indésirables catastrophiques.

Exemple 9.3.1.ii : Pour les deux transactions « T1 » et « T2 » définies comme


suit :
T1 : read (A) ; A  A – 10 ; write (A) ; read (B) ; B  B + 10 ; write (B) ; COMMIT
T2 : read (B) ; B  B – 20 ; write (B) ; read (C) ; C  C + 20 ; write (C) ; COMMIT

Les paires où, dans un plan d’exécution de ces deux transactions, les deux
opérations sont considérées comme étant en conflit sont :
T1 : read (B) - T2 : write (B)
T1 : write (B) – T2 : read (B)
T1 : write (B) – T2 : write (B).

b) Un procédé naïf pour vérifier l’équivalence de plans d’exécution et la


sériabilité d’un plan d’exécution

Deux plans d’exécution de « n » transactions concurrentes sont équivalents,


si on peut passer de l'un à l'autre par une suite de permutations d'opérations
commutables, ou en d’autres termes, si dans les deux plans, les opérations
en conflit apparaissent dans le même ordre.

Pour vérifier qu’un plan d’exécution de « n » transactions concurrentes est


sérialisable, il suffit donc de trouver le bon plan d’exécution sériel de ces
« n » transactions et de montrer que ces deux plans d’exécution sont
équivalents en procédant comme suit :
• S’assurer que les deux plans d’exécution concernent les mêmes
transactions

511
Joachim TANKOANO

• Identifier les paires d’opérations en conflit dans les deux plans


d’exécution
• Vérifier que chacune de ces paires d’opérations en conflit s’exécute
dans le même ordre dans les deux plans d’exécution.

Pour vérifier la sériabilité d’un plan d’exécution de « n » transactions


concurrentes, cette méthode n’est pas pratique, parce qu’il faut trouver
préalablement le bon plan d’exécution sérielle parmi « n ! » possibilités, ce
qui n’est pas chose aisée.

Exemple 9.3.1.iii : Soient les deux plans d’exécution ci-après des


transactions « T1 » et T2 », définies dans l’exemple précédent 9.3.1.ii. Le
2ème plan est sérialisable parce que, les paires d’opérations en conflit que
nous avions identifiées dans cet exemple, s’exécutent dans le même ordre
que dans le plan d’exécution sériel où « T1 » s’exécute après « T2 ».
Plan sériel (diminue A et B de Plan sérialisable (diminue A et
10 et augmente C de 20) B de 10 et augmente C de 20)
T1 T2 T1 T2
1 read (B) 1 read (A)
2 B  B–20 2 read (B)
3 write (B) 3 A  A–10
4 read (C) 4 B  B–20
5 C  C+20 5 write (A)
6 write (C) 6 write (B)
7 COMMIT 7 read (B)
8 read (A) 8 read (C)
9 A  A–10 9 B  B+10
10 write (A) 10 C  C+20
11 read (B) 11 write (B)
12 B  B+10 12 COMMIT
13 write (B) 13 write (C)
14 COMMIT 14 COMMIT

Paires en conflit :
T1:read (B) - T2:write (B) / T1:write (B) – T2:read (B) / T1:write (B) – T2:write (B)

Dans les trois plans d’exécution ci-après, la paire d’opérations en conflit


(« T1:read (B) - T2:write (B) ») ne s’exécute pas dans le même ordre dans
le 3ème plan d’exécution et dans le 1er plan sériel. De même, la paire en

512
SGBD relationnels – Tome 1, État de l’art

conflit (« T1:write (B) - T2:write (B) ») ne s’exécute pas non plus dans le
même ordre dans ce 3ème plan d’exécution et dans le 2ème plan sériel. Ce 3ème
plan d’exécution n’est donc pas sérialisable parce qu’il n’est équivalent à
aucun des deux plans sériels possibles.
Plan sériel (diminue A et B de Plan sériel (diminue A et B de
10 et augmente C de 20) 10 et augmente C de 20)
T1 T2 T1 T2
1 read (B) 1 read (A)
2 B  B–20 2 A  A–10
3 write (B) 3 write (A)
4 read (C) 4 read (B)
5 C  C+20 5 B  B+10
6 write (C) 6 write (B)
7 COMMIT 7 COMMIT
8 read (A) 8 read (B)
9 A  A–10 9 B  B–20
10 write (A) 10 write (B)
11 read (B) 11 read (C)
12 B  B+10 12 C  C+20
13 write (B) 13 write (C)
14 COMMIT 14 COMMIT

Plan non-sérialisable (diminue A de 10 et augmente B de 10 et C de 20)


T1 T2
1 read (A)
2 A  A–10
3 read (B)
4 write (A)
5 B  B–20
6 read (B)
7 write (B)
8 B  B+10
9 read (C)
10 write (B)
11 COMMIT
12 C  C+20
13 write (C)
14 COMMIT

513
Joachim TANKOANO

c) Le théorème de la sériabilité

Le graphe de sérialisation d’un plan d’exécution de transactions


concurrentes est le graphe où :
• Chaque transaction est représentée par un nœud
• Pour chaque paire d’opérations en conflit « Ti : O1 – Tj : O2 » telle
que « Ti : O1 » précède « Tj : O2 », il existe un arc qui va du nœud
de la transaction « Ti » vers le nœud de la transaction « Tj ».

Un plan d’exécution de transactions concurrentes est sérialisable si et


seulement si son graphe de sérialisation ne contient aucun cycle,
correspondant à un cycle de précédence entre les opérations en conflit.
L’absence de cycle dans le graphe de sérialisation d’un plan d’exécution de
transactions concurrentes traduit le fait que, lorsqu’on considère deux
transactions « Ti » et « Tj » quelconques qui ont des paires d’opérations en
conflit, pour chacune de ces paires d’opérations en conflit, la transaction qui
s’exécute en premier dans ce plan est toujours la même.

Si « G » est le graphe de sérialisation d’un plan d’exécution sérialisable, on


peut déduire de « G » le plan d’exécution sériel équivalent à ce plan
d’exécution en procédant comme suit :
• On démarre avec une liste vide de transactions
• Si dans « G », « Ti » est une transaction représentée par un nœud qui
n’a pas d’arc entrant, on l’ajoute à la fin de la liste et on enlève son
nœud de « G »
• On répète le 2ème point jusqu’à ce qu’il n’y ait plus de nœud dans
« G ».

Ce théorème offre une méthode qui permet de déterminer plus simplement


la sériabilité d’un plan d’exécution de transactions concurrentes et le plan
d’exécution sériel équivalent s’il est sérialisable.

Exemple 9.3.1.iv : Ce qui suit présente côte à côte, le plan d’exécution


sérialisable de l’exemple 9.3.1.i, les paires d’opérations en conflits
identifiées dans l’exemple 9.3.1.ii et le graphe de sérialisation de ce plan
d’exécution :

514
SGBD relationnels – Tome 1, État de l’art

Plan sérialisable
T1 T2
1 read (A)
2 read (B)
3 A  A–10 T1 : read (B) - T2 : write (B)
T1 : write (B) – T2 : read (B)
4 B  B–20
T1 : write (B) – T2 : write (B)
5 write (A)
6 write (B)
7 read (B)
(2, 11) (6, 7) (6, 11)
8 read (C) T1 T2
9 B  B+10
10 C  C+20
11 write (B)
12 COMMIT
13 write (C)
14 COMMIT

N.B. : Dans ce graphe de sérialisation comme dans ceux qui vont suivre, les
arcs sont étiquetés par des paires de numéros qui correspondent à des
numéros de lignes dans le tableau qui visualise l’entrelacement des
opérations de lectures / écritures. Chaque paire identifie les deux
opérations de lectures / écritures, en conflit, pour lesquelles l’arc a été créé.

Dans cet exemple, le graphe de sérialisation est sans cycle. Pour chaque
paire d’opérations en conflit, la transaction « T2 » est toujours celle qui
s’exécute en premier. On en déduit que ce plan d’exécution est sérialisable
et que le plan d’exécution sériel équivalent est celui où on a « T2 » suivi de
« T1 ».

Considérons à présent le plan d’exécution non sérialisable ci-dessous de


l’exemple 9.3.1.i.

Son graphe de sérialisation contient un cycle. Ce cycle indique que pour la


paire d’opérations en conflit « T1 : read (B) - T2 : write (B) », la transaction
« T1 » est celle qui s’exécute en premier, alors que pour les deux autres
paires « T1 : write (B) – T2 : read (B) » et « T1 : write (B) – T2 : write (B) »,
la transaction « T2 » est celle qui s’exécute en premier. On en déduit que ce
plan d’exécution est non-sérialisable.

515
Joachim TANKOANO

Plan non-sérialisable
T1 T2
1 read (A)
2 A  A–10
3 read (B)
4 write (A)
5 B  B–20
6 read (B)
7 write (B)
8 B  B+10
9 read (C)
10 write (B)
11 COMMIT
12 C  C+20
13 write (C)
14 COMMIT

Ce qui suit correspond au graphe de sérialisation de ce plan d’exécution.

(6, 7)
T1 T2

(3, 10), (7, 10)


Exemple 9.3.1.v : Considérons maintenant les deux transactions « T1 » et


« T2 » ci-après, qui effectuent chacune la mise à jour du prix de l’article 10
dans la table « Table_Prix » :
T1 : UPDATE Table_prix SET prix = prix+500 WHERE NoArticle = 10 ; COMMIT ;
T2 : UPDATE Table_prix SET prix = prix*2 WHERE NoArticle = 10 ; COMMIT ;

L'exécution de chacune de ces deux transactions revient à lire « Prix » en


mémoire centrale, calculer en mémoire centrale la nouvelle valeur de
« Prix », écrire la nouvelle valeur de « Prix » dans la base de données et
valider la transaction.

Si nous supposons que pour l’exécution simultanée de ces deux


transactions, le SGBD n’offre pas de mécanisme de contrôle des accès
concurrents, suivant les circonstances, leurs opérations peuvent s’entrelacer
différemment comme dans les deux plans d'exécution suivants, où nous
avons fait abstraction des calculs effectués en mémoire centrale et où

516
SGBD relationnels – Tome 1, État de l’art

l’indice associé à chaque opération désigne le numéro de la transaction


concernée :
Plan 1 → read1(Prix) ; read2(Prix) ; write1(Prix), COMMIT1 ; write2(Prix) + COMMIT2.
Plan 2 → read1(Prix) ; write1(Prix), read2(Prix) ; COMMIT1 ; write2(Prix) + COMMIT2

À la fin de l’exécution du plan 1, la valeur du prix de l’article 10 dans la


base de données sera 5000. Cette valeur n’est pas correcte à cause d’une
mise à jour perdue de « T1 ». En revanche, à la fin de l’exécution du plan 2,
la valeur du prix de l’article 10 dans la base de données sera 6000, ce qui
correspond à la bonne valeur.

De façon plus formelle, les paires en conflit de ces deux plans d’exécution
sont :
read1 (Prix) – write2 (Prix)
write1 (Prix) – read2 (Prix)
write1 (Prix) – write2 (Prix).

Ce qui suit visualise les deux plans dans des tableaux qui mettent en
évidence l’entrelacement des opérations et donne le graphe de sérialisation
de chaque plan.
Plan 1 Plan 2
T1 T2 T1 T2
1 Read (Prix) 1 Read (Prix)
2 Read (Prix) 2 Write (Prix)
3 Write (Prix) 3 Read (Prix)
4 Commit 4 Commit
5 Write (Prix) 5 Write (Prix)
6 Commit 6 Commit

(2, 3)
T1 T2 T1 (1, 5) (2, 3) (2, 5) T2
(1, 5), (3, 5)

À partir des graphes de sérialisation, on peut déduire que le plan 1 n’est


pas sérialisable alors que le plan 2 est sérialisable et est équivalent au plan
d’exécution sériel où « T1 » s’exécute avant « T2 ».

Exemple 9.3.1.vi : Dans cet exemple un peu plus complexe, nous


considérons les quatre transactions « T0 », « T1 », « T2 » et « T3 » ci-après

517
Joachim TANKOANO

qui mettent à jour deux données « O1 » et « O2 ».


T0 : UPDATE Table_produit_O1 SET nb = 15 ; (initialise O1)
UPDATE Table_produit_O2 SET nb = 20 ; (initialise O2)

T1 : UPDATE Table_produit_O1 SET nb = nb + 1 ;


UPDATE Table_produit_O2 SET nb = nb – 1 ;

T2 : UPDATE Table_produit_O1 SET nb = nb + 3 ;


UPDATE Table_produit_O2 SET nb = nb – 3 ;

T3 : SELECT nb FROM Table_produit_O1 ;


SELECT nb FROM Table_produit_O2;

Si après la transaction d’initialisation « T0 » les trois autres transactions


« T1 », « T2 » et « T3 » sont lancées simultanément, et si le SGBD n’offre pas
de mécanisme de contrôle des accès concurrents, suivant les circonstances,
on peut obtenir par exemple les deux plans d'exécution suivants, où
« ri(Oj) » désigne la lecture de « Oj » par la transaction « Ti » et « wi(Oj) »,
l’écriture de « Oj » par la transaction « Ti » :
Plan 1 → w0(O1), w0(O2), r1(O1), w1(O1), r2(O1), w2(O1), r3(O1), r1(O2), w1(O2),
r2(O2), w2(O2), r3(O2)

Plan 2 → w0(O1), w0(O2), r3(O1), r3(O2), r1(O1), r2(O1), w1(O1), r1(O2), w2(O1),
R2(O2), w2(O2), w1(O2)
Dans ces deux plans d’exécution, les paires en conflit sont :
w0 (O1) – r1 (O1) / w0 (O1) – w1 (O1) / w0 (O1) – r2 (O1) / w0 (O1) – w2 (O1) / w0
(O1) – r3 (O1)

w0 (O2) – r1 (O2) / w0 (O2) – w1 (O2) / w0 (O2) – r2 (O2) / w0 (O2) – w2 (O2) /w0


(O2) – r3 (O2)

r1 (O1) – w2 (O1) / w1 (O1) – r2 (O1) / w1 (O1) – w2 (O1) / w1 (O1) – r3 (O1)

r1 (O2) – w2 (O2) / w1 (O2) – r2 (O2) / w1 (O2) – w2 (O2) / w1 (O2) – r3 (O2)

w2 (O1) – r3 (O1) / w2 (O2) – r3 (O2)

Ce qui suit visualise les deux plans dans des tableaux qui mettent en
évidence l’entrelacement des opérations et donne le graphe de sérialisation
de chaque plan :

518
SGBD relationnels – Tome 1, État de l’art

Plan 1 Plan 2
T0 T1 T2 T3 T0 T1 T2 T3
1 w (O1) 1 w (O1)
2 w (O2) 2 w (O2)
3 r (O1) 3 r (O1)
4 w (O1) 4 r (O2)
5 r (O1) 5 r (O1)
6 w (O1) 6 r (O1)
7 r (O1) 7 w (O1)
8 r (O2) 8 r (O2)
9 w (O2) 9 w (O1)
10 r (O2) 10 r (O2)
11 w (O2) 11 w (O2)
12 r (O2) 12 w (O2)

(3, 6) (4, 5) (4, 6) (5, 9) (7, 9) (8, 11)


T1 T2 T1 T2
(8, 11) (9, 10) (9, 11)
(6, 7) (10, 12) (11, 12)
(1, 3)
(6, 7)
(1, 4) (1, 5)
(11, 12) (3, 9)
(2, 8) (1, 7)
(4, 11)
(2, 9) (2, 8)
(4, 7) (9, 12) (3, 7) (4, 12)
(2, 12)
(1, 5) (1, 6) (2, 10) (2, 11)
(1, 6) (1, 9) (2, 10) (2, 11)

T0 T3 T0 T3
(1, 7) (2, 12) (1, 3) (2, 4)

Le graphe de sérialisation du plan 1 est sans cycle, ce qui veut dire que ce
plan est sérialisable. Ce plan est équivalent au plan d’exécution sériel où
l’ordre d’exécution des transactions est « T0 », « T1 », « T2 », « T3 ». À la fin
de l’exécution de ce plan, « O1 » contiendra la valeur 19, « O2 » la valeur 16
et les valeurs lues par « T3 » seront 19 pour « O1 » et 16 pour « O2 », ce qui
correspond aux bonnes valeurs.

En revanche, il existe un cycle dans le graphe de sérialisation du plan 2, ce


qui veut dire que ce plan n’est pas sérialisable. Il conduit à une mise à jour
perdue de « O1 » par « T1 » et à une mise à jour perdue de « O2 » par « T2 ».
À la fin de l’exécution de ce plan, « O1 » contiendra la valeur 18, « O2 » la
valeur 19 et les valeurs lues par « T3 » seront 15 pour « O1 » et 20 pour

519
Joachim TANKOANO

« O2 », ce qui ne correspond pas aux bonnes valeurs.


Ces exemples montrent très bien que, lorsque des transactions s’exécutent
simultanément sans un contrôle des accès concurrents par le SGBD, selon
les circonstances, le plan d’exécution qui en résulte peut-être sérialisable,
c’est-à-dire, sans interférences entre les transactions, mais peut aussi être
non-sérialisable, c’est-à-dire, avec interférences entre les transactions
pouvant engendrer des anomalies dues à des mises à jour perdues, à des
lectures sales, à des lectures non répétables et à des tuples fantômes,
accompagnées d’une altération de l’intégrité des données.

d) L’orthogonalité des propriétés de sériabilité et de récouvrabilité d’un


plan d’exécution de transactions

La sériabilité est une propriété des plans d’exécution qui ne dépend que de
l’ordre dans lequel les opérations de lectures / écritures des transactions en
concurrence s’entrelacent. Elle garantit la non-interférence de ces
transactions.

La récouvrabilité est une propriété des plans d’exécution qui ne dépend que
de l’ordre dans lequel les opérations « COMMIT » et « ROLLBACK » sont
effectuées par les transactions en concurrence, après une lecture ou une
écriture sale. Elle garantit la possibilité de reprise en cas d’incident.

La sériabilité et la récouvrabilité sont des propriétés orthogonales. Un plan


d’exécution de transactions peut être sérialisable et non-recouvrable,
recouvrable avec annulations en cascade, recouvrable sans annulations en
cascade ou strict. Un plan d’exécution de transactions peut aussi être non-
sérialisable et non-recouvrable, recouvrable avec annulations en cascade,
recouvrable sans annulations en cascade ou strict.

Exemple 9.3.1.vii : Considérons les plans d’exécution de transactions


concurrentes ci-après :
Plan 1 → R1(X), W2(Y), R1(Y), W1(X), C1, R2(X), W2(X), C2
Plan 2 → R1(X), W1(Y), R2(Y), C1, W2(X), C2
Plan 3 → R1(Y), W2(X), R2(Y), W1(X), C2, C1

520
SGBD relationnels – Tome 1, État de l’art

Les tableaux ci-dessous visualisent l’entrelacement des opérations


exécutées par les transactions concernées dans chacun de ces trois plans
d’exécution et le graphe de sérialisation correspondant.
Plan 1 Plan 2 Plan 3
T1 T2 T1 T2 T1 T2
1 R (X) 1 R (X) 1 R (Y)
2 W (Y) 2 W (Y) 2 W (X)
3 R (Y) 3 R (Y) 3 R (Y)
4 W (X) 4 C 4 W (X)
5 C 5 W (X) 5 C
6 R (X) 6 C 6 C
7 W (X)
(1, 5) (2, 3) (2, 4)
8 C T1 T2 T1 T2
(1, 7) (4, 7)

T1 T2

(2, 3)

Le graphe de sérialisation du plan 1 contient un cycle. Ce plan n’est donc


pas sérialisable. Par ailleurs la transaction « T1 » lit une donnée (« Y ») salie
par la transaction « T2 » et valide ses traitements avant la fin de « T2 ». Ce
plan est donc non recouvrable.

Le graphe de sérialisation du plan 2 ne contient pas de cycle. Ce plan est


donc sérialisable. Par ailleurs la transaction « T2 » lit une donnée (« Y »)
salie par « T1 » et valide ses traitements après « T1 ». Ce plan est donc
recouvrable avec annulations en cascade. Au lieu de faire un « COMMIT »
en ligne 4, la transaction « T1 » pourrait faire un « ROLLBACK » qui
entraînerait un « ROLLBACK » de « T2 ».

Le graphe de sérialisation du plan 3 ne contient pas de cycle. Ce plan est


donc sérialisable. Par ailleurs la transaction « T1 » a fait une écriture sale en
ligne 4 sur « X ». Ce plan est donc recouvrable sans annulations en cascade,
mais n’est pas strict. Un « ROLLBACK » de « T1 » en ligne 6 restaurerait
« X » à la valeur qu’elle avait avant le début de « T1 » alors que la valeur de
« X » a été modifiée en ligne 2 par « T2 ». Après ce « ROLLBACK » l’état de
la base de données ne reflètera plus les traitements effectués par « T2 ».

521
Joachim TANKOANO

Exemple 9.3.1.viii : Considérons les plans d’exécution des deux transactions


« T1 » et « T2 » de l’exemple 9.3.1.i et leurs graphes de sérialisation tels que
visualisés ci-après :
Plan d’exécution sérialisable Plan d’exécution sérialisable
non-recouvrable recouvrable avec annulations
en cascade
T1 T2 T1 T2
1 read (A) 1 read (A)
2 read (B) 2 read (B)
3 A  A–10 3 A  A–10
4 B  B–20 4 B  B–20
5 write (A) 5 write (A)
6 write (B) 6 write (B)
7 read (B) 7 read (B)
8 read (C) 8 read (C)
9 B  B+10 9 B  B+10
10 C  C+20 10 C  C+20
11 write (B) 11 write (C)
12 COMMIT 12 ROOLBACK
13 write (C) 13 write (B)
14 ROOLBACK 14 COMMIT

(2, 11) (2, 13)


T1 T2 T1 T2
(6, 7) (6, 11) (6, 7) (6, 13)

Plan d’exécution sérialisable et strict


T1 T2
1 read (A)
2 read (B)
3 A  A–10
4 B  B–20
5 write (A)
6 write (B)
7 read (C)
8 C  C+20
9 write (C)
10 ROOLBACK
11 read (B)
12 B  B+10

522
SGBD relationnels – Tome 1, État de l’art

13 write (B)
14 COMMIT

(2, 13)
T1 T2
(6, 11) (6, 13)

Comme l’indiquent les graphes de sérialisation, ces trois plans d’exécution


sont sérialisables. On peut vérifier aisément qu’ils produisent le même effet
dans la base de données. Chaque plan d’exécution diminue « A » et « B »
de 10 et augmente « C » de 20.

Le premier plan d’exécution est non-recouvrable, parce que « T2 » ne peut


pas faire son « ROLLBACK », « T1 » ayant en ligne 7 lu « B » modifié par
« T2 » en ligne 6 et ayant fait son « COMMIT » en ligne 12 avant le
« ROLLBACK » de « T2 ».

Le deuxième plan d’exécution est recouvrable avec annulations en cascade


parce que le « ROLLBACK » de « T2 » doit entrainer le « ROLLBACK » de
« T1 » parce que « T1 » a lu et utilisé « B » modifié par « T2 », mais ne fait
pas son « COMMIT » avant la fin de « T2 ».

Le troisième plan d’exécution est strict parce que « T2 » peut faire son
« ROLLBACK » sans que cela ne nécessite l’annulation de « T1 », aucune
des deux transactions n’ayant fait une lecture ou une écriture sale.

La gestion des accès concurrents par les SGBD a pour principal objectif de
garantir des plans d’exécution sérialisables et stricts. Comme on peut le
constater dans l’exemple précédent 9.3.1.vi, les plans d’exécution
sérialisables et stricts dégradent la concurrence.

9.3.2. Les approches pour le contrôle de la concurrence des


transactions

Les SGBD garantissent des plans d’exécution sérialisables et stricts en


s’appuyant sur des mécanismes de deux types qui peuvent se combiner :
• Les mécanismes de prévention des interférences, qui sont des
mécanismes dits pessimistes

523
Joachim TANKOANO

• Les mécanismes de détection et de résolution des interférences, qui


sont des mécanismes dits optimistes.

Les mécanismes pessimistes prennent des mesures préventives qui


empêchent la survenue des interférences entre transactions. Ces mesures
amènent à modifier, à la volée, l’entrelacement des opérations générées par
l’exécution simultanée des transactions. Cette approche est
particulièrement adaptée aux contextes où les transactions sont de très
courte durée et peuvent être différées de façon non perceptible par les
utilisateurs. Dans la pratique, ce sont les mécanismes les plus utilisés. Ces
mécanismes sont basés principalement sur l’utilisation des verrous.

Quant aux mécanismes optimistes, ils laissent les interférences entre


transactions se produire pour ensuite les détecter et les corriger. Les
mécanismes optimistes les plus connus sont basés sur l’utilisation des
estampilles ou sur l’utilisation de techniques de certification des
transactions.

L’architecture fonctionnelle des moteurs SQL dotés d’un mécanisme de


contrôle de la concurrence des transactions peut se schématiser comme ci-
dessous.

Le rôle du processeur d’exécution des requêtes est de coordonner en


continu, l’exécution simultanée des plans d’exécution physique des
requêtes soumises par les utilisateurs connectés, y compris les opérations
de validation et d’annulation de transactions. Pour accomplir ce rôle, le
processeur d’exécution des requêtes s’appuient sur le gestionnaire de la
mémoire relationnelle présenté dans le chapitre 7, le gestionnaire des
journaux présenté dans la section 9.4 et le gestionnaire des transactions que
nous présentons dans ce paragraphe.

Les opérations (de lectures / écritures dans une base données et de


validations / annulations des transactions) engendrées par l’exécution
simultanée des plans d’exécution physique des requêtes SQL en cours de
traitement, sont soumises, au fur et à mesure, au gestionnaire des
transactions par le processeur d’exécution des requêtes.

524
SGBD relationnels – Tome 1, État de l’art

REQUÊTE SQL

ANALYSEUR SYNTAXIQUE ET SÉMANTIQUE

ARBRE DE REQUÊTE BRUT

OPTIMISEUR DE L’ARBRE DE REQUÊTE

ARBRE CANONIQUE

GENERATEUR DE CODE

PLAN D’EXECUTION

PROCESSEUR D’EXECUTION DES REQUÊTES


Gestionnaire de la mémoire Gestionnaire des Gestionnaire des journaux
relationnelle transactions

RESULTAT DE LA REQUÊTE

Le rôle du gestionnaire des transactions est, d’attribuer un identifiant


unique à chaque transaction, de soumettre la séquence d’opérations de
lectures / écritures et de validations / annulations des transactions en cours
d’exécution à un ordonnanceur chargé de modifier l’ordre de ces
opérations, afin d’obtenir en sortie une séquence d’opérations sérialisée et
stricte.

La séquence d’opérations de lectures / écritures produite par


l’ordonnanceur est ensuite soumise au gestionnaire de la mémoire
relationnelle chargé de l’exécution de ces opérations. Ce processus peut se
schématiser comme suit :
Séquence d’opérations entrelacées générée par l’exécution simultanée des
plans d’exécution physique des requêtes en cours de traitement

Gestionnaire des transactions


Ordonnanceur

Séquence d’opérations de lectures / écritures sérialisée

Gestionnaire de la mémoire relationnelle

Zone
Tampon Disque

525
Joachim TANKOANO

Ce qui suit présente de façon plus détaillée, les mécanismes pessimistes de


contrôle de la concurrence des transactions, basés sur l’utilisation des
verrous et les mécanismes optimistes de contrôle de la concurrence des
transactions, basés sur l’utilisation des estampilles et sur l’utilisation de
techniques de certification des transactions.

9.3.3. Les mécanismes de contrôle basés sur l’utilisation des


verrous

Dans ce qui suit, nous commençons par une présentation des principales
caractéristiques des mécanismes de contrôle de l’exécution concurrente des
transactions, basés sur l’utilisation des verrous. Ensuite, nous présentons
quelques approches utilisées par les SGBD pour traiter les situations
d’interblocage, inhérentes à l’utilisation des verrous avant d’aborder la
présentation détaillée des principales techniques de contrôle de la
concurrence des transactions, basées sur l’utilisation des verrous, à savoir :
le verrouillage ternaire en deux phases, le verrouillage des insertions de
lignes et le verrouillage hiérarchique en deux phases.

a) Les principales caractéristiques des mécanismes de contrôle de la


concurrence des transactions basés sur l’utilisation des verrous

Le principe général du verrouillage

Dans l’approche relative au contrôle de la concurrence des transactions basé


sur le verrouillage, les verrous sont utilisés pour la prévention des cycles de
précédence qui pourraient apparaître dans le graphe de sérialisation, en
modifiant l’ordre des opérations du plan d’exécution des transactions
concurrentes.

Comme dans les autres systèmes qui utilisent des verrous, de base le
verrouillage permet à une transaction d’acquérir une autorisation pour un
accès à un item, en mode partagé ou en mode exclusif. Un verrouillage
désigné par <I, V, T> indique qu’un verrou de type « V » a été accordé à la
transaction « T » sur l’item « I ».

Dans cette approche, l’accès à un item par une transaction est

526
SGBD relationnels – Tome 1, État de l’art

systématiquement soumis à un contrôle. Ce contrôle porte sur les verrous


accordés aux autres transactions pour leur permettre d’accéder à cet item.
Ce contrôle permet au gestionnaire des verrous :
• De bloquer l’accès à cet item par cette transaction, si le verrou qu’elle
demande pour l’opération qu’elle veut exécuter est en conflit avec
un verrou qu’il a préalablement accordé à une autre transaction pour
lui permettre d’accéder à cet item
• D’accorder un verrou à cette transaction pour un accès à cet item
pour l’opération à effectuer, si ce verrou n’est pas en conflit avec un
verrou qu’il a préalablement accordé à une autre transaction pour lui
permettre d’accéder à cet item.

La levée d’un verrou sur un item, amène le gestionnaire des verrous à


accorder un verrou pour l’accès à cet item à une ou à plusieurs des
transactions en attente, en fonction de la compatibilité des verrous
demandés par ces transactions en attente avec les verrous accordés non
encore levés.

Le blocage d’une transaction a de ce fait comme effet la modification de


l’ordre d’exécution des opérations de lectures / écritures.

Le verrouillage dégrade la concurrence et ne garantit pas tout seul la


sériabilité.

Les types de verrous et de verrouillage de base

On distingue deux types de verrous de base :


• Le verrou exclusif « X », pouvant être accordé à une transaction
« T », afin de lui donner un accès exclusif à un item « I », qui interdit
de ce fait tout accès à cet item par toute autre transaction pour toute
autre opération
• Le verrou partagé « S », pouvant être accordé à plusieurs
transactions, afin de donner à ces transaction un accès partagé en
lecture à un item « I », qui interdit de ce fait à ces transactions et à
toute autre transaction, tout accès en écriture à cet item « I ».

Par ailleurs, on distingue deux types de verrouillage de base :

527
Joachim TANKOANO

• Le verrouillage binaire
• Le verrouillage ternaire.

Le verrouillage binaire n’utilise que des verrous exclusifs. Il repose sur


deux opérations de verrouillage : « LOCK I », utilisé, aussi bien pour les
opérations de lecture que pour les opérations d’écriture pour poser un
verrou exclusif « X » sur un item « I » et « UNLOCK I », utilisé pour lever
ce verrou sur cet item « I ». Avec ce type de verrouillage, un item ne peut
prendre que deux états : verrouillé ou libre. Ce type de verrouillage est trop
restrictif. Il n’autorise pas de lectures simultanées d’un item par plusieurs
transactions, bien qu’il ne s’agisse pas d’opérations en conflit. Il a donc très
peu d’intérêt dans le contexte des SGBD.

Quant au verrouillage ternaire, il repose sur les trois opérations de


verrouillage ci-après : « RLOCK I » pour poser sur un item « I » un verrou
partagé « S » de lecture, « WLOCK I » pour poser sur un item « I » un
verrou exclusif « X » de lecture et d’écriture, « UNLOCK I » pour lever un
verrou posé sur un item « I ». Avec ce type de verrouillage, un item peut
prendre trois états : en lecture (c’est-à-dire, partagé), en écriture ou libre.
« RLOCK I » n'autorise que des lectures simultanées sur « I ». « WLOCK
I » interdit tout accès à l’item « I » par une autre transaction.

La durée d’un verrouillage

Le verrouillage peut concerner une seule opération de lecture ou d’écriture,


plusieurs opérations de lectures ou d’écritures ou l’exécution d’une
transaction.

Les verrouillages de longue durée sont à éviter. Ils ont un impact négatif
sur le parallélisme entre les transactions et sur la réactivité de l’application.

La granularité d’un item

L’item qui fait l’objet de verrouillage peut être de différentes granularités :


• La base de données dans son intégralité
• Une table de cette base de données
• Une ligne d’une table

528
SGBD relationnels – Tome 1, État de l’art

• Une colonne d’une ligne.

Le niveau de granularité de l’item peut impacter négativement le degré de


parallélisme possible entre les transactions. Le maximum de parallélisme
s’obtient avec des items de fine granularité, mais génère un nombre plus
important de verrous à gérer.

b) La gestion de l’interblocage des transactions

L’interblocage et la famine sont deux phénomènes inhérents à l’utilisation


des verrous.

Lorsqu’un item « I » est verrouillé en utilisant par exemple « RLOCK I » ou


« WLOCK I », toute autre transaction qui tente un verrouillage de cet item
« I » en utilisant « WLOCK I » doit être mise en attente de la levée du
verrou posé. Cette mise en attente peut engendrer :
• Une situation d’interblocage entre plusieurs transactions, si chacune
d’elles attend la levée d’un verrou posé par une autre transaction
• Une situation de famine ou d’attente indéfinie pour une transaction
« T » donnée en attente pour le verrouillage de « I », si chaque fois
que l’item « I » est libéré, il est alloué à une autre transaction.

Pour éviter l’interblocage, plusieurs solutions sont envisageables :


• Obliger chaque transaction à poser tous les verrous dont elle a besoin
en une seule fois. Si une transaction peut le faire, elle acquiert tous
les verrous dont elle a besoin et commence son exécution. Si elle ne
peut pas le faire, elle est mise en attente sans qu’aucun item n’ait été
verrouillé.
• Ordonner les items et imposer aux transactions de poser les verrous
sur ces items dans cet ordre.
Soient par exemple « T1 » et « T2 » deux transactions définies
comme suit :
T1 : LOCK A ; LOCK B ; UNLOCK A ; UNLOCK B

T2 : LOCK B ; LOCK A ; UNLOCK B ; UNLOCK A

529
Joachim TANKOANO

Si « T1 » et « T2 » démarrent au même moment, il y aura


interblocage. Si l’ordre des items est « A » suivi de « B », la
transaction « T2 » peut être réécrite comme suit pour éviter tout
interblocage :
T2 : LOCK A ; LOCK B ; UNLOCK B ; UNLOCK A

• Attendre que l’interblocage se produise et relancer une ou plusieurs


des transactions concernées, afin de briser l’interblocage, ce qui
nécessite de pouvoir détecter un interblocage lorsqu’il se produit. La
détection des interblocages peut se faire périodiquement en
construisant et en examinant le graphe des attentes. Il y a
interblocage si et seulement si ce graphe comporte un cycle.

c) Le verrouillage ternaire en deux phases

Le verrouillage ternaire en deux phases est l’un des mécanismes de base sur
lesquels les SGBD s’appuient pour assurer le contrôle de l’exécution des
transactions concurrentes selon une approche pessimiste. Ce mécanisme
repose sur l’utilisation des trois opérations de verrouillage ternaire
présentées plus haut (« RLOCK I » pour le verrouillage à l’aide du verrou
partagé « S », « WLOCK I » pour le verrouillage à l’aide du verrou exclusif
« X » et « UNLOCK I » pour le déverrouillage) et sur un protocole de
prévention des interférences basé sur un verrouillage en deux phases.

Ce qui suit présente :


• Une adaptation des règles de construction du graphe de sérialisation
d’un plan d’exécution de transactions concurrentes où les opérations
de lectures / écritures ont été remplacées par les trois opérations de
verrouillage / déverrouillage « RLOCK I », « WLOCK I » et
« UNLOCK I »
• Les principes du protocole de verrouillage ternaire en deux phases.

Les règles de construction du graphe de sérialisation dans le cas du


verrouillage ternaire

Étant donné « P », un plan d’exécution de « n » transactions concurrentes


« T1 », « T2 », …, « Tn » où les opérations de lectures / écritures ont été

530
SGBD relationnels – Tome 1, État de l’art

remplacées par les opérations de verrouillage / déverrouillage (« RLOCK


I », « WLOCK I » et « UNLOCK I »), on peut vérifier que ce plan « P » est
sérialisable en construisant un graphe « G » de sérialisation dans lequel on
crée un arc de « Ti » vers « Tj » dans les cas ci-après :
• Si « Tj » verrouille un item « I » en écriture après que « Ti » ait
verrouillé cet item « I » en lecture ou en écriture, ce qui correspond
aux paires en conflit « Ti:Read(I) – Tj:Write(I) » et « Ti:write(I) –
Tj:Write(I) »
• Si après un verrouillage de l’item « I » en écriture par la transaction
« Ti », « Tj » verrouille « I » en lecture, ce qui correspond à la paire
en conflit « Ti:Write(I) – Tj:Read(I) ».

Si le graphe « G » ne contient aucun cycle, on peut en déduire que le plan


« P » est sérialisable.

Exemple 9.3.3.i : Dans le plan d’exécution de transactions concurrentes


décrit dans le tableau ci-après, « T1 », « T2 », « T3 » et « T4 » représentent
quatre transactions et « A », « B » deux items de la base de données.
T1 T2 T3 T4
1 WLOCK A
2 RLOCK B
3 UNLOCK A
4 RLOCK A
5 UNLOCK B
6 WLOCK B
7 RLOCK A
8 UNLOCK B
9 WLOCK B
10 UNLOCK A
11 UNLOCK A
12 WLOCK A
13 UNLOCK B
14 RLOCK B
15 UNLOCK A
16 UNLOCK B

Ce qui suit est le graphe de sérialisation de ce plan d’exécution de


transactions concurrentes. Ce graphe contient trois cycles. Il n’est donc pas

531
Joachim TANKOANO

sérialisable.

(9, 14)
T1 T2

(7, 12)

(1, 7)
(4, 12) (2, 9) (6, 14)
(1, 4) (6, 9)

(2, 6)
T4 T3
(1, 12)

Exemple 9.3.3.ii : Dans le plan d’exécution de transactions concurrentes


décrit dans le tableau ci-après, « T1 », « T2 » et « T3 » représentent trois
transactions et « x », « y », « z » trois items de la base de données.
T1 T2 T3
1 RLOCK x
2 WLOCK y
3 RLOCK z
4 UNLOCK x
5 WLOCK x
6 RLOCK z
7 UNLOCK y
8 WLOCK y
9 UNLOCK x
10 UNLOCK z
11 UNLOCK y
12 WLOCK z
13 RLOCK y
14 RLOCK y
15 UNLOCK y
16 UNLOCK y
17 UNLOCK z

532
SGBD relationnels – Tome 1, État de l’art

Ce qui suit est le graphe de sérialisation de ce plan d’exécution de


transactions concurrentes. Ce graphe contient un cycle. Il n’est donc pas
sérialisable.
T1

(1, 5) (8, 13)


(2, 8) (8, 14)

(3, 12)
T2 T3
(2, 13)

Ces exemples montrent que toute seule, l’utilisation des verrous ternaires
n’est pas suffisante pour garantir la sérialisation des plans d’exécution de
transactions.

Le protocole de prévention des interférences basé sur le verrouillage


ternaire en deux phases

On dit qu’une transaction « T » s’est exécutée en respectant le protocole de


verrouillage ternaire en deux phases si elle a effectué toutes ses opérations
« UNLOCK » après avoir effectué toutes ses opérations « RLOCK » et
« WLOCK », ce qui rend impossible les lectures et les écritures sales.

Tout plan d’exécution où chaque transaction s’exécute en respectant ce


protocole de verrouillage est sérialisable et strict.

Ce protocole de verrouillage prévient les cycles de précédence entre


opérations conflictuelles qui pourraient exister dans le graphe de
sérialisation. Soit par exemple deux transactions « Ti » et « Tj »
quelconques, qui ont comme paires d’opérations en conflit « Ti : read (X) -
Tj : write (X) », « Ti : write (X) – Tj : read (X) » et « Ti : write (X) – Tj : write

533
Joachim TANKOANO

(X) ». Si le graphe de sérialisation fait ressortir un cycle « Ti : read (X) → Tj :


write (X) → Ti : read (X) », cela voudrait dire que « Ti » a verrouillé et
déverrouillé « X » pour qu’à son tour « Tj » puisse aussi verrouiller et
déverrouiller « X » pour qu’à nouveau « Ti » puisse une deuxième fois
verrouiller et déverrouiller « X », ce qui serrait en contradiction avec le
principe du verrouillage en deux phases qui interdit à « Ti » un verrouillage
après un déverrouillage. En d’autres termes, un plan d’exécution de
transactions concurrentes où les transactions respectent le protocole du
verrouillage ternaire en deux phases ne peut pas contenir dans son graphe
de sérialisation des cycles de précédence entre les opérations conflictuelles.

Exemple 9.3.3.iii : Soit le plan décrit dans le tableau ci-dessous :


T1 T2 T3
1 RLOCK X
2 WLOCK Y
3 RLOCK X T1
4 RLOCK Z
5 RLOCK Z
6 UNLOCK Y (2, 7)
7 WLOCK Y
8 UNLOCK X
9 UNLOCK Y
10 UNLOCK Z (1, 13) (7, 11)
11 RLOCK Y
12 WLOCK Z
13 WLOCK X
14 UNLOCK X
15 UNLOCK Y T2 T3
16 UNLOCK Z (2, 11) (4, 12)

Dans ce plan d’exécution, les trois transactions « T1 », « T2 » et « T3 »


respectent le protocole de verrouillage ternaire en deux phases. Comme on
peut le constater, son graphe de sérialisation est sans cycle, ce qui veut dire
qu’il est sérialisable.

Le protocole de verrouillage ternaire en deux phases est une condition


suffisante pour garantir des plans d’exécution sérialisables et stricts, mais

534
SGBD relationnels – Tome 1, État de l’art

n’est pas une condition nécessaire. Un plan d’exécution sérialisable peut ne


pas respecter ce protocole de verrouillage, ce qui revient à dire que ce
protocole réduit le parallélisme entre les transactions.

Par ailleurs, l’utilisation de ce protocole de verrouillage peut conduire à des


interblocages qui ne peuvent être levés qu’en effectuant des
« ROLLBACK » de transactions.

Le principal intérêt de ce protocole réside dans le fait que son


implémentation par un gestionnaire des verrous se fait simplement et de
façon transparente pour le développeur des applications. Dans cette
implémentation le gestionnaire des verrous prend entièrement en charge
tous les aspects liés au verrouillage et au déverrouillage des items. En
recevant les opérations de lectures / écritures et les ordres « COMMIT » ou
« ROLLBACK » dans un ordre quelconque, l’ordonnanceur doit :
• À la réception de chaque opération de lecture ou d’écriture,
demander, au gestionnaire des verrous, la pose d’un verrou (dont le
type est fonction de la nature de l’opération) pouvant provoquer une
mise en attente
• À la réception de chaque « COMMIT » ou « ROLLBACK »,
demander, au gestionnaire des verrous, la levée de tous les verrous
posés pour la transaction concernée, ce qui conduit à un verrouillage
en deux phases et peut amener le gestionnaire des verrous à
débloquer si nécessaire les transactions en attente de celle-ci, en
fonction des verrous demandés et des verrous non encore levés.

Exemple 9.3.3.iv : Considérons les trois transactions ci-après « T1 », « T2 »


et « T3 » qui accèdent à deux items, « A » et « B », d’une base de données :
T1 : R1(A), W1(B), W1(A), R1(B), C1
T2 : R2(B), W2(B), C2
T3 : W3(A), R3(B), W3(B), C3.

Un gestionnaire des verrous qui reçoit les opérations de ces trois


transactions dans l’ordre ci-après, les traitera comme indiqué dans le
tableau qui suit, s’il contrôle la concurrence en appliquant le protocole de
verrouillage ternaire en deux phases :

535
Joachim TANKOANO

R1(A), R2(B), W3(A), W1(B), W1(A), W2(B), C2, R3(B), R1(B), C1, W3(B), C3.

Verrous posés
Plan d’exécution Décisions gestionnaire verrous
A B
1 R1(A) <S, T1> Accorder verrou <A, S, T1>
2 R2(B) <S, T1> <S, T2> Accorder verrou <B, S, T2>
3 W3(A) <S, T1> <S, T2> Refuser verrou <A, X, T3>
Conflit avec <A, S, T1>
Mettre T3 en attente de T1
4 W1(B) <S, T1> <S, T2> Refuser verrou <B, X, T1>
Conflit avec <B, S, T2>
Mettre T1 en attente de T2
5 W2(B)) <S, T1> <S, T2> Accorder verrou <B, X, T2>
<X, T2>
6 C2 <S, T1> <X, T1> Lever verrou <B, S, T2>
W1(B) Lever verrou <B, X, T2>
Débloquer T1
Accorder verrou <B, X, T1>
7 W1(A) <S, T1> <X, T1> Accorder verrou <A, X, T1>
<X, T1>
R1(B) <S, T1> <X, T1> RAS (T1 a déjà un X sur B)
<X, T1>
C1 <X, T3> Lever verrou <A, S, T1>
W3(A) Lever verrou <A, X, T1>
Lever verrou <B, X, T1>
Débloquer T3
Accorder verrou <A, X, T3>
R3(B) <X, T3> <S, T3> Accorder verrou <B, S, T3>
W3(B) <X, T3> <S, T3> Accorder verrou <B, X, T3>
<X, T3>
C3 Lever verrou <A, X, T3>
Lever verrou <B, S, T3>
Lever verrou <B, X, T3>

L’ordre d’exécution des opérations, reconstruit par l’ordonnanceur et le


gestionnaire des verrous, sera donc :
R1(A), R2(B), W2(B), C2, W1(B), W1(A), R1(B), C1, W3(A), R3(B), W3(B), C3.

Cet ordre est presque celui du plan d’exécution sériel où l’ordre d’exécution
des transactions est « T2 », « T1 », « T3 ».

536
SGBD relationnels – Tome 1, État de l’art

d) Le verrouillage des insertions de lignes

Pour que l’insertion d’une nouvelle ligne par une transaction « T2 » ne soit
pas une ligne fantôme pour une transaction « T1 » qui répète plusieurs fois
l’exécution d’un ordre « SELECT » sur une table, cette table doit être
verrouillée par « T1 » à l’aide d’un verrou partagé et par « T2 » à l’aide d’un
verrou exclusif. En faisant de la sorte, cette table sera accessible, soit
simultanément par plusieurs transactions incluant « T1 » qui n'effectuent
que des opérations de lecture, soit de façon exclusive que par la transaction
« T2 » pour l’insertion de la nouvelle ligne. Cette façon de faire limite les
possibilités d’accès simultané aux lignes de cette table.

Lorsque la transaction « T1 » parcourt pour la première fois les lignes de la


table qui vérifient la condition de recherche, les verrous « d’intervalles »
permettent de poser sur chaque ligne rencontrée un verrou partagé ou
exclusif en fonction des besoins, mais aussi de poser un verrou sur chaque
intervalle où les lignes pourraient vérifier la condition de recherche, afin
d’empêcher l’insertion de lignes fantômes dans ces intervalles de lignes par
« T2 ». L’utilisation des verrous « d’intervalles » améliore l’approche
précédente. D’une part, « T2 » n’a plus besoin de poser sur la table un
verrou exclusif pour éviter l’insertion de lignes fantômes et d’autre part,
d’autres transactions peuvent accéder simultanément aux lignes
verrouillées en lecture et de façon exclusive aux lignes qu’elles peuvent
verrouiller en écriture.

e) Le verrouillage hiérarchique en deux phases

Une base de données peut être vue comme étant un item de grosse
granularité, composé d’une hiérarchie d’items plus élémentaires les uns que
les autres. Cette hiérarchie d’items peut être schématisée à l’aide d’un arbre,
comme ci-dessous, où les nœuds sont reliés entre eux par des relations de
composition.

Un verrouillage appliqué aux items de faible granularité, entraine peu de


blocage mais un nombre plus important de verrous, pouvant rendre leur
gestion beaucoup plus lourde.

537
Joachim TANKOANO

Un verrouillage appliqué à un item de grosse granularité, entraine peu de


verrous mais trop de blocages.

L’objectif du verrouillage hiérarchique est d’une part, de permettre une


gestion cohérente et uniforme des verrous prenant en compte cette
organisation hiérarchique et d’autre part, de faire en sorte que chaque
verrouillage puisse être appliqué à l’item de granularité la plus appropriée.
Par exemple, ne verrouiller la table que s’il est indispensable que chacune
de ses lignes soit verrouillée.

Base de données

Table 1 Table n

Ligne i
Ligne 1

Colonne 1 Colonne j

Le verrouillage hiérarchique est une variante du verrouillage ternaire. Il


s’effectue en respectant les deux principes de base ci-après :
• Un verrou, posé sur un item d’un niveau de la hiérarchie, s’applique
implicitement à tous les items de niveau inférieur qui le composent
et ne doit donc pas être en conflit avec les verrous posés sur ces items
de niveau inférieur. Par exemple, un verrou exclusif sur une table
s’applique à toutes ses lignes et à tous les champs de chacune de ces
lignes, qui ne doivent pas être verrouillés.
• Un verrou ne peut être posé sur un item de la hiérarchie que s’il n’est
pas en conflit avec un verrou de l’item de niveau supérieur qui le
contient. Par exemple, pour poser un verrou exclusif sur une ligne, il
faut que la table qui contient cette ligne ne soit pas verrouillée à l’aide
d’un verrou partagé ou exclusif.

Pour permettre l’application de ces deux principes de base, le verrouillage


hiérarchique utilise trois types de verrous supplémentaires appelés verrous

538
SGBD relationnels – Tome 1, État de l’art

d’intention et le 3ème principe de base ci-après :


• Pour poser un verrou pour une opération à effectuer sur un item, il
faut préalablement poser un verrou d’intention de cette opération
sur l’item de niveau supérieur qui le contient, afin de s’assurer qu’il
n’y a pas et qu’il n’y aura pas de conflit entre le verrou à poser et les
verrous de ce niveau supérieur. Par exemple, pour poser un verrou
exclusif en écriture sur une ligne, il faut préalablement poser un
verrou d’intention en écriture sur la table qui contient cette ligne.

On distingue de ce fait cinq types de verrous hiérarchiques :


• S (pour "Shared", le verrou partagé de lecture)
• X (pour "eXclusive", le verrou exclusif d’écriture)
• IS (le verrou d’intention de lecture)
• IX (le verrou d’intention d'écriture)
• SIX (le verrou de lecture et d’intention d'écriture – combine S et IX).

La matrice ci-après, explicite les paires de verrous qui sont considérés


comme étant incompatibles parce qu’en conflit :

X SIX IX S IS
X OUI OUI OUI OUI OUI
SIX OUI OUI OUI OUI NON*
IX OUI OUI NON* OUI NON*
S OUI OUI OUI NON NON
IS OUI NON* NON* NON NON

Les « NON* » concernent les paires de verrous d’intention qui ne sont pas
en conflit s’il n’existe pas de conflit au niveau des items de niveau inférieur
pour lesquels ils ont été posés.

Exemple 9.3.3.v : Soient trois transactions « T1 », « T2 » et « T3 » définies


comme suit :
T1 : read (cpte_A) ; write (cpte_A) ; COMMIT ; /* met à jour le compte A
T2 : read (table_cptes) ; COMMIT ; /* lit toutes les lignes de la table comptes
T3 : write (cpte_B) ; COMMIT ; /* crée le compte B

Supposons qu’un gestionnaire des verrous, qui applique le verrouillage

539
Joachim TANKOANO

hiérarchique en deux phases pour le contrôle de la concurrence, reçoit les


opérations de ces trois transactions dans l’ordre ci-après :
T1 : read (cpte_A) ; T2 : read (table_cptes) ; T3 : write (cpte_B) ; T1 : write (cpte_A) ;
T2 : COMMIT ; T1 : COMMIT ; T3 : COMMIT

Ces opérations seront traitées par ce gestionnaire des verrous comme


indiqué dans le tableau ci-après :
Verrous posés
Plan d’exécution Décisions du
table_cptes cpte_A cpte_B
gestionnaire verrous
1 T1 : read (cpte_A) <IS, T1> <S, T1> Accorder verrous
2 T2 : read <IS, T1> <S, T1> Accorder verrou
(table_cptes) <S, T2>
3 T3 : write (cpte_B) <IS, T1> <S, T1> Refuser
<S, T2> <table_cptes, IX, T3>
Conflit avec
<table_cptes, S, T2>
Mettre T3 en attente de
T2
4 T1 : write (cpte_A) <IS, T1> <S, T1> Refuser
<S, T2> <table_cptes, IX, T1>
Conflit avec
<table_cptes, S, T2>
Mettre T1 en attente de
T2
5 T2 : COMMIT <IS, T1> <S, T1> <X, T3> Lever
T3 : write (cpte_B) <IX, T3> <X, T1> <table_cptes, S, T2>
T1 : write (cpte_A) <IX, T1> Débloquer T3 /
Accorder verrous
Débloquer T1 /
Accorder verrous
6 T1 : COMMIT <IX, T3> <X, T3> Lever
<table_cptes, IS, T1>
Lever
<table_cptes, IX, T1>
Lever <cpte_A, S, T1>
Lever <cpte_A, X, T1>
7 T3 : COMMIT Lever
<table_cptes, IX, T3>
Lever <cpte_B, X, T3>

L’ordre reconstruit dans lequel l’ordonnanceur soumettra l’exécution des

540
SGBD relationnels – Tome 1, État de l’art

opérations des trois transactions sera donc :


T1 : read (cpte_A) ; T2 : read (table_cptes) ; T2 : COMMIT ; T3 : write (cpte_B) ; T1 :
write (cpte_A) ; T1 : COMMIT ; T3 : COMMIT

9.3.4. Les mécanismes de contrôle basés sur les estampilles

Le mécanisme de contrôle de l’exécution concurrente des transactions, basé


sur l’utilisation des estampilles, que nous présentons ici, est un mécanisme
optimiste qui n’utilise pas de verrous. Il garantit des plans d’exécution
sérialisables mais ne garantit pas des plans d’exécution stricts.

Ce mécanisme laisse les transactions en concurrence s’exécuter librement,


tant que les opérations en conflit s’exécutent selon un ordre total,
garantissant la sériabilité du plan d’exécution. La détection de l’incapacité
d’une transaction à respecter cette condition de sériabilité amène ce
mécanisme à provoquer l’annulation et la reprise de cette transaction.
Lorsque les conflits d’accès à une base de données sont très fréquents, ce
mécanisme peut de ce fait conduire à de fréquentes annulations et reprises
de transactions, pouvant dans le pire des cas mettre une transaction en
situation de famine.

De façon plus précise, au démarrage d’une transaction « T », ce mécanisme


lui attribue un numéro unique appelé « Estampille (T) », qui permet de
l’identifier de façon unique et de l’ordonner dans le temps par rapport aux
autres transactions. Par exemple, ce numéro unique peut être généré par un
compteur global ou peut correspondre à la date de démarrage de la
transaction fournie par l’horloge système. Dans tous les cas, si « Ti » et
« Tj » désignent deux transactions, l’équivalence ci-après doit être toujours
vraie :
Estampille (Ti) < Estampille (Tj)  « Ti » a démarré avant « Tj ».

Ce mécanisme attribue en outre deux estampilles à chaque item « I » :


• « W_Estampille (I) », correspondant à l’estampille de la dernière
transaction qui a eu accès à « I » en écriture

541
Joachim TANKOANO

• « R_Estampille (I) », correspondant à l’estampille de la dernière


transaction, la plus jeune, qui a eu accès à « I » en lecture.

Ce mécanisme laisse ensuite les transactions en compétition s’exécuter


librement tant que les opérations des paires en conflit (« lecture – écriture »,
« écriture – lecture », « écriture – écriture ») s’exécutent dans un ordre
croissant des estampilles des transactions concernées, mais provoque
l’annulation et la reprise d’une transaction, lorsque celle-ci ne peut plus
s’exécuter en respectant cette condition parce qu’elle a trop attendu.

Pour lire un item « I » dans la variable « X » pour le compte de la transaction


« T », l’ordonnanceur doit pour cela exécuter la procédure « Lire (T, I, X) »
définie comme suit :
Si Estampille (T) = W_Estampille (I)
Alors
X  lecture (I) ;
R_Estampille (I)  max (R_Estampille (I), Estampille (T))
Sinon ROLLBACK (T) ;

En d’autres termes, lorsqu’une transaction « T » veut accéder en lecture à


un item « I », elle est par définition en conflit avec toutes les opérations des
autres transactions qui ont eu accès à « I » en écriture. Pour vérifier que la
condition de sériabilité sera respectée si « T » accède à « I » en lecture,
l’ordonnanceur vérifie que cette transaction « T » est plus jeune que la
dernière transaction qui a eu accès à « I » en écriture et dont l’estampille est
dans « W_Estampille (I) ». Si c’est le cas, l’ordonnanceur effectue la lecture
de l’item « I » pour le compte de « T » et modifie en conséquence
« R_Estampille (I) » pour que sa valeur corresponde à l’estampille de la
dernière transaction la plus jeune qui a eu accès en lecture à « I ». Si ce n’est
pas le cas, l’ordonnanceur effectue tout simplement un « ROLLBACK » de
« T ».

De même, pour écrire le contenu de la variable « X » dans l’item « I » pour


le compte de la transaction « T », l’ordonnanceur doit exécuter la procédure
« Ecrire (T, I, X) » définie comme suit :

542
SGBD relationnels – Tome 1, État de l’art

Si Estampille (T) = R_Estampille (I) et Estampille (T) = W_Estampille (I)


Alors
Ecriture (I, X) ;
W_Estampille (I)  Estampille (T)
Sinon ROLLBACK (T) ;

Autrement dit, lorsqu’une transaction « T » veut accéder » en écriture à un


item « I », elle est par définition en conflit avec toutes les opérations des
autres transactions qui ont eu accès en lecture ou en écriture à « I ». Pour
vérifier que la condition de sériabilité sera respectée si « T » accède en
écriture à « I », l’ordonnanceur vérifie d’une part, que cette transaction « T »
est plus jeune que la dernière transaction la plus jeune qui a eu accès en
lecture à « I » et dont l’estampille est dans « R_Estampille (I) » et d’autre
part, que cette transaction « T » est plus jeune que la dernière transaction
qui a eu accès en écriture à « I » et dont l’estampille est dans
« W_Estampille (I) ». Si c’est le cas, l’ordonnanceur effectue l’écriture du
contenu de la variable « X » dans l’item « I » pour le compte de « T » et
modifie en conséquence « W_Estampille (I) » pour que sa valeur
corresponde à l’estampille de la dernière transaction qui a eu accès en
écriture à « I ». Si ce n’est pas le cas, l’ordonnanceur effectue tout
simplement un « ROLLBACK » de « T ».

Exemple 9.3.4.i : Soient « T1 », « T2 », « T3 » et « T4 » quatre transactions et


« x », « y » et « z » trois items d’une base de données. Supposons que les
opérations de ces quatre transactions sont soumises, dans l’ordre défini
dans le tableau ci-après, à un ordonnanceur qui contrôle la concurrence à
l’aide d’un mécanise basé sur l’utilisation des estampilles :

T1 T2 T3 T4
1 Read (x)
2 Read (x)
3 Write (y)
4 Write (x)
5 Read (z)
6 Read (y)
7 Read (y)
8 Read (x)
9 Write (z)
10 Write (x)

543
Joachim TANKOANO

Faisons l’hypothèse qu’initialement les estampilles de lecture et d’écriture


des items « x », « y » et « z » sont toutes égales à 10 et que les estampilles 15,
13, 12 et 14 ont été attribuées par le SGBD respectivement aux transactions
« T1 », « T2 », « T3 » et « T4 ». Les traitements que l’ordonnanceur doit
effectuer pour ce plan d’exécution peuvent se synthétiser comme suit dans
un tableau, où « R_E » désigne « R_Estampille », « W_E »
« W_Estampille » et « E(Ti) » « Estampille (Ti) » :
x y z
Opérations Décisions de
R_E W_E R_E W_E R_E W_E
l’ordonnanceur
État initial = 10 = 10 = 10 = 10 = 10 = 10
1 T2 : Read (x) = = 10 = 10 = 10 = 10 = 10 Exécute T2:Read (x)
E(T2) car E(T2) > W_E(x).
= R_E(x) prend la
13
valeur de
l’estampille de T2
qui devient la
dernière
transaction la plus
jeune qui a eu accès
à « x » en lecture
2 T3 : Read (x) = = 10 = 10 = 10 = 10 = 10 Exécute T3:Read(x)
E(T2) car E(T3) > W_E(x).
= R_E(x) conserve la
13
valeur de
l’estampille de T2
qui est toujours la
dernière
transaction la plus
jeune qui a eu accès
à « x » en lecture
3 T2 : Write = = 10 = 10 = = 10 = 10 Exécute T2:Write(y)
(y) E(T2) E(T2) car E(T2) > W_E(y)
= = et E(T2) > R_E(y).
13 13
W_E(y) prend la
valeur de
l’estampille de T2
qui devient la
dernière
transaction qui a eu
accès à « y » en

544
SGBD relationnels – Tome 1, État de l’art

écriture
4 T3 : Write = = 10 = 10 = = 10 = 10 Exécute :
(x) E(T2) E(T2) ROLLBACK de T3
= = car E(T3) = 12
13 13
 E(T3) < R_E(x)
5 T2 : Read (z) = = 10 = 10 = = = 10 Exécute T2:Read(z)
E(T2) E(T2) E(T2) car E(T2) > W_E(z).
= = = R_E(z) prend la
13 13 13
valeur de
l’estampille de T2
qui devient la
dernière
transaction la plus
jeune qui a eu accès
à « z » en lecture
6 T1 : Read (y) = = 10 = = = = 10 Exécute T1:Read(y)
E(T2) E(T1) E(T2) E(T2) car E(T1) > W_E(y).
= = = = R_E(y) prend la
13 15 13 13
valeur de
l’estampille de T1
qui devient la
dernière
transaction la plus
jeune qui a eu accès
à « y » en lecture
7 T4 : Read (y) = = 10 = = = = 10 Exécute T4:Read(y)
E(T2) E(T1) E(T2) E(T2) car E(T4) > W_E(y).
= = = = R_E(y) conserve la
13 15 13 13
valeur de
l’estampille de T1
est toujours la
dernière
transaction la plus
jeune qui a eu accès
à « y » en lecture
8 T1 : Read (x) = = 10 = = = = 10 Exécute T1:Read(x)
E(T1) E(T1) E(T2) E(T2) car E(T1) > W_E(x).
= = = = R_E(x) prend la
15 15 13 13
valeur de
l’estampille de T1
qui devient la
dernière

545
Joachim TANKOANO

transaction la plus
jeune qui a eu accès
à « y » en lecture)
9 T1 : Write = = 10 = = = = Exécute T1:Write(z)
(z) E(T1) E(T1) E(T2) E(T2) E(T1) car E(T1) > R_E(z)
= = = = = et E(T1) > W_E(z).
15 15 13 13 15
W_E(z) prend la
valeur de
l’estampille de T1
qui devient la
dernière
transaction qui a eu
accès à « z » en
écriture
10 T4 : Write = = 10 = = = = Exécute :
(x) E(T1) E(T1) E(T2) E(T2) E(T1) ROLLBACK de T4
= = = = = car E(T4) = 14
15 15 13 13 15
 E(T4) < R_E(x)
et E(T4) < W_E(x)

9.3.5. Les mécanismes de contrôle basés sur la certification des


transactions

Les mécanismes de contrôle de l’exécution concurrente des transactions,


basés sur la certification des transactions, sont des mécanismes optimistes
qui distinguent trois phases pour l’exécution de chaque transaction :
• Une phase d’accès
• Une phase de certification
• Une phase de répercussion des mises à jour dans la base de données.

Pendant la phase d’accès d’une transaction « Ti », ces mécanismes laissent


cette transaction s’exécuter jusqu’à son terme, sans interférer dans le
déroulement de son exécution. Pour chaque item lu et pour chaque item
écrit dans la base de données par cette transaction « Ti », sa référence est
conservée respectivement dans « R_SET (Ti) » et dans « W_SET (Ti) », en
y incluant éventuellement les autres informations requises pour les phases
suivantes. Ceci permet le maximum de parallélisme entre les transactions.

546
SGBD relationnels – Tome 1, État de l’art

La phase de certification d’une transaction « Ti » a lieu lorsque cette


transaction fait son « COMMIT ». Elle amène à exploiter les informations
collectées pendant la phase d’accès dans « R_SET (Ti) » et dans « W_SET
(Ti) », pour la détection des conflits d’accès avec toute autre transaction
« Tj » certifiée ou validée, ayant eu accès aux mêmes items que « Ti ». La
détection d’un conflit d’accès entraine l’invalidation et le « ROLLBACK »
de « Ti ». La non-détection de conflit entraine la certification de « Ti ».

La phase de répercussion, dans la base de données, des mises à jour


effectuées par une transaction « Ti » pendant la phase d’accès, n’intervient
que si la phase de certification a conduit à sa certification. Elle amène ces
mécanismes de contrôle de la concurrence à exploiter les informations
collectées pendant la phase d’accès dans « W_SET (Ti) » pour cette
transaction « Ti », pour appliquer ses mises à jour dans la base de données
et pour valider cette transaction.

Tout comme pour les mécanismes de contrôle de la concurrence basés sur


l’utilisation des estampilles, ces mécanismes fonctionnent sans avoir à
utiliser un mécanisme de verrouillage des items. Lorsque les conflits sont
rares, elles permettent d’éliminer le temps de traitement consacré à la
gestion des verrous et à l’annulation d’opérations en cas de
« ROLLBACK », ce qui assure une exécution plus rapide des transactions.

9.3.6. Les quatre niveaux d’isolation de SQL

Comme nous l’avons vu dans ce qui précède, l’utilisation des mécanismes


de contrôle de la concurrence garantit l’isolation des transactions
concurrentes, en garantissant la sérialisation des plans d’exécution de ces
transactions, afin d’éviter toute interférence due à l’entrelacement de leurs
opérations. En contrepartie de cet avantage, en fonction des algorithmes
utilisés, ces mécanismes réduisent, de façon plus ou moins importante, le
degré de parallélisme qui peut exister entre les transactions qui s’exécutent
simultanément.

La norme SQL prévoit les quatre niveaux d’isolation ci-après, que les
fournisseurs de SGBD doivent permettre au développeur d’activer, en
fonction des exigences de son application, afin de rendre plus important le

547
Joachim TANKOANO

degré de parallélisme entre les transactions. Ces quatre niveaux d’isolation


vont d’un niveau d’isolation permettant le plus de parallélisme et toutes les
interférences possibles, à un niveau d’isolation qui permet le moins de
parallélisme en évitant toute interférence, à savoir :
• Le niveau d’isolation « READ UNCOMMITED », garantissant
l’absence d’isolation, donc de restrictions, ce qui permet le maximum
de parallélisme dans l’exécution des transactions mais autorise
toutes les anomalies possibles
• Le niveau d’isolation « READ COMMITED », garantissant
l’absence de lectures sales, mais autorisant les lectures non
répétables et la création de lignes fantômes
• Le niveau d’isolation « REPEATABLE READ », garantissant
l’absence de lectures sales et de lectures non répétables, mais
autorisant la création de lignes fantômes
• Le niveau d’isolation « SERIALIZABLE », garantissant une isolation
totale, ce qui rend impossible les interférences, mais diminue le plus
le parallélisme dans l’exécution des transactions.

Le tableau qui suit résume les possibilités offertes par chaque niveau
d’isolation en termes de contrôle de la survenue des anomalies :

Niveau d’isolation Lectures Lectures non Lignes


sales répétables fantômes
READ Possible Possible Possible
UNCOMMITED
READ COMMITED Jamais Possible Possible
REPEATABLE READ Jamais Jamais Possible
SERIALIZABLE Jamais Jamais Jamais

Lorsque le niveau d’isolation choisi n’est pas suffisamment restrictif, le


programmeur doit au cas par cas utiliser explicitement les verrous pour
gérer les risques de conflit.

Exemple 9.3.6i : Soient les transactions « T1 » et « T2 » ci-après agissant sur


l’item « X » :
T1 : read (X) ; read (X) ; COMMIT ; /* effectue 2 lectures successives de X) */
T2 : read (X) ; write (X) ; write (X) ; COMMIT ; /* modifie X 2 fois */

548
SGBD relationnels – Tome 1, État de l’art

Supposons que l’ordonnanceur reçoit la séquence d’opérations ci-après,


dans le cadre de l’exécution simultanée de ces deux transactions :
T1 : read (X) ; T2 : read (X) ; T2 : write (X) ; T1 : read (X) ; T2 : write (X) ; T2 : COMMIT ;
T1 : COMMIT

Ce qui suit indique pour chaque niveau d’isolation, l’ordre d’exécution des
opérations de cette séquence, qui sera établi par l’ordonnanceur du SGBD.
READ UNCOMMITED

T1 : read (X) ; T2 : read (X) ; T2 : write (X) ; T1 : read (X) ; T2 : write (X) ; T2 : COMMIT;
T1 : COMMIT

READ COMMITED

T1 : read (X) ; T2 : read (X) ; T2 : write (X) ; T2 : write (X) ; T2 : COMMIT ; T1 : read (X);
T1 : COMMIT

REPEATABLE READ

T1 : read (X) ; T2 : read (X) ; T1 : read (X) ; T1 : COMMIT ; T2 : write (X) ; T2 : write (X);
T2 : COMMIT ;

SERIALIZABLE

T1 : read (X) ; T2 : read (X) ; T1 : read (X) ; T1 : COMMIT ; T2 : write (X) ; T2 : write (X);
T2 : COMMIT ;

9.4. La gestion des reprises après incident


La reprise après incident (« recovery ») d’une base de données vise à la
remettre en fonctionnement, le plus rapidement possible, dans son dernier
état validé avant l’incident, ce qui implique la restauration de la dernière
valeur validée de chaque item de cette base de données. Tout item modifié
par une transaction non validée doit être restauré à sa valeur validée
antérieure et aucun item modifié par une transaction validée ne doit perdre
sa nouvelle valeur.

La reprise après incident vise à garantir les propriétés d’atomicité des


transactions et de durabilité des modifications effectuées dans une base de
données. Elle n’est possible que si l’ordonnanceur garantit des plans
d’exécution de transactions strictes.

549
Joachim TANKOANO

Cette section présente d’abord les moyens sur lesquels les SGBD s’appuient
pour effectuer les différents types de reprises après incident. Ensuite, elle
aborde la présentation de la manière dont les SGBD procèdent pour
effectuer ou pour permettre à l’administrateur de la base de données
d’effectuer ces différents types de reprises.

9.4.1. Les moyens de mise en œuvre des reprises après incident

La mise en œuvre des différents types de reprises après la survenue d’un


incident nécessite principalement trois types de moyens :
• Le journal des transactions
• Le dernier point de reprise ou de sauvegarde
• Les sauvegardes périodiques de la base de données.

a) Le journal des transactions

Le journal des transactions est un fichier que les SGBD utilisent pour
enregistrer de façon systématique, sous forme d’historique, les
informations relatives aux modifications effectuées dans une base de
données, afin de disposer des données requises pour défaire ou pour refaire
ces modifications lors d’une reprise consécutive à un incident.

Pour chaque base de données, le journal des transactions est unique et doit
être enregistré sur une mémoire non volatile, différente de préférence de
celle de la base de données.

Les informations collectées dans le journal des transactions sont regroupées


par transaction. Pour chaque transaction en cours ou qui s’est terminée, on
doit y retrouver :
• L’identifiant de la transaction
• L’état de la transaction (en cours, validée, avortée)
• Pour chaque action effectuée :
o Son type (insertion, modification, suppression, COMMIT,
ROLLBACK, Point de reprise, etc.)

550
SGBD relationnels – Tome 1, État de l’art

o L’item éventuellement concerné


o L’ancienne valeur de l’item concerné (l’image avant)
o La nouvelle valeur de l’item concerné (l’image après).

Le journal des transactions intervient dans chaque action du SGBD qui


permet de faire, de défaire ou de refaire une modification dans la base de
données. Ces actions se schématisent comme suit :
• Concernant l’action « FAIRE » une modification d’un item de la base
de données

« Image avant » de « Image après » de


FAIRE
l’item dans la BD l’item dans la BD

Écriture
Journal

• Concernant l’action « DEFAIRE » une modification pour restaurer


l’ancien état d’un item de la base de données

« Image après » de « Image avant » de


DEFAIRE
l’item dans la BD l’item dans la BD

Lecture
Journal

• Concernant l’action « REFAIRE » une modification pour restaurer le


nouvel état d’un item de la base de données

« Image avant » de « Image après » de


REFAIRE
l’item dans la BD l’item dans la BD

Lecture
Journal

Après un incident, les informations contenues dans un journal des


transactions, alimenté depuis la création de la base de données, doivent être
suffisantes pour restaurer cette base de données dans son dernier état
validé, en refaisant, à partir d’une base de données vide, toutes les actions

551
Joachim TANKOANO

des transactions validées contenues dans ce journal. En termes de sécurité,


cette capacité du journal des transactions le rend plus important que la base
de données.

Dans une exploitation réelle, le journal des transactions est en général


constitué, du fait de son importance, de plusieurs groupes de fichiers
identiques, remplis par rotation avant d’être archivés.

b) Les points de reprise ou de sauvegarde

Comme nous l’avons vu dans la section 7.5, pour réduire le temps d’accès
aux blocs des disques qui servent à stocker les données, les SGBD créent en
général en mémoire centrale, une zone tampon entre les applications et la
base de données, où ils copient une partie des blocs des disques qui
contiennent cette base de données.

Chaque fois qu’une application veut accéder à un bloc d’une table, ce bloc
est d’abord recherché dans cette zone tampon. Si ce bloc s’y trouve, l’accès
au disque est évité. Si ce bloc ne s’y trouve pas, le SGBD le récupère dans la
base de données et le recopie dans la zone tampon (éventuellement en
écrasant un autre bloc) avant d’exploiter son contenu.

Ce mécanisme est également appliqué en général au journal des


transactions pour lequel les écritures sont d’abord effectuées dans une zone
tampon dédiée, avant d’être répercutées sur le disque.

Ce mécanisme de bufferisation s’accompagne, de différents types


d’algorithmes de synchronisation périodique, du contenu des blocs des
zones tampons avec le contenu des blocs correspondants sur le disque.
Lorsqu’une zone tampon est utilisée pour le journal des transactions, il est
essentiel que la synchronisation de cette zone tampon ait lieu au moins
avant chaque validation de transaction, afin de s’assurer ainsi que l’état de
ce journal sur le disque contient, à tout moment, les informations relatives
aux modifications effectuées par toutes les transactions validées. Quant à la
zone tampon de la base de données, sa synchronisation doit avoir pour
objectif l’application des modifications en attente, effectuées par les
transactions validées.

552
SGBD relationnels – Tome 1, État de l’art

Lorsque ce mécanisme de bufferisation est mis en œuvre, le SGBD est


constamment dans une situation où, des modifications effectuées par des
transactions validées ou non, peuvent avoir été appliquées dans les zones
tampons de la base de données et du journal des transactions, sans avoir été
répercutées sur le disque.

Dans ce contexte, les points de reprise ou de sauvegarde sont des étapes


dans l’évolution du cycle de vie d’une base de données, où le SGBD a
procédé à une synchronisation des blocs des zones tampons et des blocs qui
les correspondent sur le disque, suivie d’une sauvegarde de l’état de ces
zones tampons sur une mémoire non volatile. Cette synchronisation doit
garantir qu’après un point de reprise, toutes les dernières valeurs des
données modifiées par les transactions validées sont sur le disque.

Ceci permet, après la survenue d’un incident ayant entrainé la perte du


contenu des zones tampons, d’effectuer la restauration de la base de
données dans son dernier état validé, en s’appuyant sur l’état courant de la
base de données et sur un journal des transactions contenant au moins
toutes les informations relatives aux transactions validées après le dernier
point de reprise. La restauration de la base de données dans son dernier état
validé se fait dans ces conditions, en restaurant les zones tampons telles
qu’elles étaient après le dernier point de reprise, en refaisant, à l’aide du
journal des transactions, les actions effectuées après le dernier point de
reprise, afin de reconstituer les zones tampons telles qu’elles étaient au
moins lors de la dernière validation de transaction et en défaisant ensuite,
à partir de cet état reconstitué, les actions des transactions qui étaient en
cours à ce moment.

Les sauvegardes périodiques peuvent être espacées lorsque le mécanisme


des points de reprises est mis en œuvre. En outre, l’augmentation de la
fréquence des points de reprise permet des restaurations après incident
plus rapides, en réduisant la taille des données requises dans le journal des
transactions.

c) Les sauvegardes périodiques de la base de données

Les sauvegardes périodiques de la base de données sont indispensables

553
Joachim TANKOANO

pour la mise en œuvre des reprises pour lesquelles l’incident a eu comme


conséquence, la destruction de toute ou partie de la base de données sur le
disque dur.

Dans ce type de situation, la restauration d’une base de données dans son


dernier état validé se fait, en s’appuyant sur une sauvegarde de cette base
de données et sur un journal des transactions, contenant au moins les
informations relatives aux actions effectuées par les transactions validées
après cette sauvegarde. Cette restauration s’effectue en utilisant la
sauvegarde pour restaurer la base de données telle qu’elle existait au
moment de la sauvegarde et ensuite en refaisant, à l’aide du journal des
transactions, toutes les actions des transactions validées après la
sauvegarde.

Il est de ce fait essentiel, pour toute exploitation réelle où les données


doivent être fortement sécurisées, d’une part, de mettre en œuvre une
procédure de sauvegarde périodique de la base de données et du journal
des transactions et d’autre part, de prévoir un bon système d’archivage de
ces sauvegardes.

On distingue plusieurs types de sauvegardes :


• Les sauvegardes logiques, constituées des ordres SQL pouvant
permettre de recréer la base de données et ses tables et ensuite de
recharger ces tables
• Les sauvegardes physiques des fichiers où le contenu de la base de
données est stocké
• Les sauvegardes en ligne, effectuées sans avoir démonté la base de
données mais en effectuant les verrouillages nécessaires, afin
d’interdire toute modification de données au cours de l’opération
• Les sauvegardes hors ligne effectuées après avoir démonté la base
de données
• Les sauvegardes complètes, contenant toutes les données de la base
de données
• Les sauvegardes incrémentales, contenant les données de la base de
données relatives à une période donnée.

554
SGBD relationnels – Tome 1, État de l’art

Sauvegarder une base de données peut se révéler comme étant une


opération lourde et très contraignante, en particulier pour les applications
qui doivent fonctionner en continu, sans interruption.

Un bon système d’archivage des sauvegardes doit permettre de prémunir


l’entreprise contre toutes les catastrophes envisageables :
• Guerres
• Incendies
• Catastrophes naturelles (inondations, tremblements de terre, …)
• Vols et tout type de vandalisme
• Etc.

9.4.2. Les différents types de reprises après incident

On distingue trois types de reprises qui ne dépendent que de la nature de


l’incident :
• Les reprises annulation de transactions avortées, qui ne concernent
que les incidents localisés au niveau de l’exécution de ces
transactions
• Les reprises à chaud qui ne concernent que les incidents qui n’ont
comme conséquence qu’une perte du contenu de la mémoire
centrale, donc des zones tampons
• Les reprises à froid qui ne concernent que les incidents qui ont
comme conséquence une détérioration de toute ou partie du contenu
de la base de données sur les disques.

Ce qui suit présente ces trois types de reprises.

a) Les reprises annulation de transactions avortées

La décision d’avorter une transaction peut être :


• La conséquence d’une erreur de programmation, de la violation
d’une contrainte d’intégrité, du non-respect des droits d’accès, de

555
Joachim TANKOANO

l’altération de la cohérence des données ou de tout autre incident lié


à l’exécution de la transaction
• Prévue dans le code de la transaction par le développeur
• Un choix du SGBD pour résoudre un problème (interblocage,
annulation pour garantir la sérialisation d’un plan d’exécution par
exemple).

La reprise annulation d’une transaction avortée s’effectue en défaisant


toutes les actions effectuées par cette transaction, donc en s’appuyant sur
les images-avant du journal des transactions.

La reprise annulation d’une transaction avortée requiert que le plan


d’exécution des transactions concurrentes dans lequel cette transaction est
impliquée soit strict. Lorsque c’est le cas, la reprise annulation permet de
garantir l’atomicité de la transaction.

Lorsque le plan d’exécution des transactions concurrentes dans lequel la


transaction est impliquée n’est pas strict, la reprise annulation de cette
transaction doit entraîner l’annulation de toutes les autres transactions qui
ont lu ou modifié les données qu’elle a salies et peut de ce fait être
irréalisable.

Le graphe de transition de l’état d’une transaction en cours d’exécution


peut se schématiser comme suit :
Contrôle cohérence
Lecture/Écriture
Fin T
Entrée T Partiellement Validation
Active
validée

Échec Non validée


Validée

Exécution DÉFAIRE
Avortée Terminée

L’état « Partiellement validée » permet au SGBD de s’assurer, avant la

556
SGBD relationnels – Tome 1, État de l’art

validation de la transaction concernée, que celle-ci en s’exécutant n’a pas eu


comme conséquence la violation des contraintes d’intégrité.

La reprise annulation d’une transaction est la reprise la moins couteuse.

Exemple 9.4.2.i : Le schéma qui suit visualise le chronogramme d’exécution


de cinq transactions « T1 », « T2 », « T3 », « T4 » et « T5 », par rapport à la
dernière sauvegarde, à la pose du dernier point de reprise et à la survenue
du dernier incident.

T1

T2

T3
T4

T5

Dernière Point de reprise Incident (I)


sauvegarde (S) (R)

Si l’incident a conduit « T3 » ou « T5 » à exécuter un « ROLLBACK », la


reprise doit consister à faire une reprise annulation de transaction avortée
en défaisant les actions de cette transaction à l’aide des images-avant du
journal des transactions.

b) Les reprises à chaud

Les reprises à chaud concernent les incidents, comme les coupures brutales
du courant ou les plantages du moteur SQL, qui n’ont comme conséquence
que la perte du contenu de la mémoire centrale. Cette perte se traduit
principalement par la perte du contenu des zones tampons où sont stockées
des copies de blocs du disque qui contiennent la base de données et les
informations sur les écritures dans le journal des transactions non encore
répercutées sur le disque.

Ces reprises sont contrôlables par le SGBD. Elles sont effectuées au


redémarrage du moteur SQL en s’appuyant sur l’état de la base de données

557
Joachim TANKOANO

en ligne, le dernier point de reprise et le journal des transactions.

Comme nous l’avons déjà vu dans le paragraphe 9.4.1.b, un point de reprise


est une étape dans le cycle de vie d’une base de données où, la
synchronisation de la zone tampon dédiée à la base de données avec les
blocs correspondants sur le disque a permis de garantir qu’à cette étape,
toutes les dernières valeurs des données modifiées par les transactions
validées sont sur le disque.

Partant de ce postulat, une reprise à chaud consiste à :


• Restaurer les zones tampons telles qu’elles étaient après le dernier
point de reprise
• Refaire les actions effectuées après le dernier point de reprise, à l’aide
des images-après d’un journal des transactions contenant les
informations relatives aux actions des transactions effectuées après
le dernier point de reprise, afin de reconstituer les zones tampons
telles qu’elles étaient au moment de la dernière synchronisation de
la zone tampon du journal des transactions
• Défaire à partir de l’état reconstitué, les actions des transactions qui
étaient en cours à ce moment.

Exemple 9.4.2.ii : Si l’incident de l’exemple précédent 9.4.2.i est une coupure


brutale du courant, la reprise doit consister à faire une reprise à chaud, c’est-
à-dire à restaurer la zone tampon de la base de données et la zone tampon
du journal des transactions telles qu’elles étaient après la pose de « R », à
refaire ensuite les actions de « T2 », « T3 », « T4 » et « T5 » après « R » pour
reconstituer ces zones tampons telles qu’elles étaient au moment de la
dernière synchronisation de la zone tampon du journal des transactions et
à défaire les actions de « T3 » et « T5 ».

c) Les reprises à froid

Les reprises à froid concernent les incidents graves qui ont comme
conséquence la destruction partielle ou complète du contenu de la base de
données sur le disque, rendant impossible tout retour en arrière à partir de

558
SGBD relationnels – Tome 1, État de l’art

sa version courante.

Ces reprises ne sont pas contrôlables par le SGBD. Elles nécessitent une
intervention de l’administrateur de la base de données, une sauvegarde de
la base de données et un journal des transactions qui contient les
informations relatives à toutes les actions effectuées après cette sauvegarde.

Une reprise à froid s’effectue :


• En restaurant à l’aide de sa sauvegarde, la base de données telle
qu’elle était au moment de cette sauvegarde
• En refaisant à l’aide des images-après du journal des transactions,
toutes les actions effectuées par les transactions après la sauvegarde,
afin de reconstituer les zones tampons et la base de données sur le
disque, telles qu’elles étaient au moment de la dernière
synchronisation de la zone tampon du journal des transactions
• En défaisant les actions des transactions qui étaient en cours au
moment de la dernière synchronisation de la zone tampon du journal
des transactions.

Exemple 9.4.2.iii : Si l’incident de l’exemple précédent 9.4.2.i a provoqué la


destruction partielle ou complète du contenu de la base de données sur le
disque, la reprise doit consister à faire une reprise à froid, c’est-à-dire à
restaurer, à l’aide de sa sauvegarde, la base de données dans l’état qu’elle
avait lorsque cette sauvegarde a été effectuée, à refaire ensuite les actions
des transactions « T1 », « T2 », « T3 », « T4 » et « T5 », afin de reconstituer
les zones tampons et la base de données sur le disque, telles qu’elles étaient
au moment de la dernière synchronisation de la zone tampon du journal
des transactions et à défaire les actions de « T3 » et « T5 ».

9.5. La gestion des accès concurrents et des reprises


sous Oracle
Ce qui suit présente de façon plus concrète les possibilités offertes par le
SGBD Oracle pour :

559
Joachim TANKOANO

• La démarcation des transactions à l’intérieur d’une session


• La gestion de l’exécution concurrente de plusieurs transactions
• La gestion d’une reprise après un incident.

9.5.1. La démarcation des transactions à l’intérieur d’une


session Oracle

Comme nous l’avons vu, la gestion de l’accès simultané à une base de


données par plusieurs utilisateurs et la gestion d’une reprise après un
incident, nécessitent que les opérations d’accès à cette base de données,
effectuées par chaque utilisateur, soient regroupées par le développeur en
transactions qui se succèdent les unes après les autres.

En rappel, une transaction est une séquence atomique d’opérations d’accès


à la base de données, qui, au regard de la logique métier, doit être traitée
comme une séquence cohérente et indivisible d’opérations, qui ne doit en
aucun cas être traitée en partie. Lorsqu’une transaction se termine
normalement, le développeur doit le signifier en exécutant un ordre SQL
« COMMIT », afin de demander au SGBD de valider et de rendre
définitives et visibles par les autres transactions, les modifications
apportées aux données. Lorsqu’une transaction ne peut pas se terminer
normalement, le développeur doit le signifier en exécutant un ordre SQL
« ROLLBACK », afin de demander au SGBD une annulation des
modifications apportées aux données, pour que tout se passe comme si cette
transaction ne s’est jamais exécutée.

Dans une unité de traitement PL/SQL ou dans un programme écrit à l’aide


de tout autre langage manipulant une base de données Oracle à travers un
connecteur de bases de données, le code d’une transaction peut être
constitué :
• D’un ou de plusieurs ordres du langage de manipulation des
données (« SELECT », « INSERT », « UPDATE », « DELETE »)
• Éventuellement d’un ordre du langage de définition des données
(« CREATE », « ALTER », « DROP », …) ou du langage de contrôle
des données (« GRANT », « REVOKE », …).

560
SGBD relationnels – Tome 1, État de l’art

Dans chaque session utilisateur, une transaction :


• Débute :
o Avec l’exécution du premier ordre SQL rencontré, pour ce qui
concerne la première transaction
o Avec la fin de la transaction précédente, pour ce qui concerne
les autres transactions
• Se termine :
o Avec l’exécution d’un ordre « COMMIT » ou d’un ordre
« ROLLBACK »
o Avec l’exécution d’un ordre du langage de définition des
données ou du langage de contrôle des données
o Avec la fin de la session par un déclenchement implicite de
l’exécution d’un ordre « COMMIT »
o Ou lorsqu’un incident survient.

L’ordre « COMMIT » amène le SGBD à :


• Écrire dans la zone tampon dédiée au journal des transactions
(« REDO LOG BUFFER ») un enregistrement de validation de la
transaction contenant un identifiant de changement (« SYSTEM
CHANGE NUMBER (SCN) »)
• Enregistrer dans le journal des transactions (« REDO LOG FILE »)
les blocs de la zone tampon dédiée à ce journal (REDO LOG
BUFFER) relatifs à la transaction, ce qui valide cette transaction
• Libérer les verrous attribués à cette transaction.

N.B. : Le transfert vers le disque des blocs de la zone tampon dédiée à la


base de données, qui ont été modifiés par une transaction est indépendant
de la validation de cette transaction. Ce transfert est opportuniste. Il se fait
avant ou après la validation de cette transaction en fonction du taux
d’occupation de la zone tampon. En cas d’incident, il est donc possible de
perdre les dernières valeurs des items de la base de données, modifiés par
des transactions validées. La restauration de ces valeurs s’effectue à l’aide

561
Joachim TANKOANO

des données y relatives enregistrées dans le journal des transactions.

L’exécution d’un ordre « ROLLBACK » peut être :


• Prévu par le développeur dans la logique métier du code de la
transaction
• Provoqué par le développeur dans le code de la transaction à la suite
d’une fin anormale de la transaction
• Provoqué par le SGBD dans le cadre d’une reprise à chaud ou à froid
ou pour briser un interblocage de transactions concurrentes.

L’exécution d’un ROLLBACK conduit à :


• Une reprise annulation de la transaction concernée, c’est-à-dire, à
l’annulation de toutes les modifications effectuées par cette
transaction, en utilisant les images-avant qui se trouvent dans le
« ROLLBACK SEGMENT » ou dans le « UNDO TABLESPACE »
depuis la version 9
• La libération de tous les verrous alloués à cette transaction.

N.B. : Avec le connecteur JDBC, la validation et l’annulation d’une


transaction s’effectuent en appelant respectivement la méthode « commit
() » et la méthode « rollback () » d’un objet « Connection ».

9.5.2. La gestion de l’exécution concurrente des transactions


sous Oracle

Pour permettre une gestion souple et efficace de la concurrence, ORACLE


offre principalement trois mécanismes que le développeur peut combiner
en fonction de ses exigences :
• Le mécanisme des niveaux d’isolation qui sont au nombre de trois,
dont deux sont conformes à la norme SQL
• Le mécanisme des LECTURES COHERENTES qui permet
d’accroître la concurrence sans nuire à la cohérence, grâce à
l’utilisation d’images-avant multi-versions estampillées

562
SGBD relationnels – Tome 1, État de l’art

• Le mécanisme des verrous pour provoquer une mise en attente,


chaque fois que cela est nécessaire.

Ce qui suit présente ces trois mécanismes.

a) Les niveaux d’isolation retenus par Oracle

Les trois niveaux d’isolation retenus par Oracle sont :


• Le niveau d’isolation « READ COMMITED »
• Le niveau d’isolation « SERIALIZABLE »
• Le niveau d’isolation « READ-ONLY ».

Le niveau d’isolation « READ COMMITED » est le niveau d’isolation par


défaut. Ce niveau d’isolation garantit qu’une transaction qui exécute une
requête SQL (même dans le cas des requêtes les plus complexes incluant
des jointures) ne voit pendant toute l’exécution de cette requête que les
mêmes données : celles validées avant le début de l’exécution de la requête.
Le choix de ce niveau d’isolation garantit que les LECTURES SALES ne
peuvent pas survenir au cours de l’exécution d’une requête SQL mais
n’empêche pas la survenue de LECTURES NON REPETABLES,
l’apparition de TUPLES FANTÔMES et les MISE S A JOUR PERDUES.

Le niveau d’isolation « SERIALIZABLE » garantit qu’une transaction ne


peut voir au cours de son exécution que les mêmes données : celles validées
avant le début de son exécution, éventuellement avec les mises à jour
qu’elle a elle-même effectuées sur ces données. Le choix de ce niveau
d’isolation garantit que les LECTURES SALES, les LECTURES NON
REPETABLES, l’apparition de TUPLES FANTÔMES et les MISES À JOUR
PERDUES ne peuvent pas survenir au cours de l’exécution d’une
transaction.

Le niveau d’isolation « READ-ONLY » est spécifique à Oracle. Il garantit


qu’une transaction ne peut voir, au cours de son exécution, que les mêmes
données (celles validées avant le début de son exécution) et n’a aucune
possibilité de modifier ces données. Ce niveau d’isolation est utile
notamment pour les entrepôts de données ou pour garantir au cours de

563
Joachim TANKOANO

l’exécution d’une transaction des LECTURES RÉPÉTABLES.

Le développeur active dans son application le niveau d’isolation souhaité à


l’aide des ordres ci-après :
• Pour ce qui concerne le niveau d’isolation « READ COMMITED »
SET TRANSACTION ISOLATION LEVEL READ COMMITTED ;

ALTER SESSION SET ISOLATION_LEVEL = READ COMMITTED ;


• Pour ce qui concerne le niveau d’isolation « SERIALIZABLE »
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE ;

ALTER SESSION SET ISOLATION_LEVEL = SERIALIZABLE ;

• Pour ce qui concerne le niveau d’isolation « READ-ONLY »


SET TRANSACTION READ ONLY ;

N.B. : Avec un pilote Oracle JDBC, l’objet « Connection » offre la méthode


« getTransactionIsolation () » pour la récupération du niveau d’isolation
de la session courante et la méthode « setTransactionIsolation () » pour la
modification du niveau d’isolation de la session courante en fournissant la
valeur « TRANSACTION_READ_COMMITTED » pour le niveau
d’isolation « READ COMMITED » ou la valeur
« TRANSACTION_SERIALIZABLE » pour le niveau d’isolation
« SERIALIZABLE ».

b) Le mécanisme des LECTURES COHERENTES sous Oracle

Le mécanisme des LECTURES COHERENTES est utilisé par de nombreux


éditeurs de SGBD pour accroître la concurrence, sans nuire à la cohérence,
grâce à l’utilisation d’images-avant multi-versions estampillées.

Ce mécanisme permet de faire en sorte que les paires « Ti:read (I) -


« Tj:write (I) » et « Ti:write (I) - Tj:read (I) » ne soient plus des paires
d’opérations en conflit mais des paires d’opérations commutables, ce qui
permet de limiter les possibilités de conflit à la paire « Ti:write (I) - Tj:write
(I) » et d’accroître ainsi le parallélisme dans l’exécution simultanée des
transactions, sans que cela ne génère des interférences.

564
SGBD relationnels – Tome 1, État de l’art

En d’autres termes, lorsque ce mécanisme est utilisé, l’accès en lecture à un


item « I » par une transaction « Ti » ne peut plus être bloqué à cause d’un
accès en écriture à cet item par une autre transaction « Tj », tout comme
l’accès en écriture à un item « I » par une transaction « Ti » ne peut plus être
bloqué à cause d’un accès en lecture à cet item par une autre transaction
« Tj ». Seul un accès en écriture à un item « I » par une transaction « Ti »
peut empêcher une autre transaction « Tj » d’accéder aussi en écriture à cet
item.

Ceci est rendu possible grâce à la gestion d’images-avant multi-versions


estampillées, permettant de faire en sorte qu’une transaction ne voit au
cours de son exécution ou de l’exécution d’une requête que les données
validées, telles qu’elles étaient au début de son exécution ou de l’exécution
de la requête.

Avec le niveau d’isolation « READ COMMITED », la lecture cohérente


garantit que :
• Une transaction qui exécute une requête SQL ne voit pendant toute
l’exécution de cette requête que les mêmes données : celles validées
avant le début de l’exécution de cette requête
• Les autres transactions ne peuvent pas voir les modifications
effectuées par cette transaction avant sa validation
• Une transaction qui veut effectuer une lecture d’une donnée ne sera
jamais bloquée par une transaction autorisée à effectuer une
modification de cette donnée
• Une transaction qui veut effectuer une modification d’une donnée
ne sera jamais bloquée par une transaction autorisée à effectuer une
lecture de cette donnée
• Une transaction qui veut effectuer une modification d’une donnée
sera bloquée si une autre transaction a déjà été autorisée à effectuer
une modification de cette donnée et sera éligible pour reprendre son
exécution à la fin de la transaction ayant entraîné ce blocage (
Possibilité de mise à jour perdue)

De même, avec le niveau d’isolation « SERIALIZABLE », la lecture

565
Joachim TANKOANO

cohérente garantit que :


• Une transaction ne voit pendant toute son exécution que les mêmes
données : celles validées avant le début de l’exécution de cette
transaction avec les modifications qu’elle a elle-même effectuées sur
ces données
• Les autres transactions ne peuvent pas voir les modifications
effectuées par cette transaction avant sa validation
• Une transaction qui veut effectuer une lecture d’une donnée ne sera
jamais bloquée par une transaction autorisée à effectuer une
modification de cette donnée
• Une transaction qui veut effectuer une modification d’une donnée
ne sera jamais bloquée par une transaction autorisée à effectuer une
lecture de cette donnée
• Une transaction qui veut effectuer une modification d’une donnée
ne sera autorisée à le faire que si la dernière modification validée de
cette donnée a eu lieu avant le début de la transaction. L’erreur
« CANNOT SERIALIZE ACCESS FOR THIS TRANSACTION »
est provoquée lorsque la dernière modification validée de cette
donnée a eu lieu après le début de cette transaction, pour signifier au
développeur que l’opération de modification a été refusée par qu’elle
ne permet pas d’assurer la sérialisation du plan d’exécution
• Une transaction qui veut effectuer une modification d’une donnée
sera bloquée si une autre transaction a déjà été autorisée à effectuer
une modification de la même donnée et ne sera autorisée à
poursuivre que si cette transaction concurrente avorte. Si la
transaction concurrente se termine sans avorter, cela provoque
également l’erreur « CANNOT SERIALIZE ACCESS FOR THIS
TRANSACTION ».

Ce comportement est garanti grâce à l’application des règles ci-après :


• Lors de la validation d’une transaction, les données modifiées par
celle-ci sont estampillées par le SGBD avec la valeur courante de
l’identifiant de modification (« System Change Number (SCN) »),

566
SGBD relationnels – Tome 1, État de l’art

afin d’assigner la même date de validation aux nouvelles valeurs de


ces données
• Lorsqu’une transaction est autorisée à modifier une donnée (ordres
« INSERT », « UPDATE », « DELETE »), le SGBD copie l’image-
avant validée estampillée de la donnée dans le « ROLLBACK
SEGMENT » ou dans le « UNDO TABLESPACE », avant de
modifier cette donnée dans la base de données :
o Avec le niveau d’isolation « READ COMMITED », une
autorisation ne doit pas avoir été accordée à une autre
transaction pour modifier cette donnée
o Avec le niveau d’isolation « SERIALIZABLE », l’estampille
de la donnée concernée doit en outre être plus petite que
l’estampille attribuée à cette transaction à son démarrage, ce
qui veut dire que la validation de la donnée concernée doit
avoir eu lieu avant le début de cette transaction
• Lorsqu’une transaction autre que celle qui est à l’origine de la
modification veut lire une donnée modifiée, elle lit la copie validée
la moins ancienne de cette donnée, dont l’estampille est plus petite
que sa propre estampille attribuée au début de l’exécution de la
requête (si le niveau d’isolation est « READ COMMITED ») ou de
la transaction (si le niveau d’isolation est « SERIALIZABLE »), c’est-
à-dire, la copie validée le plus récemment avant le début de la
requête (ou respectivement de la transaction).

Exemple 9.5.2.i : Ce qui suit schématise des items estampillés de la base de


données et leurs copies estampillées dans le « ROLLBACK SEGMENT ».
L’estampille de chaque item et de chaque copie correspond à la date de
validation de sa valeur. Par exemple, le dernier item de la base de données
a comme estampille 48 parce que sa valeur a été validée à cette date. Cet
item a deux copies dans le « ROLLBACK SEGMENT » dont la valeur de
l’une a été validée à la date 47 et la valeur de l’autre à la date 40.

567
Joachim TANKOANO

42
42
44 42
40
48 40
37
47 37
40
48 47 40

Items dans la base de Copies des items dans le


données ROLLBACK SEGMENT

Si le niveau d’isolation est « READ COMMITED » et si la date de


démarrage d’une requête est 45, les valeurs que lira cette requête pendant
son exécution sont celles des items de la base de données et de leurs copies
qui ne sont pas grisées. Ces valeurs correspondent aux valeurs les plus
récentes qui existaient avant le début de l’exécution de la requête.

De même, si le niveau d’isolation est « SERIALIZABLE » et si la date de


démarrage d’une transaction est 45, les valeurs que lira cette transaction
pendant son exécution sont aussi celles des items de la base de données et
de leurs copies qui ne sont pas grisées, c’est-à-dire, les valeurs les plus
récentes qui existaient avant le début de l’exécution de cette transaction. Par
ailleurs, cette transaction ne sera pas autorisée à modifier les items de la
base de données validés après son démarrage, à savoir, les trois items qui
ont comme estampilles 47 et 48.

On peut noter que tel qu’expliqué, le mécanisme de lecture cohérence est


en soi un mécanisme optimiste de gestion de la concurrence basé sur les
estampilles, qui utilise les verrous pour provoquer des mises en attente.

c) Les types de verrous retenus par Oracle

Pour éviter les interférences indésirables entre les transactions


concurrentes, le SGBD ORACLE utilise implicitement, c’est-à-dire sans

568
SGBD relationnels – Tome 1, État de l’art

intervention du développeur, des verrous hiérarchiques comprenant des


verrous exclusifs, partagés et d’intention, en appliquant le protocole de
verrouillage en deux phases.

Le développeur ne peut utiliser explicitement ces verrous que sur des


tables.

Sur les lignes, le SGBD Oracle utilise implicitement le verrou exclusif « X »


pour l’exécution des ordres « INSERT », « UPDATE », « DELETE » et
« SELECT …. FOR UPDATE OF … », mais exécute les ordres « SELECT »
sans poser le verrou partagé « S », les lectures cohérentes n’étant jamais
bloquées, quel que soit la situation.

Sur les tables, le SGBD Oracle utilise :


• Le verrou exclusif « X » pour l’exécution des ordres du langage de
définition des données et de l’ordre « LOCK TABLE <Table> IN
EXCLUSIVE MODE ; »
• Le verrou partagé « S », pour l’exécution de l’ordre « LOCK TABLE
<Table> IN SHARE MODE ; »
• Les verrous d’intention (« RS », « RX », « SRX ») pour l’exécution
des ordres « INSERT », « UPDATE », « DELETE » et « SELECT ….
FOR UPDATE OF … » qui accèdent aux lignes de ces tables et pour
l’exécution des ordres ci-après :
o « LOCK TABLE Table IN ROW SHARE MODE » (« RS »)
o « LOCK TABLE Table IN ROW EXCLUSIVE MODE »
(« RX »)
o « LOCK TABLE Table IN SHARE EXCLUSIVE MODE »
(SRX).

L’application du protocole de verrouillage en deux phases a comme


conséquence que les verrous acquis par une transaction ne sont libérés par
le SGBD qu’à la fin de l’exécution de cette transaction.

Le tableau qui suit fait la synthèse des verrous requis pour l’exécution des
différents ordres SQL de manipulation des données.

569
Joachim TANKOANO

Ordre Verrous Verrous


de ligne de table
SELECT ... FROM Table
SELECT ... FROM Table ... FOR UPDATE OF ... X RS
INSERT INTO Table ... X RX
UPDATE Table ... X RX
DELETE FROM Table ... X RX
LOCK TABLE Table IN SHARE MODE S
LOCK TABLE Table IN EXCLUSIVE MODE X
LOCK TABLE Table IN ROW SHARE MODE RS
LOCK TABLE Table IN ROW EXCLUSIVE MODE RX
LOCK TABLE Table IN SHARE ROW EXCLUSIVE MODE SRX

La compatibilité de ces verrous est explicitée dans le tableau qui suit :

Verrou Verrous compatibles ?


Ordre
de S X RS RX SRX
table
SELECT ... FROM Table
SELECT ... FROM Table ... RS Oui* Non Oui* Oui* Oui*
FOR UPDATE OF ...
INSERT INTO Table ... RX Non Non Oui Oui Non
UPDATE Table ... RX Non Non Oui* Oui* Non
DELETE FROM Table ... RX Non Non Oui* Oui* Non
LOCK TABLE Table IN SHARE MODE S Oui Non Oui Non Non
LOCK TABLE Table IN EXCLUSIVE X Non Non Non Non Non
MODE
LOCK TABLE Table RS Oui Non Oui Oui Oui
IN ROW SHARE MODE
LOCK TABLE Table IN ROW RX Non Non Oui Oui Non
EXCLUSIVE MODE
LOCK TABLE Table SRX Non Non Oui Non Non
IN SHARE ROW EXCLUSIVE MODE
N.B. : *  S’il n’existe pas de conflit au niveau lignes avec d’autres transactions
et si le niveau d’isolation le permet

Exemple 9.5.2.ii : Considérons une table « Compte (Numéro, Titulaire,


Solde) » contenant deux comptes « x » et « y » du titulaire « Pierre ».
Supposons les trois transactions « T1 », « T2 » et « T3 » définies comme
suit :
T1 : R (Comptes), COMMIT /* lit tous les comptes
T2 : R (x), W(x), R(y), W(y), COMMIT : /* transfert compte x vers compte y de Pierre
T3 : R(Comptes), W(x), W(y), COMMIT : /* crédite les comptes de Pierre des intérêts de

570
SGBD relationnels – Tome 1, État de l’art

l’année

Supposons que l’exécution simultanée de ces trois transactions a généré la


séquence d’opérations ci-après :
T3:R(Comptes), T1:R(Comptes), T3:W(x), T2:R(x), T2:W(x), T1:COMMIT, T3:W(y),
T2:R(y), T3:COMMIT, T2:W(y), T2:COMMIT.

Si le gestionnaire des verrous qui reçoit cette séquence d’opérations


applique le verrouillage ternaire hiérarchique en deux phases (voir
paragraphe 9.3.3.e), il traitera ces opérations comme indiqué dans le tableau
suivant :
Verrous Verrous Verrous
Opérations Décisions du
Comptes compte x compte y
gestionnaire des
verrous
1 T3:R(Comptes) <SIX, T3> Accorder verrou SIX
sur Comptes
2 T1:R(Comptes) <SIX, T3> Refuser verrou S sur
Comptes car en conflit
avec SIX
Mettre T1 en attente de
T3
3 T3:W(x) <SIX, T3> <X, T3> Accorder verrou IX sur
<IX, T3> Comptes et verrou X
sur compte x
4 T2:R(x) <SIX, T3> <X, T3> Refuser verrou IS sur
<IX, T3> Comptes et S sur
compte x car en conflit
avec X sur compte x
Mettre T2 en attente de
T3
5 T3:W(y) <SIX, T3> <X, T3> <X, T3> Accorder verrou IX sur
<IX, T3> Comptes et verrou X
sur compte y
6 T3:COMMIT <S, T1> <S, T2> Lever SIX et IX sur
T1:R(Comptes) <IS, T2> Comptes et X sur
T2:R(x) compte x et compte y
Débloquer T1 et
accorder S sur
Comptes
Débloquer T2 et

571
Joachim TANKOANO

accorder verrou IS sur


Comptes et verrou S
sur compte x
7 T2:W(x) <S, T1> <S, T2> Refuser verrou IX sur
<IS, T2> Comptes car en conflit
avec S
Mettre T2 en attente de
T1
8 T1:COMMIT <IS, T2> <S, T2> Lever S sur Comptes
T2:W(x) <IX, T2> <X, T2> Débloquer T2 et
accorder verrou IX sur
Comptes et verrou X
sur compte x
9 T2:R(y) <IS, T2> <S, T2> <S, T2> Accorder verrou IS sur
<IX, T2> <X, T2> Comptes et verrou S
sur compte y
10 T2:W(y) <IS, T2> <S, T2> <S, T2> Accorder verrou IX sur
<IX, T2> <X, T2> <X, T2> Comptes et verrou X
sur compte y
11 T2:COMMIT Lever IS et IX sur
Comptes et S et X sur
compte x et compte y

L’ordre reconstruit par le gestionnaire des verrous pour l’exécution de cette


séquence d’opérations sera donc :
T3:R(Comptes), T3:W(x), T3:W(y), T3:COMMIT, T1:R(Comptes), T2:R(x),
T1:COMMIT, T2:W(x), T2:R(y), T2:W(y), T2:COMMIT.

Cet ordre correspond presqu’à celui du plan sériel « T3, T1, T2 ».

En revanche, si le mécanisme de la lecture cohérente est utilisé avec le


niveau d’isolation « SERIALIZABLE », cette séquence d’opérations sera
traitée par le gestionnaire des verrous comme indiqué dans le tableau
suivant :

Verrous Compte x Compte y


Opérations Décisions du
Comptes Verrous Copies Verrous Copies
gestionnaire des
verrous
1 T3:R(Comptes) <RS, T3> Équivalent à SELECT
FOR UPDATE :
Accorder verrou RS
sur Comptes
2 T1:R(Comptes) <RS, T3> Accès sans pose de

572
SGBD relationnels – Tome 1, État de l’art

verrou
3 T3:W(x) <RS, T3> <X, T3> x1 Accorder verrou X
sur compte x
Créer copie x1 de
compte x
4 T2:R(x) <RS, T3> <X, T3> x1 Équivalent à
T2:W(x) UPDATE :
Refuser verrou RX
sur Comptes et
verrou X sur compte
x car en conflit avec X
sur compte x
Mettre T2 en attente
de T3
5 T1:COMMIT <RS, T3> <X, T3> x1 RAS
6 T3:W(y) <RS, T3> <X, T3> x1 <X, T3> y1 Accorder verrou X
sur compte y
Créer copie y1 de
compte y
7 T3:COMMIT x1 y1 Lever RS sur
T2 :ROLLBACK Comptes et X sur
compte x et compte y
Générer l’erreur
« CANNOT
SERIALIZE ACCESS
FOR THIS
TRANSACTION »
car T2 ne peut plus
continuer
8 T2:R(x) <RX, T2> <X, T2> x1, x2 y1 Équivalent à
T2:W(x) UPDATE :
Accorder verrou RX
sur Comptes et
verrou X sur compte
x
Créer copie x2 de
compte x
9 T2:R(y) <RX, T2> <X, T2> x1, x2 <X, T2> y1, y2 Équivalent à
T2:W(y) UPDATE :
Accorder verrou RX
sur Comptes et
verrou X sur compte
y
Créer copie y2 de
compte y
10 T2:COMMIT x1, x2 y1, y2 Lever RX sur
Comptes, X sur
compte x et compte y

L’ordre reconstruit par l’ordonnanceur pour l’exécution de la séquence


d’opérations initiale sera dans ce cas :

573
Joachim TANKOANO

T3:R(Comptes), T1:R(Comptes), T3:W(x), T1:COMMIT, T3:W(y), T3:COMMIT,


T2:R(x), T2:W(x), T2:R(y), T2:W(y), T2:COMMIT.

Comme on peut le constater, selon les circonstances, le mécanisme de la


lecture cohérente permet dans cet exemple d’obtenir un parallélisme parfait
entre « T1 » et « T2 » ou entre « T1 » et « T3 » mais pas entre « T2 » et « T3 ».

9.5.3. La gestion d’une reprise après un incident sous Oracle

Les principaux moyens que le SGBD Oracle offre pour la gestion d’une
reprise après la survenue d’un incident sont :
• Le fichier de contrôle
• Le journal des transactions
• Les points de sauvegarde (ou de reprise)
• Les sauvegardes de la base de données.

Ce qui suit présente ces principaux moyens et les principaux types de


reprises supportés par le SGBD Oracle.

a) Le fichier de contrôle

Le fichier de contrôle est un fichier binaire, indispensable pour le


démarrage et le bon fonctionnement d’une base de données Oracle. Il
contient entre autres :
• Le nom et l’emplacement des fichiers de données et des fichiers qui
jouent le rôle de journal des transactions
• L’historique du journal des transactions
• Les informations relatives à l’archivage du journal des transactions
• Les informations relatives au dernier point de reprise
• Les sauvegardes des zones tampons effectuées lors du dernier point
de reprise
• Les informations relatives aux sauvegardes de la base de données.

574
SGBD relationnels – Tome 1, État de l’art

Le fichier de contrôle existe en plusieurs copies identiques dont le nombre


et l’emplacement peuvent être fixés par l’administrateur de la base de
données dans le but de réduire les risques de le perdre accidentellement.

b) Le journal des transactions

Le journal des transactions du SGBD Oracle est constitué de plusieurs


fichiers et d’une zone tampon :
• Le fichier « REDO LOG », constitué de plusieurs copies identiques
• La zone tampon du fichier « REDO LOG » appelée « REDO LOG
BUFFER »
• Les fichiers archives du fichier « REDO LOG » appelés
« ARCHIVED LOG FILES »
• Les « ROLLBACK SEGMENTS » ou les « UNDO TABLESPACES »
(depuis la version 9).

Le fichier « REDO LOG » d’une base de données existe en copies multiples


identiques. Ce fichier sert à l’enregistrement de toutes les images-avant et
les images-après des modifications apportées aux blocs de la base des
données, concernant les tables, les index et les « ROLLBACK
SEGMENTS ». Ces images-avant et images-après sont enregistrées,
d’abord en mémoire centrale dans le « REDO LOG BUFFER », et
transférées ensuite sur le disque, dans le fichier « REDO LOG ». Le fichier
« REDO LOG » peut être archivé à son tour dans des fichiers « ARCHIVED
LOG ».

Le transfert des blocs du « REDO LOG BUFFER » vers le fichier « REDO


LOG », a lieu :
• Lors de chaque validation de transaction
• Lorsque cette zone tampon est sur le point de se remplir ou selon
une certaine périodicité
• Avant le transfert sur le disque des blocs modifiés de la zone tampon
de la base de données.

575
Joachim TANKOANO

À tout moment, le fichier « REDO LOG » contient ainsi les images-avant et


les images-après relatives à toutes les modifications effectuées dans la base
de données par chaque transaction validée, mais peut aussi contenir ces
informations pour des transactions non encore validées. Ces informations
sont utilisées lors des reprises à chaud et des reprises à froid.

Une base de données doit avoir au moins deux groupes de fichiers copies
du fichier « REDO LOG ». Chaque groupe est constitué de fichiers
identiques pouvant se trouver sur des disques différents, ceci afin de
réduire les risques d’une perte accidentelle du fichier « REDO LOG ». Le
remplissage de ces groupes de fichiers s’effectue par rotation. Lorsque les
fichiers du groupe courant sont pleins, l’écriture dans le fichier « REDO
LOG » se poursuit avec les fichiers du groupe suivant. Une base de données
peut opérer soit en mode « ARCHIVELOG », soit en mode
« NOARCHIVELOG ». Le mode « ARCHIVELOG » amène, lors du
passage d’un groupe de fichiers à un autre, à archiver le contenu du
nouveau groupe avant de l’écraser. Quant au mode
« NOARCHIVELOG », il amène, lors du passage d’un groupe de fichiers à
un autre, à écraser le contenu du nouveau groupe, sans l’archiver.

Les « ROLLBACK SEGMENTS » servent quant à eux à la mémorisation


des images-avant multi-versions estampillées, créées lors des modifications
de données effectuées dans la base de données. Ces images-avant multi-
versions estampillées sont utilisées pour effectuer des reprises annulations
de transactions et des lectures cohérentes (voir les paragraphes 9.5.1 et
9.5.2.b).

Depuis la version 9 d’ORACLE, les « ROLLBACKS SEGMENTS » sont


gérés automatiquement dans des « UNDO TABLESPACES ».

c) Les points de sauvegarde (ou de reprise)

Le rôle du processus d’arrière-plan « CKPT » du SGBD Oracle (voir les


paragraphes 7.7.4 et 9.4.1.b) est de créer des points de reprise à intervalle de
temps régulier, d’une part en synchronisant les blocs modifiés des zones
tampons et les blocs qui les correspondent sur le disque et d’autre part, en
sauvegardant le contenu de ces zones tampons dans le fichier de contrôle.

576
SGBD relationnels – Tome 1, État de l’art

Ceci permet, après un incident ayant conduit à la perte du contenu de la


mémoire centrale, de reconstituer ces zones tampons dans leur dernier état
validé avant l’incident. Cette reconstitution se fait comme décrit dans le
paragraphe e) ci-dessous.

d) Les sauvegardes de la base de données

Le SGBD Oracle distingue deux types de sauvegardes d’une base de


données :
• Les sauvegardes consistantes, effectuées après un arrêt propre de la
base de données, en exécutant l’ordre « SHUTDOWN » avec
l’option « NORMAL », « IMMEDIATE » ou
« TRANSACTIONAL », ayant permis, d’une part à toutes les
transactions en cours de se terminer et d’autre part, d’affecter le
même identifiant de changement aux fichiers de données et au
fichier de contrôle sauvegardés
• Les sauvegardes inconsistantes, effectuées pendant que la base de
données est en fonctionnement ou de façon incrémentale, à des
moments différents, où les fichiers de données et le fichier de
contrôle n’ont pas tous le même identifiant de changement.

Une sauvegarde est dite :


• Complète, si les fichiers de données, le fichier « REDO LOG » et le
fichier de contrôle ont tous été sauvegardés
• Partielle, si la sauvegarde concerne, soient quelques tablespaces,
soient quelques fichiers de données, soit le fichier de contrôle, soit le
fichier « REDO LOG » ou une combinaison de ces éléments.

Les sauvegardes peuvent se faire :


• À l’aide de l’outil « RMAN (Recovery MANager »)
• À l’aide d’un simple utilitaire de copie du système d’exploitation.

e) Les principaux types de reprises

En plus de la reprise annulation de transaction réalisable à l’aide de l’ordre

577
Joachim TANKOANO

SQL « ROLLBACK » (voir les paragraphes 9.4.2.a et 9.5.1), le SGBD Oracle


distingue principalement deux autres types de reprise :
• La reprise après un incident sur une instance ou après un crash,
correspondant à une reprise à chaud (voir le paragraphe 9.4.2.b)
• La reprise après un incident sur un média, correspondant à une
reprise à froid (voir le paragraphe 9.4.2.c).

Pour ce qui concerne la reprise à chaud, deux cas sont possibles :


• Le cas où l’incident s’est traduit par l’arrêt d’une instance et la perte
des tampons associés. Dans ce cas, la reprise est effectuée de façon
automatique par une des instances ayant survécu
• Le cas où l’incident s’est traduit par l’arrêt de toutes les instances et
la perte de toutes les zones tampons. Dans ce cas, la reprise s’effectue
automatiquement au redémarrage de la base de données.

Dans ces deux cas, la reprise vise la restauration des zones tampons perdues
dans leur dernier état validé avant l’incident. Elle s’effectue à l’aide de leurs
sauvegardes effectuées dans le fichier de contrôle lors du dernier point de
reprise et du fichier « REDO LOG » courant. Elle se déroule comme suit en
deux phases :
• Au cours de la 1ère phase, appelée « CACHE RECOVERY », les
images-après du fichier « REDO LOG » sont utilisées pour
réappliquer les modifications apportées aux bocs de données,
d’index et des « ROLLBACK SEGMENTS », de manière à restaurer
les zones tampons perdues dans leur dernier état validé avant
l’incident. A l’issue de cette phase, l’état de ces zones tampons doit
refléter les modifications validées avant l’incident mais peut aussi
refléter des modifications non validées
• Au cours de la 2ème phase, appelée « TRANSACTIONAL
RECOVERY », les modifications des transactions non validées sont
annulées en utilisant les images-avant multi-versions estampillées
des « ROLLBACK SEGMENTS » reconstitués.

Pour ce qui concerne la reprise à froid, elle se déroule en trois phases à l’aide
d’une sauvegarde complète de la base de données et d’un fichier « REDO

578
SGBD relationnels – Tome 1, État de l’art

LOG » contenant les informations relatives aux actions des transactions


exécutées après cette sauvegarde :
• La 1ère phase concerne la restauration, à partir de la sauvegarde, de
la base de données telle qu’elle était au moment de cette sauvegarde
• Au cours de la 2ème phase, appelée « CACHE RECOVERY », les
images-après du fichier « REDO LOG » sont utilisées pour
réappliquer les modifications apportées aux bocs de données,
d’index et des « ROLLBACK SEGMENTS », depuis la dernière
sauvegarde, de manière à restaurer les zones tampons et la base de
données perdues, dans leur dernier état validé avant l’incident. À
l’issue de cette phase, l’état de ces zones tampons et de la base de
données doit refléter les modifications validées avant l’incident mais
peut aussi refléter des modifications non validées enregistrées ou
non sur le disque
• Au cours de la 3ème phase, appelée « TRANSACTIONAL
RECOVERY », les images-avant des « ROLLBACK SEGMENTS »
reconstitués sont utilisées pour annuler les modifications des
transactions non validées au moment de l’incident.

9.6. Exercice
Rédigez par groupe de deux ou trois, en s’appuyant sur une revue de
documents techniques de référence, un rapport de présentation de la
gestion des accès concurrents et des reprises après incidents par le SGBD
MySQL et par le SGBD PostgreSQL.

Pour chacun de ces deux SGBD, ce rapport doit faire ressortir les
possibilités offertes pour :
• La démarcation des transactions à l’intérieur d’une session
utilisateur
• La gestion de l’exécution concurrente de plusieurs transactions
• La gestion des reprises après incident.

579
Joachim TANKOANO

BIBLIOGRAPHIE
Barghouti N. S. & Kaiser G. E. : Concurrency Control in Advance Database
Applications - ACM Computing Survey, vol. 23, n° 3, p. 270-317, Sept.
1991

Berenson H., Bernstein P., Gray J., Melton J., O’Neil E., & O’Neil P. : A
Critique of ANSI SQL Isolation Levels - Proc. of the ACM SIGMOD Conf.
on Management of Data (1995), pages 1–10.

Bernstein P. A. & Goodman N. : Concurrency Control in Distributed Database


Systems - ACM Computing Survey, Volume 13, Number 2 (1981),
pages 185–221.

Bernstein P.A. & Hadzilacos V., Goodman N. : Concurrency Control and


Recovery in Database Systems - Addison-Weley, 1987.

Bernstein P. A. & Newcomer E. : Principles of Transaction Processing -


Morgan Kaufmann (1997).

Besancenot J., Cart M., Ferrié J., Guerraoui R., Pucheral Ph. & Traverson
B. : Les Systèmes Transactionnels : Concepts, Normes et Produits - Ed.
Hermès, Paris, 1997.

Chris J. Date : Introduction aux bases de données - 8è édition - Vuibert, Paris,


2004

Eswaran K. P., Gray J. N., Lorie & Traiger I. L. : The Notions of Consistency
and Predicate Locks in a Database System - Communications of the ACM,
Volume 19, Number 11 (1976), pages 624–633.

Georges Gardarin : Bases de données, 5e tirage 2003 - EYROLLES

Gray J.N. Lorie R. A. & Putzolu G. R. : Granularity of locks in a shared data


base – Proc. Int. Conf. on Very Large Data Bases, Framingham, Mass,
Sept 1975

Gray J.N. & Reuter A. : Transaction Processing : Concepts and Techniques -


Morgan Kaufman Ed., 1993.

Holt R.C. : Some Deadlock Properties of Computer Systems - ACM Computing

580
SGBD relationnels – Tome 1, État de l’art

Surveys, vol. 4, n° 3, p. 179-196, Sept. 1972.

Kung H.T. & Robinson J.T. : On Optimistic Method for Concurrency Control
- ACM Transaction on Database Systems (TODS), vol. 6, n° 2, p. 213-
226, Juin 1981.

MIRANDA S. & BUSTA J-M. : L'art des bases de données, Tome 2, Les bases
de données relationnelles - 2è édition - Eyrolles, 1990

ORACLE : Oracle® Database Concepts 12c Release 1 (12.1) - July 2017

Papadimitriou C.H. : Serializability of concurrent database updates -J. ACM,


Vol. 26, NO 4, October 1979

Silberschatz A., Korth H.F. & Sudarshan S. : Database system concepts, sixth
edition - McGraw-Hill, 2011

Verhofstad J.S.M : Recovery Techniques for database Systems, ACM Computing


Surveys - vol. 10, n° 2, p. 167-195, Juin 1978.

581

View publication stats

Vous aimerez peut-être aussi