Vous êtes sur la page 1sur 423

ADMINISTREZ VOS BASES DE

DONNES AVEC MYSQL

Auteur externe

29 octobre 2015
Table des matires

1 Introduction 9
1.1 Quelques exemples dapplications . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2 Points abords dans ce tutoriel . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2 MySQL et les bases du langage SQL 11


2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1.1 Concepts de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1.2 Prsentation succincte de MySQL . . . . . . . . . . . . . . . . . . . . . 14
2.1.3 et de ses concurrents . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1.4 Organisation dune base de donnes . . . . . . . . . . . . . . . . . . . . . 16
2.2 Installation de MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.2.1 Avant-propos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.2.2 Installation du logiciel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.2.3 Connexion MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.2.4 Syntaxe SQL et premires commandes . . . . . . . . . . . . . . . . . . . . 25
2.2.5 Encodage, jeux de caractres et interclassement . . . . . . . . . . . . . . . 28
2.3 Les types de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.1 Avertissement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.2 Types numriques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.3.3 Types alphanumriques . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.3.4 Types temporels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.4 Cration dune base de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.4.1 Avant-propos : conseils et conventions . . . . . . . . . . . . . . . . . . . 40
2.4.2 Cration et suppression dune base de donnes . . . . . . . . . . . . . . . 42
2.5 Cration de tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
2.5.1 Dfinition des colonnes . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.5.2 Introduction aux cls primaires . . . . . . . . . . . . . . . . . . . . . . . 45
2.5.3 Les moteurs de tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.5.4 Syntaxe de CREATE TABLE . . . . . . . . . . . . . . . . . . . . . . . . . . 47
2.5.5 Suppression dune table . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
2.6 Modification dune table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.6.1 Syntaxe de la requte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.6.2 Ajout et suppression dune colonne . . . . . . . . . . . . . . . . . . . . . 50
2.6.3 Modification de colonne . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
2.7 Insertion de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
2.7.1 Syntaxe de INSERT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
2.7.2 Syntaxe alternative de MySQL . . . . . . . . . . . . . . . . . . . . . . . . 54
2.7.3 Utilisation de fichiers externes . . . . . . . . . . . . . . . . . . . . . . . . 55
2.7.4 Remplissage de la base . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

3
Table des matires

2.8 Slection de donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59


2.8.1 Syntaxe de SELECT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
2.8.2 La clause WHERE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
2.8.3 Tri des donnes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.8.4 liminer les doublons . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
2.8.5 Restreindre les rsultats . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
2.9 largir les possibilits de la clause WHERE . . . . . . . . . . . . . . . . . . . . . 69
2.9.1 Recherche approximative . . . . . . . . . . . . . . . . . . . . . . . . . . 69
2.9.2 Recherche dans un intervalle . . . . . . . . . . . . . . . . . . . . . . . . . 71
2.9.3 Set de critres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
2.10 Suppression et modification de donnes . . . . . . . . . . . . . . . . . . . . . . 72
2.10.1 Sauvegarde dune base de donnes . . . . . . . . . . . . . . . . . . . . . . 72
2.10.2 Suppression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
2.10.3 Modification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

3 Index, jointures et sous-requtes 77


3.1 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.1.1 Quest-ce quun index ? . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.1.2 Les diffrents types dindex . . . . . . . . . . . . . . . . . . . . . . . . . 83
3.1.3 Cration et suppression des index . . . . . . . . . . . . . . . . . . . . . . 84
3.1.4 Recherches avec FULLTEXT . . . . . . . . . . . . . . . . . . . . . . . . . 88
3.2 Cls primaires et trangres . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
3.2.1 Cls primaires, le retour . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
3.2.2 Cls trangres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
3.2.3 Modification de notre base . . . . . . . . . . . . . . . . . . . . . . . . . . 102
3.3 Jointures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
3.3.1 Principe des jointures et notion dalias . . . . . . . . . . . . . . . . . . . . 106
3.3.2 Jointure interne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
3.3.3 Jointure externe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
3.3.4 Syntaxes alternatives . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
3.3.5 Exemples dapplication et exercices . . . . . . . . . . . . . . . . . . . . . 117
3.4 Sous-requtes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
3.4.1 Sous-requtes dans le FROM . . . . . . . . . . . . . . . . . . . . . . . . . 121
3.4.2 Sous-requtes dans les conditions . . . . . . . . . . . . . . . . . . . . . . 123
3.4.3 Sous-requtes corrles . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
3.5 Jointures et sous-requtes : modification de donnes . . . . . . . . . . . . . . . . 130
3.5.1 Insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
3.5.2 Modification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
3.5.3 Suppression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
3.6 Union de plusieurs requtes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
3.6.1 Syntaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
3.6.2 UNION ALL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
3.6.3 LIMIT et ORDER BY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
3.7 Options des cls trangres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
3.7.1 Option sur suppression des cls trangres . . . . . . . . . . . . . . . . . . 143
3.7.2 Option sur modification des cls trangres . . . . . . . . . . . . . . . . . 145
3.7.3 Utilisation de ces options dans notre base . . . . . . . . . . . . . . . . . . 147

4
Table des matires

3.8 Violation de contrainte dunicit . . . . . . . . . . . . . . . . . . . . . . . . . . 148


3.8.1 Ignorer les erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
3.8.2 Remplacer lancienne ligne . . . . . . . . . . . . . . . . . . . . . . . . . 150
3.8.3 Modifier lancienne ligne . . . . . . . . . . . . . . . . . . . . . . . . . . . 152

4 Fonctions : nombres, chanes et agrgats 155


4.1 Rappels et introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
4.1.1 Rappels et manipulation simple de nombres . . . . . . . . . . . . . . . . . 157
4.1.2 Dfinition dune fonction . . . . . . . . . . . . . . . . . . . . . . . . . . 160
4.1.3 Quelques fonctions gnrales . . . . . . . . . . . . . . . . . . . . . . . . 162
4.2 Fonctions scalaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
4.2.1 Manipulation de nombres . . . . . . . . . . . . . . . . . . . . . . . . . . 166
4.2.2 Manipulation de chanes de caractres . . . . . . . . . . . . . . . . . . . . 169
4.2.3 Exemples dapplication et exercices . . . . . . . . . . . . . . . . . . . . . 175
4.3 Fonctions dagrgation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
4.3.1 Fonctions statistiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
4.3.2 Concatnation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
4.4 Regroupement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
4.4.1 Regroupement sur un critre . . . . . . . . . . . . . . . . . . . . . . . . . 180
4.4.2 Regroupement sur plusieurs critres . . . . . . . . . . . . . . . . . . . . . 185
4.4.3 Super-agrgats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
4.4.4 Conditions sur les fonctions dagrgation . . . . . . . . . . . . . . . . . . 190
4.5 Exercices sur les agrgats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
4.5.1 Du simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
4.5.2 Vers le complexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192

5 Fonctions : manipuler les dates 195


5.1 Obtenir la date/lheure actuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
5.1.1 Rappels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
5.1.2 Date actuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
5.1.3 Heure actuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
5.1.4 Date et heure actuelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
5.2 Formater une donne temporelle . . . . . . . . . . . . . . . . . . . . . . . . . . 202
5.2.1 Extraire une information prcise . . . . . . . . . . . . . . . . . . . . . . . 202
5.2.2 Formater une date facilement . . . . . . . . . . . . . . . . . . . . . . . . 205
5.2.3 Crer une date partir dune chane de caractres . . . . . . . . . . . . . . 209
5.3 Calculs sur les donnes temporelles . . . . . . . . . . . . . . . . . . . . . . . . 210
5.3.1 Diffrence entre deux dates/heures . . . . . . . . . . . . . . . . . . . . . 210
5.3.2 Ajout et retrait dun intervalle de temps . . . . . . . . . . . . . . . . . . . 211
5.3.3 Divers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
5.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
5.4.1 Commenons par le format . . . . . . . . . . . . . . . . . . . . . . . . . 217
5.4.2 Passons aux calculs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217
5.4.3 Et pour finir, mlangeons le tout . . . . . . . . . . . . . . . . . . . . . . . 218

6 Scuriser et automatiser ses actions 221


6.1 Transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
6.1.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
6.1.2 Syntaxe et utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224

5
Table des matires

6.1.3 Validation implicite et commandes non-annulables . . . . . . . . . . . . . 230


6.1.4 ACID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
6.2 Verrous . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
6.2.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
6.2.2 Syntaxe et utilisation : verrous de table . . . . . . . . . . . . . . . . . . . 239
6.2.3 Syntaxe et utilisation : verrous de ligne . . . . . . . . . . . . . . . . . . . 244
6.2.4 Niveaux disolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
6.3 Requtes prpares . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
6.3.1 Variables utilisateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
6.3.2 Principe et syntaxe des requtes prpares . . . . . . . . . . . . . . . . . . 259
6.3.3 Usage et utilit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
6.4 Procdures stockes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
6.4.1 Cration et utilisation dune procdure . . . . . . . . . . . . . . . . . . . . 267
6.4.2 Les paramtres dune procdure stocke . . . . . . . . . . . . . . . . . . . 270
6.4.3 Suppression dune procdure . . . . . . . . . . . . . . . . . . . . . . . . 275
6.4.4 Avantages, inconvnients et usage des procdures stockes . . . . . . . . . 275
6.5 Structurer ses instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
6.5.1 Blocs dinstructions et variables locales . . . . . . . . . . . . . . . . . . . 277
6.5.2 Structures conditionnelles . . . . . . . . . . . . . . . . . . . . . . . . . . 282
6.5.3 Boucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
6.6 Gestionnaires derreurs, curseurs et utilisation avance . . . . . . . . . . . . . . 296
6.6.1 Gestion des erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
6.6.2 Curseurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
6.6.3 Utilisation avance des blocs dinstructions . . . . . . . . . . . . . . . . . 309
6.7 Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
6.7.1 Principe et usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
6.7.2 Cration des triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
6.7.3 Suppression des triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
6.7.4 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
6.7.5 Restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328

7 Au-del des tables classiques : vues, tables temporaires et vues matrialises333


7.1 Vues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333
7.1.1 Cration dune vue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
7.1.2 Slection des donnes dune vue . . . . . . . . . . . . . . . . . . . . . . . 343
7.1.3 Modification et suppression dune vue . . . . . . . . . . . . . . . . . . . . 344
7.1.4 Utilit des vues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
7.1.5 Algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347
7.1.6 Modification des donnes dune vue . . . . . . . . . . . . . . . . . . . . . 349
7.2 Tables temporaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
7.2.1 Principe, rgles et comportement . . . . . . . . . . . . . . . . . . . . . . 355
7.2.2 Mthodes alternatives de cration des tables . . . . . . . . . . . . . . . . . 361
7.2.3 Utilit des tables temporaires . . . . . . . . . . . . . . . . . . . . . . . . 367
7.3 Vues matrialises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
7.3.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
7.3.2 Mise jour des vues matrialises . . . . . . . . . . . . . . . . . . . . . . 372
7.3.3 Gain de performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375

6
Table des matires

8 Gestion des utilisateurs et configuration du serveur 381


8.1 Gestion des utilisateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
8.1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
8.1.2 Cration, modification et suppression des utilisateurs . . . . . . . . . . . . 388
8.1.3 Les privilges - introduction . . . . . . . . . . . . . . . . . . . . . . . . . 390
8.1.4 Ajout et rvocation de privilges . . . . . . . . . . . . . . . . . . . . . . . 392
8.1.5 Privilges particuliers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
8.1.6 Options supplmentaires . . . . . . . . . . . . . . . . . . . . . . . . . . 397
8.2 Informations sur la base de donnes et les requtes . . . . . . . . . . . . . . . . . 399
8.2.1 Commandes de description . . . . . . . . . . . . . . . . . . . . . . . . . 399
8.2.2 La base de donnes information_schema . . . . . . . . . . . . . . . . . . 402
8.2.3 Droulement dune requte de slection . . . . . . . . . . . . . . . . . . . 405
8.3 Configuration et options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
8.3.1 Variables systme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
8.3.2 Modification des variables systme avec SET . . . . . . . . . . . . . . . . . 412
8.3.3 Options au dmarrage du client mysql . . . . . . . . . . . . . . . . . . . . 414
8.3.4 Options au dmarrage du serveur mysqld . . . . . . . . . . . . . . . . . . 415
8.3.5 Fichiers de configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . 417

9 Conclusion 423
9.1 Aller plus loin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423

7
1 Introduction
[[information]] | Ce tutoriel a t initialement rdig sur le Site du Zro par Taguan sous licence
CC BY-NC-SA.

Vous avez de nombreuses donnes traiter et vous voulez les organiser correctement, avec un
outil adapt ?
Les bases de donnes ont t cres pour vous !

Ce tutoriel porte sur MySQL, qui est un Systme de Gestion de Bases de Donnes Relationnelles
(abrg SGBDR). Cest--dire un logiciel qui permet de grer des bases de donnes, et donc de
grer de grosses quantits dinformations. Il utilise pour cela le langage SQL.

Il sagit dun des SGBDR les plus connus et les plus utiliss (Wikipdia et Adobe utilisent par
exemple MySQL). Et cest certainement le SGBDR le plus utilis ce jour pour raliser des sites
web dynamiques.

MySQL peut donc sutiliser seul, mais est la plupart du temps combin un autre langage de
programmation : PHP par exemple pour de nombreux sites web, mais aussi Java, Python, C++, et
beaucoup, beaucoup dautres.

->
Direntes faons dutiliser MySQL<-

1.1 Quelques exemples dapplications


Vous grez une bote de location de matriel audiovisuel, et afin de toujours savoir o vous en tes
dans votre stock, vous voudriez un systme informatique vous permettant de grer les entres
et sorties de matriel, mais aussi ventuellement les donnes de vos clients. MySQL est une des
solutions possibles pour grer tout a.

Vous voulez crer un site web dynamique en HTML/CSS/PHP avec un espace membre, un forum,
un systme de news ou mme un simple livre dor. Une base de donnes vous sera presque
indispensable.

Vous crez un super logiciel en Java qui va vous permettre de grer vos dpenses afin de ne plus
jamais tre dcouvert, ou devoir vous affamer pendant trois semaines pour pouvoir payer le
cadeau danniversaire du petit frre. Vous pouvez utiliser une base de donnes pour stocker les
dpenses dj effectues, les dpenses venir, les rentres rgulires,

9
1 Introduction

Votre tantine leveuse danimaux voudrait un logiciel simple pour grer ses bestioles, vous savez
programmer en python et lui proposez vos services dans lespoir davoir un top cadeau Nol.
Une base de donnes vous aidera retenir que Poupouche le Caniche est n le 13 dcembre 2007,
que Sami le Persan a des poils blancs et que Igor la tortue est le dernier reprsentant dune race
super rare !

1.2 Points abords dans ce tutoriel


La conception et lutilisation de bases de donnes est un vaste sujet, il a fallu faire des choix sur
les thmes aborder. Voici les comptences que ce tutoriel vise vous faire acqurir :

Cration dune base de donnes et des tables ncessaires la gestion des donnes
Gestion des relations entre les diffrentes tables dune base
Slection des donnes selon de nombreux critres
Manipulation des donnes (modification, suppression, calculs divers)
Utilisation des triggers et des procdures stockes pour automatiser certaines actions
Utilisation des vues et des tables temporaires
Gestion des utilisateurs de la base de donnes
Et plus encore

*SGBDR : Systme de Gestion de Bases de Donnes Relationnelles

10
2 MySQL et les bases du langage SQL
Dans cette partie, vous commencerez par apprendre quelques dfinitions indispensables, pour
ensuite installer MySQL sur votre ordinateur. Les commandes de base de MySQL seront alors ex-
pliques (cration de tables, insertion, slection et modification de donnes, etc.)

2.1 Introduction
Avant de pouvoir joyeusement jouer avec des donnes, il vous faut connatre quelques concepts
de base.

la fin de ce chapitre, vous devriez :

savoir ce quest un SGBD, un SGBDR, une base de donnes, et comment y sont reprsentes
les donnes ;
en connatre un peu plus sur MySQL et ses concurrents ;
savoir ce quest le langage SQL et quoi il sert.

2.1.1 Concepts de base


2.1.1.1 Base de donnes

Une base de donnes informatique est un ensemble de donnes qui ont t stockes sur un sup-
port informatique, et organises et structures de manire pouvoir facilement consulter et
modifier leur contenu.

Prenons lexemple dun site web avec un systme de news et de membres. On va utiliser une base
de donnes MySQL pour stocker toutes les donnes du site : les news (avec la date de publication,
le titre, le contenu, ventuellement lauteur,) et les membres (leurs noms, leurs emails,).
Tout ceci va constituer notre base de donnes pour le site. Mais il ne suffit pas que la base de
donnes existe. Il faut aussi pouvoir la grer, interagir avec cette base. Il faut pouvoir envoyer
des message MySQL (messages quon appellera requtes), afin de pouvoir ajouter des news,
modifier des membres, supprimer, et tout simplement afficher des lments de la base.

Une base de donnes seule ne suffit donc pas, il est ncessaire davoir galement :

un systme permettant de grer cette base ;


un langage pour transmettre des instructions la base de donnes (par lintermdiaire
du systme de gestion).

2.1.1.2 SGBD

Un Systme de Gestion de Base de Donnes (SGBD) est un logiciel (ou un ensemble de logiciels)
permettant de manipuler les donnes dune base de donnes. Manipuler, cest--dire slec-

11
2 MySQL et les bases du langage SQL

tionner et afficher des informations tires de cette base, modifier des donnes, en ajouter ou en
supprimer (ce groupe de quatre oprations tant souvent appel CRUD, pour Create, Read, Up-
date, Delete). MySQL est un systme de gestion de bases de donnes.

2.1.1.2.1 Le paradigme client - serveur La plupart des SGBD sont bass sur un modle
Client - Serveur. Cest--dire que la base de donnes se trouve sur un serveur qui ne sert qu
a, et pour interagir avec cette base de donnes, il faut utiliser un logiciel client qui va inter-
roger le serveur et transmettre la rponse que le serveur lui aura donne. Le serveur peut tre
install sur une machine diffrente du client ; cest souvent le cas lorsque les bases de donnes
sont importantes. Ce nest cependant pas obligatoire, ne sautez pas sur votre petit frre pour lui
emprunter son ordinateur. Dans ce tutoriel, nous installerons les logiciels serveur et client sur
un seul et mme ordinateur.

Par consquent, lorsque vous installez un SGBD bas sur ce modle (cest le cas de MySQL),
vous installez en ralit deux choses (au moins) : le serveur, et le client. Chaque requte (in-
sertion/modification/lecture de donnes) est faite par lintermdiaire du client. Jamais vous ne
discuterez directement avec le serveur (dailleurs, il ne comprendrait rien ce que vous diriez).

Vous avez donc besoin dun langage pour discuter avec le client, pour lui donner les requtes que
vous souhaitez effectuer. Dans le cas de MySQL, ce langage est le SQL.

2.1.1.3 SGBDR

Le R de SGBDR signifie relationnel. Un SGBDR est un SGBD qui implmente la thorie relation-
nelle. MySQL implmente la thorie relationnelle ; cest donc un SGBDR.

La thorie relationnelle dpasse le cadre de ce tutoriel, mais ne vous inquitez pas, il nest pas
ncessaire de la matriser pour tre capable dutiliser convenablement un SGBDR. Il vous suffit
de savoir que dans un SGBDR, les donnes sont contenues dans ce quon appelle des relations,
qui sont reprsentes sous forme de tables. Une relation est compose de deux parties, len-tte
et le corps. Len-tte est lui-mme compos de plusieurs attributs. Par exemple, pour la relation
Client, on peut avoir len-tte suivant :

Numro Nom Prnom Email

Quant au corps, il sagit dun ensemble de lignes (ou n-uplets) composes dautant dlments
quil y a dattributs dans le corps. Voici donc quatre lignes pour la relation Client :

Numro Nom Prnom Email

1 Jean Dupont jdupont@email.com


2 Marie Malherbe mama@email.com
3 Nicolas Jacques jacques.nicolas@email.com
4 Hadrien Piroux happi@email.com

Diffrentes oprations peuvent alors tre appliques ces relations, ce qui permet den tirer des
informations. Parmi les oprations les plus utilises, on peut citer (soient A et B deux relations) :

la slection (ou restriction) : obtenir les lignes de A rpondant certains critres ;

12
2.1 Introduction

la projection : obtenir une partie des attributs des lignes de A ;


lunion - A B : obtenir tout ce qui se trouve dans la relation A ou dans la relation B ;
lintersection - A B : obtenir tout ce qui se trouve la fois dans la relation A et dans la
relation B ;
la diffrence - A B : obtenir ce qui se trouve dans la relation A mais pas dans la relation
B;
la jointure - A B : obtenir lensemble des lignes provenant de la liaison de la relation A
et de la relation B laide dune information commune.

Un petit exemple pour illustrer la jointure : si lon veut stocker des informations sur les clients
dune socit, ainsi que les commandes passes par ces clients, on utilisera deux relations : client
et commande, la relation commande tant lie la relation client par une rfrence au client
ayant pass commande. Un petit schma clarifiera tout a !

Figure 2.1 Schma bdd relationnelle

Le client numro 3, M. Nicolas Jacques, a donc pass une commande de trois tubes de colle, tan-
dis que Mme Marie Malherbe (cliente numro 2) a pass deux commandes, pour du papier et des
ciseaux.

2.1.1.4 Le langage SQL

Le SQL (Structured Query Language) est un langage informatique qui permet dinteragir avec des
bases de donnes relationnelles. Cest le langage pour base de donnes le plus rpandu, et cest
bien sr celui utilis par MySQL. Cest donc le langage que nous allons utiliser pour dire au client
MySQL deffectuer des oprations sur la base de donnes stocke sur le serveur MySQL

Il a t cr dans les annes 1970 et cest devenu standard en 1986 (pour la norme ANSI - 1987 en
ce qui concerne la norme ISO). Il est encore rgulirement amlior.

SGBDR : Systme de Gestion de Base de Donnes Relationnel SGBD : Systme de Gestion de Base de Donnes

13
2 MySQL et les bases du langage SQL

2.1.2 Prsentation succincte de MySQL

-> <-

MySQL est donc un Systme de Gestion de Bases de Donnes Relationnelles, qui utilise le langage
SQL. Cest un des SGBDR les plus utiliss. Sa popularit est due en grande partie au fait quil
sagit dun logiciel Open Source, ce qui signifie que son code source est librement disponible et
que quiconque qui en ressent lenvie et/ou le besoin peut modifier MySQL pour lamliorer ou
ladapter ses besoins. Une version gratuite de MySQL est par consquent disponible. noter
quune version commerciale payante existe galement.

Le logo de MySQL est un dauphin, nomm Sakila suite au concours Name the dolphin (Nommez le
dauphin).

2.1.2.1 Un peu dhistoire

->
David Axmark, fondateur de MySQL<-

Le dveloppement de MySQL commence en 1994 par David Axmark et Michael Widenius. EN 1995,
la socit MySQL AB est fonde par ces deux dveloppeurs, et Allan Larsson. Cest la mme anne
que sort la premire version officielle de MySQL.

En 2008, MySQL AB est rachete par la socit Sun Microsystems, qui est elle-mme rachete par
Oracle Corporation en 2010.

On craint alors la fin de la gratuit de MySQL, tant donn quOracle Corporation dite un des
grands concurrents de MySQL : Oracle Database, qui est payant (et trs cher). Oracle a cependant
promis de continuer dvelopper MySQL et de conserver la double licence GPL (libre) et commer-
ciale jusquen 2015 au moins.

2.1.2.2 Mise en garde

MySQL est trs utilis, surtout par les dbutants. Vous pourrez faire de nombreuses choses avec ce
logiciel, et il convient tout fait pour dcouvrir la gestion de bases de donnes. Sachez cependant
que MySQL est loin dtre parfait. En effet, il ne suit pas toujours la norme officielle. Certaines
syntaxes peuvent donc tre propres MySQL et ne pas fonctionner sous dautres SGBDR. Jes-
sayerai de le signaler lorsque le cas se prsentera, mais soyez conscients de ce problme.

14
2.1 Introduction

Par ailleurs, il nimplmente pas certaines fonctionnalits avances, qui pourraient vous tre
utiles pour un projet un tant soit peu ambitieux. Enfin, il est trs permissif, et acceptera donc
des requtes qui gnreraient une erreur sous dautres SGBDR.

[GPL] : General Public Licence SGBDR : Systme de Gestion de Base de Donnes Relationnel

2.1.3 et de ses concurrents


Il existe des dizaines de SGBDR, chacun ayant ses avantages et ses inconvnients. Je prsente ici
succinctement quatre dentre eux, parmi les plus connus. Je mexcuse tout de suite auprs des
fans (et mme simples utilisateurs) des nombreux SGBDR que jai omis.

2.1.3.1 Oracle database

Figure 2.2 Logo dOracle

Oracle, dit par Oracle Corporation (qui, je rappelle, dite galement MySQL) est un SGBDR
payant. Son cot lev fait quil est principalement utilis par des entreprises.

Oracle gre trs bien de grands volumes de donnes. Il est inutile dacheter une licence oracle
pour un projet de petite taille, car les performances ne seront pas bien diffrentes de celles de
MySQL ou dun autre SGBDR. Par contre, pour des projets consquents (plusieurs centaines de Go
de donnes), Oracle sera bien plus performant.

Par ailleurs, Oracle dispose dun langage procdural trs puissant (du moins plus puissant que le
langage procdural de MySQL) : le PL/SQL.

2.1.3.2 PostgreSQL

Figure 2.3 Logo PostgreSQL

Comme MySQL, PostgreSQL est un logiciel Open Source. Il est cependant moins utilis, notam-
ment par les dbutants, car moins connu. La raison de cette mconnaissance rside sans doute en

15
2 MySQL et les bases du langage SQL

partie dans le fait que PostgreSQL a longtemps t disponible uniquement sous Unix. La premire
version Windows nest apparue qu la sortie de la version 8.0 du logiciel, en 2005.

PostgreSQL a longtemps t plus performant que MySQL, mais ces diffrences tendent diminuer.
MySQL semble tre aujourdhui quivalent PostgreSQL en terme de performances sauf pour
quelques oprations telles que linsertion de donnes et la cration dindex. Le langage procdural
utilis par PostgreSQL sappelle le PL/pgSQL.

2.1.3.3 MS Access

Figure 2.4 Logo MS Access

MS Access ou Microsoft Access est un logiciel dit par Microsoft (comme son nom lindique)
Par consquent, cest un logiciel payant qui ne fonctionne que sous Windows. Il nest pas du tout
adapt pour grer un grand volume de donnes et a beaucoup moins de fonctionnalits que les
autres SGBDR. Son avantage principal est linterface graphique intuitive qui vient avec le logiciel.

2.1.3.4 SQLite

Figure 2.5 Logo SQLite

La particularit de SQLite est de ne pas utiliser le schma client-serveur utilis par la majorit des
SGBDR. SQLite stocke toutes les donnes dans de simples fichiers. Par consquent, il ne faut pas
installer de serveur de base de donnes, ce qui nest pas toujours possible (certains hbergeurs
web ne le permettent pas).

Pour de trs petits volumes de donnes, SQLite est trs performant. Cependant, le fait que les
informations soient simplement stockes dans des fichiers rend le systme difficile scuriser
(autant au niveau des accs, quau niveau de la gestion de plusieurs utilisateurs utilisant la base
simultanment).

*SGBDR : Systme de Gestion de Base de Donnes Relationnel

2.1.4 Organisation dune base de donnes


Bon, vous savez quune base de donnes sert grer les donnes. Trs bien. Mais comment ??
Facile ! Comment organisez-vous vos donnes dans la vie relle ?? Vos papiers par exemple ?

16
2.2 Installation de MySQL

Chacun son organisation bien sr, mais je suppose que vous les classez dune manire ou dune
autre.

Toutes les factures ensemble, tous les contrats ensemble, etc. Ensuite on subdivise : les factures
dlectricit, les factures pour la voiture. Ou bien dans lautre sens : tous les papiers concernant
la voiture ensemble, puis subdivision en taxes, communication avec lassureur, avec le garagiste,

Une base de donnes, cest pareil ! On classe les informations. MySQL tant un SGBDR, je ne par-
lerai que de lorganisation des bases de donnes relationnelles.

Comme je vous lai dit prcdemment, on reprsente les donnes sous forme de tables. Une base
va donc contenir plusieurs tables (elle peut nen contenir quune bien sr, mais cest rarement le
cas). Si je reprends mon exemple prcdent, on a donc une table reprsentant des clients (donc
des personnes).

Chaque table dfinit un certain nombre de colonnes, qui sont les caractristiques de lobjet re-
prsent par la table (les attributs de len-tte dans la thorie relationnelle). On a donc ici une
colonne Nom, une colonne Prnom, une colonne Email et une colonne Numro qui nous
permettent didentifier les clients individuellement (les noms et prnoms ne suffisent pas tou-
jours).

Numro Nom Prnom Email

1 Jean Dupont jdupont@email.com


2 Marie Malherbe mama@email.com
3 Nicolas Jacques jacques.nicolas@email.com
4 Hadrien Piroux happi@email.com

Si je rcapitule, dans une base nous avons donc des tables, et dans ces tables, on a des colonnes.
Dans ces tables, vous introduisez vos donnes. Chaque donne introduite le sera sous forme de
ligne dans une table, dfinissant la valeur de chaque colonne pour cette donne.

*SGBDR : Systme de Gestion de Bases de Donnes Relationnelles

2.1.4.1 En rsum

MySQL est un Systme de Gestion de Bases de Donnes Relationnelles (SGBDR) bas sur
le modle client-serveur.
Le langage SQL est utilis pour communiquer entre le client et le serveur.
Dans une base de donnes relationnelle, les donnes sont reprsentes sous forme de
tables.

2.2 Installation de MySQL


Maintenant quon sait peu prs de quoi on parle, il est temps dinstaller MySQL sur lordinateur,
et de commencer lutiliser. Au programme de ce chapitre :

17
2 MySQL et les bases du langage SQL

Installation de MySQL
Connexion et dconnexion au client MySQL
Cration dun utilisateur
Bases de la syntaxe du langage SQL
Introduction aux jeux de caractres et aux interclassements

2.2.1 Avant-propos
Il existe plusieurs manires dutiliser MySQL. La premire, que je vais utiliser tout au long du
tutoriel, est lutilisation en ligne de commande.

2.2.1.1 Ligne de commande

Mais quest-ce donc ? o_O

Eh bien il sagit dune fentre toute simple, dans laquelle toutes les instructions sont tapes la
main. Pas de bouton, pas de zone de saisie. Juste votre clavier.

Les utilisateurs de Linux connaissent trs certainement. Pour Mac, il faut utiliser lapplication
Terminal que vous trouverez dans Applications > Utilitaires. Quant aux utilisateurs de
Windows, cest le Command Prompt que vous devez trouver (Dmarrer > Tous les pro-
grammes > Accessoires).

Figure 2.6 Commande prompt (Windows)

2.2.1.2 Interface graphique

Si lon ne veut pas utiliser la ligne de commande (il faut bien avouer que ce nest pas trs sympa-
thique cette fentre monochrome), on peut utiliser une interface graphique, qui permet dexcu-
ter pas mal de choses simples de manire intuitive sur une base de donnes.

18
2.2 Installation de MySQL

Comme interface graphique pour MySQL, on peut citer MySQL Workbench, PhpMyAdmin (sou-
vent utilis pour crer un site web en combinant MySQL et PHP) ou MySQL Front par exemple.

2.2.1.3 Pourquoi utiliser la ligne de commande ?

Cest vrai a, pourquoi ? Si cest plus simple et plus convivial avec une interface graphique ? :euh :

Deux raisons :

primo, parce que je veux que vous matrisiez vraiment les commandes. En effet, les inter-
faces graphiques permettent de faire pas mal de choses, mais une fois que vous serez bien
lancs, vous vous mettrez faire des choses subtiles et compliques, et il ne serait pas
tonnant quil vous soit obligatoire dcrire vous-mmes vos requtes ;
ensuite, parce quil est fort probable que vous dsiriez utiliser MySQL en combinaison avec
un autre langage de programmation (si ce nest pas votre but immdiat, a viendra pro-
bablement un jour). Or, dans du code PHP (ou Java, ou Python, etc.), on ne va pas crire
Ouvre PhpMyAdmin et clique sur le bon bouton pour que je puisse insrer une donne
dans la base. On va devoir crire en dur les requtes. Il faut donc que vous sachiez com-
ment faire.

Bien sr, si vous voulez utiliser une interface graphique, je ne peux gure vous en empcher. Mais
je vous encourage vivement commencer par utiliser la ligne de commande, ou au minimum
faire leffort de dcortiquer les requtes que vous laisserez linterface graphique construire pour
vous. Ceci afin de pouvoir les crire vous-mmes le jour o vous en aurez besoin (ce jour viendra,
je vous le prdis).

2.2.2 Installation du logiciel

Pour tlcharger MySQL, vous pouvez vous rendre sur le site suivant :

http://dev.mysql.com/downloads/mysql/#downloads

Slectionnez lOS sur lequel vous travaillez (Windows, Mac OS ou Linux).

2.2.2.1 Windows

Tlchargez MySQL avec linstalleur (MSI Installer), puis excutez le fichier tlcharg. Linstal-
leur dmarre et vous guide lors de linstallation. Lorsquil vous demande de choisir entre trois
types dinstallation, choisissez Typical. Cela installera tout ce dont nous pourrions avoir be-
soin.

Linstallation se lance. Une fois quelle est termine, cliquez sur Terminer aprs vous tre
assurs que la case lancer loutil de configuration MySQL est coche.

Dans cet outil de configuration, choisissez la configuration standard, et ltape suivante, cochez
loption Include Bin Directory in Windows PATH

On vous propose alors de dfinir un nouveau mot de passe pour lutilisateur root. Choisissez
un mot de passe et confirmez-le. Ne cochez aucune autre option cette tape. Cliquez ensuite sur
Execute pour lancer la configuration.

19
2 MySQL et les bases du langage SQL

Figure 2.7 Choix du type dinstallation

Figure 2.8 Lancement outil de configuration

20
2.2 Installation de MySQL

Figure 2.9 Options configurations

2.2.2.2 Mac OS

Tlchargez larchive DMG qui vous convient (32 ou 64 bits), double-cliquez ensuite sur ce .dmg
pour ouvrir limage disque. Vous devriez y trouver 4 fichiers dont deux .pkg. Celui qui nous in-
tresse sappelle mysql-5.5.9-osx10.6-x86_64.pkg (les chiffres peuvent changer selon la ver-
sion de MySQL tlcharge et votre ordinateur). Ouvrez ce fichier qui est en fait linstallateur
de MySQL, et suivez les instructions.

Une fois le programme install, vous pouvez ouvrir votre terminal (pour rappel, il se trouve dans
Applications -> Utilitaires).

Tapez les commandes et excutez les instructions suivantes :

cd/usr/local/mysql
sudo./bin/mysqld_safe

Entrez votre mot de passe si ncessaire


Tapez ||Ctrl|| + ||Z||

bg

Tapez ||Ctrl|| + ||D||


Quittez le terminal

MySQL est prt tre utilis !

21
2 MySQL et les bases du langage SQL

2.2.2.2.1 Configuration Par dfaut, aucun mot de passe nest demand pour se connecter,
mme avec lutilisateur root (qui a tous les droits). Je vous propose donc de dfinir un mot de
passe pour cet utilisateur :

/usr/local/mysql/bin/mysqladmin-urootpassword<votre_mot_de_passe>

Ensuite, pour pouvoir accder directement au logiciel client depuis la console, sans devoir aller
dans le dossier o est install le client, il vous faut ajouter ce dossier votre variable denviron-
nement PATH. Pour cela, tapez la commande suivante dans le terminal :

echo'exportPATH=/usr/local/mysql/bin:$PATH'>>~/.profile

/usr/local/mysql/bin est donc le dossier dans lequel se trouve le logiciel client (plusieurs
logiciels clients en fait). Redmarrez votre terminal pour que le changement prenne effet.

2.2.2.3 Linux

2.2.2.3.1 Sous Debian ou Ubuntu Excuter la commande suivante pour installer MySQL :

sudoapt-getinstallmysql-servermysql-client

Une fois votre mot de passe introduit, MySQL va tre install.

2.2.2.3.2 Sous RedHat Excuter la commande suivante pour installer MySQL :

sudoyuminstallmysqlmysql-server

Une fois votre mot de passe introduit, MySQL va tre install.

2.2.2.3.3 Dans tous les cas, aprs installation Pensez ensuite modifier le mot de passe
de lutilisateur root (administrateur ayant tous les droits) avec la commande suivante :

sudomysqladmin-uroot-hlocalhostpassword'<votremotdepasse>'

2.2.3 Connexion MySQL


Je vous ai dit que MySQL tait bas sur un modle client - serveur, comme la plupart des SGBD.
Cela implique donc que votre base de donnes se trouve sur un serveur auquel vous navez pas
accs directement, il faut passer par un client qui fera la liaison entre vous et le serveur.

Lorsque vous installez MySQL, plusieurs choses sont donc installes sur votre ordinateur :

un serveur de base de donnes MySQL ;


plusieurs logiciels clients qui permettent dinteragir avec le serveur.

22
2.2 Installation de MySQL

2.2.3.1 Connexion au client

Parmi ces clients, celui dont nous allons parler prsent est mysql (original comme nom o_O
). Cest celui que vous utiliserez tout au long de ce cours pour vous connecter votre base de
donnes et y insrer, consulter et modifier des donnes. La commande pour lancer le client est
tout simplement son nom :

mysql

Cependant cela ne suffit pas. Il vous faut galement prciser un certain nombre de paramtres.
Le client mysql a besoin dau minimum trois paramtres :

lhte : cest--dire lendroit o est localis le serveur ;


le nom dutilisateur ;
et le mot de passe de lutilisateur.

Lhte et lutilisateur ont des valeurs par dfaut, et ne sont donc pas toujours indispensables. La
valeur par dfaut de lhte est localhost, ce qui signifie que le serveur est sur le mme ordi-
nateur que le client. Cest bien notre cas, donc nous naurons pas prciser ce paramtre. Pour
le nom dutilisateur, la valeur par dfaut dpend de votre systme. Sous Windows, lutilisateur
courant est ODBC, tandis que pour les systmes Unix (Mac et Linux), il sagit de votre nom
dutilisateur (le nom qui apparat dans linvite de commande).

Pour votre premire connexion MySQL, il faudra vous connecter avec lutilisateur root, pour
lequel vous avez normalement dfini un mot de passe (si vous ne lavez pas fait, inutile dutiliser
ce paramtre, mais ce nest pas trs scuris). Par la suite, nous crerons un nouvel utilisateur.

Pour chacun des trois paramtres, deux syntaxes sont possibles :

###########
#Hte#
###########

--hote=nom_hote

####ou

-hnom_hote

###########
#User#
###########

--user=nom_utilisateur

####ou

-unom_utilisateur

###################

23
2 MySQL et les bases du langage SQL

#Motdepasse#
###################

--password=password

####ou

-ppassword

Remarquez labsence despace entre -p et le mot de passe. Cest voulu (mais uniquement pour ce
paramtre-l), et souvent source derreurs. La commande complte pour se connecter est donc :

mysql-hlocalhost-uroot-pmotdepassetopsecret

####ou

mysql--host=localhost--user=root--password=motdepassetopsecret

####ouunmlangedesparamtrescourtsetlongssiavousamuse

mysql-hlocalhost--user=root-pmotdepassetopsecret

Jutiliserai uniquement les paramtres courts partir de maintenant. Choisissez ce qui vous
convient le mieux. Notez que pour le mot de passe, il est possible (et cest mme trs conseill)
de prciser uniquement que vous utilisez le paramtre, sans lui donner de valeur :

mysql-hlocalhost-uroot-p

Apparaissent alors dans la console les mots suivants :

Enterpassword:

Tapez donc votre mot de passe, et l, vous pouvez constater que les lettres que vous tapez ne saf-
fichent pas. Cest normal, cessez donc de martyriser votre clavier, il ny peut rien le pauvre :co-
lere : . Cela permet simplement de cacher votre mot de passe dventuels curieux qui regarde-
raient par-dessus votre paule.

Donc pour rsumer, pour me connecter mysql, je tape la commande suivante :

mysql-uroot-p

Jai omis lhte, puisque mon serveur est sur mon ordinateur. Je nai plus qu taper mon mot de
passe et je suis connect.

2.2.3.2 Dconnexion

Pour se dconnecter du client, il suffit dutiliser la commande quit ou exit.

*SGBD : Systmes de Gestion de Bases de Donnes

24
2.2 Installation de MySQL

2.2.4 Syntaxe SQL et premires commandes


Maintenant que vous savez vous connecter, vous allez enfin pouvoir discuter avec le serveur
MySQL (en langage SQL videmment). Donc, reconnectez-vous si vous tes dconnects.

Vous pouvez constater que vous tes connects grce au joli (quoiquun peu formel) message de
bienvenue, ainsi quau changement de linvite de commande. On voit maintenant mysql>.

2.2.4.1 Hello World !

Traditionnellement, lorsque lon apprend un langage informatique, la premire chose que lon
fait, cest afficher le clbre message Hello World !. Pour ne pas droger la rgle, je vous pro-
pose de taper la commande suivante (sans oublier le || ;|| la fin) :

SELECT 'Hello World !' ;

SELECT est la commande qui permet la slection de donnes, mais aussi laffichage. Vous devriez
donc voir safficher Hello World !

Hello World !

Hello World !

Comme vous le voyez, Hello World ! saffiche en ralit deux fois. Cest parce que MySQL repr-
sente les donnes sous forme de table. Il affiche donc une table avec une colonne, quil appelle
Hello World ! faute de meilleure information. Et dans cette table nous avons une ligne de don-
nes, le Hello World ! que nous avons demand.

2.2.4.2 Syntaxe

Avant daller plus loin, voici quelques rgles gnrales retenir concernant le SQL qui, comme
tout langage informatique, obit des rgles syntaxiques trs strictes.

2.2.4.2.1 Fin dune instruction Pour signifier MySQL quune instruction est termine, il
faut mettre le caractre || ;||. Tant quil ne rencontre pas ce caractre, le client MySQL pense que
vous navez pas fini dcrire votre commande et attend gentiment que vous continuiez.

Par exemple, la commande suivante devrait afficher 100. Mais tant que MySQL ne recevra pas de
|| ;||, il attendra simplement la suite.

SELECT 100

En appuyant sur la touche ||Entre|| vous passez la ligne suivante, mais la commande ne sef-
fectue pas. Remarquez au passage le changement dans linvite de commande. mysql> signifie
que vous allez entrer une commande, tandis que -> signifie que vous allez entrer la suite dune
commande commence prcdemment.

Tapez maintenant || ;|| puis appuyer sur ||Entre||. Ca y est, la commande est envoye, laffi-
chage se fait !

Ce caractre de fin dinstruction obligatoire va vous permettre :

25
2 MySQL et les bases du langage SQL

dcrire une instruction sur plusieurs lignes ;


dcrire plusieurs instructions sur une seule ligne.

2.2.4.2.2 Commentaires Les commentaires sont des parties de code qui ne sont pas interpr-
tes. Ils servent principalement vous reprer dans votre code. En SQL, les commentaires sont
introduits par |||| (deux tirets). Cependant, MySQL droge un peu la rgle SQL et accepte deux
syntaxes :

||#|| : tout ce qui suit ce caractre sera considr comme commentaire


|||| : la syntaxe normale est accepte uniquement si les deux tirets sont suivis dune es-
pace au moins

Afin de suivre au maximum la norme SQL, ce sont les |||| qui seront utiliss tout au long de ce
tutoriel.

2.2.4.2.3 Chanes de caractres Lorsque vous crivez une chane de caractres dans une
commande SQL, il faut absolument lentourer de guillemets simples (donc des apostrophes).

[[information]] | MySQL permet galement lutilisation des guillemets doubles, mais ce nest
pas le cas de la plupart des SGBDR. Histoire de ne pas prendre de mauvaises habitudes, je vous
conseille donc de nutiliser que les guillemets simples pour dlimiter vos chanes de caractres.

Exemple : la commande suivante sert afficher Bonjour petit Zeste !

SELECT 'Bonjour petit Zeste !' ;

Par ailleurs, si vous dsirez utiliser un caractre spcial dans une chane, il vous faudra lchapper
avec ||||. Par exemple, si vous entourez votre chane de caractres de guillemets simples mais
voulez utiliser un tel guillemet lintrieur de votre chane :

SELECT 'Salut l'ami' ; -- Pas bien !


SELECT 'Salut l\'ami' ; -- Bien !

Quelques autres caractres spciaux :

Caractre Explication

\n retour la ligne
\t tabulation
\ antislash (eh oui, il faut chapper le caractre dchappement)
% pourcent (vous verrez pourquoi plus tard)
_ soulign (vous verrez pourquoi plus tard aussi)

[[attention]] | Cette manire dchapper les caractres spciaux (avec ||||) est propre MySQL.
Dautres SGBDR demanderont quon leur prcise quel caractre sert lchappement ; dautres
encore demanderont de doubler le caractre spcial pour lchapper. Soyez donc prudent et
renseignez-vous si vous nutilisez pas MySQL.

Notez que pour chapper un guillemet simple (et uniquement ce caractre), vous pouvez gale-
ment lcrire deux fois. Cette faon dchapper les guillemets correspond dailleurs la norme
SQL. Je vous encourage par consquent essayer de lutiliser au maximum.

SELECT 'Salut l'ami' ; -- ne fonctionne pas !

26
2.2 Installation de MySQL

SELECT 'Salut l\'ami' ; -- fonctionne !


SELECT 'Salut l''ami' ; -- fonctionne aussi et correspond la norme !

2.2.4.3 Un peu de math

MySQL est galement dou en calcul :

SELECT (5+3)*2 ;

Pas de guillemets cette fois puisquil sagit de nombres. MySQL calcule pour nous et nous affiche :

(5+3)*2

16

MySQL est sensible la priorit des oprations, comme vous pourrez le constater en tapant cette
commande :

SELECT (5+3)*2, 5+3*2 ;

Rsultat :

(5+3)*2 5+3*2

16 11

2.2.4.4 Utilisateur

Il nest pas trs conseill de travailler en tant que root dans MySQL, moins den avoir spcifi-
quement besoin. En effet, root a tous les droits. Ce qui signifie que vous pouvez faire nimporte
quelle btise dans nimporte quelle base de donnes pendant que jai le dos tourn. Pour viter a,
nous allons crer un nouvel utilisateur, qui aura des droits trs restreints. Je lappellerai sdz,
mais libre vous de lui donner le nom que vous prfrez. Pour ceux qui sont sous Unix, notez
que si vous crez un utilisateur du mme nom que votre utilisateur Unix, vous pourrez ds lors
omettre ce paramtre lors de votre connexion mysql.

Je vous demande ici de me suivre aveuglment, car je ne vous donnerai que trs peu dexplica-
tions. En effet, la gestion des droits et des utilisateurs fera lobjet dun chapitre entier dans une
prochaine partie du cours. Tapez donc cette commande dans mysql, en remplaant sdz par le nom
dutilisateur que vous avez choisi, et mot_de_passe par le mot de passe que vous voulez lui attri-
buer :

GRANT ALL PRIVILEGES ON elevage.* TO 'sdz'@'localhost' IDENTIFIED BY 'mot_de_passe' ;

Je dcortique donc rapidement :

GRANT ALL PRIVILEGES : Cette commande permet dattribuer tous les droits (cest--
dire insertions de donnes, slections, modifications, suppressions)
ON elevage.* : dfinit les bases de donnes et les tables sur lesquelles ces droits sont
acquis. Donc ici, on donne les droits sur la base elevage (qui nexiste pas encore, mais ce
nest pas grave, nous la crerons plus tard), pour toutes les tables de cette base (grce *).

27
2 MySQL et les bases du langage SQL

TO 'sdz' : dfinit lutilisateur auquel on accorde ces droits. Si lutilisateur nexiste pas, il
est cr.
@'localhost' : dfinit partir do lutilisateur peut exercer ces droits. Dans notre cas,
localhost, donc il devra tre connect partir de cet ordinateur.
IDENTIFIED BY 'mot_de_passe' : dfinit le mot de passe de lutilisateur.

Pour vous connecter mysql avec ce nouvel utilisateur, il faut donc taper la commande suivante
(aprs stre dconnect bien sr) :

mysql-usdz-p

2.2.5 Encodage, jeux de caractres et interclassement

2.2.5.1 La table ASCII

Le processeur dun ordinateur, cest--dire le composant qui soccupe de traiter les informations,
excuter les programmes, etc., ne comprend que les instructions formules en binaire ; il ne peut
que lire une suite dlments pouvant tre dans deux tats : 0 ou 1.

Donc, tous les programmes informatiques que vous pourriez crire, toutes les instructions SQL,
tous les fichiers, sont in ne traduits en code machine, donc une longue suite de 0 et de 1, pour
pouvoir tre lus ou excuts. Chacun de ces 0 ou 1 est un bit. Ces bits sont regroups par huit pour
former un octet.

Lordinateur ne connat donc pas la notion de caractres. Il ne sait pas ce quest un A, ou un .


Il a donc fallu crer une table de conversion pour traduire les caractres de la langue courante en
une srie de bits. La table ASCII tait ne !

La table ASCII est donc une table de conversion, qui permet de traduire en code binaire 128 ca-
ractres, dont 33 caractres de contrle (sparateur de fichier, saut de page, ) et 95 caractres
affichables. Les caractres affichables sont les 26 lettres de lalphabet, en majuscules et en mi-
nuscules, les 10 chiffres arabes et toute une srie de caractres spciaux courants (||#||, || ;||,
||)||, ||<||, ).

[[question]] | De combien de bits avons-nous besoin pour stocker ces 128 caractres ?

Chaque bit peut prendre deux valeurs diffrentes. Donc avec 1 bit, je peux reprsenter 2 carac-
tres. Avec 2 bits, je reprsente 22 , donc 4 caractres. Et 128 = 27 . Jai donc besoin de 7 bits pour
pouvoir reprsenter toute ma table ASCII. Lordinateur travaillant sur des octets, on code donc la
table ASCII sur un octet, avec le 8e bit 0. 00110100 reprsente par exemple le caractre 4,
01001101 le caractre M (le premier bit tant celui de droite, le 8e celui de gauche).

2.2.5.2 Jeux de caractres

On sest ensuite rendu compte que ces 128 caractres ntaient pas suffisants. En effet, ils ne
comprennent pas les caractres accentus (, ). Ils ne comprennent pas non plus tous les
caractres cyrilliques, japonais,etc. On a alors commenc utiliser le huitime bit. Ce qui permet-
tait de reprsenter 128 caractres supplmentaires (donc 28 = 256 en tout). Mais avec 128 carac-
tres supplmentaires, on na pas de quoi reprsenter tous les caractres qui nexistent pas dans
la table ASCII. On a donc cr plusieurs jeux de caractres diffrents. Exemples :

28
2.2 Installation de MySQL

lISO 8859-1 (ou latin1) : qui permet de couvrir une bonne partie des langues dEurope occi-
dentale en ajoutant les lettres accentues aux caractres ASCII de base (, , , ,
)
lISO 8859-7 : qui permet de reprsenter les lettres grecques
lISO 8859-11 : qui contient une bonne partie des glyphes de la langue tha
lISO 8859-15 : qui est une rvision de lISO 8859-1, et qui remplace quelques caractres
peu utiliss par dautres, plus ncessaires, comme leuro ()

Ds lors, lorsque lon cre un fichier, un programme ou autre, il faut prciser quel jeu de caractre
(ou encodage) est utilis.

2.2.5.2.1 LUTF-8 Il restait un petit problme : comment faire des documents (ou autres) qui
doivent utiliser plusieurs jeux de caractres diffrents ? On a donc cr un nouveau type denco-
dage, permettant de reprsenter les caractres avec deux octets au lieu dun. 16 bits donnent 216
= 65536 possibilits. On peut donc, en utilisant deux octets, reprsenter plus de 65000 caractres.
Cet encodage sappelle lUTF-8. Le dsavantage dun tel encodage est videmment le cot en
mmoire, deux octets prenant plus de place quun. Cependant, tous les caractres ne sont pas
cods sur deux octets. Sil sagit dun caractre de base de la table ASCII, le 8e bit est 0, ce qui
indique quil nest cod que sur un octet. Par contre, lorsque le 8e bit est 1, cela indique quon a
affaire un caractre spcial et quil est cod sur deux octets. Donc, en UTF-8, un prendra
plus de place quun e (deux octets au lieu dun).

[[question]] | En quoi cela nous concerne-t-il ?

Je vous lai dit, il est ncessaire, pour tout fichier/programme/, de prciser lencodage utilis.
Je vous propose de choisir lUTF-8.

A chaque connexion MySQL, excutez donc la commande suivante :

SET NAMES 'utf8' ;

Cette commande dfinit lUTF-8 comme lencodage dans lequel les requtes sont mises par le
client, ainsi que celui utilis par le serveur pour communiquer avec le client. Lors de la cration
de notre base de donnes dans un prochain chapitre, nous dfinirons galement son encodage.

[[attention]] | Il faut bien excuter cette commande chaque connexion !

Il est galement possible dajouter une option lors de la connexion, pour viter de devoir excuter
SET NAMES. En vous connectant de la manire suivante, lencodage sera dfini UTF-8 directe-
ment la connexion :

mysql-uroot-p--default-character-set=utf8

2.2.5.3 Interclassement

Linterclassement est un ensemble de rgles qui vient sajouter lencodage. Pour chaque jeu de
caractres, plusieurs interclassements peuvent exister, qui dfinissent deux choses :

lordre des caractres : par exemple, lordre alphabtique. Lorsque lon trie les donnes
avec MySQL, le tri sera bas sur linterclassement.
les quivalences de caractres : on peut par exemple dfinir que les majuscules sont qui-
valentes aux minuscules (interclassement insensible la casse). De sorte que si lon re-
cherche les donnes qui contienne a, on trouve galement celles qui contiennent A.

29
2 MySQL et les bases du langage SQL

Je vous propose de garder linterclassement par dfaut (qui est insensible la casse).

*[ASCII] : American Standard Code for Information Interchange

2.2.5.4 En rsum

MySQL peut sutiliser en ligne de commande ou avec une interface graphique.


Pour se connecter MySQL en ligne de commande, on utilise : mysql -u utilisateur [-h hte] -p.
Pour terminer une instruction SQL, on utilise le caractre || ;||.
En SQL, les chanes de caractres doivent tre entoures de guillemets simples ||||.
Lorsque lon se connecte MySQL, il faut dfinir lencodage utilis, soit directement dans
la connexion avec loption --default-character-set, soit avec la commande SET
NAMES.

2.3 Les types de donnes


Nous avons vu dans lintroduction quune base de donnes contenait des tables qui, elles-mmes
sont organises en colonnes, dans lesquelles sont stockes des donnes. En SQL (et dans la plu-
part des langages informatiques), les donnes sont spares en plusieurs types (par exemple :
texte, nombre entier, date). Lorsque lon dfinit une colonne dans une table de la base, il faut
donc lui donner un type, et toutes les donnes stockes dans cette colonne devront correspondre
au type de la colonne. Nous allons donc voir les diffrents types de donnes existant dans MySQL.

2.3.1 Avertissement
Il est important de bien comprendre les usages et particularits de chaque type de donnes, afin
de choisir le meilleur type possible lorsque vous dfinissez les colonnes de vos tables. En effet,
choisir un mauvais type de donnes pourrait entraner :

un gaspillage de mmoire (ex. : si vous stockez de toutes petites donnes dans une colonne
faite pour stocker de grosses quantits de donnes) ;
des problmes de performance (ex. : il est plus rapide de faire une recherche sur un nombre
que sur une chane de caractres) ;
un comportement contraire celui attendu (ex. : trier sur un nombre stock comme tel, ou
sur un nombre stock comme une chane de caractres ne donnera pas le mme rsultat) ;
limpossibilit dutiliser des fonctionnalits propres un type de donnes (ex. : stocker une
date comme une chane de caractres vous prive des nombreuses fonctions temporelles
disponibles).

2.3.2 Types numriques


On peut subdiviser les types numriques en deux sous-catgories : les nombres entiers, et les
nombres dcimaux.

30
2.3 Les types de donnes

2.3.2.1 Nombres entiers

Les types de donnes qui acceptent des nombres entiers comme valeur sont dsigns par le mot-
cl INT, et ses dclinaisons TINYINT, SMALLINT, MEDIUMINT et BIGINT. La diffrence entre ces
types est le nombre doctets (donc la place en mmoire) rservs la valeur du champ. Voici un
tableau reprenant ces informations, ainsi que lintervalle dans lequel la valeur peut tre comprise
pour chaque type.

Type Nombre doctets Minimum Maximum

TINYINT 1 -128 127


SMALLINT 2 -32768 32767
MEDIUMINT 3 -8388608 8388607
INT 4 -2147483648 2147483647
BIGINT 8 -9223372036854775808 9223372036854775807

[[information]] | Si vous essayez de stocker une valeur en dehors de lintervalle permis par le type
de votre champ, MySQL stockera la valeur la plus proche. Par exemple, si vous essayez de stocker
12457 dans un TINYINT, la valeur stocke sera 127 ; ce qui nest pas exactement pareil, vous en
conviendrez. Rflchissez donc bien aux types de vos champs.

2.3.2.1.1 Lattribut UNSIGNED Vous pouvez galement prciser que vos colonnes sont
UNSIGNED, cest--dire quon ne prcise pas sil sagit dune valeur positive ou ngative (on aura
donc toujours une valeur positive). Dans ce cas, la longueur de lintervalle reste la mme, mais les
valeurs possibles sont dcales, le minimum valant 0. Pour les TINYINT, on pourra par exemple
aller de 0 255.

2.3.2.1.2 Limiter la taille daffichage et lattribut ZEROFILL Il est possible de pr-


ciser le nombre de chiffres minimum laffichage dune colonne de type INT (ou un de ses d-
rivs). Il suffit alors de prciser ce nombre entre parenthses : INT(x). Notez bien que cela ne
change pas les capacits de stockage dans la colonne. Si vous dclarez un INT(2), vous pourrez
toujours y stocker 45282 par exemple. Simplement, si vous stockez un nombre avec un nombre de
chiffres infrieur au nombre dfini, le caractre par dfaut sera ajout gauche du chiffre, pour
quil prenne la bonne taille. Sans prcision, le caractre par dfaut est lespace.

[[attention]] | Soyez prudents cependant. Si vous stockez des nombres dpassant la taille daf-
fichage dfinie, il est possible que vous ayez des problmes lors de lutilisation de ces nombres,
notamment pour des jointures (nous le verrons dans la deuxime partie).

Cette taille daffichage est gnralement utilise en combinaison avec lattribut ZEROFILL. Cet
attribut ajoute des zros gauche du nombre lors de son affichage, il change donc le caractre par
dfaut par 0. Donc, si vous dclarez une colonne comme tant

INT(4) ZEROFILL

Vous aurez laffichage suivant :

Nombre stock Nombre affich

45 0045

31
2 MySQL et les bases du langage SQL

Nombre stock Nombre affich

4156 4156
785164 785164

2.3.2.2 Nombres dcimaux

Cinq mots-cls permettent de stocker des nombres dcimaux dans une colonne : DECIMAL,
NUMERIC, FLOAT, REAL et DOUBLE.

2.3.2.2.1 NUMERIC et DECIMAL NUMERIC et DECIMAL sont quivalents et acceptent


deux paramtres : la prcision et lchelle.

La prcision dfinit le nombre de chiffres significatifs stocks, donc les 0 gauche ne


comptent pas. En effet 0024 est quivalent 24. Il ny a donc que deux chiffres significatifs
dans 0024.
Lchelle dfinit le nombre de chiffres aprs la virgule.

Dans un champ DECIMAL(5,3), on peut donc stocker des nombres de 5 chiffres significatifs maxi-
mum, dont 3 chiffres sont aprs la virgule. Par exemple : 12.354, -54.258, 89.2 ou -56. DECIMAL(4)
quivaut crire DECIMAL(4, 0).

[[information]] | En SQL pur, on ne peut pas stocker dans un champ DECIMAL(5,3) un nombre
suprieur 99.999, puisque le nombre ne peut avoir que deux chiffres avant la virgule (5 chiffres
en tout, dont 3 aprs la virgule, 5-3 = 2 avant). Cependant, MySQL permet en ralit de stocker des
nombres allant jusqu 999.999. En effet, dans le cas de nombres positifs, MySQL utilise loctet
qui sert stocker le signe ||-|| pour stocker un chiffre supplmentaire.

Comme pour les nombres entiers, si lon entre un nombre qui nest pas dans lintervalle support
par la colonne, MySQL le remplacera par le plus proche support. Donc si la colonne est dfinie
comme un DECIMAL(5,3) et que le nombre est trop loin dans les positifs (1012,43 par exemple),
999.999 sera stock, et -99.999 si le nombre est trop loin dans les ngatifs. Sil y a trop de chiffres
aprs la virgule, MySQL arrondira lchelle dfinie.

2.3.2.2.2 FLOAT, DOUBLE et REAL Le mot-cl FLOAT peut sutiliser sans paramtre, au-
quel cas quatre octets sont utiliss pour stocker les valeurs de la colonne. Il est cependant possible
de spcifier une prcision et une chelle, de la mme manire que pour DECIMAL et NUMERIC.

Quant REAL et DOUBLE, ils ne supportent pas de paramtres. DOUBLE est normalement plus
prcis que REAL (stockage dans 8 octets contre stockage dans 4 octets), mais ce nest pas le cas
avec MySQL qui utilise 8 octets dans les deux cas. Je vous conseille donc dutiliser DOUBLE pour
viter les surprises en cas de changement de SGBDR.

2.3.2.2.3 Valeurs exactes vs. valeurs approches Les nombres stocks en tant que
NUMERIC ou DECIMAL sont stocks sous forme de chanes de caractres. Par consquent, cest
la valeur exacte qui est stocke. Par contre, les types FLOAT, DOUBLE et REAL sont stocks sous
forme de nombres, et cest une valeur approche qui est stocke.

Cela signifie que si vous stockez par exemple 56,6789 dans une colonne de type FLOAT, en ralit,
MySQL stockera une valeur qui se rapproche de 56,6789 (par exemple, 56,678900000000000001).
Cela peut poser problme pour des comparaison notamment (56,678900000000000001 ntant

32
2.3 Les types de donnes

pas gal 56,6789). Sil est ncessaire de conserver la prcision exacte de vos donnes (lexemple
type est celui des donnes bancaires), il est donc conseill dutiliser un type numrique valeur
exacte (NUMERIC ou DECIMAL donc).

[[attention]] | La documentation anglaise de MySQL donne des exemples de problmes rencon-


trs avec les valeurs approches. Nhsitez pas y faire un tour si vous pensez pouvoir tre concer-
ns par ce problme, ou si vous tes simplement curieux.

2.3.3 Types alphanumriques

2.3.3.1 Chanes de type texte

2.3.3.1.1 CHAR et VARCHAR Pour stocker un texte relativement court (moins de 255 oc-
tets), vous pouvez utiliser les types CHAR et VARCHAR. Ces deux types sutilisent avec un para-
mtre qui prcise la taille que peut prendre votre texte (entre 1 et 255). La diffrence entre CHAR
et VARCHAR est la manire dont ils sont stocks en mmoire. Un CHAR(x) stockera toujours x oc-
tets, en remplissant si ncessaire le texte avec des espaces vides pour le complter, tandis quun
VARCHAR(x) stockera jusqu x octets (entre 0 et x), et stockera en plus en mmoire la taille du
texte stock.

Si vous entrez un texte plus long que la taille maximale dfinie pour le champ, celui-ci sera tron-
qu.

[[information]] | Je parle ici en octets, et non en caractres pour ce qui est de la taille des champs.
Cest important : si la plupart des caractres sont stocks en mmoire sur un seul octet, ce nest
pas toujours le cas. Par exemple, lorsque lon utilise lencodage UTF-8, les caractres accentus
(, , ) sont cods sur deux octets.

Petit tableau explicatif, en prenant lexemple dun CHAR ou dun VARCHAR de 5 octets maximum :

VARCHAR(5)
Texte CHAR(5) Mmoire requise Mmoire requise

' ' 5 octets '' 1 octet


tex 'tex ' 5 octets 'tex' 4 octets
texte 'texte' 5 octets 'texte' 6 octets
texte trop long 'texte' 5 octets 'texte' 6 octets

Vous voyez donc que dans le cas o le texte fait la longueur maximale autorise, un CHAR(x) prend
moins de place en mmoire quun VARCHAR(x). Prfrez donc le CHAR(x) dans le cas o vous
savez que vous aurez toujours x octets (par exemple si vous stockez un code postal). Par contre, si
la longueur de votre texte risque de varier dune ligne lautre, dfinissez votre colonne comme
un VARCHAR(x).

2.3.3.1.2 TEXT [[question]] | Et si je veux pouvoir stocker des textes de plus de 255 octets ?

Il suffit alors dutiliser le type TEXT, ou un de ses drivs TINYTEXT, MEDIUMTEXT ou LONGTEXT.
La diffrence entre ceux-ci tant la place quils permettent doccuper en mmoire. Petit tableau
habituel :

33
2 MySQL et les bases du langage SQL

Type Longueur maximale Mmoire occupe

TINYTEXT 28 octets Longueur de la chane + 1 octet


TEXT 216 octets Longueur de la chane + 2 octets
MEDIUMTEXT 224 octets Longueur de la chane + 3 octets
LONGTEXT 232 octets Longueur de la chane + 4 octets

2.3.3.2 Chanes de type binaire

Comme les chanes de type texte que lon vient de voir, une chane binaire nest rien dautre
quune suite de caractres.

Cependant, si les textes sont affects par lencodage et linterclassement, ce nest pas le cas des
chanes binaires. Une chane binaire nest rien dautre quune suite doctets. Aucune interprta-
tion nest faite sur ces octets. Ceci a deux consquences principales.

Une chane binaire traite directement loctet, et pas le caractre que loctet reprsente.
Donc par exemple, une recherche sur une chane binaire sera toujours sensible la
casse, puisque A (code binaire : 01000001) sera toujours diffrent de a (code binaire :
01100001).
Tous les caractres sont utilisables, y compris les fameux caractres de contrle non-
affichables dfinis dans la table ASCII.

Par consquent, les types binaires sont parfaits pour stocker des donnes brutes comme des
images par exemple, tandis que les chanes de texte sont parfaites pour stockerdu texte ! :D

Les types binaires sont dfinis de la mme faon que les types de chanes de texte. VARBINARY(x)
et BINARY(x) permettent de stocker des chanes binaires de x caractres maximum (avec une
gestion de la mmoire identique VARCHAR(x) et CHAR(x)). Pour les chanes plus longues, il
existe les types TINYBLOB, BLOB, MEDIUMBLOB et LONGBLOB, galement avec les mmes limites
de stockage que les types TEXT.

2.3.3.3 SET et ENUM

2.3.3.3.1 ENUM Une colonne de type ENUM est une colonne pour laquelle on dfinit un certain
nombre de valeurs autorises, de type chane de caractre. Par exemple, si lon dfinit une
colonne espece (pour une espce animale) de la manire suivante :

espece ENUM('chat', 'chien', 'tortue')

La colonne espece pourra alors contenir les chanes chat, chien ou tortue, mais pas les
chanes lapin ou cheval.

En plus de chat, chien et tortue, la colonne espece pourrait prendre deux autres valeurs :

si vous essayez dintroduire une chane non-autorise, MySQL stockera une chane vide ''
dans le champ ;
si vous autorisez le champ ne pas contenir de valeur (vous verrez comment faire a dans
le chapitre sur la cration des tables), le champ contiendra NULL, qui correspond pas de
valeur en SQL (et dans beaucoup de langages informatiques).

Pour remplir un champ de type ENUM, deux possibilits soffrent vous :

34
2.3 Les types de donnes

soit remplir directement avec la valeur choisie (chat, chien ou tortue dans notre
exemple) ;
soit utiliser lindex de la valeur, cest--dire le nombre associ par MySQL la valeur. Ce
nombre est compris entre 1 et le nombre de valeurs dfinies. Lindex est attribu selon
lordre dans lequel les valeurs ont t donnes lors de la cration du champ. De plus, la
chane vide (stocke en cas de valeur non-autorise) correspond lindex 0. Le tableau
suivant reprend les valeurs dindex pour notre exemple prcdent : le champ espece.

Valeur Index

NULL NULL
'' 0
chat 1
chien 2
tortue 3

Afin que tout soit bien clair : si vous voulez stocker chien dans votre champ, vous pouvez donc
y insrer chien ou insrer 2 (sans guillemets, il sagit dun nombre, pas dun caractre).

[[information]] | Un ENUM peut avoir maximum 65535 valeurs possibles

2.3.3.3.2 SET SET est fort semblable ENUM. Une colonne SET est en effet une colonne qui
permet de stocker une chane de caractres dont les valeurs possibles sont prdfinies par luti-
lisateur. La diffrence avec ENUM, cest quon peut stocker dans la colonne entre 0 et x valeur(s),
x tant le nombre de valeurs autorises.

Donc, si lon dfinit une colonne de type SET de la manire suivante :

espece SET('chat', 'chien', 'tortue')

On pourra stocker dans cette colonne :

'' (chane vide ) ;


'chat' ;
'chat,tortue' ;
'chat,chien,tortue' ;
'chien,tortue' ;

Vous remarquerez que lorsquon stocke plusieurs valeurs, il faut les sparer par une virgule, sans
espace et entourer la totalit des valeurs par des guillemets (non pas chaque valeur sparment).
Par consquent, les valeurs autorises dune colonne SET ne peuvent pas contenir de virgule elles-
mmes.

[[information]] | On ne peut pas stocker la mme valeur plusieurs fois dans un SET. chien,chien
par exemple, nest donc pas valable.

Les colonnes SET utilisent galement un systme dindex, quoiquun peu plus complexe que pour
le type ENUM. SET utilise en effet un systme dindex binaire. Concrtement, la prsence/absence
des valeurs autorises va tre enregistre sous forme de bits, mis 1 si la valeur correspondante
est prsente, 0 si la valeur correspondante est absente. Si lon reprend notre exemple, on a
donc :

35
2 MySQL et les bases du langage SQL

espece SET('chat', 'chien', 'tortue')

Trois valeurs sont autorises. Il nous faut donc trois bits pour savoir quelles valeurs sont stockes
dans le champ. Le premier, droite, correspondra chat, le second (au milieu) chien et le
dernier ( gauche) tortue.

000 signifie quaucune valeur nest prsente.


001 signifie que chat est prsent.
100 signifie que tortue est prsent.
110 signifie que chien et tortue sont prsents.

Par ailleurs, ces suites de bits reprsentent des nombres en binaire convertibles en dcimal. Ainsi
000 en binaire correspond 0 en nombre dcimal, 001 correspond 1, 010 correspond 2, 011
3

Puisque jaime bien les tableaux, je vous en fais un, ce sera peut-tre plus clair.

Valeur Binaire Dcimal

chat 001 1
chien 010 2
tortue 100 4

Pour stocker chat et tortue dans un champ, on peut donc utiliser chat,tortue ou 101 (addition
des nombres binaires correspondants) ou 5 (addition des nombres dcimaux correspondants).

Notez que cette utilisation des binaires a pour consquence que lordre dans lequel vous rentrez
vos valeurs na pas dimportance. Que vous criviez chat,tortue ou tortue,chat ne fait aucune
diffrence. Lorsque vous rcuprerez votre champ, vous aurez chat,tortue (dans le mme ordre
que lors de la dfinition du champ).

[[information]] | Un champ de type SET peut avoir au plus 64 valeurs dfinies

2.3.3.3.3 Avertissement SET et ENUM sont des types propres MySQL. Ils sont donc utiliser
avec une grande prudence !

[[question]] | Pourquoi avoir invent ces types propres MySQL ?

La plupart des SGBD implmentent ce quon appelle des contraintes dassertions, qui permettent
de dfinir les valeurs que peuvent prendre une colonne (par exemple, on pourrait dfinir une
contrainte pour une colonne contenant un ge, devant tre compris entre 0 et 130).

MySQL nimplmente pas ce type de contrainte et a par consquent cr deux types de donnes
spcifiques (SET et ENUM), pour pallier en partie ce manque.

[[question]] | Dans quelles situations faut-il utiliser ENUM ou SET ?

La meilleure rponse cette question est : jamais ! Je dconseille fortement lutilisation des SET
et des ENUM. Je vous ai prsent ces deux types par souci dexhaustivit, mais il faut toujours viter
autant que possible les fonctionnalits propres un seul SGBD. Ceci afin dviter les problmes
si un jour vous voulez en utiliser un autre.

Mais ce nest pas la seule raison. Imaginez que vous vouliez utiliser un ENUM ou un SET pour un
systme de catgories. Vous avez donc des lments qui peuvent appartenir une catgorie (dans

36
2.3 Les types de donnes

ce cas, vous utilisez une colonne ENUM pour la catgorie) ou appartenir plusieurs catgories (et
vous utilisez SET).

categorie ENUM("Soupes", "Viandes", "Tarte", "Dessert")


categorie SET("Soupes", "Viandes", "Tarte", "Dessert")

Tout se passe plutt bien tant que vos lments appartiennent aux catgories que vous avez d-
finies au dpart. Et puis tout coup, vous vous retrouvez avec un lment qui ne correspond
aucune de vos catgories, mais qui devrait plutt se trouver dans la catgorie Entres. Avec
SET ou ENUM, il vous faut modifier la colonne categorie pour ajouter Entres aux valeurs pos-
sibles. Or, une des rgles de base respecter lorsque lon conoit une base de donnes, est que
la structure de la base (donc les tables, les colonnes) ne doit pas changer lorsque lon ajoute
des donnes. Par consquent, tout ce qui est susceptible de changer doit tre une donne, et non
faire partie de la structure de la base.

Il existe deux solutions pour viter les ENUM, et une solution pour viter les SET.

Pour viter ENUM

Vous pouvez faire de la colonne categorie une simple colonne VARCHAR(100). Le dsavan-
tage est que vous ne pouvez pas limiter les valeurs entres dans cette colonne. Cette vri-
fication pourra ventuellement se faire un autre niveau (par exemple au niveau du PHP
si vous faites un site web avec PHP et MySQL).
Vous pouvez aussi ajouter une table Categorie qui reprendra toutes les catgories possibles.
Dans la table des lments, il suffira alors de stocker une rfrence vers la catgorie de
llment.

Pour viter SET La solution consiste en la cration de deux tables : une table Categorie, qui re-
prend les catgories possibles, et une table qui lie les lments aux catgories auxquels ils appar-
tiennent.

[ASCII] : American Standard Code for Information Interchange SGBD : Systme de Gestion de Bases de Don-
nes

2.3.4 Types temporels


Pour les donnes temporelles, MySQL dispose de cinq types qui permettent, lorsquils sont bien
utiliss, de faire normment de choses.

Avant dentrer dans le vif du sujet, une petite remarque importante : lorsque vous stockez une
date dans MySQL, certaines vrifications sont faites sur la validit de la date entre. Cependant,
ce sont des vrifications de base : le jour doit tre compris entre 1 et 31 et le mois entre 1 et 12. Il
vous est tout fait possible dentrer une date telle que le 31 fvrier 2011. Soyez donc prudents avec
les dates que vous entrez et rcuprez.

Les cinq types temporels de MySQL sont DATE, DATETIME, TIME, TIMESTAMP et YEAR.

2.3.4.1 DATE, TIME et DATETIME

Comme son nom lindique, DATE sert stocker une date. TIME sert quant lui stocker une heure,
et DATETIME stockeune date ET une heure ! :D

37
2 MySQL et les bases du langage SQL

2.3.4.1.1 DATE Pour entrer une date, lordre des donnes est la seule contrainte. Il faut don-
ner dabord lanne (deux ou quatre chiffres), ensuite le mois (deux chiffres) et pour finir, le jour
(deux chiffres), sous forme de nombre ou de chane de caractres. Sil sagit dune chane de carac-
tres, nimporte quelle ponctuation peut tre utilise pour dlimiter les parties (ou aucune). Voici
quelques exemples dexpressions correctes (A reprsente les annes, M les mois et J les jours) :

'AAAA-MM-JJ' (cest sous ce format-ci quune DATE est stocke dans MySQL)
'AAMMJJ'
'AAAA/MM/JJ'
'AA+MM+JJ'
'AAAA%MM%JJ'
AAAAMMJJ (nombre)
AAMMJJ (nombre)

Lanne peut donc tre donne avec deux ou quatre chiffres. Dans ce cas, le sicle nest pas prcis,
et cest MySQL qui va dcider de ce quil utilisera, selon ces critres :

si lanne donne est entre 00 et 69, on utilisera le 21e sicle, on ira donc de 2000 2069 ;
par contre, si lanne est comprise entre 70 et 99, on utilisera le 20e sicle, donc entre 1970
et 1999.

[[information]] | MySQL supporte des DATE allant de 1001-01-01 9999-12-31

2.3.4.1.2 DATETIME Trs proche de DATE, ce type permet de stocker une heure, en plus
dune date. Pour entrer un DATETIME, cest le mme principe que pour DATE : pour la date, anne-
mois-jour, et pour lheure, il faut donner dabord lheure, ensuite les minutes, puis les secondes.
Si on utilise une chane de caractres, il faut sparer la date et lheure par une espace. Quelques
exemples corrects (H reprsente les heures, M les minutes et S les secondes) :

'AAAA-MM-JJ HH:MM:SS' (cest sous ce format-ci quun DATETIME est stock dans
MySQL)
'AA*MM*JJ HH+MM+SS'
AAAAMMJJHHMMSS (nombre)

[[information]] | MySQL supporte des DATETIME allant de 1001-01-01 00 :00 :00 9999-12-31
23 :59 :59

2.3.4.1.3 TIME Le type TIME est un peu plus compliqu, puisquil permet non seulement
de stocker une heure prcise, mais aussi un intervalle de temps. On nest donc pas limit 24
heures, et il est mme possible de stocker un nombre de jours ou un intervalle ngatif. Comme
dans DATETIME, il faut dabord donner lheure, puis les minutes, puis les secondes, chaque partie
pouvant tre spare des autres par le caractre || :||. Dans le cas o lon prcise galement un
nombre de jours, alors les jours sont en premier et spars du reste par une espace. Exemples :

'HH:MM:SS'
'HHH:MM:SS'
'MM:SS'
'J HH:MM:SS'
'HHMMSS'
HHMMSS (nombre)

[[information]] | MySQL supporte des TIME allant de -838 :59 :59 838 :59 :59

38
2.3 Les types de donnes

2.3.4.2 YEAR

Si vous navez besoin de retenir que lanne, YEAR est un type intressant car il ne prend quun
seul octet en mmoire. Cependant, un octet ne pouvant contenir que 256 valeurs diffrentes, YEAR
est fortement limit : on ne peut y stocker que des annes entre 1901 et 2155. Ceci dit, a devrait
suffire la majorit dentre vous pour au moins les cent prochaines annes.

On peut entrer une donne de type YEAR sous forme de chane de caractres ou dentiers, avec 2
ou 4 chiffres. Si lon ne prcise que deux chiffres, le sicle est ajout par MySQL selon les mmes
critres que pour DATE et DATETIME, une exception prs : si lon entre 00 (un entier donc),
il sera interprt comme la valeur par dfaut de YEAR 0000. Par contre, si lon entre '00' (une
chane de caractres), elle sera bien interprte comme lanne 2000. Plus de prcisions sur les
valeurs par dfaut des types temporels dans quelques instants !

2.3.4.3 TIMESTAMP

Par dfinition, le timestamp dune date est le nombre de secondes coules depuis le 1er janvier
1970, 0h0min0s (TUC) et la date en question. Les timestamps tant stocks sur 4 octets, il existe
une limite suprieure : le 19 janvier 2038 3h14min7s. Par consquent, vrifiez bien que vous tes
dans lintervalle de validit avant dutiliser un timestamp.

Le type TIMESTAMP de MySQL est cependant un peu particulier. Prenons par exemple le 4 octobre
2011, 21h05min51s. Entre cette date et le 1er janvier 1970, 0h0min0s, il sest coul exactement
1317755151 secondes. Le nombre 1317755151 est donc, par dfinition, le timestamp de cette date du
4 octobre 2011, 21h05min51s. Pourtant, pour stocker cette date dans un TIMESTAMP SQL, ce nest
pas 1317755151 quon utilisera, mais 20111004210551. Cest--dire lquivalent, au format num-
rique, du DATETIME '2011-10-04 21:05:51'. Le TIMESTAMP SQL na donc de timestamp que
le nom. Il ne sert pas stocker un nombre de secondes, mais bien une date sous format numrique
AAAAMMJJHHMMSS (alors quun DATETIME est donc stock sous forme de chane de caractres).

Il nest donc pas possible de stocker un vrai timestamp dans une colonne de type TIMESTAMP.
Cest videmment contre-intuitif, et source derreur. Notez que malgr cela, le TIMESTAMP SQL
a les mme limites quun vrai timestamp : il nacceptera que des date entre le 1e janvier 1970
00h00min00s et le 19 janvier 2038 3h14min7s.

2.3.4.4 La date par dfaut

Lorsque MySQL rencontre une date/heure incorrecte, ou qui nest pas dans lintervalle de validit
du champ, la valeur par dfaut est stocke la place. Il sagit de la valeur zro du type. On peut
se rfrer cette valeur par dfaut en utilisant '0' (caractre), 0 (nombre) ou la reprsentation
du zro correspondant au type de la colonne (voir tableau ci-dessous).

Type Date par dfaut (zro)

DATE '0000-00-00'
DATETIME '0000-00-00 00:00:00'
TIME '00:00:00'
YEAR 0000
TIMESTAMP 00000000000000

39
2 MySQL et les bases du langage SQL

Une exception toutefois, si vous insrez un TIME qui dpasse lintervalle de validit, MySQL ne le
remplacera pas par le zro, mais par la plus proche valeur appartenant lintervalle de validit
(-838 :59 :59 ou 838 :59 :59).

*[TUC] : Temps Universel Coordonn

2.3.4.5 En rsum

MySQL dfinit plusieurs types de donnes : des numriques entiers, des numriques dci-
maux, des textes alphanumriques, des chanes binaires alphanumriques et des donnes
temporelles.
Il est important de toujours utiliser le type de donnes adapt la situation.
SET et ENUM sont des types de donnes qui nexistent que chez MySQL. Il vaut donc mieux
viter de les utiliser.

2.4 Cration dune base de donnes


a y est, le temps est venu dcrire vos premires lignes de commande. Dans ce chapitre plutt
court, je vous donnerai pour commencer quelques conseils indispensables. Ensuite, je vous pr-
senterai la problmatique sur laquelle nous allons travailler tout au long de ce tutoriel : la base de
donnes dun levage danimaux. Pour finir, nous verrons comment crer et supprimer une base
de donnes.

La partie purement thorique est donc bientt finie. Gardez la tte et les mains lintrieur du
vhicule. Cest parti !

2.4.1 Avant-propos : conseils et conventions

2.4.1.1 Conseils

2.4.1.1.1 Noms de tables et de colonnes Nutilisez jamais, au grand jamais, despaces


ou daccents dans vos noms de bases, tables ou colonnes. Au lieu davoir une colonne date de
naissance, prfrez date_de_naissance ou date_naissance. Et au lieu davoir une colonne
prnom, utilisez prenom. Avouez que a reste lisible, et a vous vitera pas mal dennuis.

vitez galement dutiliser des mots rservs comme nom de colonnes/tables/bases. Par mot
rserv, jentends un mot-cl SQL, donc un mot qui sert dfinir quelque chose dans le lan-
gage SQL. Vous trouverez une liste exhaustive des mots rservs dans la documentation officielle.
Parmi les plus frquents : date, text, type. Ajoutez donc une prcision vos noms dans ces cas-
l (date_naissance, text_article ou type_personnage par exemple).

Notez que MySQL permet lutilisation de mots-cls comme noms de tables ou de colonnes,
condition que ce nom soit entour de (accent grave/backquote). Cependant, ceci est propre
MySQL et ne devrait pas tre utilis.

40
2.4 Cration dune base de donnes

2.4.1.1.2 Soyez cohrents Vous vous y retrouverez bien mieux si vous restez cohrents dans
votre base. Par exemple, mettez tous vos noms de tables au singulier, ou au contraire au pluriel.
Choisissez, mais tenez-vous-y. Mme chose pour les noms de colonnes. Et lorsquun nom de
table ou de colonne ncessite plusieurs mots, sparez les toujours avec _ (ex : date_naissance)
ou bien toujours avec une majuscule (ex : dateNaissance).

Ce ne sont que quelques exemples de situations dans lesquelles vous devez dcider dune marche
suivre, et la garder tout au long de votre projet (voire pour tous vos projets futurs). Vous gagnerez
normment de temps en prenant de telles habitudes.

2.4.1.2 Conventions

2.4.1.2.1 Mots-cls Une convention largement rpandue veut que les commandes et mots-
cls SQL soient crits compltement en majuscules. Je respecterai cette convention et vous en-
courage le faire galement. Il est plus facile de relire une commande de 5 lignes lorsquon peut
diffrencier au premier coup dil les commandes SQL des noms de tables et de colonnes.

2.4.1.2.2 Noms de bases, de tables et de colonnes Je viens de vous dire que les mots-
cls SQL seront crits en majuscule pour les diffrencier du reste, donc videmment, les noms
de bases, tables et colonnes seront crits en minuscule. Toutefois, par habitude et parce que je
trouve cela plus clair, je mettrai une majuscule la premire lettre de mes noms de tables (et
uniquement pour les tables : ni pour la base de donnes ni pour les colonnes). Notez que MySQL
nest pas ncessairement sensible la casse en ce qui concerne les noms de tables et de colonnes.
En fait, il est trs probable que si vous travaillez sous Windows, MySQL ne soit pas sensible la
casse pour les noms de tables et de colonnes. Sous Mac et Linux par contre, cest le contraire qui
est le plus probable. Quoi quil en soit, jutiliserai des majuscules pour la premire lettre de mes
noms de tables. Libre vous de me suivre ou non.

2.4.1.2.3 Options facultatives Lorsque je commencerai vous montrer les commandes SQL
utiliser pour interagir avec votre base de donnes, vous verrez que certaines commandes ont des
options facultatives. Dans ces cas-l, jutiliserai des crochets [ ] pour indiquer ce qui est faculta-
tif. La mme convention est utilise dans la documentation officielle MySQL (et dans beaucoup
dautres documentations dailleurs). La requte suivante signifie donc que vous pouvez comman-
der votre glace vanille toute seule, ou avec du chocolat, ou avec de la chantilly, ou avec du chocolat
ET de la chantilly.

COMMANDEglacevanille[avecchocolat][avecchantilly]

2.4.1.3 Mise en situation

Histoire que nous soyons sur la mme longueur donde, je vous propose de baser le cours sur
une problmatique bien prcise. Nous allons crer une base de donnes qui permettra de grer
un levage danimaux. Pourquoi un levage ? Tout simplement parce que jai d moi-mme crer
une telle base pour le laboratoire de biologie pour lequel je travaillais. Par consquent, jai une
assez bonne ide des problmes quon peut rencontrer avec ce type de bases, et je pourrai donc
appuyer mes explications sur des problmes ralistes, plutt que dessayer den inventer.

41
2 MySQL et les bases du langage SQL

Nous nous occupons donc dun levage danimaux. On travaille avec plusieurs espces : chats,
chiens, tortues entre autres (tiens, a me rappelle quelque chose :- ). Dans la suite de cette partie,
nous nous contenterons de crer une table Animal qui contiendra les caractristiques principales
des animaux prsents dans llevage, mais ds le dbut de la deuxime partie, dautres tables
seront cres afin de pouvoir grer un grand nombre de donnes complexes.

2.4.2 Cration et suppression dune base de donnes


2.4.2.1 Cration

Nous allons donc crer notre base de donnes, que nous appellerons elevage. Rappelez-vous, lors
de la cration de votre utilisateur MySQL, vous lui avez donn tous les droits sur la base elevage, qui
nexistait pas encore. Si vous choisissez un autre nom de base, vous naurez aucun droit dessus.

La commande SQL pour crer une base de donnes est la suivante :

CREATE DATABASE nom_base;

Avouez que je ne vous surmne pas le cerveau pour commencer

Cependant, attendez avant de crer votre base de donnes elevage. Je vous rappelle quil faut ga-
lement dfinir lencodage utilis (lUTF-8 dans notre cas). Voici donc la commande complte
taper pour crer votre base :

CREATE DATABASE elevage CHARACTER SET 'utf8' ;

Lorsque nous crerons nos tables dans la base de donnes, automatiquement elles seront enco-
des galement en UTF-8.

2.4.2.2 Suppression

Si vous avez envie dessayer cette commande, faites-le maintenant, tant quil ny a rien dans
votre base de donnes. Soyez trs prudents, car vous effacez tous les fichiers crs par MySQL qui
servent stocker les informations de votre base.

DROP DATABASE elevage;

Si vous essayez cette commande alors que la base de donnes elevage nexiste pas, MySQL vous
affichera une erreur :

mysql>DROPDATABASEelevage;
ERROR1008(HY000):Can'tdropdatabase'elevage' ;databasedoesn'texist
mysql>

Pour viter ce message derreur, si vous ntes pas srs que la base de donnes existe, vous pouvez
utiliser loption IF EXISTS, de la manire suivante :

DROP DATABASE IF EXISTS elevage;

Si la base de donnes existe, vous devriez alors avoir un message du type :

QueryOK,0rowsaffected(0.00sec)

42
2.5 Cration de tables

Si elle nexiste pas, vous aurez :

QueryOK,0rowsaffected,1warning(0.00sec)

Pour afficher les warnings de MySQL, il faut utiliser la commande

SHOW WARNINGS;

Cette commande affiche un tableau :

Level Code Message

Note 1008 Cant drop database elevage ; database doesnt exist

2.4.2.3 Utilisation dune base de donnes

Vous avez maintenant cr une base de donnes (si vous lavez efface avec DROP DATABASE,
recrez-la). Mais pour pouvoir agir sur cette base, vous devez encore avertir MySQL que cest bien
sr cette base-l que vous voulez travailler. Une fois de plus, la commande est trs simple :

USE elevage

Cest tout ! partir de maintenant, toutes les actions effectues le seront sur la base de donnes
elevage (cration et modification de tables par exemple).

Notez que vous pouvez spcifier la base de donnes sur laquelle vous allez travailler lors de la
connexion MySQL. Il suffit dajouter le nom de la base la fin de la commande de connexion :

mysql-usdz-pelevage

2.4.2.4 En rsum

Pour crer une base de donnes, on utilise la commande CREATE DATABASE nom_base.
Pour supprimer une base de donnes : DROP DATABASE nom_base.
chaque connexion MySQL, il faut prciser avec quelle base on va travailler, avec USE
nom_base.

2.5 Cration de tables


Dans ce chapitre, nous allons crer, tape par tape, une table Animal, qui servira stocker les
animaux prsents dans notre levage. Soyez gentils avec cette table, car cest elle qui vous ac-
compagnera tout au long de la premire partie (on apprendra jongler avec plusieurs tables dans
la deuxime partie).

Pour commencer, il faudra dfinir de quelles colonnes (et leur type) la table sera compose. Ne
ngligez pas cette tape, cest la plus importante. Une base de donnes mal conue est un cau-
chemar utiliser. Ensuite, petit passage oblig par de la thorie : vous apprendrez ce quest une

43
2 MySQL et les bases du langage SQL

cl primaire et quoi a sert, et dcouvrirez cette fonctionnalit exclusive de MySQL que sont les
moteurs de table. Enfin, la table Animal sera cre, et la requte de cration des tables dcortique.
Et dans la foule, nous verrons galement comment supprimer une table.

2.5.1 Dfinition des colonnes

2.5.1.1 Type de colonne

Avant de choisir le type des colonnes, il faut choisir les colonnes que lon va dfinir. On va donc
crer une table Animal. Quest-ce qui caractrise un animal ? Son espce, son sexe, sa date de nais-
sance. Quoi dautre ? Une ventuelle colonne commentaires qui peut servir de fourre-tout. Dans le
cas dun levage sentimental, on peut avoir donn un nom nos bestioles. Disons que cest tout
pour le moment. Examinons donc les colonnes afin den choisir le type au mieux.

Espce : on a des chats, des chiens et des tortues pour linstant. On peut donc caractri-
ser lespce par un ou plusieurs mots. Ce sera donc un champ de type alphanumrique.
Les noms despces sont relativement courts, mais nont pas tous la mme longueur. On
choisira donc un VARCHAR. Mais quelle longueur lui donner ? Beaucoup de noms despces
ne contiennent quun mot, mais harfang des neiges, par exemple, en contient trois, et
18 caractres. Histoire de ne prendre aucun risque, autant autoriser jusqu 40 caractres
pour lespce.
Sexe : ici, deux choix possibles (mle ou femelle). Le risque de voir un troisime sexe ap-
paratre est extrmement faible. Par consquent, il serait possible dutiliser un ENUM. Ce-
pendant, ENUM reste un type non standard. Pour cette raison, nous utiliserons plutt une
colonne CHAR(1), contenant soit 'M' (mle), soit 'F' (femelle).
Date de naissance : pas besoin de rflchir beaucoup ici. Il sagit dune date, donc soit un
DATETIME, soit une DATE. Lheure de la naissance est-elle importante ? Disons que oui, du
moins pour les soins lors des premiers jours. DATETIME donc !
Commentaires : de nouveau un type alphanumrique videmment, mais on a ici aucune
ide de la longueur. Ce sera sans doute succinct mais il faut prvoir un minimum de place
quand mme. Ce sera donc un champ TEXT.
Nom : plutt facile dterminer. On prendra simplement un VARCHAR(30). On ne pourra
pas appeler nos tortues Petite maison dans la prairie verdoyante, mais cest amplement
suffisant pour Rox ou Roucky.

2.5.1.2 NULL or NOT NULL ?

Il faut maintenant dterminer si lon autorise les colonnes ne pas stocker de valeur (ce qui est
donc reprsent par NULL).

Espce : un leveur digne de ce nom connat lespce des animaux quil lve. On nauto-
risera donc pas la colonne espece tre NULL.
Sexe : le sexe de certains animaux est trs difficile dterminer la naissance. Il nest donc
pas impossible quon doive attendre plusieurs semaines pour savoir si Rox est en ralit
Roxa. Par consquent, la colonne sexe peut contenir NULL.
Date de naissance : pour garantir la puret des races, on ne travaille quavec des individus
dont on connat la provenance (en cas dapport extrieur), les parents, la date de naissance.
Cette colonne ne peut donc pas tre NULL.

44
2.5 Cration de tables

Commentaires : ce champ peut trs bien ne rien contenir, si la bestiole concerne ne pr-
sente absolument aucune particularit.
Nom : en cas de panne dinspiration (a a lair facile comme a mais, une chatte pouvant
avoir entre 1 et 8 petits dun coup, il est parfois difficile dinventer 8 noms originaux comme
a !), il vaut mieux autoriser cette colonne tre NULL.

2.5.1.3 Rcapitulatif

Comme dhabitude, un petit tableau pour rcapituler tout a :

Caractristique Nom de la colonne Type NULL ?


Espce espece VARCHAR(40) Non
Sexe sexe CHAR(1) Oui
Date de naissance date_naissance DATETIME Non
Commentaires commentaires TEXT Oui
Nom nom VARCHAR(30) Oui

[[attention]] | Ne pas oublier de donner une taille aux colonnes qui en ncessitent une, comme
les VARCHAR(x), les CHAR(x), les DECIMAL(n, d),

2.5.2 Introduction aux cls primaires


On va donc dfinir cinq colonnes : espece, sexe, date_naissance, commentaires et nom. Ces colonnes per-
mettront de caractriser nos animaux. Mais que se passe-t-il si deux animaux sont de la mme
espce, du mme sexe, sont ns exactement le mme jour, et ont exactement les mmes com-
mentaires et le mme nom ? Comment les diffrencier ? videmment, on pourrait sarranger pour
que deux animaux naient jamais le mme nom. Mais imaginez la situation suivante : une chatte
vient de donner naissance sept petits. On ne peut pas encore dfinir leur sexe, on na pas encore
trouv de nom pour certains dentre eux et il ny a encore aucun commentaire faire leur pro-
pos. Ils auront donc exactement les mmes caractristiques. Pourtant, ce ne sont pas les mmes
individus. Il faut donc les diffrencier. Pour cela, on va ajouter une colonne notre table.

2.5.2.1 Identit

Imaginez que quelquun ait le mme nom de famille que vous, le mme prnom, soit n dans la
mme ville et ait la mme taille. En dehors de la photo et de la signature, quelle sera la diffrence
entre vos deux cartes didentit ? Son numro !

Suivant le mme principe, on va donner chaque animal un numro didentit. La colonne quon
ajoutera sappellera donc id, et il sagira dun INT, toujours positif donc UNSIGNED. Selon la taille
de llevage (la taille actuelle mais aussi la taille quon imagine quil pourrait avoir dans le fu-
tur !), il peut tre plus intressant dutiliser un SMALLINT, voire un MEDIUMINT. Comme il est
peu probable que lon dpasse les 65000 animaux, on utilisera SMALLINT. Attention, il faut bien
considrer tous les animaux qui entreront un jour dans la base, pas uniquement le nombre dani-
maux prsents en mme temps dans llevage. En effet, si lon supprime pour une raison ou une
autre un animal de la base, il nest pas question de rutiliser son numro didentit.

Ce champ ne pourra bien sr pas tre NULL, sinon il perdrait toute son utilit.

45
2 MySQL et les bases du langage SQL

2.5.2.2 Cl primaire

La cl primaire dune table est une contrainte dunicit, compose dune ou plusieurs colonnes.
La cl primaire dune ligne permet didentifier de manire unique cette ligne dans la table. Si
lon parle de la ligne dont la cl primaire vaut x, il ne doit y avoir aucun doute quant la ligne dont
on parle. Lorsquune table possde une cl primaire (et il est extrmement conseill de dfinir
une cl primaire pour chaque table cre), celle-ci doit tre dfinie.

Cette dfinition correspond exactement au numro didentit dont nous venons de parler. Nous
dfinirons donc id comme la cl primaire de la table Animal, en utilisant les mots-cls PRIMARY
KEY(id).

Lorsque vous insrerez une nouvelle ligne dans la table, MySQL vrifiera que vous insrez bien
un id, et que cet id nexiste pas encore dans la table. Si vous ne respectez pas ces deux contraintes,
MySQL ninsrera pas la ligne et vous renverra une erreur.

Par exemple, dans le cas o vous essayez dinsrer un id qui existe dj, vous obtiendrez lerreur
suivante :

ERROR1062(23000):Duplicateentry'1'forkey'PRIMARY'

Je nen dirai pas plus pour linstant sur les cls primaires mais jy reviendrai de manire dtaille
dans la seconde partie de ce cours.

2.5.2.3 Auto-incrmentation

Il faut donc, pour chaque animal, dcider dune valeur pour id. Le plus simple, et le plus logique,
est de donner le numro 1 au premier individu enregistr, puis le numro 2 au second, etc.

Mais si vous ne vous souvenez pas quel numro vous avez utilis en dernier, pour insrer un nou-
vel animal il faudra rcuprer cette information dans la base, ensuite seulement vous pourrez
ajouter une ligne en lui donnant comme id le dernier_id_ utilis + 1. Cest bien sr faisable, mais
cest fastidieux Heureusement, il est possible de demander MySQL de faire tout a pour nous !

Comment ? En utilisant lauto-incrmentation des colonnes. Incrmenter veut dire ajouter une
valeur fixe. Donc, si lon dclare quune colonne doit sauto-incrmenter (grce au mot-cl
AUTO_INCREMENT), plus besoin de chercher quelle valeur on va mettre dedans lors de la prochaine
insertion. MySQL va chercher a tout seul comme un grand en prenant la dernire valeur insre
et en lincrmentant de 1.

2.5.3 Les moteurs de tables

Les moteurs de tables sont une spcificit de MySQL. Ce sont des moteurs de stockage. Cela per-
met de grer diffremment les tables selon lutilit quon en a. Je ne vais pas vous dtailler tous
les moteurs de tables existant. Si vous voulez plus dinformations, je vous renvoie la documen-
tation officielle. Les deux moteurs les plus connus sont MyISAM et InnoDB.

46
2.5 Cration de tables

2.5.3.0.1 MyISAM Cest le moteur par dfaut. Les commandes dinsertion et slection de
donnes sont particulirement rapides sur les tables utilisant ce moteur. Cependant, il ne gre
pas certaines fonctionnalits importantes comme les cls trangres, qui permettent de vrifier
lintgrit dune rfrence dune table une autre table (voir la deuxime partie du cours) ou les
transactions, qui permettent de raliser des sries de modifications en bloc ou au contraire
dannuler ces modifications (voir la cinquime partie du cours).

2.5.3.0.2 InnoDB Plus lent et plus gourmand en ressources que MyISAM, ce moteur gre les
cls trangres et les transactions. tant donn que nous nous servirons des cls trangres ds
la deuxime partie, cest celui-l que nous allons utiliser. De plus, en cas de crash du serveur, il
possde un systme de rcupration automatique des donnes.

2.5.3.1 Prciser un moteur lors de la cration de la table

Pour quune table utilise le moteur de notre choix, il suffit dajouter ceci la fin de la commande
de cration :

ENGINE = moteur;

En remplaant bien sr moteur par le nom du moteur que nous voulons utiliser, ici InnoDB :

ENGINE = INNODB;

2.5.4 Syntaxe de CREATE TABLE


Avant de voir la syntaxe permettant de crer une table, rsumons un peu. Nous voulons donc crer
une table Animal avec six colonnes telles que dcrites dans le tableau suivant.

Caractristique Nom du NULL ?


champ Type Divers

Numro id SMALLINT Non Cl primaire + auto-incrment +


didentit UNSIGNED
Espce espece VARCHAR(40)Non

Sexe sexe CHAR(1) Oui


Date de date_naissance DATETIME Non


naissance

Commentaires commentaires TEXT Oui


Nom nom VARCHAR(30)Oui


47
2 MySQL et les bases du langage SQL

2.5.4.1 Syntaxe

Par souci de clart, je vais diviser lexplication de la syntaxe de CREATE TABLE en deux. La pre-
mire partie vous donne la syntaxe globale de la commande, et la deuxime partie sattarde sur
la description des colonnes cres dans la table.

2.5.4.1.1 Cration de la table


CREATE TABLE [IF NOT EXISTS] Nom_table (
colonne1 description_colonne1,
[colonne2 description_colonne2,
colonne3 description_colonne3,
...,]
[PRIMARY KEY (colonne_cl_primaire)]
)
[ENGINE=moteur];
Le IF NOT EXISTS est facultatif (do lutilisation de crochets [ ]), et a le mme rle que dans
la commande CREATE DATABASE : si une table de ce nom existe dj dans la base de donnes, la
requte renverra un warning plutt quune erreur si IF NOT EXISTS est spcifi. Ce nest pas
non plus une erreur de ne pas prciser la cl primaire directement la cration de la table. Il est
tout fait possible de lajouter par la suite. Nous verrons comment un peu plus tard.

2.5.4.1.2 Dfinition des colonnes Pour dfinir une colonne, il faut donc donner son nom
en premier, puis sa description. La description est constitue au minimum du type de la colonne.
Exemple :

nom VARCHAR(30),
sexe CHAR(1)

Cest aussi dans la description que lon prcise si la colonne peut contenir NULL ou pas (par dfaut,
NULL est autoris). Exemple :

espece VARCHAR(40) NOT NULL,


date_naissance DATETIME NOT NULL

Lauto-incrmentation se dfinit galement cet endroit. Notez quil est galement possible de
dfinir une colonne comme tant la cl primaire dans sa description. Il ne faut alors plus lindi-
quer aprs la dfinition de toutes les colonnes. Je vous conseille nanmoins de ne pas lindiquer
cet endroit, nous verrons plus tard pourquoi.

id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT [PRIMARY KEY]

Enfin, on peut donner une valeur par dfaut au champ. Si lorsque lon insre une ligne, aucune
valeur nest prcise pour le champ, cest la valeur par dfaut qui sera utilise. Notez que si une
colonne est autorise contenir NULL et quon ne prcise pas de valeur par dfaut, alors NULL est
implicitement considr comme valeur par dfaut.

Exemple :

espece VARCHAR(40) NOT NULL DEFAULT 'chien'

[[attention]] | Une valeur par dfaut DOIT tre une constante. Ce ne peut pas tre une fonction
(comme par exemple la fonction NOW() qui renvoie la date et lheure courante).

48
2.5 Cration de tables

2.5.4.2 Application : cration de Animal

Si lon met tout cela ensemble pour crer la table Animal (je rappelle que nous utiliserons le moteur
InnoDB), on a donc :

CREATE TABLE Animal (


id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
espece VARCHAR(40) NOT NULL,
sexe CHAR(1),
date_naissance DATETIME NOT NULL,
nom VARCHAR(30),
commentaires TEXT,
PRIMARY KEY (id)
)
ENGINE=INNODB;

[[information]] | Je nai pas gard la valeur par dfaut pour le champ espece, car je trouve que a na
pas beaucoup de sens dans ce contexte. Ctait juste un exemple pour vous montrer la syntaxe.

2.5.4.3 Vrifications

Au cas o vous ne me croiriez pas (et aussi un peu parce que cela pourrait vous tre utile un jour),
voici deux commandes vous permettant de vrifier que vous avez bien cr une jolie table Animal
avec les six colonnes que vous vouliez.

SHOW TABLES ; -- liste les tables de la base de donnes

DESCRIBE Animal; -- liste les colonnes de la table avec leurs caractristiques

2.5.5 Suppression dune table


La commande pour supprimer une table est la mme que celle pour supprimer une base de don-
nes. Elle est, bien sr, utiliser avec prudence, car irrversible.

DROP TABLE Animal;

2.5.5.1 En rsum

Avant de crer une table, il faut dfinir ses colonnes. Pour cela, il faut donc dterminer le
type de chacune des colonnes et dcider si elles peuvent ou non contenir NULL (cest--dire
ne contenir aucune donne).
Chaque table cre doit dfinir une cl primaire, donc une colonne qui permettra didenti-
fier chaque ligne de manire unique.
Le moteur dune table dfinit la manire dont elle est gre. Nous utiliserons le moteur
InnoDB, qui permet notamment de dfinir des relations entre plusieurs tables.

49
2 MySQL et les bases du langage SQL

2.6 Modification dune table


La cration et la suppression de tables tant acquises, parlons maintenant des requtes permet-
tant de modifier une table. Plus prcisment, ce chapitre portera sur la modification des colonnes
dune table (ajout dune colonne, modification, suppression de colonnes). Il est possible de mo-
difier dautres lments (des contraintes, ou des index par exemple), mais cela ncessite des no-
tions que vous ne possdez pas encore, aussi nen parlerai-je pas ici.

Notez quidalement, il faut penser lavance la structure de votre base et crer toutes vos tables
directement et proprement, de manire ne les modifier quexceptionnellement.

2.6.1 Syntaxe de la requte


Lorsque lon modifie une table, on peut vouloir lui ajouter, retirer ou modifier quelque chose. Dans
les trois cas, cest la commande ALTER TABLE qui sera utilise, une variante existant pour cha-
cune des oprations :

ALTER TABLE nom_table ADD ... -- permet d'ajouter quelque chose (une colonne par exempl

ALTER TABLE nom_table DROP ... -- permet de retirer quelque chose

ALTER TABLE nom_table CHANGE ...


ALTER TABLE nom_table MODIFY ... -- permettent de modifier une colonne

2.6.1.0.1 Crons une table pour faire joujou Dans la seconde partie de ce tutoriel, nous
devrons faire quelques modifications sur notre table Animal, mais en attendant, je vous propose
dutiliser la table suivante, si vous avez envie de tester les diffrentes possibilits dALTER
TABLE :

CREATE TABLE Test_tuto (


id INT NOT NULL,
nom VARCHAR(10) NOT NULL,
PRIMARY KEY(id)
);

2.6.2 Ajout et suppression dune colonne

2.6.2.1 Ajout

On utilise la syntaxe suivante :

ALTER TABLE nom_table


ADD [COLUMN] nom_colonne description_colonne;

Le [COLUMN] est facultatif, donc si la suite de ADD vous ne prcisez pas ce que vous voulez ajouter,
MySQL considrera quil sagit dune colonne. description_colonne correspond la mme
chose que lorsque lon cre une table. Il contient le type de donne et ventuellement NULL ou
NOT NULL, etc.

50
2.6 Modication dune table

Ajoutons une colonne date_insertion notre table de test. Il sagit dune date, donc une colonne de
type DATE convient parfaitement. Disons que cette colonne ne peut pas tre NULL (si cest dans
la table, a a forcment t insr). Cela nous donne :

ALTER TABLE Test_tuto


ADD COLUMN date_insertion DATE NOT NULL ;

Un petit DESCRIBE Test_tuto; vous permettra de vrifier les changements apports.

2.6.2.2 Suppression

La syntaxe de ALTER TABLE ... DROP ... est trs simple :

ALTER TABLE nom_table


DROP [COLUMN] nom_colonne;

Comme pour les ajouts, le mot COLUMN est facultatif. Par dfaut, MySQL considrera que vous
parlez dune colonne.

Exemple : nous allons supprimer la colonne date_insertion, que nous remercions pour son passage
clair dans le cours.

ALTER TABLE Test_tuto


DROP COLUMN date_insertion; -- Suppression de la colonne date_insertion

2.6.3 Modification de colonne

2.6.3.1 Changement du nom de la colonne

Vous pouvez utiliser la commande suivante pour changer le nom dune colonne :

ALTER TABLE nom_table


CHANGE ancien_nom nouveau_nom description_colonne;

Par exemple, pour renommer la colonne nom en prenom, vous pouvez crire

ALTER TABLE Test_tuto


CHANGE nom prenom VARCHAR(10) NOT NULL ;

Attention, la description de la colonne doit tre complte, sinon elle sera galement modifie.
Si vous ne prcisez pas NOT NULL dans la commande prcdente, prenom pourra contenir NULL,
alors que du temps o elle sappelait nom, cela lui tait interdit.

2.6.3.2 Changement du type de donnes

Les mots-cls CHANGE et MODIFY peuvent tre utiliss pour changer le type de donne de
la colonne, mais aussi changer la valeur par dfaut ou ajouter/supprimer une proprit
AUTO_INCREMENT. Si vous utilisez CHANGE, vous pouvez, comme on vient de le voir, renom-
mer la colonne en mme temps. Si vous ne dsirez pas la renommer, il suffit dindiquer deux fois
le mme nom. Voici les syntaxes possibles :

51
2 MySQL et les bases du langage SQL

ALTER TABLE nom_table


CHANGE ancien_nom nouveau_nom nouvelle_description;

ALTER TABLE nom_table


MODIFY nom_colonne nouvelle_description;

Des exemples pour illustrer :

ALTER TABLE Test_tuto


CHANGE prenom nom VARCHAR(30) NOT NULL ; -- Changement du type + changement du nom

ALTER TABLE Test_tuto


CHANGE id id BIGINT NOT NULL ; -- Changement du type sans renommer

ALTER TABLE Test_tuto


MODIFY id BIGINT NOT NULL AUTO_INCREMENT; -- Ajout de l'auto-incrmentation

ALTER TABLE Test_tuto


MODIFY nom VARCHAR(30) NOT NULL DEFAULT 'Blabla' ; -- Changement de la description (mm

Il existe pas mal dautres possibilits et combinaisons pour la commande ALTER TABLE mais en
faire la liste complte ne rentre pas dans le cadre de ce cours. Si vous ne trouvez pas votre bonheur
ici, je vous conseille de le chercher dans la documentation officielle.

2.6.3.3 En rsum

La commande ALTER TABLE permet de modifier une table


Lorsque lon ajoute ou modifie une colonne, il faut toujours prciser sa (nouvelle) descrip-
tion complte (type, valeur par dfaut, auto-incrment ventuel)

2.7 Insertion de donnes


Ce chapitre est consacr linsertion de donnes dans une table. Rien de bien compliqu, mais
cest videmment crucial. En effet, que serait une base de donnes sans donnes ?

Nous verrons entre autres :

comment insrer une ligne dans une table ;


comment insrer plusieurs lignes dans une table ;
comment excuter des requtes SQL crites dans un fichier (requtes dinsertion ou
autres) ;
comment insrer dans une table des lignes dfinies dans un fichier de format particulier.

Et pour terminer, nous peuplerons notre table Animal dune soixantaine de petites bestioles
sur lesquelles nous pourrons tester toutes sortes de tortures requtes dans la suite de ce tuto-
riel. :diable :

52
2.7 Insertion de donnes

2.7.1 Syntaxe de INSERT


Deux possibilits soffrent nous lorsque lon veut insrer une ligne dans une table : soit donner
une valeur pour chaque colonne de la ligne, soit ne donner les valeurs que de certaines colonnes,
auquel cas il faut bien sr prciser de quelles colonnes il sagit.

2.7.1.1 Insertion sans prciser les colonnes

Je rappelle pour les distraits que notre table Animal est compose de six colonnes : id, espece, sexe,
date_naissance, nom et commentaires.

Voici donc la syntaxe utiliser pour insrer une ligne dans Animal, sans renseigner les colonnes
pour lesquelles on donne une valeur (implicitement, MySQL considre que lon donne une valeur
pour chaque colonne de la table).

INSERT INTO Animal


VALUES (1, 'chien', 'M', '2010-04-05 13:43:00', 'Rox', 'Mordille beaucoup');

Deuxime exemple : cette fois-ci, on ne connat pas le sexe et on na aucun commentaire faire
sur la bestiole :

INSERT INTO Animal


VALUES (2, 'chat', NULL, '2010-03-24 02:23:00', 'Roucky', NULL);

Troisime et dernier exemple : on donne NULL comme valeur did, ce qui en principe est
impossible puisque id est dfini comme NOT NULL et comme cl primaire. Cependant, lauto-
incrmentation fait que MySQL va calculer tout seul comme un grand quel id il faut donner la
ligne (ici : 3).

INSERT INTO Animal


VALUES (NULL , 'chat', 'F', '2010-09-13 15:02:00', 'Schtroumpfette', NULL);

Vous avez maintenant trois animaux dans votre table :

Id Espce Sexe Date de naissance Nom Commentaires

1 chien M 2010-04-05 13 :43 :00 Rox Mordille beaucoup


2 chat NULL 2010-03-24 Roucky NULL
02 :23 :00
3 chat F 2010-09-13 15 :02 :00 Schtroumpfette NULL

Pour vrifier, vous pouvez utiliser la requte suivante :

SELECT * FROM Animal;

Deux choses importantes retenir ici.

id est un nombre, on ne met donc pas de guillemets autour. Par contre, lespce, le nom,
la date de naissance et le sexe sont donns sous forme de chanes de caractres. Les guille-
mets sont donc indispensables. Quant NULL, il sagit dun marqueur SQL qui, je rappelle,
signifie pas de valeur. Pas de guillemets donc.

53
2 MySQL et les bases du langage SQL

Les valeurs des colonnes sont donnes dans le bon ordre (donc dans lordre donn lors de la
cration de la table). Cest indispensable videmment. Si vous changez le nom et lespce
par exemple, comment MySQL pourrait-il le savoir ?

2.7.1.2 Insertion en prcisant les colonnes

Dans la requte, nous allons donc crire explicitement quelle(s) colonne(s) nous donnons une
valeur. Ceci va permettre deux choses.

On ne doit plus donner les valeurs dans lordre de cration des colonnes, mais dans lordre
prcis par la requte.
On nest plus oblig de donner une valeur chaque colonne ; plus besoin de NULL lorsquon
na pas de valeur mettre.

Quelques exemples :

INSERT INTO Animal (espece, sexe, date_naissance)


VALUES ('tortue', 'F', '2009-08-03 05:12:00');
INSERT INTO Animal (nom, commentaires, date_naissance, espece)
VALUES ('Choupi', 'N sans oreille gauche', '2010-10-03 16:44:00', 'chat');
INSERT INTO Animal (espece, date_naissance, commentaires, nom, sexe)
VALUES ('tortue', '2009-06-13 08:17:00', 'Carapace bizarre', 'Bobosse', 'F');

Ce qui vous donne trois animaux supplmentaires (donc six en tout, il faut suivre !)

2.7.1.3 Insertion multiple

Si vous avez plusieurs lignes introduire, il est possible de le faire en une seule requte de la
manire suivante :

INSERT INTO Animal (espece, sexe, date_naissance, nom)


VALUES ('chien', 'F', '2008-12-06 05:18:00', 'Caroline'),
('chat', 'M', '2008-09-11 15:38:00', 'Bagherra'),
('tortue', NULL, '2010-08-23 05:18:00', NULL);

Bien entendu, vous tes alors obligs de prciser les mmes colonnes pour chaque entre, quitte
mettre NULL pour certaines. Mais avouez que a fait quand mme moins crire !

2.7.2 Syntaxe alternative de MySQL


MySQL propose une syntaxe alternative INSERT INTO ... VALUES ... pour insrer des don-
nes dans une table.

INSERT INTO Animal


SET nom='Bobo', espece='chien', sexe='M', date_naissance='2010-07-21 15:41:00' ;

Cette syntaxe prsente deux avantages.

Le fait davoir lun ct de lautre la colonne et la valeur quon lui attribue (nom =
'Bobo') rend la syntaxe plus lisible et plus facile manipuler. En effet, ici il ny a que
six colonnes, mais imaginez une table avec 20, voire 100 colonnes. Difficile dtre srs
que lordre dans lequel on a dclar les colonnes est bien le mme que lordre des valeurs
quon leur donne

54
2.7 Insertion de donnes

Elle est trs semblable la syntaxe de UPDATE, que nous verrons plus tard et qui permet
de modifier des donnes existantes. Cest donc moins de choses retenir (mais bon, une
requte de plus ou de moins, ce nest pas non plus norme)

[[attention]] | Cependant, cette syntaxe alternative prsente galement des dfauts, qui pour moi
sont plus importants que les avantages apports. Cest pourquoi je vous dconseille de lutiliser.
Je vous la montre surtout pour que vous ne soyez pas surpris si vous la rencontrez quelque part.

En effet, cette syntaxe prsente deux dfauts majeurs.

Elle est propre MySQL. Ce nest pas du SQL pur. De ce fait, si vous dcidez un jour de
migrer votre base vers un autre SGBDR, vous devrez rcrire toutes les requtes INSERT
utilisant cette syntaxe.
Elle ne permet pas linsertion multiple.

2.7.3 Utilisation de fichiers externes


Maintenant que vous savez insrer des donnes, je vous propose de remplir un peu cette table,
histoire quon puisse samuser par la suite.

Rassurez-vous, je ne vais pas vous demander dinventer cinquante bestioles et dcrire une une
les requtes permettant de les insrer. Je vous ai prmch le boulot. De plus, a nous permettra
davoir, vous et moi, la mme chose dans notre base. Ce sera ainsi plus facile de vrifier que vos
requtes font bien ce quelles doivent faire.

Et pour viter dcrire vous-mmes toutes les requtes dinsertion, nous allons donc voir com-
ment on peut utiliser un fichier texte pour interagir avec notre base de donnes.

2.7.3.1 Excuter des commandes SQL partir dun fichier

crire toutes les commandes la main dans la console, a peut vite devenir pnible. Quand cest
une petite requte, pas de problme. Mais quand vous avez une longue requte, ou beaucoup de
requtes faire, a peut tre assez long.

Une solution sympathique est dcrire les requtes dans un fichier texte, puis de dire MySQL
dexcuter les requtes contenues dans ce fichier. Et pour lui dire a, cest facile :

SOURCE monFichier.sql;

Ou

\. monFichier.sql;

Ces deux commandes sont quivalentes et vont excuter le fichier monFichier.sql. Il nest pas
indispensable de lui donner lextension .sql, mais je prfre le faire pour reprer mes fichiers
SQL directement. De plus, si vous utilisez un diteur de texte un peu plus volu que le bloc-note
(ou textEdit sur Mac), cela colorera votre code SQL, ce qui vous facilitera aussi les choses.

Attention : si vous ne lui indiquez pas le chemin, MySQL va aller chercher votre fichier dans le
dossier o vous tiez lors de votre connexion.

Exemple : on donne le chemin complet vers le fichier

SOURCE C:\Document and Settings\dossierX\monFichier.sql;

55
2 MySQL et les bases du langage SQL

2.7.3.2 Insrer des donnes partir dun fichier format

Par fichier format, jentends un fichier qui suit certaines rgles de format. Un exemple typique
serait les fichiers .csv. Ces fichiers contiennent un certain nombre de donnes et sont organiss
en tables. Chaque ligne correspond une entre, et les colonnes de la table sont spares par un
caractre dfini (souvent une virgule ou un point-virgule). Ceci par exemple, est un format csv :

nom;prenom;date_naissance
Charles;Myeur;1994-12-30
Bruno;Debor;1978-05-12
Mireille;Franelli;1990-08-23

Ce type de fichier est facile produire (et lire) avec un logiciel de type tableur (Microsoft Excel,
ExcelViewer, Numbers). La bonne nouvelle est quil est aussi possible de lire ce type de fichier
avec MySQL, afin de remplir une table avec les donnes contenues dans le fichier.

La commande SQL permettant cela est LOAD DATA INFILE, dont voici la syntaxe :

LOAD DATA [LOCAL] INFILE 'nom_fichier'


INTO TABLE nom_table
[FIELDS
[TERMINATED BY '\t']
[ENCLOSED BY '']
[ESCAPED BY '\\' ]
]
[LINES
[STARTING BY '']
[TERMINATED BY '\n']
]
[IGNORE nombre LINES]
[(nom_colonne,...)];

Le mot-cl LOCAL sert spcifier si le fichier se trouve ct client (dans ce cas, on utilise LOCAL)
ou ct serveur (auquel cas, on ne met pas LOCAL dans la commande). Si le fichier se trouve du ct
serveur, il est obligatoire, pour des raisons de scurit, quil soit dans le rpertoire de la base de
donnes, cest--dire dans le rpertoire cr par MySQL la cration de la base de donnes, et qui
contient les fichiers dans lesquels sont stockes les donnes de la base. Pour ma part, jutiliserai
toujours LOCAL, afin de pouvoir mettre simplement mes fichiers dans mon dossier de travail.

Les clauses FIELDS et LINES permettent de dfinir le format de fichier utilis. FIELDS se rapporte
aux colonnes, et LINES aux lignes (si si ). Ces deux clauses sont facultatives. Les valeurs que
jai mises ci-dessus sont les valeurs par dfaut.

Si vous prcisez une clause FIELDS, il faut lui donner au moins une des trois sous-clauses.

TERMINATED BY, qui dfinit le caractre sparant les colonnes, entre guillemets bien sr.
'\t' correspond une tabulation. Cest le caractre par dfaut.
ENCLOSED BY, qui dfinit le caractre entourant les valeurs dans chaque colonne (vide par
dfaut).
ESCAPED BY, qui dfinit le caractre dchappement pour les caractres spciaux. Si par
exemple vous dfinissez vos valeurs comme entoures dapostrophes, mais que certaines

56
2.7 Insertion de donnes

valeurs contiennent des apostrophes, il faut chapper ces apostrophes internes afin
quelles ne soient pas considres comme un dbut ou une fin de valeur. Par dfaut, il sagit
du |||| habituel. Remarquez quil faut lui-mme lchapper dans la clause.

De mme pour LINES, si vous lutilisez, il faut lui donner une ou deux sous-clauses.

STARTING BY, qui dfinit le caractre de dbut de ligne (vide par dfaut).
TERMINATED BY, qui dfinit le caractre de fin de ligne ('\n' par dfaut, mais attention :
les fichiers gnrs sous Windows ont souvent '\r\n' comme caractre de fin de ligne).

La clause IGNORE nombre LINES permet dignorer un certain nombre de lignes. Par exemple,
si la premire ligne de votre fichier contient les noms des colonnes, vous ne voulez pas linsrer
dans votre table. Il suffit alors dutiliser IGNORE 1 LINES.

Enfin, vous pouvez prciser le nom des colonnes prsentes dans votre fichier. Attention videm-
ment ce que les colonnes absentes acceptent NULL ou soient auto-incrmentes.

Si je reprends mon exemple, en imaginant que nous ayons une table Personne contenant les
colonnes id (cl primaire auto-incrmente), nom, prenom, date_naissance et adresse (qui peut tre
NULL).

nom;prenom;date_naissance
Charles;Myeur;1994-12-30
Bruno;Debor;1978-05-12
Mireille;Franelli;1990-08-23

Si ce fichier est enregistr sous le nom personne.csv, il vous suffit dexcuter la commande sui-
vante pour enregistrer ces trois lignes dans la table Personne, en spcifiant si ncessaire le chemin
complet vers personne.csv :

LOAD DATA LOCAL INFILE 'personne.csv'


INTO TABLE Personne
FIELDS TERMINATED BY ' ;'
LINES TERMINATED BY '\n' -- ou '\r\n' selon l'ordinateur et le programme utiliss pour
IGNORE 1 LINES
(nom,prenom,date_naissance);

2.7.4 Remplissage de la base


Nous allons utiliser les deux techniques que je viens de vous montrer pour remplir un peu notre
base. Noubliez pas de modifier les commandes donnes pour ajouter le chemin vers vos fichiers,

2.7.4.1 Excution de commandes SQL

Voici donc le code que je vous demande de copier-coller dans votre diteur de texte prfr, puis
de le sauver sous le nom remplissageAnimal.sql (ou un autre nom de votre choix).

[[secret]] | sql | INSERT INTO Animal (espece, sexe, date_naissance, nom, commentaires)
VALUES | ('chien', 'F', '2008-02-20 15:45:00' , 'Canaille', NULL), | ('chien',
'F','2009-05-26 08:54:00' , 'Cali', NULL), | ('chien', 'F','2007-04-24
12:54:00' , 'Rouquine', NULL), | ('chien', 'F','2009-05-26 08:56:00' ,

57
2 MySQL et les bases du langage SQL

'Fila', NULL), | ('chien', 'F','2008-02-20 15:47:00' , 'Anya', NULL), |


('chien', 'F','2009-05-26 08:50:00' ,'Louya' , NULL), | ('chien', 'F',
'2008-03-10 13:45:00','Welva' , NULL), | ('chien', 'F','2007-04-24 12:59:00'
,'Zira' , NULL), | ('chien', 'F', '2009-05-26 09:02:00','Java' , NULL), |
('chien', 'M','2007-04-24 12:45:00' ,'Balou' , NULL), | ('chien', 'M','2008-03-10
13:43:00' ,'Pataud' , NULL), | ('chien', 'M','2007-04-24 12:42:00' , 'Bouli',
NULL), | ('chien', 'M', '2009-03-05 13:54:00','Zoulou' , NULL), | ('chien',
'M','2007-04-12 05:23:00' ,'Cartouche' , NULL), | ('chien', 'M', '2006-05-14
15:50:00', 'Zambo', NULL), | ('chien', 'M','2006-05-14 15:48:00' ,'Samba'
, NULL), | ('chien', 'M', '2008-03-10 13:40:00','Moka' , NULL), | ('chien',
'M', '2006-05-14 15:40:00','Pilou' , NULL), | ('chat', 'M','2009-05-14
06:30:00' , 'Fiero', NULL), | ('chat', 'M','2007-03-12 12:05:00' ,'Zonko',
NULL), | ('chat', 'M','2008-02-20 15:45:00' , 'Filou', NULL), | ('chat',
'M','2007-03-12 12:07:00' , 'Farceur', NULL), | ('chat', 'M','2006-05-19
16:17:00' ,'Caribou' , NULL), | ('chat', 'M','2008-04-20 03:22:00' , 'Capou',
NULL), | ('chat', 'M','2006-05-19 16:56:00' , 'Raccou', 'Pas de queue depuis
la naissance'); |

Vous navez alors qu taper :

SOURCE remplissageAnimal.sql;

2.7.4.2 LOAD DATA INFILE

nouveau, copiez-collez le texte ci-dessous dans votre diteur de texte, et enregistrez le fichier.
Cette fois, sous le nom animal.csv.

[[secret]] | CSV | "chat";"M";"2009-05-14 06:42:00";"Boucan";NULL | "chat";"F";"2006-05-19


| "chat";"F";"2009-05-14 06:45:00";"Boule";NULL | "chat";"F";"2008-04-20 03:26:00";"Zar
| "chat";"F";"2007-03-12 12:00:00";"Milla";NULL | "chat";"F";"2006-05-19 15:59:00";"Fet
| "chat";"F";"2008-04-20 03:20:00";"Bilba";"Sourde de l'oreille droite 80%"
| "chat";"F";"2007-03-12 11:54:00";"Cracotte";NULL | "chat";"F";"2006-05-19 16:16:00";"
| "tortue";"F";"2007-04-01 18:17:00";"Nikki";NULL | "tortue";"F";"2009-03-24 08:23:00";
| "tortue";"F";"2009-03-26 01:24:00";"Scroupy";NULL | "tortue";"F";"2006-03-15 14:56:00
| "tortue";"F";"2008-03-15 12:02:00";"Dana";NULL | "tortue";"F";"2009-05-25 19:57:00";"
| "tortue";"F";"2007-04-01 03:54:00";"Chicaca";NULL | "tortue";"F";"2006-03-15 14:26:00
| "tortue";"M";"2007-04-02 01:45:00";"Spoutnik";NULL | "tortue";"M";"2008-03-16 08:20:0
| "tortue";"M";"2008-03-15 18:45:00";"Relou";"Surpoids" | "tortue";"M";"2009-05-25 18:5
| "perroquet";"M";"2007-03-04 19:36:00";"Safran";NULL | "perroquet";"M";"2008-02-20 02:
| "perroquet";"M";"2009-03-26 08:28:00";"Bavard";NULL | "perroquet";"F";"2009-03-26 07:
|

[[attention]] | Attention, le fichier doit se terminer par un saut de ligne !

Excutez ensuite la commande suivante :

LOAD DATA LOCAL INFILE 'animal.csv'


INTO TABLE Animal
FIELDS TERMINATED BY ' ;' ENCLOSED BY '"'
LINES TERMINATED BY '\n' -- ou '\r\n' selon l'ordinateur et le programme utiliss pour
(espece, sexe, date_naissance, nom, commentaires);

58
2.8 Slection de donnes

Et hop ! Vous avez plus dune cinquantaine danimaux dans votre table.

Si vous voulez vrifier, je rappelle que vous pouvez utiliser la commande suivante, qui vous affi-
chera toutes les donnes contenues dans la table Animal.

SELECT * FROM Animal;

Nous pouvons maintenant passer au chapitre suivant !

2.7.4.3 En rsum

Pour insrer des lignes dans une table, on utilise la commande

INSERT INTO nom_table [(colonne1, colonne2, ...)] VALUES (valeur1, valeur2, ...);

Si lon ne prcise pas quelles colonnes on donne une valeur, il faut donner une valeur
toutes les colonnes, et dans le bon ordre.
Il est possible dinsrer plusieurs lignes en une fois, en sparant les listes de valeurs par
une virgule.
Si lon a un fichier texte contenant des requtes SQL, on peut lexcuter en utilisant SOURCE
nom_fichier; ou \. nom_fichier;.
La commande LOAD DATA [LOCAL] INFILE permet de charger des donnes dans une
table partir dun fichier format (.csv par exemple).

2.8 Slection de donnes


Comme son nom lindique, ce chapitre traitera de la slection et de laffichage de donnes.

Au menu :

syntaxe de la requte SELECT (que vous avez dj croise il y a quelque temps) ;


slection de donnes rpondant certaines conditions ;
tri des donnes ;
limination des donnes en double ;
rcupration de seulement une partie des donnes (uniquement les 10 premires lignes,
par exemple).

Motivs ? Alors cest parti !!!

2.8.1 Syntaxe de SELECT


La requte qui permet de slectionner et afficher des donnes sappelle SELECT. Nous lavons
dj un peu utilise dans le chapitre dinstallation, ainsi que pour afficher tout le contenu de la
table Animal.

SELECT permet donc dafficher des donnes directement. Des chanes de caractres, des rsultats
de calculs, etc.

Exemple

59
2 MySQL et les bases du langage SQL

SELECT 'Hello World !' ;


SELECT 3+2 ;

SELECT permet galement de slectionner des donnes partir dune table. Pour cela, il faut
ajouter une clause la commande SELECT : la clause FROM, qui dfinit de quelle structure (dans
notre cas, une table) viennent les donnes.

SELECT colonne1, colonne2, ...


FROM nom_table;

Par exemple, si lon veut slectionner lespce, le nom et le sexe des animaux prsents dans la
table Animal, on utilisera :

SELECT espece, nom, sexe


FROM Animal;

2.8.1.1 Slectionner toutes les colonnes

Si vous dsirez slectionner toutes les colonnes, vous pouvez utiliser le caractre * dans votre
requte :

SELECT *
FROM Animal;

Il est cependant dconseill dutiliser SELECT * trop souvent. Donner explicitement le nom des
colonnes dont vous avez besoin prsente deux avantages :

dune part, vous tes certains de ce que vous rcuprez ;


dautre part, vous rcuprez uniquement ce dont vous avez vraiment besoin, ce qui permet
dconomiser des ressources.

Le dsavantage est bien sr que vous avez plus crire, mais le jeu en vaut la chandelle.

Comme vous avez pu le constater, les requtes SELECT faites jusqu prsent slectionnent toutes
les lignes de la table. Or, bien souvent, on ne veut quune partie des donnes. Dans la suite de
ce chapitre, nous allons voir ce que nous pouvons ajouter cette requte SELECT pour faire des
slections laide de critres.

2.8.2 La clause WHERE


La clause WHERE (o en anglais) permet de restreindre les rsultats selon des critres de re-
cherche. On peut par exemple vouloir ne slectionner que les chiens :

SELECT *
FROM Animal
WHERE espece='chien' ;

[[information]] | Comme chien est une chane de caractres, je dois bien sr lentourer de guille-
mets.

2.8.2.1 Les oprateurs de comparaison

Les oprateurs de comparaison sont les symboles que lont utilise pour dfinir les critres de
recherche (le = dans notre exemple prcdent). Huit oprateurs simples peuvent tre utiliss.

60
2.8 Slection de donnes

Oprateur Signification

= gal
< infrieur
<= infrieur ou gal
> suprieur
>= suprieur ou gal
<> ou != diffrent
<=> gal (valable pour NULL aussi)

Exemples :

SELECT *
FROM Animal
WHERE date_naissance < '2008-01-01' ; -- Animaux ns avant 2008

SELECT *
FROM Animal
WHERE espece <> 'chat' ; -- Tous les animaux sauf les chats

2.8.2.2 Combinaisons de critres

Tout a cest bien beau, mais comment faire si on veut les chats et les chiens par exemple ? Faut-il
faire deux requtes ? Non bien sr, il suffit de combiner les critres. Pour cela, il faut des opra-
teurs logiques, qui sont au nombre de quatre :

Oprateur Symbole Signification

AND && ET
OR || OU
XOR OU exclusif
NOT ! NON

Voici quelques exemples, srement plus efficaces quun long discours.

2.8.2.2.1 AND Je veux slectionner toutes les chattes. Je veux donc slectionner les animaux
qui sont la fois des chats ET des femelles. Jutilise loprateur AND :

SELECT *
FROM Animal
WHERE espece='chat'
AND sexe='F' ;
-- OU
SELECT *
FROM Animal
WHERE espece='chat'
&& sexe='F' ;

61
2 MySQL et les bases du langage SQL

2.8.2.2.2 OR Slection des tortues et des perroquets. Je dsire donc obtenir les animaux qui
sont des tortues OU des perroquets :

SELECT *
FROM Animal
WHERE espece='tortue'
OR espece='perroquet' ;
-- OU
SELECT *
FROM Animal
WHERE espece='tortue'
|| espece='perroquet' ;

[[information]] | Je vous conseille dutiliser plutt OR que ||, car dans la majorit des SGBDR (et
dans la norme SQL), loprateur || sert la concatnation. Cest--dire rassembler plusieurs
chanes de caractres en une seule. Il vaut donc mieux prendre lhabitude dutiliser OR, au cas o
vous changeriez un jour de SGBDR (ou tout simplement parce que cest une bonne habitude).

2.8.2.2.3 NOT Slection de tous les animaux femelles sauf les chiennes.

SELECT *
FROM Animal
WHERE sexe='F'
AND NOT espece='chien' ;
-- OU
SELECT *
FROM Animal
WHERE sexe='F'
AND ! espece='chien' ;

2.8.2.2.4 XOR Slection des animaux qui sont soit des mles, soit des perroquets (mais pas
les deux) :

SELECT *
FROM Animal
WHERE sexe='M'
XOR espece='perroquet' ;

Et voil pour les oprateurs logiques. Rien de bien compliqu, et pourtant, cest souvent source
derreur. Pourquoi ? Tout simplement parce que tant que vous nutilisez quun seul oprateur lo-
gique, tout va trs bien. Mais on a souvent besoin de combiner plus de deux critres, et cest l
que a se corse.

2.8.2.3 Slection complexe

Lorsque vous utilisez plusieurs critres, et que vous devez donc combiner plusieurs oprateurs
logiques, il est extrmement important de bien structurer la requte. En particulier, il faut placer
des parenthses au bon endroit. En effet, cela na pas de sens de mettre plusieurs oprateurs
logiques diffrents sur un mme niveau.

62
2.8 Slection de donnes

Petit exemple simple :


Critres : rouge AND vert OR bleu

Quaccepte-t-on ?

Ce qui est rouge et vert, et ce qui est bleu ?


Ou ce qui est rouge et, soit vert soit bleu ?

Dans le premier cas, [rouge, vert] et [bleu] seraient accepts. Dans le deuxime, cest [rouge, vert]
et [rouge, bleu] qui seront accepts, et non [bleu].

En fait, le premier cas correspond (rouge AND vert) OR bleu, et le deuxime cas rouge AND (vert
OR bleu).
Avec des parenthses, pas moyen de se tromper sur ce quon dsire slectionner !

2.8.2.3.1 Exercice/Exemple Alors, imaginons une requte bien tordue

Je voudrais les animaux qui sont, soit ns aprs 2009, soit des chats mles ou femelles, mais dans
le cas des femelles, elles doivent tre nes avant juin 2007.

Je vous conseille dessayer dcrire cette requte tout seuls. Si vous ny arrivez pas, voici une petite
aide : lastuce, cest de penser en niveaux. Je vais donc dcouper ma requte.

Je cherche :

les animaux ns aprs 2009 ;


les chats mles et femelles (uniquement nes avant juin 2007 pour les femelles).

Cest mon premier niveau. Loprateur logique sera OR puisquil faut que les animaux rpondent
un seul des deux critres pour tre slectionns.

On continue dcouper. Le premier critre ne peut plus tre subdivis, contrairement au


deuxime. Je cherche :

les animaux ns aprs 2009 ;


les chats :
mles ;
et femelles nes avant juin 2007.

Et voil, vous avez bien dfini les diffrents niveaux, il ny a plus qu crire la requte avec les
bons oprateurs logiques !

[[secret]] | sql | SELECT * | FROM Animal | WHERE date_naissance > '2009-12-31'


| OR | ( espece='chat' | AND | ( sexe='M' | OR
| ( sexe='F' AND date_naissance < '2007-06-01' ) | ) |
); |

2.8.2.4 Le cas de NULL

Vous vous souvenez sans doute de la liste des oprateurs de comparaison que je vous ai prsente
(sinon, retournez au dbut de la partie sur la clause WHERE). Vous avez probablement t un peu
tonns de voir dans cette liste loprateur <=> : gal (valable aussi pour NULL). Dautant plus
que jai fait un peu semblant de rien et ne vous ai pas donn dexplication sur cette mystrieuse
prcision aussi valable pour NULL :- . Mais je vais me rattraper maintenant ! En fait, cest trs
simple, le marqueur NULL (qui reprsente donc pas de valeur) est un peu particulier. En effet,
vous ne pouvez pas tester directement colonne = NULL. Essayons donc :

63
2 MySQL et les bases du langage SQL

SELECT *
FROM Animal
WHERE nom = NULL ; -- slection des animaux sans nom

SELECT *
FROM Animal
WHERE commentaires <> NULL ; -- slection des animaux pour lesquels un commentaire exis

Comme vous pouvez vous en douter aprs ma petite introduction, ces deux requtes ne renvoient
pas les rsultats que lon pourrait esprer. En fait, elles ne renvoient aucun rsultat. Cest donc
ici quintervient notre oprateur de comparaison un peu spcial <=> qui permet de reconnatre
NULL. Une autre possibilit est dutiliser les mots-cls IS NULL, et si lon veut exclure les NULL :
IS NOT NULL. Nous pouvons donc rcrire nos requtes, correctement cette fois-ci :

SELECT *
FROM Animal
WHERE nom <=> NULL ; -- slection des animaux sans nom
-- OU
SELECT *
FROM Animal
WHERE nom IS NULL ;

SELECT *
FROM Animal
WHERE commentaires IS NOT NULL ; -- slection des animaux pour lesquels un commentaire

Cette fois-ci, a fonctionne parfaitement !

commentaires
id espece sexe date_naissance nom

4 tortue F 2009-08-03 NULL NULL


05 :12 :00
9 tortue NULL 2010-08-23 05 :18 :00 NULL NULL

sexe
id espece date_naissance nom commentaires

1 chien M 2010-04-05 Rox Mordille beaucoup


13 :43 :00
5 chat NULL 2010-10-03 Choupi N sans oreille gauche
16 :44 :00
6 tortue F 2009-06-13 Bobosse Carapace bizarre
08 :17 :00
35 chat M 2006-05-19 Raccou Pas de queue depuis la naissance
16 :56 :00
52 tortue F 2006-03-15 Redbul Insomniaque
14 :26 :00

64
2.8 Slection de donnes

sexe
id espece date_naissance nom commentaires

55 tortue M 2008-03-15 Relou Surpoids


18 :45 :00

2.8.3 Tri des donnes

Lorsque vous faites un SELECT, les donnes sont rcupres dans un ordre dfini par MySQL, mais
qui na aucun sens pour vous. Vous avez sans doute limpression que MySQL renvoie tout simple-
ment les lignes dans lordre dans lequel elles ont t insres, mais ce nest pas exactement le
cas. En effet, si vous supprimez des lignes, puis en ajoutez de nouvelles, les nouvelles lignes vien-
dront remplacer les anciennes dans lordre de MySQL. Or, bien souvent, vous voudrez trier votre
manire. Par date de naissance par exemple, ou bien par espce, ou par sexe, etc.

Pour trier vos donnes, cest trs simple, il suffit dajouter ORDER BY tri votre requte (aprs
les critres de slection de WHERE sil y en a) et de remplacer tri par la colonne sur laquelle vous
voulez trier vos donnes bien sr.

Par exemple, pour trier par date de naissance :

SELECT *
FROM Animal
WHERE espece='chien'
ORDER BY date_naissance;

Et hop ! Vos donnes sont tries, les plus vieux chiens sont rcuprs en premier, les jeunes la
fin.

2.8.3.1 Tri ascendant ou descendant

Tout a cest bien beau, jai mes chiens tris du plus vieux au plus jeune. Et si je veux le contraire ?
Pour dterminer le sens du tri effectu, SQL possde deux mots-cls : ASC pour ascendant, et DESC
pour descendant. Par dfaut, si vous ne prcisez rien, cest un tri ascendant qui est effectu : du
plus petit nombre au plus grand, de la date la plus ancienne la plus rcente, et pour les chanes
de caractres et les textes, cest lordre alphabtique normal qui est utilis. Si par contre vous
utilisez le mot DESC, lordre est invers : plus grand nombre dabord, date la plus rcente dabord,
et ordre anti-alphabtique pour les caractres.

[[information]] | Petit cas particulier : les ENUM sont des chanes de caractres, mais sont tris
selon lordre dans lequel les possibilits ont t dfinies. Si par exemple on dfinit une colonne
exemple ENUM('a', 'd', 'c', 'b'), lordre ASC sera a, d, c puis b et lordre DESC b,
c, d suivi de a.

SELECT *
FROM Animal
WHERE espece='chien'
AND nom IS NOT NULL
ORDER BY nom DESC ;

65
2 MySQL et les bases du langage SQL

2.8.3.2 Trier sur plusieurs colonnes

Il est galement possible de trier sur plusieurs colonnes. Par exemple, si vous voulez que les rsul-
tats soient tris par espce et, dans chaque espce, tris par date de naissance, il suffit de donner
les deux colonnes correspondantes ORDER BY :

SELECT *
FROM Animal
ORDER BY espece, date_naissance;

[[attention]] | Lordre dans lequel vous donnez les colonnes est important, le tri se fera dabord
sur la premire colonne donne, puis sur la seconde, etc.

Vous pouvez trier sur autant de colonnes que vous voulez.

2.8.4 liminer les doublons

Il peut arriver que MySQL vous donne plusieurs fois le mme rsultat. Non pas parce que MySQL
fait des btises, mais tout simplement parce que certaines informations sont prsentes plusieurs
fois dans la table.

Petit exemple trs parlant : vous voulez savoir quelles sont les espces que vous possdez dans
votre levage. Facile, une petite requte :

SELECT espece
FROM Animal;

En effet, vous allez bien rcuprer toutes les espces que vous possdez, mais si vous avez 500
chiens, vous allez rcuprer 500 lignes chien. Un peu embtant lorsque la table devient bien
remplie.

Heureusement, il y a une solution : le mot-cl DISTINCT. Ce mot-cl se place juste aprs SELECT
et permet dliminer les doublons.

SELECT DISTINCT espece


FROM Animal;

Ceci devrait gentiment vous ramener quatre lignes avec les quatre espces qui se trouvent dans
la table. Cest quand mme plus clair non ?

Attention cependant, pour liminer un doublon, il faut que toute la ligne slectionne soit gale
une autre ligne du jeu de rsultats. a peut paratre logique, mais cela en perd plus dun. Ne seront
donc prises en compte que les colonnes que vous avez prcises dans votre SELECT. Uniquement
espece donc, dans notre exemple.

2.8.5 Restreindre les rsultats

En plus de restreindre une recherche en lui donnant des critres grce la clause WHERE, il est
possible de restreindre le nombre de lignes rcupres. Cela se fait grce la clause LIMIT.

66
2.8 Slection de donnes

2.8.5.1 Syntaxe

LIMIT sutilise avec deux paramtres.

Le nombre de lignes que lon veut rcuprer.


Le dcalage, introduit par le mot-cl OFFSET et qui indique partir de quelle ligne on r-
cupre les rsultats. Ce paramtre est facultatif. Sil nest pas prcis, il est mis 0.

LIMIT nombre_de_lignes [OFFSET decalage];

Exemple :

SELECT *
FROM Animal
ORDER BY id
LIMIT 6 OFFSET 0 ;

SELECT *
FROM Animal
ORDER BY id
LIMIT 6 OFFSET 3 ;

Avec la premire requte, vous devriez obtenir six lignes, les six plus petits id puisque nous
navons demand aucun dcalage (OFFSET 0).

id espece sexe date_naissance nom commentaires

1 chien M 2010-04-05 13 :43 :00 Rox Mordille beaucoup


2 chat NULL 2010-03-24 Roucky NULL
02 :23 :00
3 chat F 2010-09-13 15 :02 :00 Schtroumpfette NULL

4 tortue F 2009-08-03 NULL NULL


05 :12 :00
5 chat NULL 2010-10-03 16 :44 :00 Choupi N sans oreille gauche

6 tortue F 2009-06-13 08 :17 :00 Bobosse Carapace bizarre

Par contre, dans la deuxime, vous rcuprez toujours six lignes, mais vous devriez commencer
au quatrime plus petit id, puisquon a demand un dcalage de trois lignes.

id espece sexe date_naissance nom commentaires

4 tortue F 2009-08-03 NULL NULL


05 :12 :00
5 chat NULL 2010-10-03 16 :44 :00 Choupi N sans oreille gauche

6 tortue F 2009-06-13 08 :17 :00 Bobosse Carapace bizarre

7 chien F 2008-12-06 05 :18 :00 Caroline NULL

67
2 MySQL et les bases du langage SQL

id espece sexe date_naissance nom commentaires

8 chat M 2008-09-11 15 :38 :00 Bagherra NULL


9 tortue NULL 2010-08-23 05 :18 :00 NULL NULL

Exemple avec un seul paramtre :

SELECT *
FROM Animal
ORDER BY id
LIMIT 10 ;

Cette requte est donc quivalente :

SELECT *
FROM Animal
ORDER BY id
LIMIT 10 OFFSET 0 ;

2.8.5.2 Syntaxe alternative

MySQL accepte une autre syntaxe pour la clause LIMIT. Ce nest cependant pas la norme SQL donc
idalement vous devriez toujours utiliser la syntaxe officielle. Vous vous apercevrez toutefois que
cette syntaxe est normment usite, je ne pouvais donc pas ne pas la mentionner

SELECT *
FROM Animal
ORDER BY id
LIMIT [decalage, ]nombre_de_lignes;

Tout comme pour la syntaxe officielle, le dcalage nest pas obligatoire, et vaudra 0 par dfaut. Si
vous le prcisez, noubliez pas la virgule entre le dcalage et le nombre de lignes dsires.

2.8.5.3 En rsum

La commande SELECT permet dafficher des donnes.


La clause WHERE permet de prciser des critres de slection.
Il est possible de trier les donnes grce ORDER BY, selon un ordre ascendant (ASC) ou
descendant (DESC).
Pour liminer les doublons, on utilise le mot-cl DISTINCT, juste aprs SELECT.
LIMIT nb_lignes OFFSET decalage permet de slectionner uniquement nb_lignes de
rsultats, avec un certain dcalage.

68
2.9 largir les possibilits de la clause WHERE

2.9 largir les possibilits de la clause WHERE


Dans le chapitre prcdent, vous avez dcouvert la commande SELECT, ainsi que plusieurs clauses
permettant de restreindre et dordonner les rsultats selon diffrents critres. Nous allons main-
tenant revenir plus particulirement sur la clause WHERE. Jusquici, les conditions permises par
WHERE taient trs basiques. Mais cette clause offre bien dautres possibilits parmi lesquelles :

la comparaison avec une valeur incomplte (chercher les animaux dont le nom commence
par une certaine lettre par exemple) ;
la comparaison avec un intervalle de valeurs (entre 2 et 5 par exemple) ;
la comparaison avec un ensemble de valeurs (comparaison avec 5, 6, 10 ou 12 par exemple).

2.9.1 Recherche approximative


Pour linstant, nous avons vu huit oprateurs de comparaison :

Oprateur Signification

= gal
< infrieur
<= infrieur ou gal
> suprieur
>= suprieur ou gal
<> ou != diffrent
<=> gal (valable pour NULL aussi)

lexception de <=> qui est un peu particulier, ce sont les oprateurs classiques, que vous retrou-
verez dans tous les langages informatiques. Cependant, il arrive que ces oprateurs ne soient pas
suffisants. En particulier pour des recherches sur des chanes de caractres. En effet, comment
faire lorsquon ne sait pas si le mot que lon recherche est au singulier ou au pluriel par exemple ?
Ou si lon cherche toutes les lignes dont le champ commentaires contient un mot particulier ?

Pour ce genre de recherches, loprateur LIKE est trs utile, car il permet de faire des recherches
en utilisant des jokers, cest--dire des caractres qui reprsentent nimporte quel caractre.

Deux jokers existent pour LIKE :

'%' : qui reprsente nimporte quelle chane de caractres, quelle que soit sa longueur (y
compris une chane de longueur 0) ;
'_' : qui reprsente un seul caractre (ou aucun).

Quelques exemples :

'b%' cherchera toutes les chanes de caractres commenant par 'b' ('brocoli',
'bouli', 'b')
'B_' cherchera toutes les chanes de caractres contenant une ou deux lettres dont la pre-
mire est 'b' ('ba', 'bf', 'b')
'%ch%ne' cherchera toutes les chanes de caractres contenant 'ch' et finissant par
'ne' ('chne', 'chine', 'chine', 'le pays le plus peupl du monde est
la Chine')

69
2 MySQL et les bases du langage SQL

'_ch_ne' cherchera toutes les chanes de caractres commenant par 'ch', ventuelle-
ment prcdes dune seule lettre, suivies de zro ou dun caractre au choix et enfin se
terminant par 'ne' ('chine', 'chne', 'echine')

2.9.1.0.1 Rechercher % ou _ Comment faire si vous cherchez une chane de caractres


contenant % ou ? videmment, si vous crivez LIKE '%' ou LIKE '_', MySQL vous donnera absolument
toutes les chanes de caractres dans le premier cas, et toutes les chanes de 0 ou 1 caractre dans le deuxime. Il
faut donc signaler MySQL que vous ne dsirez pas utiliser ||%|| ou |||| en tant que joker, mais bien en tant
que caractre de recherche. Pour a, il suffit de mettre le caractre dchappement ||||, dont je
vous ai dj parl, devant le '%' ou le '_'.

Exemple :

SELECT *
FROM Animal
WHERE commentaires LIKE '%\%%' ;

Rsultat :

id espece sexe date_naissance nom commentaires

42 chat F 2008-04-20 Bilba Sourde de loreille droite 80%


03 :20 :00

2.9.1.0.2 Exclure une chane de caractres Cest logique, mais je prcise quand mme
(et puis a fait un petit rappel) : loprateur logique NOT est utilisable avec LIKE. Si lon veut
rechercher les animaux dont le nom ne contient pas la lettre ||a||, on peut donc crire :

SELECT *
FROM Animal
WHERE nom NOT LIKE '%a%' ;

2.9.1.1 Sensibilit la casse

Vous laurez peut-tre remarqu en faisant des essais, LIKE 'chane de caractres' nest
pas sensible la casse (donc aux diffrences majuscules-minuscules). Pour rappel, ceci est d
linterclassement. Nous avons gard linterclassement par dfaut du jeu de caractre UTF-8, qui
nest pas sensible la casse. Si vous dsirez faire une recherche sensible la casse, vous pouvez
dfinir votre chane de recherche comme une chane de type binaire, et non plus comme une
simple chane de caractres :

SELECT *
FROM Animal
WHERE nom LIKE '%Lu%' ; -- insensible la casse

SELECT *
FROM Animal
WHERE nom LIKE BINARY '%Lu%' ; -- sensible la casse

70
2.9 largir les possibilits de la clause WHERE

2.9.1.2 Recherche dans les numriques

Vous pouvez bien entendu utiliser des chiffres dans une chane de caractres. Aprs tout, ce
sont des caractres comme les autres. Par contre, utiliser LIKE sur un type numrique (INT par
exemple), cest dj plus tonnant. Et pourtant, MySQL le permet. Attention cependant, il sagit
bien dune particularit MySQL, qui prend souvent un malin plaisir tendre la norme SQL pure.

LIKE '1%' sur une colonne de type numrique trouvera donc des nombres comme 10, 1000, 153

SELECT *
FROM Animal
WHERE id LIKE '1%' ;

2.9.2 Recherche dans un intervalle


Il est possible de faire une recherche sur un intervalle laide uniquement des oprateurs de com-
paraison >= et <=. Par exemple, on peut rechercher les animaux qui sont ns entre le 5 janvier 2008
et le 23 mars 2009 de la manire suivante :

SELECT *
FROM Animal
WHERE date_naissance <= '2009-03-23'
AND date_naissance >= '2008-01-05' ;

a fonctionne trs bien. Cependant, SQL dispose dun oprateur spcifique pour les intervalles,
qui pourrait vous viter les erreurs dinattention classiques (< au lieu de > par exemple) en plus de
rendre votre requte plus lisible et plus performante : BETWEEN minimum AND maximum (between
signifie entre en anglais). La requte prcdente peut donc scrire :

SELECT *
FROM Animal
WHERE date_naissance BETWEEN '2008-01-05' AND '2009-03-23' ;

BETWEEN peut sutiliser avec des dates, mais aussi avec des nombres (BETWEEN 0 AND 100) ou
avec des chanes de caractres (BETWEEN 'a' AND 'd') auquel cas cest lordre alphabtique
qui sera utilis (toujours insensible la casse sauf si lon utilise des chanes binaires : BETWEEN
BINARY 'a' AND BINARY 'd'). Bien videmment, on peut aussi exclure un intervalle avec NOT
BETWEEN.

2.9.3 Set de critres


Le dernier oprateur utiliser dans la clause WHERE que nous verrons dans ce chapitre est IN. Ce
petit mot de deux lettres, bien souvent mconnu des dbutants, va probablement vous permettre
dconomiser du temps et des lignes.

Imaginons que vous vouliez rcuprer les informations des animaux rpondant aux doux noms de
Moka, Bilba, Tortilla, Balou, Dana, Redbul et Gingko. Jusqu maintenant, vous auriez sans doute
fait quelque chose comme a :

SELECT *
FROM Animal

71
2 MySQL et les bases du langage SQL

WHERE nom = 'Moka'


OR nom = 'Bilba'
OR nom = 'Tortilla'
OR nom = 'Balou'
OR nom = 'Dana'
OR nom = 'Redbul'
OR nom = 'Gingko' ;

Un peu fastidieux non :( ? Eh bien rjouissez-vous, car IN est dans la place ! Cet oprateur vous
permet de faire des recherches parmi une liste de valeurs. Parfait pour nous donc, qui voulons
rechercher les animaux correspondant une liste de noms. Voici la manire dutiliser IN :

SELECT *
FROM Animal
WHERE nom IN ('Moka', 'Bilba', 'Tortilla', 'Balou', 'Dana', 'Redbul', 'Gingko');

Cest quand mme plus agrable crire ! :soleil :

2.9.3.1 En rsum

Loprateur LIKE permet de faire des recherches approximatives, grce aux deux carac-
tres joker : '%' (qui reprsente 0 ou plusieurs caractres) et '_' (qui reprsente 0 ou 1
caractre).
Loprateur BETWEEN permet de faire une recherche sur un intervalle. WHERE colonne
BETWEEN a AND b tant quivalent WHERE colonne >= a AND colonne <= b.
Enfin, loprateur IN permet de faire une recherche sur une liste de valeurs.

2.10 Suppression et modification de donnes


Vous savez comment insrer des donnes, vous savez comment les slectionner et les ordonner
selon les critres de votre choix, il est temps maintenant dapprendre les supprimer et les modi-
fier ! Avant cela, un petit dtour par le client mysqldump, qui vous permet de sauvegarder vos bases
de donnes. Je ne voudrais en effet pas vous lcher dans le chapitre de suppression de donnes
sans que vous nayez la possibilit de faire un backup de votre base. Je vous connais, vous allez
faire des btises, et vous direz encore que cest de ma faute ;)

2.10.1 Sauvegarde dune base de donnes


Il est bien utile de pouvoir sauvegarder facilement sa base de donnes, et trs important de la
sauvegarder rgulirement. Une mauvaise manipulation (ou un mchant pirate :pirate : sil sagit
dun site web) et toutes les donnes peuvent disparatre. MySQL dispose donc dun outil spcia-
lement ddi la sauvegarde des donnes sous forme de fichiers texte : mysqldump.

Cette fonction de sauvegarde sutilise partir de la console. Vous devez donc tre dconnects de
MySQL pour la lancer. Si cest votre cas, tapez simplement exit

Vous tes maintenant dans la console Windows (ou Mac, ou Linux). La manire classique de faire
une sauvegarde dune base de donnes est de taper la commande suivante :

72
2.10 Suppression et modication de donnes

mysqldump-uuser-p--optnom_de_la_base>sauvegarde.sql

Dcortiquons cette commande.

mysqldump : il sagit du client permettant de sauvegarder les bases. Rien de spcial si-
gnaler.
--opt : cest une option de mysqldump qui lance la commande avec une srie de para-
mtres qui font que la commande seffectue trs rapidement.
nom_de_la_base : vous lavez sans doute devin, cest ici quil faut indiquer le nom de la
base quon veut sauvegarder.
> sauvegarde.sql : le signe ||>|| indique que lon va donner la destination de ce qui va
tre gnr par la commande : sauvegarde.sql. Il sagit du nom du fichier qui contiendra
la sauvegarde de notre base. Vous pouvez bien sr lappeler comme bon vous semble.

Lancez la commande suivante pour sauvegarder elevage dans votre dossier courant (cest--dire
le dossier dans lequel vous tiez au moment de la connexion) :

mysqldump-usdz-p--optelevage>elevage_sauvegarde.sql

Puis, allez voir dans le dossier. Vous devriez y trouver un fichier elevage_sauvegarde.sql. Ouvrez-
le avec un diteur de texte. Vous pouvez voir nombre de commandes SQL qui servent la cration
des tables de la base de donnes, ainsi qu linsertion des donnes. Sajoutent cela quelques
commandes qui vont slectionner le bon encodage, etc.

[[information]] | Vous pouvez bien entendu sauver votre fichier dans un autre dossier que celui
o vous tes au moment de lancer la commande. Il suffit pour cela de prciser le chemin vers le
dossier dsir. Ex : C:\"Mes Documents"\mysql\sauvegardes\elevage_sauvegarde.sql
au lieu de elevage_sauvegarde.sql

La base de donnes est donc sauvegarde. Notez que la commande pour crer la base elle-mme
nest pas sauve. Donc, si vous effacez votre base par mgarde, il vous faut dabord recrer la base
de donnes (avec CREATE DATABASE nom_base), puis excuter la commande suivante (dans la
console) :

mysqlnom_base<chemin_fichier_de_sauvegarde.sql

Concrtement, dans notre cas :

mysqlelevage<elevage_sauvegarde.sql

Ou, directement partir de MySQL :

USE nom_base;
source fichier_de_sauvegarde.sql;

Donc :

USE elevage;
source elevage_sauvegarde.sql;

[[information]] | Vous savez maintenant sauvegarder de manire simple vos bases de donnes.
Notez que je ne vous ai donn ici quune manire dutiliser mysqldump. En effet, cette commande
possde de nombreuses options. Si cela vous intresse, je vous renvoie la documentation de
MySQL qui sera toujours plus complte que moi.

73
2 MySQL et les bases du langage SQL

2.10.2 Suppression
La commande utilise pour supprimer des donnes est DELETE. Cette opration est irrversible,
soyez trs prudents ! On utilise la clause WHERE de la mme manire quavec la commande SELECT
pour prciser quelles lignes doivent tre supprimes.

DELETE FROM nom_table


WHERE critres;

Par exemple : Zoulou est mort, paix son me :ange : Nous allons donc le retirer de la base de
donnes.

DELETE FROM Animal


WHERE nom = 'Zoulou' ;

Et voil, plus de Zoulou :( !

Si vous dsirez supprimer toutes les lignes dune table, il suffit de ne pas prciser de clause WHERE.

DELETE FROM Animal;

[[erreur]] | Attention, je le rpte, cette opration est irrversible. Soyez toujours bien srs
davoir sous la main une sauvegarde de votre base de donnes au cas o vous regretteriez votre
geste (on ne pourra pas dire que je ne vous ai pas prvenus).

2.10.3 Modification
La modification des donnes se fait grce la commande UPDATE, dont la syntaxe est la suivante :

UPDATE nom_table
SET col1 = val1 [, col2 = val2, ...]
[WHERE ...];

Par exemple, vous tiez persuads que ce petit Pataud tait un mle mais, quelques semaines plus
tard, vous vous rendez compte de votre erreur. Il vous faut donc modifier son sexe, mais aussi son
nom. Voici la requte qui va vous le permettre :

UPDATE Animal
SET sexe='F', nom='Pataude'
WHERE id=21 ;

Vrifiez dabord chez vous que lanimal portant le numro didentification 21 est bien Pataud.
Jutilise ici la cl primaire (donc id) pour identifier la ligne modifier, car cest la seule manire
dtre sr que je ne modifierai que la ligne que je dsire. En effet, il est possible que plusieurs
animaux aient pour nom Pataud. Ce nest a priori pas notre cas, mais prenons tout de suite de
bonnes habitudes.

[[attention]] | Tout comme pour la commande DELETE, si vous omettez la clause WHERE dans un
UPDATE, la modification se fera sur toutes les lignes de la table. Soyez prudents !
La requte suivante changerait donc le commentaire de tous les animaux stocks dans la table
Animal (ne lexcutez pas).

UPDATE Animal
SET commentaires='modification de toutes les lignes' ;

74
2.10 Suppression et modication de donnes

2.10.3.1 En rsum

Le client mysqldump est un programme qui permet de sauvegarder facilement ses bases de
donnes.
La commande DELETE permet de supprimer des donnes.
La commande UPDATE permet de modifier des donnes.

75
3 Index, jointures et sous-requtes
Cette partie est au moins aussi indispensable que la premire. Vous y apprendrez ce que sont les
cls et les index, et surtout la manipulation de plusieurs tables dans une mme requte grce aux
jointures et aux sous-requtes.

3.1 Index
Un index est une structure qui reprend la liste ordonne des valeurs auxquelles il se rapporte.
Les index sont utiliss pour acclrer les requtes (notamment les requtes impliquant plusieurs
tables, ou les requtes de recherche), et sont indispensables la cration de cls, trangres et
primaires, qui permettent de garantir lintgrit des donnes de la base et dont nous discuterons
au chapitre suivant.

Au programme de ce chapitre :

Quest-ce quun index et comment est-il reprsent ?


En quoi un index peut-il acclrer une requte ?
Quels sont les diffrents types dindex ?
Comment crer (et supprimer) un index ?
Quest-ce que la recherche FULLTEXT et comment fonctionne-t-elle ?

3.1.0.1 Etat actuelle de la base de donnes

Note : les tables de test ne sont pas reprises

[[secret]] | sql | SET NAMES utf8; |


| DROP TABLE IF EXISTS Animal; | | CREATE
TABLE Animal ( | id smallint(6) UNSIGNED NOT NULL AUTO_INCREMENT, | espece
varchar(40) NOT NULL, | sexe char(1) DEFAULT NULL, | date_naissance
datetime NOT NULL, | nom varchar(30) DEFAULT NULL, | commentaires text,
| PRIMARY KEY (id) | ) ENGINE=InnoDB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8;
| | LOCK TABLES Animal WRITE; | INSERT INTO Animal VALUES | (1,'chien','M','2010-04-0
13:43:00','Rox','Mordille beaucoup'),(2,'chat',NULL,'2010-03-24 02:23:00','Roucky',NULL
15:02:00','Schtroumpfette',NULL), | (4,'tortue','F','2009-08-03 05:12:00',NULL,NULL),(5
16:44:00','Choupi','N sans oreille gauche'),(6,'tortue','F','2009-06-13
08:17:00','Bobosse','Carapace bizarre'), | (7,'chien','F','2008-12-06 05:18:00','Caroli
15:38:00','Bagherra',NULL),(9,'tortue',NULL,'2010-08-23 05:18:00',NULL,NULL),
| (10,'chien','M','2010-07-21 15:41:00','Bobo',NULL),(11,'chien','F','2008-02-20
15:45:00','Canaille',NULL),(12,'chien','F','2009-05-26 08:54:00','Cali',NULL),
| (13,'chien','F','2007-04-24 12:54:00','Rouquine',NULL),(14,'chien','F','2009-05-26
08:56:00','Fila',NULL),(15,'chien','F','2008-02-20 15:47:00','Anya',NULL),
| (16,'chien','F','2009-05-26 08:50:00','Louya',NULL),(17,'chien','F','2008-03-10
13:45:00','Welva',NULL),(18,'chien','F','2007-04-24 12:59:00','Zira',NULL),

77
3 Index, jointures et sous-requtes

| (19,'chien','F','2009-05-26 09:02:00','Java',NULL),(20,'chien','M','2007-04-24
12:45:00','Balou',NULL),(21,'chien','F','2008-03-10 13:43:00','Pataude',NULL),
| (22,'chien','M','2007-04-24 12:42:00','Bouli',NULL),(24,'chien','M','2007-04-12
05:23:00','Cartouche',NULL),(25,'chien','M','2006-05-14 15:50:00','Zambo',NULL),
| (26,'chien','M','2006-05-14 15:48:00','Samba',NULL),(27,'chien','M','2008-03-10
13:40:00','Moka',NULL),(28,'chien','M','2006-05-14 15:40:00','Pilou',NULL),
| (29,'chat','M','2009-05-14 06:30:00','Fiero',NULL),(30,'chat','M','2007-03-12
12:05:00','Zonko',NULL),(31,'chat','M','2008-02-20 15:45:00','Filou',NULL),
| (32,'chat','M','2007-03-12 12:07:00','Farceur',NULL),(33,'chat','M','2006-05-19
16:17:00','Caribou',NULL),(34,'chat','M','2008-04-20 03:22:00','Capou',NULL),
| (35,'chat','M','2006-05-19 16:56:00','Raccou','Pas de queue depuis la
naissance'),(36,'chat','M','2009-05-14 06:42:00','Boucan',NULL),(37,'chat','F','2006-05
16:06:00','Callune',NULL), | (38,'chat','F','2009-05-14 06:45:00','Boule',NULL),(39,'ch
03:26:00','Zara',NULL),(40,'chat','F','2007-03-12 12:00:00','Milla',NULL),
| (41,'chat','F','2006-05-19 15:59:00','Feta',NULL),(42,'chat','F','2008-04-20
03:20:00','Bilba','Sourde de l''oreille droite 80%'),(43,'chat','F','2007-03-12
11:54:00','Cracotte',NULL), | (44,'chat','F','2006-05-19 16:16:00','Cawette',NULL),(45,
18:17:00','Nikki',NULL),(46,'tortue','F','2009-03-24 08:23:00','Tortilla',NULL),
| (47,'tortue','F','2009-03-26 01:24:00','Scroupy',NULL),(48,'tortue','F','2006-03-15
14:56:00','Lulla',NULL),(49,'tortue','F','2008-03-15 12:02:00','Dana',NULL),
| (50,'tortue','F','2009-05-25 19:57:00','Cheli',NULL),(51,'tortue','F','2007-04-01
03:54:00','Chicaca',NULL),(52,'tortue','F','2006-03-15 14:26:00','Redbul','Insomniaque'
| (53,'tortue','M','2007-04-02 01:45:00','Spoutnik',NULL),(54,'tortue','M','2008-03-16
08:20:00','Bubulle',NULL),(55,'tortue','M','2008-03-15 18:45:00','Relou','Surpoids'),
| (56,'tortue','M','2009-05-25 18:54:00','Bulbizard',NULL),(57,'perroquet','M','2007-03
19:36:00','Safran',NULL),(58,'perroquet','M','2008-02-20 02:50:00','Gingko',NULL),
| (59,'perroquet','M','2009-03-26 08:28:00','Bavard',NULL),(60,'perroquet','F','2009-03
07:55:00','Parlotte',NULL); | UNLOCK TABLES; |

3.1.1 Quest-ce quun index ?


Revoyons la dfinition dun index.

Structure de donnes qui reprend la liste ordonne des valeurs auxquelles il se rap-
porte. Source : Dfinition

Lorsque vous crez un index sur une table, MySQL stocke cet index sous forme dune structure
particulire, contenant les valeurs des colonnes impliques dans lindex. Cette structure stocke
les valeurs tries et permet daccder chacune de manire efficace et rapide. Petit schma expli-
catif dans le cas dun index sur lid de la table Animal (je ne prends que les neuf premires lignes
pour ne pas surcharger).

[[attention]] | Pour permettre une comprhension plus facile, je reprsente ici lindex sous forme
de table. En ralit, par dfaut, MySQL stocke les index dans une structure de type arbre (lindex
est alors de type BTREE). Le principe est cependant le mme.

Les donnes dAnimal ne sont pas stockes suivant un ordre intelligible pour nous. Par contre,
lindex sur lid est tri simplement par ordre croissant. Cela permet de grandement acclrer toute
recherche faite sur cet id.

78
3.1 Index

Figure 3.1 Index sur lid

Imaginons en effet, que nous voulions rcuprer toutes les lignes dont lid est infrieur ou gal 5.
Sans index, MyQSL doit parcourir toutes les lignes une une. Par contre, grce lindex, ds quil
tombe sur la ligne dont lid est 6, il sait quil peut sarrter, puisque toutes les lignes suivantes au-
ront un id suprieur ou gal 6. Dans cet exemple, on ne gagne que quelques lignes, mais imaginez
une table contenant des milliers de lignes. Le gain de temps peut tre assez considrable.

Par ailleurs, avec les id tris par ordre croissant, pour rechercher un id particulier, MySQL nest
pas oblig de simplement parcourir les donnes ligne par ligne. Il peut utiliser des algorithmes de
recherche puissants (comme la recherche dichotomique), toujours afin dacclrer la recherche.

Mais pourquoi ne pas simplement trier la table complte sur la base de la colonne id ? Pourquoi
crer et stocker une structure spcialement pour lindex ? Tout simplement parce quil peut y
avoir plusieurs index sur une mme table, et que lordre des lignes pour chacun de ces index nest
pas ncessairement le mme. Par exemple, nous pouvons crer un second index pour notre table
Animal, sur la colonne date_naissance.

Comme vous pouvez le voir, lordre nest pas du tout le mme.

3.1.1.1 Intrt des index

Vous devriez avoir compris maintenant que tout lintrt des index est dacclrer les requtes
qui utilisent des colonnes indexes comme critres de recherche. Par consquent, si vous savez
que dans votre application, vous ferez normment de recherches sur la colonne X, ajoutez donc
un index sur cette colonne, vous ne vous en porterez que mieux.

Les index permettent aussi dassurer lintgrit des donnes de la base. Pour cela, il existe en fait
plusieurs types dindex diffrents, et deux types de cls. Lorsque je parle de garantir lintgrit
de vos donnes, cela signifie en gros garantir la qualit de vos donnes, vous assurer que vos

79
3 Index, jointures et sous-requtes

Figure 3.2 Index sur id et date_naissance

donnes ont du sens. Par exemple, soyez srs que vous ne faites pas rfrence un client dans la
table Commande, alors quen ralit, ce client nexiste absolument pas dans la table Client.

3.1.1.2 Dsavantages

Si tout ce que fait un index, cest acclrer les requtes utilisant des critres de recherche cor-
respondants, autant en mettre partout, et en profiter chaque requte ! Sauf quvidemment, ce
nest pas si simple, les index ont deux inconvnients.

Ils prennent de la place en mmoire


Ils ralentissent les requtes dinsertion, modification et suppression, puisqu chaque fois,
il faut remettre lindex jour en plus de la table.

Par consquent, najoutez pas dindex lorsque ce nest pas vraiment utile.

3.1.1.3 Index sur plusieurs colonnes

Reprenons lexemple dune table appele Client, qui reprend les informations des clients dune
socit. Elle se prsente comme suit :

id nom prenom init_2e_prenom email

1 Dupont Charles T charles.dupont@email.com


2 Franois Damien V fdamien@email.com
3 Vandenbush Guillaume A guillaumevdb@email.com

4 Dupont Valrie C valdup@email.com


5 Dupont Valrie G dupont.valerie@email.com
6 Franois Martin D mdmartin@email.com
7 Caramou Arthur B leroiarthur@email.com
8 Boulian Grard M gebou@email.com
9 Loupiot Laura F loulau@email.com
10 Sunna Christine I chrichrisun@email.com

Vous avez bien sr un index sur la colonne id, mais vous constatez que vous faites normment
de recherches par nom, prnom et initiale du second prnom. Vous pourriez donc faire trois in-
dex, un pour chacune de ces colonnes. Mais, si vous faites souvent des recherches sur les trois
colonnes la fois, il vaut encore mieux faire un seul index, sur les trois colonnes (nom, prenom,
80
init_2e_prenom). Lindex contiendra donc les valeurs des trois colonnes et sera tri par nom, en-
suite par prnom, et enfin par initiale (lordre des colonnes a donc de limportance !).
3.1 Index

Figure 3.3 Index sur trois colonnes

de recherche ?

H bien non ! MySQl est beaucoup trop fort : il est capable de tirer parti de votre index sur (nom,
prenom, init_2e_prenom) pour certaines autres recherches. En effet, si lon reprsente les index sous
forme de tables, voici ce que cela donnerait pour les quatre index (prenom), (nom), (nom, prenom) et
(nom, prenom, init_2e_prenom).

Figure 3.4 Index par la gauche

[[question]] | Remarquez-vous quelque chose dintressant ?

Oui ! Bien vu ! Lindex (nom, prenom) correspond exactement aux deux premires colonnes de lin-
dex (nom, prenom, init_2e_prenom) ; pas seulement au niveau des valeurs mais surtout au niveau de
lordre. Et lindex (nom) correspond la premire colonne de lindex (nom, prenom, init_2e_prenom).

Or, je vous ai dit que lorsque vous faites une recherche sur le nom, le prnom et linitiale avec

81
3 Index, jointures et sous-requtes

lindex (nom, prenom, init_2e_prenom), MySQL regarde dabord le nom, puis le prnom, et pour finir
linitiale. Donc, si vous ne faites une recherche que sur le nom et le prnom, MySQL va intelli-
gemment utiliser lindex (nom, prenom, init_2e_prenom) : il va simplement laisser tomber ltape
de linitiale du second prnom. Idem si vous faites une recherche sur le nom : MySQL se basera
uniquement sur la premire colonne de lindex existant.

Par consquent, pas besoin de dfinir un index (nom, prenom) ou un index (nom). Ils sont en quelque
sorte dj prsents.

Mais quen est-il des index (prenom), ou (prenom, init_2e_prenom) ? Vous pouvez voir que la table
contenant lindex (prenom) ne correspond aucune colonne dun index existant (au niveau de
lordre des lignes). Par consquent, si vous voulez un index sur (prenom), il vous faut le crer.
Mme chose pour (prenom, init_2e_prenom) ou (nom, init_2e_prenom).

Figure 3.5 Index par la gauche

On parle dindex par la gauche. Donc si lon prend des sous-parties dindex existant en prenant
des colonnes par la gauche, ces index existent. Mais si lon commence par la droite ou que lon
saute une colonne, ils nexistent pas et doivent ventuellement tre crs.

3.1.1.4 Index sur des colonnes de type alphanumrique

3.1.1.4.1 Types CHAR et VARCHAR Lorsque lon indexe une colonne de type VARCHAR
ou CHAR (comme la colonne nom de la table Client par exemple), on peut dcomposer lindex de la
manire suivante :

1e 2e 3e 4e 5e 6e 7e 8e 9e 10e
lettre lettre lettre lettre lettre lettre lettre lettre lettre lettre

B o u l i a n
C a r a m o u
D u p o n t
D u p o n t
D u p o n t
F r a n o i s
F r a n o i s
L o u p i o t
S u n n a
V a n d e n b u s h

82
3.1 Index

nom

Boulian
Caramou
Dupont
Dupont
Dupont
Franois
Franois
Loupiot
Sunna
Vandenbush

[[question]] | En quoi cela nous intresse-t-il ?

Cest trs simple. Ici, nous navons que des noms assez courts. La colonne nom peut par exemple
tre de type VARCHAR(30). Mais imaginez une colonne de type VARCHAR(150), qui contient des
titres de livres par exemple. Si lon met un index dessus, MySQL va indexer jusqu 150 caractres.
Or, il est fort probable que les 25-30 premiers caractres du titre suffisent trier ceux-ci. Au
pire, un ou deux ne seront pas exactement la bonne place, mais les requtes en seraient dj
grandement acclres.

Il serait donc plutt pratique de dire MySQL : Indexe cette colonne, mais base-toi seulement
sur les x premiers caractres. Et cest possible videmment (sinon je ne vous en parlerais pas :p ),
et cest mme trs simple. Lorsque lon crera lindex sur la colonne titre_livre, il suffira dindiquer
un nombre entre parenthses : titre_livre(25) par exemple. Ce nombre tant bien sr le nombre de
caractres (dans le sens de la lecture, donc partir de la gauche) prendre en compte pour lindex.

Le fait dutiliser ces index partiels sur des champs alphanumriques permet de gagner de la place
(un index sur 150 lettres prend videmment plus de place quun index sur 20 lettres), et si la
longueur est intelligemment dfinie, lacclration permise par lindex sera la mme que si lon
avait pris la colonne entire.

3.1.1.4.2 Types BLOB et TEXT (et drivs) Si vous mettez un index sur une colonne
de type BLOB ou TEXT (ou un de leurs drivs), MySQL exige que vous prcisiez un nombre de
caractres prendre en compte. Et heureusement Vu la longueur potentielle de ce que lon
stocke dans de telles colonnes.

3.1.2 Les diffrents types dindex

En plus des index simples, que je viens de vous dcrire, il existe trois types dindex qui ont
des proprits particulires. Les index UNIQUE, les index FULLTEXT, et enfin les index SPATIAL.
Je ne dtaillerai pas les proprits et utilisations des index SPATIAL. Sachez simplement quil
sagit dun type dindex utilis dans des bases de donnes recensant des donnes spatiales (tiens
donc ! :p ), donc des points, des lignes, des polygones

Sur ce, cest parti !

83
3 Index, jointures et sous-requtes

3.1.2.1 Index UNIQUE

Avoir un index UNIQUE sur une colonne (ou plusieurs) permet de sassurer que jamais vous nin-
srerez deux fois la mme valeur (ou combinaison de valeurs) dans la table.

Par exemple, vous crez un site internet, et vous voulez le doter dun espace membre. Chaque
membre devra se connecter grce un pseudo et un mot de passe. Vous avez donc une table
Membre, qui contient 4 colonnes : id, pseudo, mot_de_passe et date_inscription. Deux membres peuvent
avoir le mme mot de passe, pas de problme. Par contre, que se passerait-il si deux membres
avaient le mme pseudo ? Lors de la connexion, il serait impossible de savoir quel mot de passe uti-
liser et sur quel compte connecter le membre. Il faut donc absolument viter que deux membres
utilisent le mme pseudo. Pour cela, on va utiliser un index UNIQUE sur la colonne pseudo de la
table Membre.

Autre exemple, dans notre table Animal cette fois. Histoire de ne pas confondre les animaux, vous
prenez la dcision de ne pas nommer de la mme manire deux animaux de la mme espce. Il
peut donc ny avoir quune seule tortue nomme Toto. Un chien peut aussi se nommer Toto, mais
un seul. La combinaison (espce, nom) doit nexister quune et une seule fois dans la table. Pour
sen assurer, il suffit de crer un index unique sur les colonnes espece et nom (un seul index, sur
les deux colonnes).

3.1.2.1.1 Contraintes Lorsque vous mettez un index UNIQUE sur une table, vous ne mettez
pas seulement un index, vous ajoutez surtout une contrainte. Les contraintes sont une notion
importante en SQL. Sans le savoir, ou sans savoir que ctait appel comme a, vous en avez
dj utilis. En effet, lorsque vous empchez une colonne daccepter NULL, vous lui mettez une
contrainte NOT NULL. De mme, les valeurs par dfaut que vous pouvez donner aux colonnes
sont des contraintes. Vous contraignez la colonne prendre une certaine valeur si aucune autre
nest prcise.

3.1.2.2 Index FULLTEXT

Un index FULLTEXT est utilis pour faire des recherches de manire puissante et rapide sur un
texte. Seules les tables utilisant le moteur de stockage MyISAM peuvent avoir un index FULLTEXT,
et cela uniquement sur les colonnes de type CHAR, VARCHAR ou TEXT (ce qui est plutt logique).

Une diffrence importante (trs importante !!! :diable : ) entre les index FULLTEXT et les index
classiques (et UNIQUE) est que lon ne peut plus utiliser les fameux index par la gauche dont
je vous ai parl prcdemment. Donc, si vous voulez faire des recherches fulltext sur deux co-
lonnes (parfois lune, parfois lautre, parfois les deux ensemble), il vous faudra crer trois index
FULLTEXT : (colonne1), (colonne2) et (colonne1, colonne2).

3.1.3 Cration et suppression des index

Les index sont reprsents par le mot-cl INDEX (surprise ! o_O ) ou KEY et peuvent tre crs de
deux manires :

soit directement lors de la cration de la table ;


soit en les ajoutant par la suite.

84
3.1 Index

3.1.3.1 Ajout des index lors de la cration de la table

Ici aussi, deux possibilits : vous pouvez prciser dans la description de la colonne quil sagit dun
index, ou lister les index par la suite.

3.1.3.1.1 Index dans la description de la colonne [[attention]] | Seuls les index clas-
siques et uniques peuvent tre crs de cette manire.

Je rappelle que la description de la colonne se rapporte lendroit o vous indiquez le type de


donnes, si la colonne peut contenir NULL, etc. Il est donc possible, ce mme endroit, de prciser
si la colonne est un index.

CREATE TABLE nom_table (


colonne1 INT KEY, -- Cre un index simple sur colonne1
colonne2 VARCHAR(40) UNIQUE, -- Cre un index unique sur colonne2
);

Quelques petites remarques ici :

Avec cette syntaxe, seul le mot KEY peut tre utilis pour dfinir un index simple. Ailleurs,
vous pourrez utiliser KEY ou INDEX.
Pour dfinir un index UNIQUE de cette manire, on nutilise que le mot-cl UNIQUE, sans
le faire prcder de INDEX ou de KEY (comme ce sera le cas avec dautres syntaxes).
Il nest pas possible de dfinir des index composites (sur plusieurs colonnes) de cette
manire.
Il nest pas non plus possible de crer un index sur une partie de la colonne (les x premiers
caractres).

3.1.3.1.2 Liste dindex Lautre possibilit est dajouter les index la suite des colonnes, en
sparant chaque lment par une virgule :

CREATE TABLE nom_table (


colonne1 description_colonne1,
[colonne2 description_colonne2,
colonne3 description_colonne3,
...,]
[PRIMARY KEY (colonne_cl_primaire)],
[INDEX [nom_index] (colonne1_index [, colonne2_index, ...]]
)
[ENGINE=moteur];

Exemple : si lon avait voulu crer la table Animal avec un index sur la date de naissance, et un
autre sur les 10 premires lettres du nom, on aurait pu utiliser la commande suivante :

CREATE TABLE Animal (


id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
espece VARCHAR(40) NOT NULL,
sexe CHAR(1),
date_naissance DATETIME NOT NULL,
nom VARCHAR(30),
commentaires TEXT,

85
3 Index, jointures et sous-requtes

PRIMARY KEY (id),


INDEX ind_date_naissance (date_naissance), -- index sur la date de naissance
INDEX ind_nom (nom(10)) -- index sur le nom (le chiffre entre p
)
ENGINE=INNODB;

Vous ntes pas obligs de prciser un nom pour votre index. Si vous ne le faites pas, MySQL en
crera un automatiquement pour vous. Je prfre nommer mes index moi-mme plutt que de
laisser MySQL crer un nom par dfaut, et respecter certaines conventions personnelles. Ainsi,
mes index auront toujours le prfixe ind suivi du ou des nom(s) des colonnes concernes, le tout
spar par des _. Il vous appartient de suivre vos propres conventions bien sr. Limportant
tant de vous y retrouver.

Et pour ajouter des index UNIQUE ou FULLTEXT, cest le mme principe :

CREATE TABLE nom_table (


colonne1 INT NOT NULL,
colonne2 VARCHAR(40),
colonne3 TEXT,
UNIQUE [INDEX] ind_uni_col2 (colonne2), -- Cre un index UNIQUE sur la colonne2
FULLTEXT [INDEX] ind_full_col3 (colonne3) -- Cre un index FULLTEXT sur la colonn
)
ENGINE=MyISAM;

Exemple : cration de la table Animal comme prcdemment, en ajoutant un index UNIQUE sur
(nom, espece).

CREATE TABLE Animal (


id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
espece VARCHAR(40) NOT NULL,
sexe CHAR(1),
date_naissance DATETIME NOT NULL,
nom VARCHAR(30),
commentaires TEXT,
PRIMARY KEY (id),
INDEX ind_date_naissance (date_naissance),
INDEX ind_nom (nom(10)),
UNIQUE INDEX ind_uni_nom_espece (nom, espece) -- Index sur le nom et l'espece
)
ENGINE=INNODB;

[[information]] | Avec cette syntaxe, KEY ne peut tre utilis que pour les index simples et INDEX
pour tous les types dindex. Bon, je sais que cest assez pnible retenir, mais en gros, utilisez
INDEX partout, sauf pour dfinir un index dans la description mme de la colonne, et vous naurez
pas de problme.

3.1.3.2 Ajout des index aprs cration de la table

En dehors du fait que parfois vous ne penserez pas tout au moment de la cration de votre table,
il peut parfois tre intressant de crer les index aprs la table. En effet, je vous ai dit que lajout

86
3.1 Index

dindex sur une table ralentissait lexcution des requtes dcriture (insertion, suppression, mo-
dification de donnes). Par consquent, si vous crez une table, que vous comptez remplir avec
un grand nombre de donnes immdiatement, grce la commande LOAD DATA INFILE par
exemple, il vaut bien mieux crer la table, la remplir, et ensuite seulement crer les index voulus
sur cette table.

Il existe deux commandes permettant de crer des index sur une table existante : ALTER TABLE,
que vous connaissez dj un peu, et CREATE INDEX. Ces deux commandes sont quivalentes, uti-
lisez celle qui vous parle le plus.

3.1.3.2.1 Ajout avec ALTER TABLE


ALTER TABLE nom_table
ADD INDEX [nom_index] (colonne_index [, colonne2_index ...]); --Ajout d'un index simple

ALTER TABLE nom_table


ADD UNIQUE [nom_index] (colonne_index [, colonne2_index ...]); --Ajout d'un index UNIQU

ALTER TABLE nom_table


ADD FULLTEXT [nom_index] (colonne_index [, colonne2_index ...]); --Ajout d'un index FUL

Contrairement ce qui se passait pour lajout dune colonne, il est ici obligatoire de prciser INDEX
(ou UNIQUE, ou FULLTEXT) aprs ADD. Dans le cas dun index multi-colonnes, il suffit comme
dhabitude de toutes les indiquer entre parenthses, spares par des virgules.

Reprenons la table Test_tuto, utilise pour tester ALTER TABLE, et ajoutons-lui un index sur la
colonne nom :

ALTER TABLE Test_tuto


ADD INDEX ind_nom (nom);

Si vous affichez maintenant la description de votre table Test_tuto, vous verrez que dans la colonne
Key, il est indiqu MUL pour la colonne nom. Lindex a donc bien t cr.

3.1.3.2.2 Ajout avec CREATE INDEX La syntaxe de CREATE INDEX est trs simple :

CREATE INDEX nom_index


ON nom_table (colonne_index [, colonne2_index ...]); -- Cre un index simple

CREATE UNIQUE INDEX nom_index


ON nom_table (colonne_index [, colonne2_index ...]); -- Cre un index UNIQUE

CREATE FULLTEXT INDEX nom_index


ON nom_table (colonne_index [, colonne2_index ...]); -- Cre un index FULLTEXT

Exemple : lquivalent de la commande ALTER TABLE que nous avons utilise pour ajouter un
index sur la colonne nom est donc :

CREATE INDEX ind_nom


ON Test_tuto (nom);

87
3 Index, jointures et sous-requtes

3.1.3.3 Complment pour la cration dun index UNIQUE - le cas des contraintes

Vous vous rappelez, jespre, que les index UNIQUE sont ce quon appelle des contraintes.

Or, lorsque vous crez un index UNIQUE, vous pouvez explicitement crer une contrainte. Cest
fait automatiquement bien sr si vous ne le faites pas, mais ne soyez donc pas surpris de voir
apparatre le mot CONSTRAINT, cest a quil se rfre.

Pour pouvoir crer explicitement une contrainte lors de la cration dun index UNIQUE, vous devez
crer cet index soit lors de la cration de la table, en listant lindex (et la contrainte) la suite des
colonnes, soit aprs la cration de la table, avec la commande ALTER TABLE.

CREATE TABLE nom_table (


colonne1 INT NOT NULL,
colonne2 VARCHAR(40),
colonne3 TEXT,
CONSTRAINT [symbole_contrainte] UNIQUE [INDEX] ind_uni_col2 (colonne2)
);

ALTER TABLE nom_table


ADD CONSTRAINT [symbole_contrainte] UNIQUE ind_uni_col2 (colonne2);

Il nest pas obligatoire de donner un symbole (un nom en fait) la contrainte. Dautant plus que
dans le cas des index, vous pouvez donner un nom lindex (ici : ind_uni_col).

3.1.3.4 Suppression dun index

Rien de bien compliqu :

ALTER TABLE nom_table


DROP INDEX nom_index;

Notez quil nexiste pas de commande permettant de modifier un index. Le cas chant, il vous
faudra supprimer, puis recrer votre index avec vos modifications.

3.1.4 Recherches avec FULLTEXT


Nous allons maintenant voir comment utiliser la recherche FULLTEXT, qui est un outil trs puis-
sant, et qui peut se rvler trs utile.

Quelques rappels dabord :

un index FULLTEXT ne peut tre dfini que pour une table utilisant le moteur MyISAM ;
un index FULLTEXT ne peut tre dfini que sur une colonne de type CHAR, VARCHAR ou TEXT
les index par la gauche ne sont pas pris en compte par les index FULLTEXT

a, cest fait ! Nous allons maintenant passer la recherche proprement dite, mais avant, je vais
vous demander dexcuter les instructions SQL suivantes, qui servent crer la table que nous
utiliserons pour illustrer ce chapitre. Nous sortons ici du contexte de llevage danimaux. En
effet, toutes les tables que nous crerons pour llevage utiliseront le moteur de stockage InnoDB.

Pour illustrer la recherche FULLTEXT, je vous propose de crer la table Livre, contenant les co-
lonnes id (cl primaire), titre et auteur. Les recherches se feront sur les colonnes auteur et titre, s-
parment ou ensemble. Il faut donc crer trois index FULLTEXT : (auteur), (titre) et (auteur, titre).

88
3.1 Index

[[secret]] | sql | CREATE TABLE Livre ( |


id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
| auteur VARCHAR(50), | titre VARCHAR(200) | ) ENGINE = MyISAM; | |
INSERT INTO Livre (auteur, titre) | VALUES ('Daniel Pennac', 'Au bonheur
des ogres'), | ('Daniel Pennac', 'La Fe Carabine'), | ('Daniel Pennac',
'Comme un roman'), | ('Daniel Pennac', 'La Petite marchande de prose'),
| ('Jacqueline Harpman', 'Le Bonheur est dans le crime'), | ('Jacqueline
Harpman', 'La Dormition des amants'), | ('Jacqueline Harpman', 'La Plage
d''Ostende'), | ('Jacqueline Harpman', 'Histoire de Jenny'), | ('Terry
Pratchett', 'Les Petits Dieux'), | ('Terry Pratchett', 'Le Cinquime lphant'),
| ('Terry Pratchett', 'La Vrit'), | ('Terry Pratchett', 'Le Dernier hros'),
| ('Terry Goodkind', 'Le Temple des vents'), | ('Jules Verne', 'De la Terre
la Lune'), | ('Jules Verne', 'Voyage au centre de la Terre'), | ('Henri-Pierre
Roch', 'Jules et Jim'); | | CREATE FULLTEXT INDEX ind_full_titre | ON
Livre (titre); | | CREATE FULLTEXT INDEX ind_full_aut | ON Livre (auteur);
| | CREATE FULLTEXT INDEX ind_full_titre_aut | ON Livre (titre, auteur);
|

3.1.4.1 Comment fonctionne la recherche FULLTEXT ?

Lorsque vous faites une recherche FULLTEXT sur une chane de caractres, cette chane est d-
coupe en mots. **Est considr comme un mot : toute suite de caractres compose de lettres,
chiffres, tirets bas ||_|| et apostrophes ||||**. Par consquent, un mot compos, comme porte-
cls par exemple, sera considr comme deux mots : porte et cls. Chacun de ces mots sera
ensuite compar avec les valeurs des colonnes sur lesquelles se fait la recherche. Si la colonne
contient un des mots recherchs, on considre alors quelle correspond la recherche.

Lorsque MySQL compare la chane de caractres que vous lui avez donne, et les valeurs dans
votre table, il ne tient pas compte de tous les mots quil rencontre. Les rgles sont les suivantes :

les mots rencontrs dans au moins la moiti des lignes sont ignors (rgle des 50 %) ;
les mots trop courts (moins de quatre lettres) sont ignors ;
et les mots trop communs (en anglais, about, after, once, under, the) ne sont galement pas
pris en compte.

Par consquent, si vous voulez faire des recherches sur une table, il est ncessaire que cette table
comporte au moins trois lignes, sinon chacun des mots sera prsent dans au moins la moiti des
lignes et aucun ne sera pris en compte.

Il est possible de redfinir la longueur minimale des mots pris en compte, ainsi que la liste des
mots trop communs. Je nentrerai pas dans ces dtails ici, vous trouverez ces informations dans
la documentation officielle.

3.1.4.2 Les types de recherche

Il existe trois types de recherche FULLTEXT : la recherche naturelle, la recherche avec boolen, et
enfin la recherche avec extension de requte.

3.1.4.2.1 Recherche naturelle Lorsque lon fait une recherche naturelle, il suffit quun seul
mot de la chane de caractres recherche se retrouve dans une ligne pour que celle-ci apparaisse

89
3 Index, jointures et sous-requtes

dans les rsultats. Attention cependant au fait que le mot exact doit se retrouver dans la valeur
des colonnes de lindex FULLTEXT examin.

Voici la syntaxe utilise pour faire une recherche FULLTEXT :

SELECT * -- Vous mettez videmment les colonnes que vo


FROM nom_table
WHERE MATCH(colonne1[, colonne2, ...]) -- La (ou les) colonne(s) dans laquelle (ou le
AGAINST ('chane recherche'); -- La chane de caractres recherche, entre

Si lon veut prciser quon fait une recherche naturelle, on peut ajouter IN NATURAL LANGUAGE
MODE. Ce nest cependant pas obligatoire puisque la recherche naturelle est le mode de recherche
par dfaut.

SELECT *
FROM nom_table
WHERE MATCH(colonne1[, colonne2, ...])
AGAINST ('chane recherche' IN NATURAL LANGUAGE MODE);

Premier exemple : on recherche Terry dans la colonne auteur de la table Livre .

SELECT *
FROM Livre
WHERE MATCH(auteur)
AGAINST ('Terry');

id auteur titre

8 Terry Pratchett Les Petits Dieux


9 Terry Pratchett Le Cinquime lphant
10 Terry Pratchett La Vrit
11 Terry Pratchett Le Dernier hros
12 Terry Goodkind Le Temple des vents

Deuxime exemple : On recherche dabord Petite, puis Petit dans la colonne titre.

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('Petite');

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('Petit');

Rsultat de la premire requte :

id auteur titre

3 Daniel Pennac La Petite marchande de prose

90
3.1 Index

La deuxime requte (avec Petit) ne renvoie aucun rsultat. En effet, bien que Petit se re-
trouve deux fois dans la table (dans La Petite marchande de prose et Les Petits Dieux), il
sagit chaque fois dune partie dun mot, pas du mot exact.

Troisime exemple : on recherche Henri dans la colonne auteur.

SELECT *
FROM Livre
WHERE MATCH(auteur)
AGAINST ('Henri');

id auteur titre

16 Henri-Pierre Roch Jules et Jim

Ici par contre, on retrouve bien Henri-Pierre Roch en faisant une recherche sur Henri, puisque
Henri et Pierre sont considrs comme deux mots.

Quatrime exemple : on recherche Jules, puis Jules Verne dans les colonnes titre et auteur.

SELECT *
FROM Livre
WHERE MATCH(auteur, titre)
AGAINST ('Jules');

SELECT *
FROM Livre
WHERE MATCH(titre, auteur)
AGAINST ('Jules Verne');

id auteur titre

14 Jules Verne De la Terre la Lune


16 Henri-Pierre Roch Jules et Jim
15 Jules Verne Voyage au centre de la Terre

id auteur titre

14 Jules Verne De la Terre la Lune


15 Jules Verne Voyage au centre de la Terre
16 Henri-Pierre Roch Jules et Jim

Ces deux requtes retournent les mmes lignes. Vous pouvez donc voir que lordre des colonnes
dans MATCH na aucune importance, du moment quun index FULLTEXT existe sur ces deux co-
lonnes. Par ailleurs, la recherche se fait bien sur les deux colonnes, et sur chaque mot sparment,
puisque les premires et troisimes lignes contiennent Jules Verne dans lauteur, tandis que la
deuxime contient uniquement Jules dans le titre.

Par contre, lordre des lignes renvoyes par ces deux requtes nest pas le mme. Lorsque vous
utilisez MATCH... AGAINST dans une clause WHERE, les rsultats sont par dfaut tris par perti-

91
3 Index, jointures et sous-requtes

nence.

La pertinence est une valeur suprieure ou gale 0 qui qualifie le rsultat dune recherche
FULLTEXT sur une ligne. Si la ligne ne correspond pas du tout la recherche, sa pertinence sera
de 0. Si par contre elle correspond la recherche, sa pertinence sera suprieure 0. Ensuite,
plus la ligne correspond bien la recherche (nombre de mots trouvs par exemple), plus la perti-
nence sera leve. Vous pouvez voir la pertinence attribue une ligne en mettant lexpression
MATCH... AGAINST dans le SELECT.

Cinquime exemple : affichage de la pertinence de la recherche

SELECT *, MATCH(titre, auteur) AGAINST ('Jules Verne Lune')


FROM Livre;

MATCH(titre, auteur) AGAINST (Jules


id auteur titre Verne Lune)

1 Daniel Pennac Au bonheur des ogres 0


2 Daniel Pennac La Fe Carabine 0
3 Daniel Pennac La Petite marchande de 0
prose
4 Jacqueline Le Bonheur est dans le 0
Harpman crime
5 Jacqueline La Dormition des 0
Harpman amants
6 Jacqueline La Plage dOstende 0
Harpman
7 Jacqueline Histoire de Jenny 0
Harpman
8 Terry Pratchett Les Petits Dieux 0
9 Terry Pratchett Le Cinquime lphant 0
10 Terry Pratchett La Vrit 0
11 Terry Pratchett Le Dernier hros 0
12 Terry Goodkind Le Temple des vents 0
13 Daniel Pennac Comme un roman 0
14 Jules Verne De la Terre la Lune 5.851144790649414
15 Jules Verne Voyage au centre de la 3.2267112731933594
Terre
16 Henri-Pierre Jules et Jim 1.4018518924713135
Roch

En fait, crire :

WHERE MATCH(colonne(s)) AGAINST (mot(s) recherch(s))

Cela revient crire :

WHERE MATCH(colonne(s)) AGAINST (mot(s) recherch(s)) > 0

Donc seules les lignes ayant une pertinence suprieure 0 (donc correspondant la recherche)
seront slectionnes.

92
3.1 Index

3.1.4.2.2 Recherche avec boolens La recherche avec boolens possde les caractris-
tiques suivantes :

elle ne tient pas compte de la rgle des 50 % qui veut quun mot prsent dans 50 % des
lignes au moins soit ignor ;
elle peut se faire sur une ou des colonne(s) sur laquelle (lesquelles) aucun index FULLTEXT
nest dfini (ce sera cependant beaucoup plus lent que si un index est prsent) ;
les rsultats ne seront pas tris par pertinence par dfaut.

Pour faire une recherche avec boolens, il suffit dajouter IN BOOLEAN MODE aprs la chane re-
cherche.

SELECT *
FROM nom_table
WHERE MATCH(colonne)
AGAINST('chane recherche' IN BOOLEAN MODE); -- IN BOOLEAN MODE l'intrieur des par

La recherche avec boolens permet dtre la fois plus prcis et plus approximatif dans ses re-
cherches.

Plus prcis, car on peut exiger que certains mots se trouvent ou soient absents dans la ligne
pour la slectionner. On peut mme exiger la prsence de groupes de mots, plutt que de
rechercher chaque mot sparment.
Plus approximatif, car on peut utiliser un astrisque ||*|| en fin de mot, pour prciser que
le mot peut finir de nimporte quelle manire.

Pour exiger la prsence ou labsence de certains mots, on utilise les caractres ||+|| et ||-||. Un
mot prcd par ||+|| devra tre prsent dans la ligne et inversement, prcd par ||-|| il ne
pourra pas tre prsent.

Exemple : Recherche sur le titre, qui doit contenir bonheur mais ne peut pas contenir ogres.

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('+bonheur -ogres' IN BOOLEAN MODE);

id auteur titre

4 Jacqueline Harpman Le Bonheur est dans le crime

Seule une ligne est ici slectionne, bien que bonheur soit prsent dans deux. En effet, le second
livre dont le titre contient bonheur est Le Bonheur des ogres, qui contient le mot interdit
ogres.

Pour spcifier un groupe de mots exigs, on utilise les doubles guillemets. Tous les mots entre
doubles guillemets devront non seulement tre prsents mais galement apparatre dans lordre
donn, et sans rien entre eux. Il faudra donc que lon retrouve exactement ces mots pour avoir
un rsultat.

Exemple : recherche sur titre, qui doit contenir tout le groupe de mot entre guillemets doubles.

SELECT *
FROM Livre

93
3 Index, jointures et sous-requtes

WHERE MATCH(titre)
AGAINST ('"Terre la Lune"' IN BOOLEAN MODE);

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('"Lune la Terre"' IN BOOLEAN MODE);

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('"Terre la Lune"' IN BOOLEAN MODE);

Rsultat premire requte :

id auteur titre

14 Jules Verne De la Terre la Lune

La premire requte renverra bien un rsultat, contrairement la seconde (car les mots ne sont
pas dans le bon ordre) et la troisime (car il manque le dans la recherche - ou il y a un
en trop dans la ligne, a dpend du point de vue). Voyage au centre de la Terre nest pas un
rsultat puisque seul le mot Terre est prsent.

Pour utiliser lastrisque, il suffit dcrire le dbut du mot dont on est sr, et de complter avec
un astrisque.

Exemple : recherche sur titre, sur tous les mots commenant par petit.

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('petit*' IN BOOLEAN MODE);

id auteur titre

3 Daniel Pennac La Petite marchande de prose


8 Terry Pratchett Les Petits Dieux

Petite et Petits sont trouvs.

Exemple : recherche sur titre et auteur, de tous les mots commenant par d.

SELECT *
FROM Livre
WHERE MATCH(titre, auteur)
AGAINST ('d*' IN BOOLEAN MODE);

id auteur titre

1 Daniel Pennac Au bonheur des ogres


2 Daniel Pennac La Fe Carabine

94
3.1 Index

id auteur titre

3 Daniel Pennac La Petite marchande de prose


13 Daniel Pennac Comme un roman
4 Jacqueline Harpman Le Bonheur est dans le crime
11 Terry Pratchett Le Dernier hros
8 Terry Pratchett Les Petits Dieux
5 Jacqueline Harpman La Dormition des amants

Chacun des rsultats contient au moins un mot commenant par d dans son titre ou son auteur
(Daniel, Dormition). Mais quen est-il de Voyage au centre de la Terre par exemple ?
Avec le de, il aurait d tre slectionn. Mais cest sans compter la rgle qui dit que les mots de
moins de quatre lettres sont ignors. De nayant que deux lettres, ce rsultat est ignor.

Pour en finir avec la recherche avec boolens, sachez quil est bien sr possible de mixer ces dif-
frentes possibilits. Les combinaisons sont nombreuses.

Exemple : recherche sur titre, qui doit contenir un mot commenant par petit, mais ne peut pas
contenir le mot prose.

SELECT *
FROM Livre
WHERE MATCH(titre)
AGAINST ('+petit* -prose' IN BOOLEAN MODE); -- mix d'un astrisque avec les + et -

id auteur titre

8 Terry Pratchett Les Petits Dieux

3.1.4.2.3 Recherche avec extension de requte Le dernier type de recherche est un peu
particulier. En effet la recherche avec extension de requte se droule en deux tapes.

1. Une simple recherche naturelle est effectue.


2. Les rsultats de cette recherche sont utiliss pour faire une seconde recherche naturelle.

Un exemple tant souvent plus clair quun long discours, passons-y tout de suite.

Une recherche naturelle effectue avec la chane Daniel sur les colonnes auteur et titre donnerait
ceci :

SELECT *
FROM Livre
WHERE MATCH(titre, auteur)
AGAINST ('Daniel');

id auteur titre

2 Daniel Pennac La Fe Carabine


1 Daniel Pennac Au bonheur des ogres
13 Daniel Pennac Comme un roman
3 Daniel Pennac La Petite marchande de prose

95
3 Index, jointures et sous-requtes

Par contre, si lon utilise lextension de requte, en ajoutant WITH QUERY EXPANSION, on obtient
ceci :

SELECT *
FROM Livre
WHERE MATCH(titre, auteur)
AGAINST ('Daniel' WITH QUERY EXPANSION);

id auteur titre

3 Daniel Pennac La Petite marchande de prose


13 Daniel Pennac Comme un roman
1 Daniel Pennac Au bonheur des ogres
2 Daniel Pennac La Fe Carabine
4 Jacqueline Harpman Le Bonheur est dans le crime

En effet, dans la seconde tape, une recherche naturelle a t faite avec les chanes Daniel Pen-
nac, La Petite marchande de prose, Comme un roman, Au bonheur des ogres et La Fe
Carabine, puisque ce sont les rsultats de la premire tape (une recherche naturelle sur Da-
niel). Le Bonheur est dans le crime a donc t ajout aux rsultats, cause de la prsence
du mot bonheur dans son titre (Bonheur tant galement prsent dans Au bonheur des
ogres)

Ainsi sachve la prsentation des requtes FULLTEXT, ainsi que le chapitre sur les index.

3.1.4.3 En rsum

Un index est une structure de donnes qui reprend la liste ordonne des valeurs aux-
quelles il se rapporte.
Un index peut se faire sur une ou plusieurs colonnes ; et dans les cas dune colonne de type
alphanumrique (CHAR, VARCHAR, TEXT, etc.), il peut ne prendre en compte quune partie
de la colonne (les x premiers caractres).
Un index permet dacclrer les recherches faites sur les colonnes constituant celui-ci.
Un index UNIQUE ne peut contenir quune seule fois chaque valeur (ou combinaison de
valeurs si lindex est composite, cest--dire sur plusieurs colonnes).
Un index FULLTEXT (rserv aux tables MyISAM) permet de faire des recherches complexes
sur le contenu des colonnes le constituant.

3.2 Cls primaires et trangres


Maintenant que les index nont plus de secret pour vous, nous allons passer une autre notion
trs importante : les cls. Les cls sont, vous allez le voir, intimement lies aux index. Et tout
comme NOT NULL et les index UNIQUE, les cls font partie de ce quon appelle les contraintes. Il
existe deux types de cls :

les cls primaires, qui ont dj t survoles lors du chapitre sur la cration dune table, et
qui servent identifier une ligne de manire unique ;

96
3.2 Cls primaires et trangres

les cls trangres, qui permettent de grer des relations entre plusieurs tables, et garan-
tissent la cohrence des donnes.

Il sagit nouveau dun chapitre avec beaucoup de blabla, mais je vous promets quaprs celui-ci,
on samusera nouveau ! Donc un peu de courage. ;)

3.2.1 Cls primaires, le retour


Les cls primaires ont dj t introduites dans le chapitre de cration des tables. Je vous avais
alors donn la dfinition suivante :

La cl primaire dune table est une contrainte dunicit, compose dune ou plusieurs colonnes, et qui permet diden-
tier de manire unique chaque ligne de la table.

Examinons plus en dtail cette dfinition.

Contrainte dunicit : ceci ressemble fort un index UNIQUE.


Compose dune ou plusieurs colonnes : comme les index, les cls peuvent donc tre com-
posites.
Permet didentifier chaque ligne de manire unique : dans ce cas, une cl primaire ne
peut pas tre NULL.

Ces quelques considrations rsument trs bien lessence des cls primaires. En gros, une cl pri-
maire est un index UNIQUE sur une colonne qui ne peut pas tre NULL. Dailleurs, vous savez dj
que lon dfinit une cl primaire grce aux mots-cls PRIMARY KEY. Or, nous avons vu dans le
prcdent chapitre que KEY sutilise pour dfinir un index. Par consquent, lorsque vous dfinis-
sez une cl primaire, pas besoin de dfinir en plus un index sur la (les) colonne(s) qui compose(nt)
celle-ci, cest dj fait ! Et pas besoin non plus de rajouter une contrainte NOT NULL Pour le dire
diffremment, une contrainte de cl primaire est donc une combinaison de deux des contraintes
que nous avons vues jusqu prsent : UNIQUE et NOT NULL.

3.2.1.1 Choix de la cl primaire

Le choix dune cl primaire est une tape importante dans la conception dune table. Ce nest pas
parce que vous avez limpression quune colonne, ou un groupe de colonnes, pourrait faire une
bonne cl primaire que cest le cas. Reprenons lexemple dune table Client, qui contient le nom,
le prnom, la date de naissance et lemail des clients dune socit.

Chaque client a bien sr un nom et un prnom. Est-ce que (nom, prenom) ferait une bonne cl pri-
maire ? Non bien sr : il est vident ici que vous risquez des doublons. Et si on ajoute la date de
naissance ? Les chances de doublons sont alors quasi nulles. Mais quasi nul, ce nest pas nul
Quarrivera-t-il le jour o vous voyez dbarquer un client qui a les mmes nom et prnom quun
autre, et qui est n le mme jour ? On refait toute la base de donnes ? Non, bien sr. Et lemail
alors ? Il est impossible que deux personnes aient la mme adresse email, donc la contrainte duni-
cit est respecte. Par contre, tout le monde nest pas oblig davoir une adresse email. Difficile
donc de mettre une contrainte NOT NULL sur cette colonne.

Par consquent, on est bien souvent oblig dajouter une colonne pour jouer le rle de la cl pri-
maire. Cest cette fameuse colonne id, auto-incrmente que nous avons dj vue pour la table
Animal.

Il y a une autre raison dutiliser une colonne spciale auto-incrmente, de type INT (ou un de
ses drivs) pour la cl primaire. En effet, si lon dfinit une cl primaire, cest en partie dans le

97
3 Index, jointures et sous-requtes

but dutiliser au maximum cette cl pour faire des recherches dans la table. Bien sr, parfois ce
nest pas possible, parfois vous ne connaissez pas lid du client, et vous tes obligs de faire une
recherche par nom. Cependant, vous verrez bientt que les cls primaires peuvent servir faire
des recherches de manire indirecte sur la table. Du coup, comme les recherches sont beaucoup
plus rapides sur des nombres que sur des textes, il est souvent intressant davoir une cl primaire
compose de colonnes de type INT.

Enfin, il y a galement largument de lauto-incrmentation. Si vous devez remplir vous-mmes


la colonne de la cl primaire, tant donn que vous tes humains (comment a pas du tout ? :waw :
), vous risquez de faire une erreur. Avec une cl primaire auto-incrmente, vous ne risquez rien :
MySQL fait tout pour vous. De plus, on ne peut dfinir une colonne comme auto-incrmente
que si elle est de type INT et quil existe un index dessus. Dans le cas dune cl primaire auto-
incrmente, on dfinit gnralement la colonne comme un entier UNSIGNED, comme on la fait
pour la table Animal.

[[information]] | Il peut bien sr ny avoir quune seule cl primaire par table. De mme, une
seule colonne peut tre auto-incrmente (la cl primaire en gnral).

3.2.1.1.1 PRIMARY KEY or not PRIMARY KEY Je me dois de vous dire que dun point
de vue technique, avoir une cl primaire sur chaque table nest pas obligatoire. Vous pourriez
travailler toute votre vie sur une base de donnes sans aucune cl primaire, et ne jamais voir un
message derreur ce propos. Cependant, dun point de vue conceptuel, ce serait une grave erreur.
Ce nest pas le propos de ce tutoriel que de vous enseigner les tapes de conception dune base
de donnes mais, sil vous plat, pensez mettre une cl primaire sur chacune de vos tables. Si
lutilit nen est pas compltement vidente pour vous pour le moment, elle devrait le devenir au
fur et mesure de votre lecture.

3.2.1.2 Cration dune cl primaire

La cration des cls primaires tant extrmement semblable la cration dindex simples, jes-
pre que vous me pardonnerez si je ne dtaille pas trop mes explications. :ange :

Donc, nouveau, la cl primaire peut tre cre en mme temps que la table, ou par la suite.

3.2.1.2.1 Lors de la cration de la table On peut donc prciser PRIMARY KEY dans la
description de la colonne qui doit devenir la cl primaire (pas de cl composite dans ce cas) :

CREATE TABLE [IF NOT EXISTS] Nom_table (


colonne1 description_colonne1 PRIMARY KEY [,
colonne2 description_colonne2,
colonne3 description_colonne3,
...,]
)
[ENGINE=moteur];

Exemple : cration de la table Animal en donnant la cl primaire dans la description de la colonne.

CREATE TABLE Animal (


id SMALLINT AUTO_INCREMENT PRIMARY KEY,
espece VARCHAR(40) NOT NULL,

98
3.2 Cls primaires et trangres

sexe CHAR(1),
date_naissance DATETIME NOT NULL,
nom VARCHAR(30),
commentaires TEXT
)
ENGINE=InnoDB;

Ou bien, on ajoute la cl la suite des colonnes.

CREATE TABLE [IF NOT EXISTS] Nom_table (


colonne1 description_colonne1 [,
colonne2 description_colonne2,
colonne3 description_colonne3,
...],
[CONSTRAINT [symbole_contrainte]] PRIMARY KEY (colonne_pk1 [, colonne_pk2, ...]) -
)
[ENGINE=moteur];

Cest ce que nous avions fait dailleurs pour la table Animal. Cette mthode permet bien sr la
cration dune cl composite (avec plusieurs colonnes).

Exemple : cration de Animal.

CREATE TABLE Animal (


id SMALLINT AUTO_INCREMENT,
espece VARCHAR(40) NOT NULL,
sexe CHAR(1),
date_naissance DATETIME NOT NULL,
nom VARCHAR(30),
commentaires TEXT,
PRIMARY KEY (id)
)
ENGINE=InnoDB;

3.2.1.2.2 Aprs cration de la table On peut toujours utiliser ALTER TABLE. Par contre,
CREATE INDEX nest pas utilisable pour les cls primaires.

[[attention]] | Si vous crez une cl primaire sur une table existante, assurez-vous que la (les)
colonne(s) o vous souhaitez lajouter ne contienne(nt) pas NULL.

ALTER TABLE nom_table


ADD [CONSTRAINT [symbole_contrainte]] PRIMARY KEY (colonne_pk1 [, colonne_pk2, ...]);

3.2.1.3 Suppression de la cl primaire

ALTER TABLE nom_table


DROP PRIMARY KEY

Pas besoin de prciser de quelle cl il sagit, puisquil ne peut y en avoir quune seule par table !

99
3 Index, jointures et sous-requtes

3.2.2 Cls trangres


Les cls trangres ont pour fonction principale la vrification de lintgrit de votre base. Elles
permettent de sassurer que vous ninsrez pas de btises

Reprenons lexemple dans lequel on a une table Client et une table Commande. Dans la table Com-
mande, on a une colonne qui contient une rfrence au client. Ici, le client numro 3, M. Nico-
las Jacques, a donc pass une commande de trois tubes de colle, tandis que Mme Marie Malherbe
(cliente numro 2) a pass deux commandes, pour du papier et des ciseaux.

Figure 3.6 Rfrence dune table une autre

Cest bien joli, mais que se passe-t-il si M. Hadrien Piroux passe une commande de 15 tubes de
colle, et qu linsertion dans la table Commande, votre doigt drape et met 45 comme numro de
client ? Cest lhorreur ! Vous avez dans votre base de donnes une commande passe par un client
inexistant, et vous passez votre aprs-midi du lendemain vrifier tous vos bons de commande
de la veille pour retrouver qui a command ces 15 tubes de colle. Magnifique perte de temps !

Ce serait quand mme sympathique si, linsertion dune ligne dans la table Commande, un gentil
petit lutin allait vrifier que le numro de client indiqu correspond bien quelque chose dans la
table Client, non ? Ce lutin, ou plutt cette lutine, existe ! Elle sappelle cl trangre.

Par consquent, si vous crez une cl trangre sur la colonne client de la table Commande, en lui
donnant comme rfrence la colonne numero de la table Client, MySQL ne vous laissera plus jamais
insrer un numro de client inexistant dans la table Commande. Il sagit bien dune contrainte !

Avant dentamer une danse de joie, parce que quand mme le SQL cest gnial, restez concentrs
cinq minutes, le temps de lire et retenir quelques points importants.

Comme pour les index et les cls primaires, il est possible de crer des cls trangres
composites.
Lorsque vous crez une cl trangre sur une colonne (ou un groupe de colonnes) la co-
lonne client de Commande dans notre exemple , un index est automatiquement ajout sur
celle-ci (ou sur le groupe).
Par contre, la colonne (le groupe de colonnes) qui sert de rfrence - la colonne numero de
Client - doit dj possder un index (o tre cl primaire bien sr).

100
3.2 Cls primaires et trangres

La colonne (ou le groupe de colonnes) sur laquelle (lequel) la cl est cre doit tre exac-
tement du mme type que la colonne (le groupe de colonnes) quelle (il) rfrence. Cela
implique quen cas de cl composite, il faut le mme nombre de colonnes dans la cl et la
rfrence. Donc, si numero (dans Client) est un INT UNSIGNED, client (dans Commande) doit
tre de type INT UNSIGNED aussi.
Tous les moteurs de table ne permettent pas lutilisation des cls trangres. Par exemple,
MyISAM ne le permet pas, contrairement InnoDB.

3.2.2.1 Cration

Une cl trangre est un peu plus complexe crer quun index ou une cl primaire, puisquil faut
deux lments :

la ou les colonnes sur laquelle (lesquelles) on cre la cl - on utilise FOREIGN KEY ;


la ou les colonnes qui va (vont) servir de rfrence - on utilise REFERENCES.

3.2.2.1.1 Lors de la cration de la table Du fait de la prsence de deux paramtres, une


cl trangre ne peut que sajouter la suite des colonnes, et pas directement dans la description
dune colonne. Par ailleurs, je vous conseille ici de crer explicitement une contrainte (grce au
mot-cl CONSTRAINT) et de lui donner un symbole. En effet, pour les index, on pouvait utiliser
leur nom pour les identifier ; pour les cls primaires, le nom de la table suffisait puisquil ny en
a quune par table. Par contre, pour diffrencier facilement les cls trangres dune table, il est
utile de leur donner un nom, travers la contrainte associe.

nouveau, je respecte certaines conventions de nommage : mes cls trangres ont des noms
commenant par fk (pour FOREIGN KEY), suivi du nom de la colonne dans la table puis (si
elle sappelle diffremment) du nom de la colonne de rfrence, le tout spar par des ||_||
(fk_client_numero par exemple).

CREATE TABLE [IF NOT EXISTS] Nom_table (


colonne1 description_colonne1,
[colonne2 description_colonne2,
colonne3 description_colonne3,
...,]
[ [CONSTRAINT [symbole_contrainte]] FOREIGN KEY (colonne(s)_cl_trangre) REFEREN
)
[ENGINE=moteur];

Donc si on imagine les tables Client et Commande, pour crer la table Commande avec une cl tran-
gre ayant pour rfrence la colonne numero de la table Client, on utilisera :

CREATE TABLE Commande (


numero INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
client INT UNSIGNED NOT NULL,
produit VARCHAR(40),
quantite SMALLINT DEFAULT 1,
CONSTRAINT fk_client_numero -- On donne un nom notre cl
FOREIGN KEY (client) -- Colonne sur laquelle on cre la cl
REFERENCES Client(numero) -- Colonne de rfrence
)
ENGINE=InnoDB; -- MyISAM interdit, je le rappelle encore une f

101
3 Index, jointures et sous-requtes

3.2.2.1.2 Aprs cration de la table Tout comme pour les cls primaires, pour crer une
cl trangre aprs cration de la table, il faut utiliser ALTER TABLE.

ALTER TABLE Commande


ADD CONSTRAINT fk_client_numero FOREIGN KEY client REFERENCES Client(numero);

3.2.2.2 Suppression dune cl trangre

Il peut y avoir plusieurs cls trangres par table. Par consquent, lors dune suppression il faut
identifier la cl dtruire. Cela se fait grce au symbole de la contrainte.

ALTER TABLE nom_table


DROP FOREIGN KEY symbole_contrainte

3.2.3 Modification de notre base


Maintenant que vous connaissez les cls trangres, nous allons en profiter pour modifier notre
base et ajouter quelques tables afin de prparer le prochain chapitre, qui portera sur les jointures.

Jusqu maintenant, la seule information sur lespce des animaux de notre levage tait son nom
courant. Nous voudrions maintenant stocker aussi son nom latin, ainsi quune petite description.
Que faire ? Ajouter deux colonnes notre table ? nom_latin_espece et description_espece ?

Jespre que vous navez envisag cette possibilit quune demi-seconde, car il est assez vident
que cest une trs mauvaise solution. En effet, a obligerait stocker la mme description pour
chaque chien, chaque chat, etc. Ainsi que le mme nom latin. Nous le faisions dj avec le nom
courant, et a aurait dj pu poser problme. Imaginez que pour un animal vous fassiez une faute
dorthographe au nom de lespce, chein ou lieu de chien par exemple. Lanimal en question
napparatrait jamais lorsque vous feriez une recherche par espce.

Il faudrait donc un systme qui nous permette de ne pas rpter la mme information plusieurs
fois, et qui limite les erreurs que lon pourrait faire.

La bonne solution est de crer une seconde table : la table Espece. Cette table aura 4 colonnes : le
nom courant, le nom latin, une description, et un numro didentification (qui sera la cl primaire
de cette table).

3.2.3.1 La table Espece

Voici donc la commande que je vous propose dexcuter pour crer la table Espece :

CREATE TABLE Espece (


id SMALLINT UNSIGNED AUTO_INCREMENT,
nom_courant VARCHAR(40) NOT NULL,
nom_latin VARCHAR(40) NOT NULL UNIQUE,
description TEXT,
PRIMARY KEY(id)
)
ENGINE=InnoDB;

102
3.2 Cls primaires et trangres

On met un index UNIQUE sur la colonne nom_latin pour tre sr que lon ne rentrera pas deux fois
la mme espce. Pourquoi sur le nom latin et pas sur le nom courant ? Tout simplement parce
que le nom latin est beaucoup plus rigoureux et rglement que le nom courant. On peut avoir
plusieurs dnominations courantes pour une mme espce ; ce nest pas le cas avec le nom latin.

Bien, remplissons donc cette table avec les espces dj prsentes dans la base :

INSERT INTO Espece (nom_courant, nom_latin, description) VALUES


('Chien', 'Canis canis', 'Bestiole quatre pattes qui aime les caresses et tire so
('Chat', 'Felis silvestris', 'Bestiole quatre pattes qui saute trs haut et grimp
('Tortue d''Hermann', 'Testudo hermanni', 'Bestiole avec une carapace trs dure'),
('Perroquet amazone', 'Alipiopsitta xanthops', 'Joli oiseau parleur vert et jaune')

Ce qui nous donne la table suivante :

id nom_courant
nom_latin description

1 Chien Canis canis Bestiole quatre pattes qui aime les caresses et tire
souvent la langue
2 Chat Felis silvestris Bestiole quatre pattes qui saute trs haut et grimpe
aux arbres
3 Tortue Testudo Bestiole avec une carapace trs dure
dHermann hermanni
4 Perroquet Alipiopsitta Joli oiseau parleur vert et jaune
amazone xanthops

Vous aurez remarqu que jai lgrement modifi les noms des espces perroquet et tortue.
En effet, et jespre que les biologistes pourront me pardonner, perroquet et tortue ne sont
pas des espces, mais des ordres. Jai donc prcis un peu (si je donne juste lordre, cest comme
si je mettais carnivore au lieu de chat - ou chien dailleurs).

Bien, mais cela ne suffit pas ! Il faut galement modifier notre table Animal. Nous allons ajouter
une colonne espece_id, qui contiendra lid de lespce laquelle appartient lanimal, et remplir
cette colonne. Ensuite nous pourrons supprimer la colonne espece, qui naura plus de raison dtre.

La colonne espece_id sera une cl trangre ayant pour rfrence la colonne id de la table Espece.
Je rappelle donc que a signifie quil ne sera pas possible dinsrer dans espece_id un nombre qui
nexiste pas dans la colonne id de la table Espece.

3.2.3.2 La table Animal

Je vous conseille dessayer dcrire vous-mme les requtes avant de regarder comment je fais.

[[secret]] |
sql | -- Ajout d'une colonne espece_id | ALTER TABLE Animal ADD
COLUMN espece_id SMALLINT UNSIGNED; -- mme type que la colonne id de Espece
| | -- Remplissage de espece_id | UPDATE Animal SET espece_id = 1 WHERE
espece = 'chien' ; | UPDATE Animal SET espece_id = 2 WHERE espece = 'chat' ;
| UPDATE Animal SET espece_id = 3 WHERE espece = 'tortue' ; | UPDATE Animal
SET espece_id = 4 WHERE espece = 'perroquet' ; | | -- Suppression de la
colonne espece | ALTER TABLE Animal DROP COLUMN espece; | | -- Ajout de

103
3 Index, jointures et sous-requtes

la cl trangre | ALTER TABLE Animal | ADD CONSTRAINT fk_espece_id FOREIGN


KEY (espece_id) REFERENCES Espece(id); |

Pour tester lefficacit de la cl trangre, essayons dajouter un animal dont lespece_id est 5 (qui
nexiste donc pas dans la table Espece) :

INSERT INTO Animal (nom, espece_id, date_naissance)


VALUES ('Caouette', 5, '2009-02-15 12:45:00');

ERROR1452(23000):Cannotaddorupdateachildrow:aforeignkeyconstraintfails(`

Elle est pas belle la vie ?

Jen profite galement pour ajouter une contrainte NOT NULL sur la colonne espece_id. Aprs tout,
si espece ne pouvait pas tre NULL, pas de raison quespece_id puisse ltre ! Ajoutons galement
lindex UNIQUE sur (nom, espece_id) dont on a dj discut.

[[secret]] | sql | ALTER TABLE Animal MODIFY espece_id SMALLINT UNSIGNED NOT
NULL; | | CREATE UNIQUE INDEX ind_uni_nom_espece_id ON Animal (nom, espece_id);
|

Notez quil ntait pas possible de mettre la contrainte NOT NULL la cration de la colonne,
puisque tant que lon navait pas rempli espece_id, elle contenait NULL pour toutes les lignes.

Voil, nos deux tables sont maintenant prtes. Mais avant de vous lcher dans lunivers mer-
veilleux des jointures et des sous-requtes, nous allons encore compliquer un peu les choses.
Parce que cest bien joli pour un leveur de pouvoir reconnatre un chien dun chat, mais il se-
rait de bon ton de reconnatre galement un berger allemand dun teckel, non ?

Par consquent, nous allons encore ajouter deux choses notre base. Dune part, nous allons
ajouter une table Race, base sur le mme schma que la table Espece. Il faudra galement ajouter
une colonne la table Animal, qui contiendra lid de la race de lanimal. Contrairement la colonne
espece_id, celle-ci pourra tre NULL. Il nest pas impossible que nous accueillions des btards, ou
que certaines espces que nous levons ne soient pas classes en plusieurs races diffrentes.

Ensuite, nous allons garder une trace du pedigree de nos animaux. Pour ce faire, il faut pouvoir
connatre ses parents. Donc, nous ajouterons deux colonnes la table Animal : pere_id et mere_id,
qui contiendront respectivement lid du pre et lid de la mre de lanimal.

Ce sont toutes des commandes que vous connaissez, donc je ne dtaille pas plus.

-- --------------------------
-- CREATION DE LA TABLE Race
-- --------------------------
CREATE TABLE Race (
id SMALLINT UNSIGNED AUTO_INCREMENT,
nom VARCHAR(40) NOT NULL,
espece_id SMALLINT UNSIGNED NOT NULL, -- pas de nom latin, mais une rfrence v
description TEXT,
PRIMARY KEY(id),
CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES Espece(id) -- pour
)
ENGINE = InnoDB;

104
3.2 Cls primaires et trangres

-- -----------------------
-- REMPLISSAGE DE LA TABLE
-- -----------------------
INSERT INTO Race (nom, espece_id, description)
VALUES ('Berger allemand', 1, 'Chien sportif et lgant au pelage dense, noir-marron-fa
('Berger blanc suisse', 1, 'Petit chien au corps compact, avec des pattes courtes mais
('Boxer', 1, 'Chien de taille moyenne, au poil ras de couleur fauve ou bring avec quel
('Bleu russe', 2, 'Chat aux yeux verts et la robe paisse et argente.'),
('Maine coon', 2, 'Chat de grande taille, poils mi-longs.'),
('Singapura', 2, 'Chat de petite taille aux grands yeux en amandes.'),
('Sphynx', 2, 'Chat sans poils.');

-- ---------------------------------------------
-- AJOUT DE LA COLONNE race_id A LA TABLE Animal
-- ---------------------------------------------
ALTER TABLE Animal ADD COLUMN race_id SMALLINT UNSIGNED;

ALTER TABLE Animal


ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race(id);

-- -------------------------
-- REMPLISSAGE DE LA COLONNE
-- -------------------------
UPDATE Animal SET race_id = 1 WHERE id IN (1, 13, 20, 18, 22, 25, 26, 28);
UPDATE Animal SET race_id = 2 WHERE id IN (12, 14, 19, 7);
UPDATE Animal SET race_id = 3 WHERE id IN (23, 17, 21, 27);
UPDATE Animal SET race_id = 4 WHERE id IN (33, 35, 37, 41, 44, 31, 3);
UPDATE Animal SET race_id = 5 WHERE id IN (43, 40, 30, 32, 42, 34, 39, 8);
UPDATE Animal SET race_id = 6 WHERE id IN (29, 36, 38);

-- -------------------------------------------------------
-- AJOUT DES COLONNES mere_id ET pere_id A LA TABLE Animal
-- -------------------------------------------------------
ALTER TABLE Animal ADD COLUMN mere_id SMALLINT UNSIGNED;

ALTER TABLE Animal


ADD CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal(id);

ALTER TABLE Animal ADD COLUMN pere_id SMALLINT UNSIGNED;

ALTER TABLE Animal


ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id) REFERENCES Animal(id);

-- -------------------------------------------
-- REMPLISSAGE DES COLONNES mere_id ET pere_id
-- -------------------------------------------
UPDATE Animal SET mere_id = 18, pere_id = 22 WHERE id = 1 ;

105
3 Index, jointures et sous-requtes

UPDATE Animal SET mere_id = 7, pere_id = 21 WHERE id = 10 ;


UPDATE Animal SET mere_id = 41, pere_id = 31 WHERE id = 3 ;
UPDATE Animal SET mere_id = 40, pere_id = 30 WHERE id = 2 ;

A priori, la seule chose qui pourrait vous avoir surpris dans ces requtes ce sont les cls trangres
sur mere_id et pere_id qui rfrencent toutes deux une autre colonne de la mme table.

Bien, maintenant que nous avons trois tables et des donnes sympathiques exploiter, nous al-
lons passer aux choses srieuses avec les jointures dabord, puis les sous-requtes.

3.2.3.3 En rsum

Une cl primaire permet didentifier chaque ligne de la table de manire unique : cest la
fois une contrainte dunicit et une contrainte NOT NULL.
Chaque table doit dfinir une cl primaire, et une table ne peut avoir quune seule cl
primaire.
Une cl trangre permet de dfinir une relation entre deux tables, et dassurer la coh-
rence des donnes.

3.3 Jointures
Vous vous attaquez maintenant au plus important chapitre de cette partie. Le principe des join-
tures est plutt simple comprendre (quoiquon puisse faire des requtes trs compliques avec),
et totalement indispensable.

Les jointures vont vous permettre de jongler avec plusieurs tables dans la mme requte. Pour
commencer, nous verrons le principe gnral des jointures. Puis, nous ferons un petit dtour par
les alias, qui servent beaucoup dans les jointures (mais pas seulement). Ensuite, retour sur le
sujet avec les deux types de jointures : internes et externes. Et pour finir, aprs un rapide tour
dhorizon des syntaxes possibles pour faire une jointure, je vous propose quelques exercices pour
mettre tout a en pratique.

3.3.1 Principe des jointures et notion dalias

3.3.1.1 Principe des jointures

Sans surprise, le principe des jointures est de joindre plusieurs tables. Pour ce faire, on utilise les
informations communes des tables.

Par exemple, lorsque nous avons ajout dans notre base les informations sur les espces (leur
nom latin et leur description), je vous ai dit que ce serait une trs mauvaise ide de tout mettre
dans la table Animal, car il nous faudrait alors rpter la mme description pour tous les chiens,
la mme pour toutes les tortues, etc. Cependant, vous avez sans doute remarqu que du coup, si
vous voulez afficher la description de lespce de Cartouche (votre petit prfr), vous avez besoin
de deux requtes.

tape 1 : on trouve lid de lespce de Cartouche grce la table Animal.

106
3.3 Jointures

SELECT espece_id FROM Animal WHERE nom = 'Cartouche' ;

107
3 Index, jointures et sous-requtes

espece_id

tape 2 : on trouve la description de lespce grce son id.

SELECT description FROM Espece WHERE id = 1 ;

description

Bestiole quatre pattes qui aime les caresses et tire souvent la langue

Ne serait-ce pas merveilleux de pouvoir faire tout a (et plus encore) en une seule requte ? Cest
l que les jointures entrent en jeu ; on va utiliser linformation commune entre les deux tables : lid
de lespce, qui est prsente dans Animal avec la colonne espece_id, et dans Espece avec la colonne
id.

SELECT Espece.description
FROM Espece
INNER JOIN Animal
ON Espece.id = Animal.espece_id
WHERE Animal.nom = 'Cartouche' ;

Et voil le travail !

description

Bestiole quatre pattes qui aime les caresses et tire souvent la langue

En fait, lorsque lon fait une jointure, on cre une table virtuelle et temporaire qui reprend les
colonnes des tables lies. Le schma ci-dessous illustre ce principe.

Au dpart, on a deux tables : Animal (id, sexe, nom, race_id, espece_id) et Espece (id, nom_courant,
nom_latin). Les deux premires lignes dAnimal correspondent la premire ligne dEspece, et la
troisime ligne dAnimal la deuxime ligne dEspece. Une fois les deux tables jointes, on obtient
une table possdant toutes les colonnes dAnimal et toutes les colonnes dEspece, avec les valeurs
correspondantes de chaque table. On peut voir que les cinquime et sixime colonnes de la table
de jointure ont les mmes valeurs. Ensuite, de cette table virtuelle, on peut extraire ce que lon
veut. La colonne nom_latin pour la ligne ayant Caribou dans la colonne nom, par exemple.

3.3.1.2 Notion dalias

Je fais ici une petite parenthse avant de vous expliquer en dtail le fonctionnement des jointures
pour vous parler dun petit truc bien utile : les alias.

Les alias sont des noms de remplacement, que lon donne de manire temporaire (le temps dune
requte en fait) une colonne, une table, une donne. Les alias sont introduits par le mot-cl AS.
Ce mot-cl est facultatif, vous pouvez trs bien dfinir un alias sans utiliser AS, mais je prfre
personnellement toujours le mettre. Je trouve quon y voit plus clair.

Comment a marche ?

108
3.3 Jointures

Figure 3.7 Principe des jointures

Prenons cette requte toute simple :

SELECT 5+3 ;

5+3

Imaginons que ce calcul savant reprsente en fait le nombre de chiots de Cartouche, qui a eu une
premire porte de 5 chiots, et une seconde de seulement 3 chiots. Nous voudrions donc indiquer
quil sagit bien de a, et non pas dun calcul inutile destin simplement illustrer une notion
obscure.

Facile ! Il suffit dutiliser les alias :

SELECT 5+3 AS Chiots_Cartouche;

-- OU, sans utiliser AS

SELECT 5+3 Chiots_Cartouche;

Chiots_Cartouche

Bon, tout a cest bien joli, mais pour linstant a na pas lair trs utile Prenons un exemple
plus parlant : retournez voir le schma qui explique le principe des jointures un peu plus haut. La
table virtuelle rsultant de la jointure des tables Espece et Animal possde plusieurs colonnes : _

id
sexe
nom
race_id
espece_id
id
nom_courant
nom_latin 109

_ Mais que vois-je ? Jai deux colonnes id ! Comment faire pour les diffrencier ? Comment tre
3 Index, jointures et sous-requtes

les jointures. En effet, une fois que vous aurez compris comment rflchir aux jointures, tout
se fera tout seul. Personnellement, a maide vraiment dimaginer la table virtuelle cre par la
jointure, et de travailler sur cette table pour tout ce qui est conditions, tris, etc.

Revoici la jointure que je vous ai fait faire, et qui est en fait une jointure interne.

SELECT Espece.description
FROM Espece
INNER JOIN Animal
ON Espece.id = Animal.espece_id
WHERE Animal.nom = 'Cartouche' ;

Dcomposons !

SELECT Espece.description : je slectionne la colonne description de la table Espece.


FROM Espece : je travaille sur la table Espece.
INNER JOIN Animal : je la joins (avec une jointure interne) la table Animal.
ON Espece.id = Animal.espece_id : la jointure se fait sur les colonnes id de la table
Espece et espece_id de la table Animal, qui doivent donc correspondre.
WHERE Animal.nom = 'Cartouche' : dans la table rsultant de la jointure, je slec-
tionne les lignes qui ont la valeur Cartouche dans la colonne nom venant de la table
Animal.

Si vous avez compris a, vous avez tout compris !

3.3.2.1 Syntaxe

Comme dhabitude, voici donc la syntaxe utiliser pour faire des requtes avec jointure(s) in-
terne(s).

SELECT * -- comme d'habitude, vous slectionnez les c


FROM nom_table1
[INNER] JOIN nom_table2 -- INNER explicite le fait qu'il s'agit d'un
ON colonne_table1 = colonne_table2 -- sur quelles colonnes se fait la jointure
-- vous pouvez mettre colonne_table2 = colon

[WHERE ...]
[ORDER BY ...] -- les clauses habituelles sont bien sr util
[LIMIT ...]

3.3.2.1.1 Condition de jointure La clause ON sert prciser la condition de la jointure. Cest-


-dire sur quel(s) critre(s) les deux tables doivent tre jointes. Dans la plupart des cas, il sagira
dune condition dgalit simple, comme ON Animal.espece_id = Espece.id. Il est cepen-
dant tout fait possible davoir plusieurs conditions remplir pour lier les deux tables. On utilise
alors les oprateurs logiques habituels. Par exemple, une jointure peut trs bien se faire sur plu-
sieurs colonnes :

SELECT *
FROM nom_table1
INNER JOIN nom_table2
ON colonne1_table1 = colonne1_table2

110
3.3 Jointures

AND colonne2_table1 = colonne2_table2


[AND ...];

3.3.2.1.2 Expliciter le nom des colonnes Il peut arriver que vous ayez dans vos deux tables
des colonnes portant le mme nom. Cest le cas dans notre exemple, puisque la table Animal com-
porte une colonne id, tout comme la table Espece. Il est donc important de prciser de quelle co-
lonne on parle dans ce cas-l. Vous lavez vu dans notre requte, on utilise pour a loprateur .
(nom_table.nom_colonne). Pour les colonnes ayant un nom non-ambigu (qui nexiste dans aucune
autre table de la jointure), il nest pas obligatoire de prciser la table. En gnral, je prcise la
table quand il sagit de grosses requtes avec plusieurs jointures. En revanche, pour les petites
jointures courantes, il est vrai que cest moins long crire si on ne prcise pas la table.

Exemple : slection du nom des animaux commenant par Ch, ainsi que de lid et la description
de leur espce.

SELECT Espece.id, -- ici, pas le choix, il faut prciser


Espece.description, -- ici, on pourrait mettre juste description
Animal.nom -- idem, la prcision n'est pas obligatoire. C'est
FROM Espece
INNER JOIN Animal
ON Espece.id = Animal.espece_id
WHERE Animal.nom LIKE 'Ch%' ;

id description nom

2 Bestiole quatre pattes qui saute trs haut et grimpe aux arbres Choupi
3 Bestiole avec une carapace trs dure Cheli
3 Bestiole avec une carapace trs dure Chicaca

3.3.2.1.3 Utiliser les alias Les alias sont souvent utiliss avec les jointures. Ils permettent
notamment de renommer les tables, et ainsi dcrire moins de code.

Exemple : on renomme la table Espece e, et la table Animal a.

SELECT e.id,
e.description,
a.nom
FROM Espece AS e -- On donne l'alias "e" Espece
INNER JOIN Animal AS a -- et l'alias "a" Animal.
ON e.id = a.espece_id
WHERE a.nom LIKE 'Ch%' ;

Comme vous le voyez, le code est plus compact. Ici encore, cest quelque chose que jutilise sou-
vent pour de petites requtes ponctuelles. Par contre, pour de grosses requtes, je prfre les
noms explicites ; cest ainsi plus facile de sy retrouver.

Une autre utilit des alias est de renommer les colonnes pour que le rsultat soit plus clair. Ob-
servez le rsultat de la requte prcdente. Vous avez trois colonnes : id, description et nom. Le nom
de la table dont provient la colonne nest indiqu nulle part. A priori, vous savez ce que vous avez
demand, surtout quil ny a pas encore trop de colonnes, mais imaginez que vous slectionniez
une vingtaine de colonnes. Ce serait quand mme mieux de savoir de quel id on parle, sil sagit

111
3 Index, jointures et sous-requtes

du nom de lanimal, de son matre, du pre, du fils ou du Saint-Esprit ! Il est intressant l aussi
dutiliser les alias.

Exemple : on donne des alias aux colonnes (id_espece pour id de la table Espece, description_espece
pour Espece.description et nom_bestiole pour Animal.nom).

SELECT Espece.id AS id_espece,


Espece.description AS description_espece,
Animal.nom AS nom_bestiole
FROM Espece
INNER JOIN Animal
ON Espece.id = Animal.espece_id
WHERE Animal.nom LIKE 'Ch%' ;

id_espece nom_bestiole
description_espece

2 Bestiole quatre pattes qui saute trs haut et grimpe aux arbres Choupi
3 Bestiole avec une carapace trs dure Cheli
3 Bestiole avec une carapace trs dure Chicaca

Cest tout de suite plus clair !

3.3.2.2 Pourquoi interne ?

INNER JOIN permet donc de faire une jointure interne sur deux tables. Mais que signifie donc ce
interne ?

Cest trs simple ! Lorsque lon fait une jointure interne, cela veut dire quon exige quil y ait des
donnes de part et dautre de la jointure. Donc, si lon fait une jointure sur la colonne a de la table
A et la colonne b de la table B :

SELECT *
FROM A
INNER JOIN B
ON A.a = B.b

Ceci retournera uniquement les lignes pour lesquelles A.a et B.b correspondent.

Exemple : on veut connatre la race des chats :

SELECT Animal.nom AS nom_animal, Race.nom AS race


FROM Animal
INNER JOIN Race
ON Animal.race_id = Race.id
WHERE Animal.espece_id = 2 -- ceci correspond aux chats
ORDER BY Race.nom, Animal.nom;

nom_animal race

Callune Bleu russe


Caribou Bleu russe

112
3.3 Jointures

nom_animal race

Cawette Bleu russe


Feta Bleu russe
Filou Bleu russe
Raccou Bleu russe
Schtroumpfette Bleu russe
Bagherra Maine coon
Bilba Maine coon
Capou Maine coon
Cracotte Maine coon
Farceur Maine coon
Milla Maine coon
Zara Maine coon
Zonko Maine coon
Boucan Singapura
Boule Singapura
Fiero Singapura

On peut voir ici que les chats Choupi et Roucky pour lesquels je nai pas dinformation sur la race
(race_id est NULL), ne sont pas repris dans les rsultats. De mme, aucun des chats nest de la
race Sphynx, celle-ci nest donc pas reprise. Si je veux les inclure, je dois utiliser une jointure
externe.

3.3.3 Jointure externe


Comme je viens de vous le dire, une jointure externe permet de slectionner galement les lignes
pour lesquelles il ny a pas de correspondance dans une des tables jointes. MySQL permet deux
types de jointures externes : les jointures par la gauche et les jointures par la droite.

3.3.3.1 Jointures par la gauche

Lorsque lon fait une jointure par la gauche (grce aux mots-cls LEFT JOIN ou LEFT OUTER
JOIN), cela signifie que lon veut toutes les lignes de la table de gauche (sauf restrictions dans
une clause WHERE bien sr), mme si certaines nont pas de correspondance avec une ligne de la
table de droite.

Alors, table de gauche, table de droite, laquelle est laquelle ? Cest trs simple, nous lisons de
gauche droite, donc la table de gauche est la premire table mentionne dans la requte, cest-
-dire, en gnral, la table donne dans la clause FROM.

Si lon veut de nouveau connatre la race des chats, mais que cette fois-ci nous voulons galement
afficher les chats qui nont pas de race, on peut utiliser la jointure suivante (je ne prends que
les chats dont le nom commence par C afin de rduire le nombre de lignes, mais vous pouvez
choisir les conditions que vous voulez) :

SELECT Animal.nom AS nom_animal, Race.nom AS race


FROM Animal -- Table de gauche
LEFT JOIN Race -- Table de droite

113
3 Index, jointures et sous-requtes

ON Animal.race_id = Race.id
WHERE Animal.espece_id = 2
AND Animal.nom LIKE 'C%'
ORDER BY Race.nom, Animal.nom;

-- OU

SELECT Animal.nom AS nom_animal, Race.nom AS race


FROM Animal -- Table de gauche
LEFT OUTER JOIN Race -- Table de droite
ON Animal.race_id = Race.id
WHERE Animal.espece_id = 2
AND Animal.nom LIKE 'C%'
ORDER BY Race.nom, Animal.nom;

nom_animal race

Choupi NULL
Callune Bleu russe
Caribou Bleu russe
Cawette Bleu russe
Capou Maine coon
Cracotte Maine coon

On ne connat pas la race de Choupi, et pourtant il fait bien partie des lignes slectionnes alors
quavec la jointure interne il napparaissait pas. Simplement, les colonnes qui viennent de la table
Race (la colonne Race.nom AS race dans ce cas-ci) sont NULL pour les lignes qui nont pas de
correspondance (la ligne de Choupi ici).

3.3.3.2 Jointures par la droite

Les jointures par la droite (RIGHT JOIN ou RIGHT OUTER JOIN), cest videmment le mme
principe, sauf que ce sont toutes les lignes de la table de droite qui sont slectionnes mme sil
ny a pas de correspondance dans la table de gauche.

Exemple : toujours avec les races de chats.

SELECT Animal.nom AS nom_animal, Race.nom AS race


FROM Animal -- Table de gauche
RIGHT JOIN Race -- Table de droite
ON Animal.race_id = Race.id
WHERE Race.espece_id = 2
ORDER BY Race.nom, Animal.nom;

- OU

SELECT Animal.nom AS nom_animal, Race.nom AS race


FROM Animal -- Table de gauche
RIGHT OUTER JOIN Race -- Table de droite

114
3.3 Jointures

ON Animal.race_id = Race.id
WHERE Race.espece_id = 2
ORDER BY Race.nom, Animal.nom;

nom_animal race

Callune Bleu russe


Caribou Bleu russe
Cawette Bleu russe
Feta Bleu russe
Filou Bleu russe
Raccou Bleu russe
Schtroumpfette Bleu russe
Bagherra Maine coon
Bilba Maine coon
Capou Maine coon
Cracotte Maine coon
Farceur Maine coon
Milla Maine coon
Zara Maine coon
Zonko Maine coon
Boucan Singapura
Boule Singapura
Fiero Singapura
NULL Sphynx

On a bien une ligne avec la race Sphynx, bien que nous nayons aucun sphynx dans notre table
Animal.

noter que toutes les jointures par la droite peuvent tre faites grce une jointure par la gauche
(et vice versa). Voici lquivalent avec une jointure par la gauche de la requte que nous venons
dcrire :

SELECT Animal.nom AS nom_animal, Race.nom AS race


FROM Race -- Table de gauche
LEFT JOIN Animal -- Table de droite
ON Animal.race_id = Race.id
WHERE Race.espece_id = 2
ORDER BY Race.nom, Animal.nom;

3.3.4 Syntaxes alternatives

Les syntaxes que je vous ai montres jusquici, avec [INNER] JOIN et LEFT|RIGHT [OUTER]
JOIN, sont les syntaxes classiques que vous retrouverez le plus souvent. Il existe cependant
dautres manires de faire des jointures.

115
3 Index, jointures et sous-requtes

3.3.4.1 Jointures avec USING

Lorsque les colonnes qui servent joindre les deux tables ont le mme nom, vous pouvez utiliser
la clause USING au lieu de la clause ON.

SELECT *
FROM table1
[INNER | LEFT | RIGHT] JOIN table2 USING (colonneJ); -- colonneJ est prsente dans les

-- quivalent

SELECT *
FROM table1
[INNER | LEFT | RIGHT] JOIN table2 ON Table1.colonneJ = table2.colonneJ;

[[information]] | Si la jointure se fait sur plusieurs colonnes, il suffit de lister les colonnes en les
sparant par des virgules : USING (colonne1, colonne2,)

3.3.4.2 Jointures naturelles

Comme pour les jointures avec USING, il est possible dutiliser les jointures naturelles dans le cas
o les colonnes servant la jointure ont le mme nom dans les deux tables. Simplement, dans le
cas dune jointure naturelle, on ne donne pas la (les) colonne(s) sur laquelle (lesquelles) joindre
les tables : cest dtermin automatiquement. Donc si on a les trois tables suivantes :

table1 : colonnes A, B, C
table2 : colonnes B, E, F
table3 : colonnes A, C, E

Exemple 1 : jointure de table1 et table2 (une colonne ayant le mme nom : B).

SELECT *
FROM table1
NATURAL JOIN table2;

-- EST QUIVALENT

SELECT *
FROM table1
INNER JOIN table2
ON table1.B = table2.B;

Exemple 2 : jointure de table1 et table3 (deux colonnes ayant le mme nom : A et C).

SELECT *
FROM table1
NATURAL JOIN table3;

-- EST QUIVALENT

SELECT *

116
3.3 Jointures

FROM table1
INNER JOIN table3
ON table1.A = table3.A AND table1.C = table3.C;

Pour utiliser ce type de jointure, il faut donc tre certain que toutes les colonnes ncessaires la
jointure ont le mme nom dans les deux tables, mais aussi que les colonnes ayant le mme nom
sont uniquement celles qui servent la jointure.

Notez que vous pouvez galement raliser une jointure externe par la gauche avec les jointures
naturelles laide de NATURAL LEFT JOIN.

3.3.4.3 Jointures sans JOIN

Cela peut paratre absurde, mais il est tout fait possible de faire une jointure sans utiliser le
mot JOIN. Ce nest cependant possible que pour les jointures internes. Il suffit de mentionner
les tables que lon veut joindre dans la clause FROM (spares par des virgules), et de mettre la
condition de jointure dans la clause WHERE (sans clause ON).

SELECT *
FROM table1, table2
WHERE table1.colonne1 = table2.colonne2;

-- quivalent

SELECT *
FROM table1
[INNER] JOIN table2
ON table1.colonne1 = table2.colonne2;

Je vous dconseille cependant dutiliser cette syntaxe. En effet, lorsque vous ferez des grosses
jointures, avec plusieurs conditions dans la clause WHERE, vous serez bien contents de pouvoir
diffrencier au premier coup dil les conditions de jointures des conditions normales.

3.3.5 Exemples dapplication et exercices


Maintenant que vous savez comment faire une jointure, on va un peu samuser. Cette partie sera
en fait un mini-TP. Je vous dis quel rsultat vous devez parvenir en utilisant des jointures, vous
essayez, et ensuite, vous allez voir la rponse et les explications.

Techniquement, vous avez vu toute la thorie ncessaire pour raliser toutes les requtes que je
vous demanderai ici. Cependant, il y aura des choses que je ne vous ai pas montres explicite-
ment, comme les jointures avec plus de deux tables, ou les auto-jointures (joindre une table avec
elle-mme). Cest voulu !

Je ne mattends pas ce que vous russissiez construire toutes les requtes sans jeter un il
la solution. Le but ici est de vous faire rflchir, et surtout de vous faire prendre conscience quon
peut faire pas mal de chose en SQL, en combinant plusieurs techniques par exemple. Si un jour
vous vous dites Tiens, ce serait bien si en SQL on pouvait, arrtez de vous le dire et faites-le,
simplement ! Vous serez probablement plus souvent limits par votre imagination que par SQL.

Bien, a cest dit, donc allons-y !

117
3 Index, jointures et sous-requtes

[[attention]] | Pour jouer le jeu jusquau bout, je vous propose de considrer que vous ne connais-
sez pas les id correspondants aux diffrentes races et espces. Donc quand je demande la liste des
chiens par exemple, il nest pas intressant de slectionner les animaux WHERE espece_id =
1 ; il est bien plus utile de faire une jointure avec la table Espece.

3.3.5.1 A/ Commenons par des choses faciles

Des jointures sur deux tables, avec diffrentes conditions respecter.

3.3.5.1.1 1. Moi, jaime bien les chiens de berger Vous devez obtenir la liste des races
de chiens qui sont des chiens de berger.

[[information]] | On considre (mme si ce nest pas tout fait vrai) que les chiens de berger ont
berger dans leur nom de race.

[[secret]] |
sql | SELECT Race.nom AS Race | FROM Race | INNER JOIN Espece
ON Espece.id = Race.espece_id | WHERE Espece.nom_courant = 'chien' AND
Race.nom LIKE '%berger%' ; | | | | Bon, ctait juste un chauffement. Normalement
vous ne devriez pas avoir eu de difficults avec cette requte. Peut-tre avez-vous oubli la
condition Espece.nom_courant = 'chien' ? On ne sait jamais, une race de chat (ou autre)
pourrait trs bien contenir berger, or jai explicitement demand les chiens.

3.3.5.1.2 2. Mais de quelle couleur peut bien tre son pelage ? Vous devez obtenir
la liste des animaux (leur nom, date de naissance et race) pour lesquels nous navons aucune
information sur la couleur que devrait avoir leur pelage.

[[information]] | Dans la description des races, jutilise parfois pelage, parfois poils, et par-
fois robe.

[[secret]]| sql | SELECT Animal.nom AS nom_animal, Animal.date_naissance,


Race.nom AS race | FROM Animal | LEFT JOIN Race | ON Animal.race_id =
Race.id | WHERE (Race.description NOT LIKE '%poil%' | AND Race.description
NOT LIKE '%robe%' | AND Race.description NOT LIKE '%pelage%' |
) | OR Race.id IS NULL; | | | | Il faut donc : | | | - soit quon ne connaisse pas
la race (on na alors aucune information sur le pelage de la race, a fortiori) ; | - soit quon
connaisse la race, mais que dans la description de celle-ci il ny ait pas dinformation sur le
pelage. | | | | 1/ On ne connat pas la race | | Les animaux dont on ne connat pas la race ont NULL
dans la colonne race_id. Mais vu que lon fait une jointure sur cette colonne, il ne faut pas oublier
de faire une jointure externe, sinon tous ces animaux sans race seront limins. | Une fois que
cest fait, pour les slectionner, il suffit de mettre la condition Animaux.race_id IS NULL par
exemple, ou simplement Race.#n'importe quelle colonne# IS NULL vu quil ny a pas de
correspondance avec Race. | | 2/ Pas dinformation sur le pelage dans la description de la race
| | Je vous ai donn comme indice le fait que jutilisais les mots pelage, poil ou robe dans
les descriptions des espces. Il fallait donc slectionner les races pour lesquelles la description
ne contient pas ces mots. Do lutilisation de NOT LIKE. | | Si on met tout a ensemble : il
fallait faire une jointure externe des tables Animal et Race, et ensuite slectionner les animaux qui
rpondaient lune ou lautre des conditions (oprateur logique OR).

118
3.3 Jointures

3.3.5.2 B/ Compliquons un peu les choses

Jointures sur deux tables, ou plus !

3.3.5.2.1 1. La race ET lespce Vous devez obtenir la liste des chats et des perroquets
amazones, avec leur sexe, leur espce (nom latin) et leur race sils en ont une. Regroupez les
chats ensemble, les perroquets ensemble et, au sein de lespce, regroupez les races.

[[secret]] | sql | SELECT Animal.nom as nom_animal, Animal.sexe, Espece.nom_latin


as espece, Race.nom as race | FROM Animal | INNER JOIN Espece | ON
Animal.espece_id = Espece.id | LEFT JOIN Race | ON Animal.race_id =
Race.id | WHERE Espece.nom_courant IN ('Perroquet amazone', 'Chat') | ORDER
BY Espece.nom_latin, Race.nom; | | | | Comme vous voyez, cest non seulement trs
simple de faire des jointures sur plus dune table, mais cest galement possible de mlanger
jointures internes et externes. Si a vous pose problme, essayez vraiment de vous imaginer les
tapes. | Dabord, on fait la jointure dAnimal et dEspece. On se retrouve alors avec une grosse table
qui possde toutes les colonnes dAnimal et toutes les colonnes dEspece. Ensuite, cette grosse
table ( la fois virtuelle et intermdiaire), on joint la table Race, grce la colonne Animal.race_id. |
Notez que lordre dans lequel vous faites les jointures nest pas important. | | En ce qui concerne
la clause ORDER BY, jai choisi de trier par ordre alphabtique, mais il est vident que vous
pouviez galement trier sur les id de lespce et de la race. Limportant ici tait de trier dabord
sur une colonne dEspece, ensuite sur une colonne de Race.

3.3.5.2.2 2. Futures gnitrices Vous devez obtenir la liste des chiennes dont on connat la
race, et qui sont en ge de procrer (cest--dire nes avant juillet 2010). Affichez leur nom,
date de naissance et race.

[[secret]] |
sql | SELECT Animal.nom AS nom_chienne, Animal.date_naissance,
Race.nom AS race | FROM Animal | INNER JOIN Espece | ON Animal.espece_id
= Espece.id | INNER JOIN Race | ON Animal.race_id = Race.id | WHERE
Espece.nom_courant = 'chien' | AND Animal.date_naissance < '2010-07-01'
| AND Animal.sexe = 'F' ; | | | | Cette fois, il fallait faire une jointure interne avec
Race puisquon voulait que la race soit connue. Le reste de la requte ne prsentait pas de difficult
majeure.

3.3.5.3 C/ Et maintenant, le test ultime !

Jointures sur deux tables ou plus, avec ventuelles auto-jointures.

Je vous ai fait rajouter, la fin du chapitre prcdent, deux jolies petites colonnes dans la table
Animal : mere_id et pere_id. Le moment est venu de les utiliser !

3.3.5.3.1 1. Mon pre, ma mre, mes frres et mes surs (Wohooooo) Vous devez
obtenir la liste des chats dont on connat les parents, ainsi que le nom de ces parents.

[[secret]] |
sql | SELECT Animal.nom, Pere.nom AS Papa, Mere.nom AS Maman |
FROM Animal | INNER JOIN Animal AS Pere | ON Animal.pere_id = Pere.id
| INNER JOIN Animal AS Mere | ON Animal.mere_id = Mere.id | INNER JOIN
Espece | ON Animal.espece_id = Espece.id | WHERE Espece.nom_courant =

119
3 Index, jointures et sous-requtes

'chat' ; | | | | Si celle-l, vous lavez trouve tout seuls, je vous flicite ! Sinon, cest un peu
normal. Vous voici face au premier cas dans lequel les alias sont obligatoires. En effet, vous aviez
sans doute compris que vous pouviez faire FROM Animal INNER JOIN Animal, puisque javais
mentionn les auto-jointures, mais vous avez probablement bloqu sur la clause ON. Comment
diffrencier les colonnes dAnimal dans FROM des colonnes dAnimal dans JOIN ? Vous savez
maintenant quil suffit dutiliser des alias. | Il faut faire une jointure sur trois tables puisquau
final, vous avez besoin des noms de trois animaux. Or en liant deux tables Animal ensemble, vous
avez deux colonnes nom. Pour pouvoir en avoir trois, il faut lier trois tables. | | Prenez le temps de
bien comprendre les auto-jointures, le pourquoi du comment et le comment du pourquoi. Faites
des schmas si besoin, imaginez les tables intermdiaires.

3.3.5.3.2 2. Je suis ton pre Histoire de se dtendre un peu, vous devez maintenant ob-
tenir la liste des enfants de Bouli (nom, sexe et date de naissance).

[[secret]] |
sql | SELECT Animal.nom, Animal.sexe, Animal.date_naissance |
FROM Animal | INNER JOIN Animal AS Pere | ON Animal.pere_id = Pere.id
| WHERE Pere.nom = 'Bouli' ; | | | | Aprs la requte prcdente, celle-ci devrait vous
sembler plutt facile ! Notez quil y a plusieurs manires de faire bien sr. En voici une autre : | |
| sql | SELECT Enfant.nom, Enfant.sexe, Enfant.date_naissance | FROM Animal
| INNER JOIN Animal AS Enfant | ON Enfant.pere_id = Animal.id | WHERE
Animal.nom = 'Bouli' ; | | | Limportant, cest le rsultat ! videmment, si vous avez utilis
45 jointures et 74 conditions, alors ce nest pas bon non plus. Du moment que vous navez joint
que deux tables, a devrait tre bon.

3.3.5.3.3 3. Cest un pure race ? Courage, cest la dernire (et la plus thrash :ninja : ) !

Vous devez obtenir la liste des animaux dont on connat le pre, la mre, la race, la race du
pre, la race de la mre. Affichez le nom et la race de lanimal et de ses parents, ainsi que
lespce de lanimal (pas des parents).

[[secret]] | sql | SELECT Espece.nom_courant AS espece, Animal.nom AS nom_animal,


Race.nom AS race_animal, | Pere.nom AS papa, Race_pere.nom AS race_papa,
| Mere.nom AS maman, Race_mere.nom AS race_maman | FROM Animal | INNER
JOIN Espece | ON Animal.espece_id = Espece.id | INNER JOIN Race | ON
Animal.race_id = Race.id | INNER JOIN Animal AS Pere | ON Animal.pere_id
= Pere.id | INNER JOIN Race AS Race_pere | ON Pere.race_id = Race_pere.id
| INNER JOIN Animal AS Mere | ON Animal.mere_id = Mere.id | INNER JOIN
Race AS Race_mere | ON Mere.race_id = Race_mere.id; | | | | Pfiou :euh : ! Le
principe est exactement le mme que pour avoir le nom des parents. Il suffit de rajouter une
jointure avec Race pour le pre, pour la mre, et pour lenfant, en noubliant pas de bien utiliser
les alias bien sr. | Cest avec ce genre de requte que lon se rend compte quel point il est
important de bien structurer et indenter sa requte, et quel point un choix dalias intelligent
peut clarifier les choses.

Si vous avez survcu jusquici, vous devriez maintenant avoir compris en profondeur le principe
des jointures, et tre capables de manipuler de nombreuses tables sans faire tout tomber par terre.

120
3.4 Sous-requtes

3.3.5.4 En rsum

Une jointure est une opration qui consiste joindre plusieurs tables ensemble, sur la base
dinformations communes.
Lorsque lon fait une jointure interne, on ne prend que les donnes pour lesquelles il existe
une correspondance entre la table 1 et la table 2.
Par contre, dans une jointure externe, on rcupre toutes les donnes dune des deux
tables (table 1), plus les donnes de la seconde table (table 2) pour lesquelles il existe une
correspondance dans la table 1.
On peut joindre une table elle-mme. Cela sappelle une auto-jointure.

3.4 Sous-requtes
Nous allons maintenant apprendre imbriquer plusieurs requtes, ce qui vous permettra de faire
en une seule fois ce qui vous aurait, jusquici, demand plusieurs tapes.

Une sous-requte est une requte lintrieur dune autre requte. Avec le SQL, vous pouvez
construire des requtes imbriques sur autant de niveaux que vous voulez. Vous pouvez gale-
ment mlanger jointures et sous-requtes. Tant que votre requte est correctement structure,
elle peut tre aussi complexe que vous voulez.

Une sous-requte peut tre faite dans une requte de type SELECT, INSERT, UPDATE ou DELETE
(et quelques autres que nous navons pas encore vues). Nous ne verrons dans ce chapitre que
les requtes de slection. Les jointures et sous-requtes pour la modification, linsertion et la
suppression de donnes tant traites dans le prochain chapitre.

La plupart des requtes de slection que vous allez voir dans ce chapitre sont tout fait rali-
sables autrement, souvent avec une jointure. Certains prfrent les sous-requtes aux jointures
parce que cest lgrement plus clair comme syntaxe, et peut-tre plus intuitif. Il faut cependant
savoir quune jointure sera toujours au moins aussi rapide que la mme requte faite avec une
sous-requte. Par consquent, sil est important pour vous doptimiser les performances de votre
application, utilisez plutt des jointures lorsque cest possible.

3.4.1 Sous-requtes dans le FROM


Lorsque lon fait une requte de type SELECT, le rsultat de la requte nous est envoy sous forme
de table. Et grce aux sous-requtes, il est tout fait possible dutiliser cette table et de refaire
une recherche uniquement sur les lignes de celle-ci.

Exemple : on slectionne toutes les femelles parmi les perroquets et les tortues .

SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.espece_id


FROM Animal
INNER JOIN Espece
ON Espece.id = Animal.espece_id
WHERE sexe = 'F'
AND Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone');

121
3 Index, jointures et sous-requtes

id sexe date_naissance nom espece_id

4 F 2009-08-03 05 :12 :00 NULL 3


6 F 2009-06-13 08 :17 :00 Bobosse 3
45 F 2007-04-01 18 :17 :00 Nikki 3
46 F 2009-03-24 08 :23 :00 Tortilla 3
47 F 2009-03-26 01 :24 :00 Scroupy 3
48 F 2006-03-15 14 :56 :00 Lulla 3
49 F 2008-03-15 12 :02 :00 Dana 3
50 F 2009-05-25 19 :57 :00 Cheli 3
51 F 2007-04-01 03 :54 :00 Chicaca 3
52 F 2006-03-15 14 :26 :00 Redbul 3
60 F 2009-03-26 07 :55 :00 Parlotte 4

Parmi ces femelles perroquets et tortues, on veut connatre la date de naissance de la plus ge.
On va donc faire une slection dans la table des rsultats de la requte.

SELECT MIN(date_naissance)
FROM (
SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.espece_id
FROM Animal
INNER JOIN Espece
ON Espece.id = Animal.espece_id
WHERE sexe = 'F'
AND Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
) AS tortues_perroquets_F;

MIN(date_naissance)

2006-03-15 14 :26 :00

[[information]] | MIN() est une fonction qui va chercher la valeur minimale dans une colonne,
nous reparlerons plus en dtail des fonctions dans la troisime partie de ce cours.

3.4.1.1 Les rgles respecter

3.4.1.1.1 Parenthses Une sous-requte doit toujours se trouver dans des parenthses, afin
de dfinir clairement ses limites.

3.4.1.1.2 Alias Dans le cas des sous-requtes dans le FROM, il est galement obligatoire de
prciser un alias pour la table intermdiaire (le rsultat de notre sous-requte). Si vous ne
le faites pas, MySQL dclenchera une erreur. Ici, on la appele tortues_perroquets_F. Nommer
votre table intermdiaire permet de plus de vous y rfrer si vous faites une jointure dessus,
ou si certains noms de colonnes sont ambigus et que le nom de la table doit tre prcis.
Attention au fait quil ne sagit pas de la table Animal, mais bien dune table tire dAnimal.
Par consquent, si vous voulez prciser le nom de la table dans le SELECT principal, vous
devez crire SELECT MIN(tortues_perroquets_F.date_naissance), et non pas SELECT
MIN(Animal.date_naissance).

122
3.4 Sous-requtes

3.4.1.1.3 Cohrence des colonnes Les colonnes slectionnes dans le SELECT principal
doivent bien sr tre prsentes dans la table intermdiaire. La requte suivante, par exemple, ne
fonctionnera pas :

SELECT MIN(date_naissance)
FROM (
SELECT Animal.id, Animal.nom
FROM Animal
INNER JOIN Espece
ON Espece.id = Animal.espece_id
WHERE sexe = 'F'
AND Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
) AS tortues_perroquets_F;

En effet, tortues_perroquets_F na que deux colonnes : id et nom. Il est donc impossible de slection-
ner la colonne date_naissance de cette table.

3.4.1.1.4 Noms ambigus Pour finir, attention aux noms de colonnes ambigus. Une table,
mme intermdiaire, ne peut pas avoir deux colonnes ayant le mme nom. Si deux colonnes ont
le mme nom, il est ncessaire de renommer explicitement au moins lune des deux.

Donc, si on veut slectionner la colonne Espece.id en plus dans la sous-requte, on peut procder
ainsi :

SELECT MIN(date_naissance)
FROM (
SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.espece_id,
Espece.id AS espece_espece_id -- On renomme la colonne id de Espece
FROM Animal -- Attention de ne pas la renommer es
INNER JOIN Espece
ON Espece.id = Animal.espece_id
WHERE sexe = 'F'
AND Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
) AS tortues_perroquets_F;

3.4.2 Sous-requtes dans les conditions


Je vous ai donc dit que lorsque vous faites une requte SELECT, le rsultat est sous forme de table.
Ces tables de rsultats peuvent avoir :

plusieurs colonnes et plusieurs lignes ;


plusieurs colonnes mais une seule ligne ;
plusieurs lignes mais une seule colonne ;
ou encore une seule ligne et une seule colonne (cest--dire juste une valeur).

Les sous-requtes renvoyant plusieurs lignes et plusieurs colonnes ne sont utilises que dans les
clauses FROM. Nous allons ici nous intresser aux trois autres possibilits uniquement.

3.4.2.1 Comparaisons

Pour rappel, voici un tableau des oprateurs de comparaison.

123
3 Index, jointures et sous-requtes

Oprateur Signification

= gal
< infrieur
<= infrieur ou gal
> suprieur
>= suprieur ou gal
<> ou != diffrent
<=> gal (valable pour NULL aussi)

On peut utiliser des comparaisons de ce type avec des sous-requtes qui donnent comme rsul-
tat soit une valeur (cest--dire une seule ligne et une seule colonne), soit une ligne (plusieurs
colonnes mais une seule ligne).

3.4.2.1.1 Sous-requte renvoyant une valeur Le cas le plus simple est videmment duti-
liser une sous-requte qui renvoie une valeur.

SELECT id, sexe, nom, commentaires, espece_id, race_id


FROM Animal
WHERE race_id =
(SELECT id FROM Race WHERE nom = 'Berger Allemand'); -- la sous-requte renvoie si

Remarquez que cette requte peut galement scrire avec une jointure plutt quune sous-
requte :

SELECT Animal.id, sexe, Animal.nom, commentaires, Animal.espece_id, race_id


FROM Animal
INNER JOIN Race ON Race.id = Animal.race_id
WHERE Race.nom = 'Berger Allemand' ;

Voici un exemple de requte avec sous-requte quil est impossible de faire avec une simple join-
ture :

SELECT id, nom, espece_id


FROM Race
WHERE espece_id = (
SELECT MIN(id) -- Je rappelle que MIN() permet de rcuprer la plus petite vale
FROM Espece);

id nom espece_id

1 Berger allemand 1
2 Berger blanc suisse 1
3 Boxer 1

En ce qui concerne les autres oprateurs de comparaison, le principe est exactement le mme :

SELECT id, nom, espece_id


FROM Race
WHERE espece_id < (

124
3.4 Sous-requtes

SELECT id
FROM Espece
WHERE nom_courant = 'Tortue d''Hermann');

id nom espece_id

1 Berger allemand 1
2 Berger blanc suisse 1
3 Boxer 1
4 Bleu russe 2
5 Maine coon 2
6 Singapura 2
7 Sphynx 2

Ici la sous-requte renvoie 3, donc nous avons bien les races dont lespce a un id infrieur 3
(donc 1 et 2 :p ).

3.4.2.1.2 Sous-requte renvoyant une ligne [[attention]] | Seuls les oprateurs = et != (ou
<>) sont utilisables avec une sous-requte de ligne, toutes les comparaisons de type plus grand
ou plus petit ne sont pas supportes.

Dans le cas dune sous-requte dont le rsultat est une ligne, la syntaxe est la suivante :

SELECT *
FROM nom_table1
WHERE [ROW](colonne1, colonne2) = ( -- le ROW n'est pas obligatoire
SELECT colonneX, colonneY
FROM nom_table2
WHERE...); -- Condition qui ne retourne qu'UNE SEULE LIGNE

Cette requte va donc renvoyer toutes les lignes de la table1 dont la colonne1 = la colonneX de
la ligne rsultat de la sous-requte ET la colonne2 = la colonneY de la ligne rsultat de la sous-
requte.

Vous voulez un exemple peut-tre ? Allons-y !

SELECT id, sexe, nom, espece_id, race_id


FROM Animal
WHERE (id, race_id) = (
SELECT id, espece_id
FROM Race
WHERE id = 7);

id sexe nom espece_id race_id

7 F Caroline 1 2

Dcomposons calmement. Voyons dabord ce que la sous-requte donne comme rsultat.

SELECT id, espece_id

125
3 Index, jointures et sous-requtes

FROM Race
WHERE id = 7 ;

id espece_id

7 2

Et comme condition, on a WHERE (id, race_id) = #le rsultat de la sous-requte#.


Donc la requte renverra les lignes de la table Animal pour lesquelles id vaut 7 et race_id vaut 2.

[[attention]] | Attention, il est impratif que la sous-requte ne renvoie quune seule ligne. Dans
le cas contraire, la requte chouera.

3.4.2.2 Conditions avec IN et NOT IN

3.4.2.2.1 IN Vous connaissez dj loprateur IN, qui compare une colonne avec une liste de
valeurs.

Exemple

SELECT Animal.id, Animal.nom, Animal.espece_id


FROM Animal
INNER JOIN Espece
ON Espece.id = Animal.espece_id
WHERE Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone');

Cet oprateur peut galement sutiliser avec une sous-requte dont le rsultat est une colonne
ou une valeur. On peut donc rcrire la requte ci-dessus en utilisant une sous-requte plutt
quune jointure :

SELECT id, nom, espece_id


FROM Animal
WHERE espece_id IN (
SELECT id
FROM Espece
WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
);

Le fonctionnement est plutt facile comprendre. La sous-requte donne les rsultats suivants :

SELECT id -- On ne slectionne bien qu'UNE SEULE COLONNE.


FROM Espece
WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone');

id

3
4

Ainsi, la requte principale slectionnera les lignes qui ont un espece_id parmi ceux renvoys par
la sous-requte, donc 3 ou 4.

126
3.4 Sous-requtes

3.4.2.2.2 NOT IN Si lon utilise NOT IN, cest bien sr le contraire, on exclut les lignes qui
correspondent au rsultat de la sous-requte. La requte suivante nous renverra donc les animaux
dont lespece_id nest pas 3 ou 4.

SELECT id, nom, espece_id


FROM Animal
WHERE espece_id NOT IN (
SELECT id
FROM Espece
WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
);

3.4.2.3 Conditions avec ANY, SOME et ALL

Les conditions avec IN et NOT IN sont un peu limites, puisquelles ne permettent que des com-
paraisons de type est gal ou est diffrent. Avec ANY et ALL, on va pouvoir utiliser les autres
comparateurs (plus grand, plus petit, etc.).

[[attention]] | Bien entendu, comme pour IN, il faut des sous-requtes dont le rsultat est soit
une valeur, soit une colonne.

ANY : veut dire au moins une des valeurs.


SOME : est un synonyme de ANY.
ALL : signifie toutes les valeurs.

3.4.2.3.1 ANY (ou SOME) La requte suivante signifie donc Slectionne les lignes de la
table Animal, dont lespece_id est infrieur au moins une des valeurs slectionnes dans la sous-
requte. Cest--dire infrieur 3 ou 4. Vous aurez donc dans les rsultats toutes les lignes
dont lespece_id vaut 1, 2 ou 3 (puisque 3 est infrieur 4).

SELECT *
FROM Animal
WHERE espece_id < ANY (
SELECT id
FROM Espece
WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
);

3.4.2.3.2 ALL Par contre, si vous utilisez ALL plutt que ANY, cela signifiera Slectionne les
lignes de la table Animal, dont lespece_id est infrieur toutes les valeurs slectionnes dans la
sous-requte. Donc infrieur 3 et 4. Vous naurez donc plus que les lignes dont lespece_id
vaut 1 ou 2.

SELECT *
FROM Animal
WHERE espece_id < ALL (
SELECT id
FROM Espece
WHERE nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
);

127
3 Index, jointures et sous-requtes

3.4.2.3.3 Remarque : lien avec IN Remarquez que = ANY est lquivalent de IN, tandis que
<> ALL est lquivalent de NOT IN. Attention cependant que ANY et ALL (et SOME) ne peuvent
sutiliser quavec des sous-requtes, et non avec des valeurs comme on peut le faire avec IN. On
ne peut donc pas faire ceci :

SELECT id
FROM Espece
WHERE nom_courant = ANY ('Tortue d''Hermann', 'Perroquet amazone');

####1064-YouhaveanerrorinyourSQLsyntax;

3.4.3 Sous-requtes corrles


Une sous-requte corrle est une sous-requte qui fait rfrence une colonne (ou une table)
qui nest pas dfinie dans sa clause FROM, mais bien ailleurs dans la requte dont elle fait partie.

Vu que ce nest pas une dfinition extrmement claire de prime abord, voici un exemple de requte
avec une sous-requte corrle :

SELECT colonne1
FROM tableA
WHERE colonne2 IN (
SELECT colonne3
FROM tableB
WHERE tableB.colonne4 = tableA.colonne5
);

Si lon prend la sous-requte toute seule, on ne pourra pas lexcuter :

SELECT colonne3
FROM tableB
WHERE tableB.colonne4 = tableA.colonne5

En effet, seule la tableB est slectionne dans la clause FROM, il ny a pas de jointure avec la tableA,
et pourtant on utilise la tableA dans la condition.

Par contre, aucun problme pour lutiliser comme sous-requte, puisque la clause FROM de la
requte principale slectionne la tableA. La sous-requte est donc corrle la requte principale.

Attention : si MySQL rencontre une table inconnue dans une sous-requte, elle va aller chercher
dans les niveaux suprieurs uniquement si cette table existe. Donc, imaginons que lon a une
requte avec 3 niveaux : la requte principale (niveau 1), une ou plusieurs sous-requtes (niveau
2) et une ou plusieurs sous-sous-requtes, cest--dire une sous-requte dans une sous-requte
(niveau 3).

Une sous-requte (niveau 2) peut tre corrle la requte principale (niveau 1).
Une sous-sous-requte (niveau 3) peut tre corrle la sous-requte dont elle dpend
(niveau 2), ou la requte principale (niveau 1).
Mais une sous-requte (niveau 2) ne peut pas tre corrle une autre sous-requte (ni-
veau 2).

128
3.4 Sous-requtes

Figure 3.8 Sous-requtes corrles

Si lon prend le schma suivant, on peut donc remonter larbre, mais jamais descendre dun cran
pour trouver les tables ncessaires.

Peuvent tre corrles :

A : B, C, D, E, F, G et H
B : aucune
C : D, E, F, G et H
D : F, G et H
E : aucune

Le temps de vous expliquer le fonctionnement de EXISTS et NOT EXISTS, et nous verrons un


exemple de sous-requte corrle.

3.4.3.0.1 Conditions avec EXISTS et NOT EXISTS Les conditions EXISTS et NOT
EXISTS sutilisent de la manire suivante :

SELECT * FROM nom_table


WHERE [NOT] EXISTS (sous-requte)

Une condition avec EXISTS sera vraie (et donc la requte renverra quelque chose) si la sous-
requte correspondante renvoie au moins une ligne. Une condition avec NOT EXISTS sera vraie
si la sous-requte correspondante ne renvoie aucune ligne.

Exemple : on slectionne les races sil existe un animal qui sappelle Balou.

SELECT id, nom, espece_id FROM Race


WHERE EXISTS (SELECT * FROM Animal WHERE nom = 'Balou');

Vu quil existe bien un animal du nom de Balou dans notre table Animal, la condition est vraie, on
slectionne donc toutes les races. Si lon avait utilis un nom qui nexiste pas, la requte naurait
renvoy aucun rsultat.

id nom espece_id

1 Berger allemand 1

129
3 Index, jointures et sous-requtes

id nom espece_id

2 Berger blanc suisse 1


3 Boxer 1
4 Bleu russe 2
5 Maine coon 2
6 Singapura 2
7 Sphynx 2
8 Nebelung 2

Vous conviendrez cependant quune telle requte na pas beaucoup de sens, ctait juste pour
vous faire comprendre le principe. En gnral, on utilise WHERE [NOT] EXISTS avec des sous-
requtes corrles.

Exemple : je veux slectionner toutes les races dont on ne possde aucun animal.

SELECT * FROM Race


WHERE NOT EXISTS (SELECT * FROM Animal WHERE Animal.race_id = Race.id);

La sous-requte est bien corrle la requte principale, puisquelle utilise la table Race, qui nest
pas slectionne dans la sous-requte.

En rsultat, on a bien le Sphynx, puisquon nen possde aucun.

id nom espece_id description

7 Sphynx 2 Chat sans poils.

3.4.3.1 En rsum

Une sous-requte est une requte imbrique dans une autre requte.
Il est obligatoire de donner un alias au rsultat dune sous-requte lorsquon lutilise dans
une clause FROM.
IN, ANY, SOME et ALL sutilisent uniquement avec des sous-requtes renvoyant une seule
colonne.
Une sous-requte corrle est une sous-requte utilisant une table rfrence uniquement
dans la requte dont elle dpend, et non dans la sous-requte elle-mme.
EXISTS renvoie vrai si la sous-requte qui y est associe renvoie au moins un rsultat.

3.5 Jointures et sous-requtes : modification de donnes


Voici un petit chapitre bonus sur les jointures et les sous-requtes. Vous allez apprendre ici
utiliser ces outils, non pas dans le cadre de la slection de donnes comme on la fait jusqu
prsent, mais pour :

linsertion : les sous-requtes vont permettre dinsrer des donnes dans une table partir
de donnes venant dautres tables (mais pas exclusivement) ;

130
3.5 Jointures et sous-requtes : modication de donnes

la modification : jointures et sous-requtes vont permettre non seulement dutiliser des


critres de slection complexes mais galement daller chercher les nouvelles valeurs dans
dautres tables ;
la suppression de donnes : comme pour la modification, les critres de slection pourront
tre plus complexes grce aux jointures et sous-requtes.

3.5.1 Insertion
Pour linsertion nous nallons nous servir que de sous-requtes, pas de jointures. Quoique La
sous-requte pourrait trs bien contenir une jointure !

3.5.1.1 Sous-requte pour linsertion

Vous venez dacqurir un magnifique Maine Coon chez un leveur voisin. Vous vous apprtez
linsrer dans votre base de donnes, mais vous ne vous souvenez absolument pas de lid de la
race Maine Coon, ni de celui de lespce Chat. Du coup, deux possibilits soffrent vous.

Vous pouvez faire une requte pour slectionner lid de la race et de lespce, puis faire
ensuite une requte dinsertion avec les donnes que vous venez dafficher.
Vous pouvez utiliser une sous-requte pour insrer directement lid de la race et de lespce
partir du nom de la race.

Je ne sais pas ce que vous en pensez, mais moi je trouve la seconde option bien plus sexy !

Donc, allons-y. Quavons-nous comme information ?

Nom : Yoda
Date de naissance : 2010-11-09
Sexe : mle
Espce : chat
Race : Maine coon

De quoi avons-nous besoin en plus pour linsrer dans notre table ? Lid de lespce et de la race.
Comment rcuprer ces deux id ? a, vous savez faire : une simple requte suffit.

SELECT id AS race_id, espece_id FROM Race WHERE nom = 'Maine coon' ;

race_id espece_id

5 2

Bien, mais le but tait de tout faire en une seule requte, pas dinsrer nous-mmes 5 et 2 aprs
les avoir rcuprs.

Cest ici que les sous-requtes interviennent. Nous allons utiliser une nouvelle syntaxe dinser-
tion.

3.5.1.1.1 INSERT INTO SELECT Cette syntaxe permet de slectionner des lments
dans des tables, afin de les insrer directement dans une autre.

INSERT INTO nom_table


[(colonne1, colonne2, ...)]

131
3 Index, jointures et sous-requtes

SELECT [colonne1, colonne2, ...]


FROM nom_table2
[WHERE ...]

Vous ntes bien sr pas obligs de prciser dans quelles colonnes se fait linsertion, si vous slec-
tionnez une valeur pour toutes les colonnes de la table. Ce sera cependant rarement le cas puisque
nous avons des cls primaires auto-incrmentes.

Avec cette requte, il est absolument indispensable (sinon linsertion ne se fera pas), davoir
le mme nombre de colonnes dans linsertion et dans la slection, et quelles soient dans le
mme ordre. Si vous navez pas le mme nombre de colonnes, cela dclenchera une erreur. De
plus, si lordre nest pas bon, vous aurez probablement une insertion errone.

Nous allons donc insrer le rsultat de notre requte qui slectionne les id de lespce et la race
directement dans notre table Animal. Pas de souci, mais il faut galement insrer le nom, le sexe,
etc.

En effet ! Mais cest trs facile. Souvenez-vous, vous pouvez trs bien faire des requtes de ce
type :

SELECT 'Yoda' AS nom;

Et donc, en combinant avec notre requte prcdente :

SELECT id AS race_id, espece_id FROM Race WHERE nom = 'Maine coon' ;

Vous pouvez obtenir trs facilement, et en une seule requte, tous les renseignements indispen-
sables linsertion de notre petit Yoda !

SELECT 'Yoda', 'M', '2010-11-09', id AS race_id, espece_id


FROM Race WHERE nom = 'Maine coon' ;

[[attention]] | Attention ne pas oublier les guillemets autour des chanes de caractres, sinon
MySQL va essayer de trouver la colonne Yoda de la table Race, et forcment a gnrera une erreur.

Si tout se passe bien, cette requte devrait vous donner ceci :

M
Yoda 2010-11-09 race_id espece_id

Yoda M 2010-11-09 5 2

Les noms qui sont donns aux colonnes nont pas dimportance, mais vous pouvez les changer
avec des alias si cela vous perturbe.

Venons-en maintenant notre super insertion !

INSERT INTO Animal


(nom, sexe, date_naissance, race_id, espece_id) -- Je prcise les colo
SELECT 'Yoda', 'M', '2010-11-09', id AS race_id, espece_id -- Attention l'ordre
FROM Race WHERE nom = 'Maine coon' ;

Slectionnons maintenant les Maine coon de notre base, pour vrifier que linsertion sest faite
correctement.

132
3.5 Jointures et sous-requtes : modication de donnes

SELECT Animal.id, Animal.sexe, Animal.nom, Race.nom AS race, Espece.nom_courant as espe


FROM Animal
INNER JOIN Race ON Animal.race_id = Race.id
INNER JOIN Espece ON Race.espece_id = Espece.id
WHERE Race.nom = 'Maine coon' ;

Et qui voyons-nous apparatre dans les rsultats ? Notre petit Yoda !

id sexe nom race espece

8 M Bagherra Maine coon Chat


30 M Zonko Maine coon Chat
32 M Farceur Maine coon Chat
34 M Capou Maine coon Chat
39 F Zara Maine coon Chat
40 F Milla Maine coon Chat
42 F Bilba Maine coon Chat
43 F Cracotte Maine coon Chat
61 F Yoda Maine coon Chat

3.5.2 Modification
3.5.2.1 Utilisation des sous-requtes

3.5.2.1.1 Pour la slection Imaginez que pour une raison bizarre, vous vouliez que tous les
perroquets aient en commentaire Coco veut un gteau !.

Si vous saviez que lid de lespce est 4, ce serait facile :

UPDATE Animal SET commentaires = 'Coco veut un gteau' WHERE espece_id = 4 ;

Seulement voil, vous ne savez videmment pas que lid de cette espce est 4. Sinon, ce nest pas
drle ! Vous allez donc utiliser une magnifique sous-requte pour modifier ces bruyants volatiles !

UPDATE Animal SET commentaires = 'Coco veut un gteau !' WHERE espece_id =
(SELECT id FROM Espece WHERE nom_courant LIKE 'Perroquet%');

Bien sr, toutes les possibilits de conditions que lon a vues pour la slection sont encore valables
pour les modifications. Aprs tout, une clause WHERE est une clause WHERE !

3.5.2.1.2 Pour llment modifier Ce matin, un client demande voir vos chats Bleu
Russe, car il compte en offrir un sa fille. Ni une ni deux, vous vrifiez dans la base de donnes,
puis allez chercher Schtroumpfette, Filou, Caribou, Raccou, Callune, Feta et Cawette. Et l, hor-
reur et damnation ! linstant o ses yeux se posent sur Cawette, le client devient vert de rage. Il
prend peine le temps de vous expliquer, outr, que Cawette nest pas un Bleu Russe mais bien
un Nebelung, cause de ses poils longs, puis sen va chercher un leveur plus comptent.

Bon Lerreur est humaine, mais autant la rparer rapidement. Vous insrez donc une nouvelle
race dans la table ad hoc.

INSERT INTO Race (nom, espece_id, description)


VALUES ('Nebelung', 2, 'Chat bleu russe, mais avec des poils longs...');

133
3 Index, jointures et sous-requtes

Une fois cela fait, il vous reste encore modifier la race de Cawette. Pour cela, vous avez besoin de
lid de la race Nebelung que vous venez dajouter. Vous vous en doutez, il est tout fait possible
de le faire grce une sous-requte :

UPDATE Animal SET race_id =


(SELECT id FROM Race WHERE nom = 'Nebelung' AND espece_id = 2)
WHERE nom = 'Cawette' ;

Il est bien entendu indispensable que le rsultat de la sous-requte soit une valeur !

3.5.2.1.3 Limitation des sous-requtes dans un UPDATE Une limitation importante


des sous-requtes est quon ne peut pas modifier un lment dune table que lon utilise dans
une sous-requte.

Exemple : vous trouvez que Callune ressemble quand mme fichtrement Cawette, et ses poils
sont aussi longs. Du coup, vous vous dites que vous auriez d galement modifier la race de Cal-
lune. Vous essayez donc la requte suivante :

UPDATE Animal SET race_id =


(SELECT race_id FROM Animal WHERE nom = 'Cawette' AND espece_id = 2)
WHERE nom = 'Callune' ;

Malheureusement :

ERROR1093(HY000):Youcan'tspecifytargettable'Animal'forupdateinFROMclause

La sous-requte utilise la table Animal, or vous cherchez modifier le contenu de celle-ci. Cest
impossible !

Il vous faudra donc utiliser la mme requte que pour Cawette, en changeant simplement le nom
(je ne vous fais pas laffront de vous lcrire).

3.5.2.2 Modification avec jointure

Imaginons que vous vouliez que, pour les tortues et les perroquets, si un animal na pas de com-
mentaire, on lui ajoute comme commentaire la description de lespce. Vous pourriez slection-
ner les descriptions, les copier, retenir lid de lespce, et ensuite faire un UPDATE pour les tortues
et un autre pour les perroquets. Ou alors, vous pourriez simplement faire un UPDATE avec join-
ture !

Voici la syntaxe que vous devriez utiliser pour le faire avec une jointure :

UPDATE Animal -- Classique


INNER JOIN Espece -- Jointure.
ON Animal.espece_id = Espece.id -- Condition
SET Animal.commentaires = Espece.description -- Ensuite,
WHERE Animal.commentaires IS NULL -- Seulement
AND Espece.nom_courant IN ('Perroquet amazone', 'Tortue d''Hermann'); -- Et seulem
Vous pouvez bien sr mettre ce que vous voulez comme modifications. Ici, jai utilis la va-
leur de la colonne dans lautre table, mais vous auriez pu mettre Animal.commentaires
= 'Tralala', et la jointure naurait alors servi qu slectionner les tortues et les perro-
quets grce au nom courant de lespce.

134
3.5 Jointures et sous-requtes : modication de donnes

Toutes les jointures sont possibles. Vous ntes pas limits aux jointures internes, ni deux
tables jointes.

3.5.3 Suppression

Cette partie sera relativement courte, puisque lutilisation des sous-requtes et des jointures est
assez ressemblante entre la suppression et la modification. Simplement, pour la suppression, les
sous-requtes et jointures ne peuvent servir qu slectionner les lignes supprimer.

3.5.3.1 Utilisation des sous-requtes

On peut, tout simplement, utiliser une sous-requte dans la clause WHERE. Par exemple, imaginez
que nous ayons deux animaux dont le nom est Carabistouille, un chat et un perroquet. Vous d-
sirez supprimer Carabistouille-le-chat, mais garder Carabistouille-le-perroquet. Vous ne pouvez
donc pas utiliser la requte suivante, qui supprimera les deux :

DELETE FROM Animal WHERE nom = 'Carabistouille' ;

Mais il suffit dune sous-requte dans la clause WHERE pour slectionner lespce, et le tour est
jou !

DELETE FROM Animal


WHERE nom = 'Carabistouille' AND espece_id =
(SELECT id FROM Espece WHERE nom_courant = 'Chat');

3.5.3.1.1 Limitations Les limitations sur DELETE sont les mmes que pour UPDATE : on ne
peut pas supprimer des lignes dune table qui est utilise dans une sous-requte.

3.5.3.2 Suppression avec jointure

Pour les jointures, cest le mme principe. Si je reprends le mme problme que ci-dessus, voici
comment supprimer la ligne voulue avec une jointure :

DELETE Animal -- Je prcise de quelles tables le


FROM Animal -- Table principale
INNER JOIN Espece ON Animal.espece_id = Espece.id -- Jointure
WHERE Animal.nom = 'Carabistouille' AND Espece.nom_courant = 'Chat' ;

Vous remarquez une petite diffrence avec la syntaxe classique de DELETE (sans jointure) : je
prcise le nom de la table dans laquelle les lignes doivent tre supprimes juste aprs le DELETE.
En effet, comme on utilise plusieurs tables, cette prcision est obligatoire. Ici, on ne supprimera
que les lignes dAnimal correspondantes.

135
3 Index, jointures et sous-requtes

3.5.3.3 En rsum

INSERT INTO ... SELECT ... permet dinsrer dans une table des donnes prove-
nant directement dune autre (ou un mlange de donnes provenant dune table, et de
constantes dfinies par lutilisateur).
On peut utiliser des jointures et des sous-requtes pour crer des critres de slection com-
plexes pour une modification ou une suppression de donnes.
Dans le cas dune suppression avec jointure, il est indispensable de prciser de quelle(s)
table(s) les donnes doivent tre supprimes
On peut utiliser une sous-requte dans la clause SET dune modification de donnes, pour
dfinir la nouvelle valeur dun champ partir dune autre table.

3.6 Union de plusieurs requtes


Toujours dans loptique de rassembler plusieurs requtes en une seule, voici lUNION. Faire
lunion de deux requtes, cela veut simplement dire runir les rsultats de la premire requte
et les rsultats de la seconde requte. Voyons comment a fonctionne !

3.6.1 Syntaxe
La syntaxe dUNION est simplissime : vous avez deux requtes SELECT dont vous voulez addition-
ner les rsultats ; il vous suffit dajouter UNION entre ces deux requtes.

SELECT ...
UNION
SELECT ...

Le nombre de requtes quil est possible dunir est illimit. Si vous avez cinquante requtes de
slection, placez un UNION entre les cinquante requtes.

SELECT ...
UNION
SELECT ...
UNION
SELECT ...
....
UNION
SELECT ...

Par exemple, vous pouvez obtenir les chiens et les tortues de la manire suivante :

SELECT Animal.* FROM Animal


INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat'
UNION
SELECT Animal.* FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann' ;

136
3.6 Union de plusieurs requtes

[[information]] | Cette requte peut bien sr tre crite sans UNION, en faisant tout simplement
un seul SELECT avec WHERE Espece.nom_courant = 'Chat' OR Espece.nom_courant =
'Tortue d''Hermann'. Il ne sagit ici que dun exemple destin illustrer la syntaxe. Nous
verrons plus loin des exemples de cas o UNION est indispensable.

3.6.1.1 Les rgles

Bien sr, ce nest pas si simple que a, il faut respecter certaines contraintes.

3.6.1.1.1 Nombre de colonnes Il est absolument indispensable que toutes les requtes unies
renvoient le mme nombre de colonnes.

Dans la requte que lon a crite ci-dessus, aucun problme puisque lon slectionne _Animal.*_
dans les deux requtes. Mais il ne serait donc pas possible de slectionner un nombre de colonnes
diffrent dans chaque requte intermdiaire.

Par exemple, la requte ci-dessous renverra une erreur :

-- Pas le mme nombre de colonnes --


------------------------------------

SELECT Animal.id, Animal.nom, Espece.nom_courant -- 3 colonnes slec


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat'
UNION
SELECT Animal.id, Animal.nom, Espece.nom_courant, Animal.espece_id -- 4 colonnes slect
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann' ;

ERROR1222(21000):TheusedSELECTstatementshaveadifferentnumberofcolumns

3.6.1.1.2 Type et ordre des colonnes En ce qui concerne le type des colonnes, je pense
vous avoir dj signal que MySQL est trs (vraiment trs) permissif. Par consquent, si vous
slectionnez des colonnes de diffrents types, vous naurez pas derreurs, mais vous aurez des
rsultats un peu spciaux. o_O

Prenons la requte suivante :

SELECT Animal.id, Animal.nom, Espece.nom_courant -- 3e colonne : nom_courant VARCHAR


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat' AND Animal.nom LIKE 'C%'
UNION
SELECT Animal.id, Animal.nom, Espece.id -- 3e colonne : id SMALLINT
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann' AND Animal.nom LIKE 'C%' ;

Vous aurez bien un rsultat :

137
3 Index, jointures et sous-requtes

id nom nom_courant

5 Choupi Chat
33 Caribou Chat
34 Capou Chat
37 Callune Chat
43 Cracotte Chat
44 Cawette Chat
50 Cheli 3
51 Chicaca 3

Mais avouez que ce nest pas un rsultat trs cohrent. MySQL va simplement convertir tout en
chanes de caractres pour ne pas avoir de problme. Soyez donc trs prudents !

[[information]] | Vous pouvez constater en passant que les noms de colonnes utiliss dans les
rsultats sont ceux de la premire requte effectue. Vous pouvez bien sr les renommer avec
des alias.

Et enfin, pour lordre des colonnes, nouveau vous naurez pas derreur tant que vous avez le
mme nombre de colonnes dans chaque requte, mais vous pouvez avoir des rsultats bizarres. En
effet, MySQL nanalyse pas les noms des colonnes pour trouver une quelconque correspondance
dune requte lautre ; tout se fait sur la base de la position de la colonne dans la requte.

SELECT Animal.id, Animal.nom, Espece.nom_courant FROM Animal


INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Chat' AND Animal.nom LIKE 'C%'
UNION
SELECT Animal.nom, Animal.id, Espece.nom_courant FROM Animal -- 1e et 2e colonnes inv
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE Espece.nom_courant = 'Tortue d''Hermann' AND Animal.nom LIKE 'C%' ;

id nom nom_courant

5 Choupi Chat
33 Caribou Chat
34 Capou Chat
37 Callune Chat
43 Cracotte Chat
44 Cawette Chat
Cheli 50 Tortue dHermann
Chicaca 51 Tortue dHermann

3.6.2 UNION ALL

Excutez la requte suivante :

SELECT * FROM Espece


UNION
SELECT * FROM Espece;

138
3.6 Union de plusieurs requtes

Non, non, je ne me suis pas trompe, je vous demande bien dunir deux requtes qui sont exacte-
ment les mmes.

Rsultat :

id nom_courant
nom_latin description

1 Chien Canis canis Bestiole quatre pattes qui aime les caresses et tire
souvent la langue
2 Chat Felis silvestris Bestiole quatre pattes qui saute trs haut et grimpe
aux arbres
3 Tortue Testudo Bestiole avec une carapace trs dure
dHermann hermanni
4 Perroquet Alipiopsitta Joli oiseau parleur vert et jaune
amazone xanthops

Chaque rsultat napparat quune seule fois. Pour la simple et bonne raison que lorsque vous
faites UNION, les doublons sont effacs. En fait, UNION est quivalent UNION DISTINCT. Si vous
voulez conserver les doublons, vous devez utiliser UNION ALL.

SELECT * FROM Espece


UNION ALL
SELECT * FROM Espece;

id nom_courant
nom_latin description

1 Chien Canis canis Bestiole quatre pattes qui aime les caresses et tire
souvent la langue
2 Chat Felis silvestris Bestiole quatre pattes qui saute trs haut et grimpe
aux arbres
3 Tortue Testudo Bestiole avec une carapace trs dure
dHermann hermanni
4 Perroquet Alipiopsitta Joli oiseau parleur vert et jaune
amazone xanthops
1 Chien Canis canis Bestiole quatre pattes qui aime les caresses et tire
souvent la langue
2 Chat Felis silvestris Bestiole quatre pattes qui saute trs haut et grimpe
aux arbres
3 Tortue Testudo Bestiole avec une carapace trs dure
dHermann hermanni
4 Perroquet Alipiopsitta Joli oiseau parleur vert et jaune
amazone xanthops

[[information]] | Il nest pas possible de mlanger UNION DISTINCT et UNION ALL dans une
mme requte. Si vous utilisez les deux, les UNION ALL seront considrs comme des UNION
DISTINCT.

139
3 Index, jointures et sous-requtes

3.6.3 LIMIT et ORDER BY

3.6.3.1 LIMIT

Il est possible de restreindre les rsultats avec LIMIT au niveau de la requte entire, ou au niveau
des diffrentes requtes unies. Limportant est que ce soit clair. Par exemple, vous unissez deux
requtes et vous voulez limiter les rsultats de la premire. La requte suivante est parfaite pour
a :

SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 3


UNION
SELECT id, nom_latin, 'Espce' AS table_origine FROM Espece;

Vous avez bien trois noms de races, suivis de toutes les espces.

id nom table_origine

1 Berger allemand Race


2 Berger blanc suisse Race
3 Boxer Race
4 Alipiopsitta xanthops Espce
1 Canis canis Espce
2 Felis silvestris Espce
3 Testudo hermanni Espce

[[information]] | En passant, voici un rsultat quil nest pas possible dobtenir sans utiliser
UNION !

Par contre, si vous voulez limiter les rsultats de la seconde requte, comment faire ? Essayons la
requte suivante.

SELECT id, nom, 'Race' AS table_origine FROM Race


UNION
SELECT id, nom_latin, 'Espce' AS table_origine FROM Espece LIMIT 2 ;

id nom table_origine

1 Berger allemand Race


2 Berger blanc suisse Race

Visiblement, ce nest pas ce que nous voulions En fait, LIMIT a t appliqu lensemble des r-
sultats, aprs UNION. Par consquent, si lon veut que LIMIT ne porte que sur la dernire requte,
il faut le prciser. Pour a, il suffit dutiliser des parenthses.

SELECT id, nom, 'Race' AS table_origine FROM Race


UNION
(SELECT id, nom_latin, 'Espce' AS table_origine FROM Espece LIMIT 2);

id nom table_origine

1 Berger allemand Race

140
3.6 Union de plusieurs requtes

id nom table_origine

2 Berger blanc suisse Race


3 Boxer Race
4 Bleu russe Race
5 Maine coon Race
6 Singapura Race
7 Sphynx Race
8 Nebelung Race
4 Alipiopsitta xanthops Espce
1 Canis canis Espce

Et voil le travail !

3.6.3.2 ORDER BY

Par contre, sil est possible de trier le rsultat final dune requte avec UNION, on ne peut pas trier
les rsultats des requtes intermdiaires. Par exemple, cette requte trie bien les rsultats par
ordre anti-alphabtique du nom :

SELECT id, nom, 'Race' AS table_origine FROM Race


UNION
SELECT id, nom_latin, 'Espce' AS table_origine FROM Espece
ORDER BY nom DESC ;

[[attention]] | Il faut bien mettre ici ORDER BY nom, et surtout pas ORDER BY Race.nom ou
ORDER BY Espece.nom_latin. En effet, lORDER BY agit sur lensemble de la requte, donc en
quelque sorte, sur une table intermdiaire compose des rsultats des deux requtes unies. Cette
table nest pas nomme, et ne possde que deux colonnes : id et nom (dfinies par la premire
clause SELECT rencontre).

id nom table_origine

3 Testudo hermanni Espce


7 Sphynx Race
6 Singapura Race
8 Nebelung Race
5 Maine coon Race
2 Felis silvestris Espce
1 Canis canis Espce
3 Boxer Race
4 Bleu russe Race
2 Berger blanc suisse Race
1 Berger allemand Race
4 Alipiopsitta xanthops Espce

Vous pouvez bien sr combiner LIMIT et ORDER BY.

141
3 Index, jointures et sous-requtes

(SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 6)


UNION
(SELECT id, nom_latin, 'Espce' AS table_origine FROM Espece LIMIT 3)
ORDER BY nom LIMIT 5 ;

id nom table_origine

4 Alipiopsitta xanthops Espce


1 Berger allemand Race
2 Berger blanc suisse Race
4 Bleu russe Race
3 Boxer Race

3.6.3.2.1 Exception pour les tris sur les requtes intermdiaires Je vous ai dit quil
ntait pas possible de trier les rsultats dune requte intermdiaire. En ralit, cest plus sub-
til que a. Dans une requte intermdiaire, il est possible dutiliser un ORDER BY mais unique-
ment combin un LIMIT. Cela permettra de restreindre les rsultats voulus (les X premiers dans
lordre dfini par lORDER BY par exemple).

Prenons la requte suivante :

(SELECT id, nom, 'Race' AS table_origine FROM Race LIMIT 6)


UNION
(SELECT id, nom_latin, 'Espce' AS table_origine FROM Espece LIMIT 3);

Cela vous renvoie bien 6 races et 3 espces, mais imaginons que vous ne vouliez pas nimporte
quelles races, mais les 6 dernires par ordre alphabtique du nom. Dans ce cas-l, vous pouvez
utiliser ORDER BY en combinaison avec LIMIT dans la requte intermdiaire :

(SELECT id, nom, 'Race' AS table_origine FROM Race ORDER BY nom DESC LIMIT 6)
UNION
(SELECT id, nom_latin, 'Espce' AS table_origine FROM Espece LIMIT 3);

id nom table_origine

7 Sphynx Race
6 Singapura Race
8 Nebelung Race
5 Maine coon Race
3 Boxer Race
4 Bleu russe Race
4 Alipiopsitta xanthops Espce
1 Canis canis Espce
2 Felis silvestris Espce

142
3.7 Options des cls trangres

3.6.3.3 En rsum

Pour grouper les rsultats de deux requtes SELECT, il suffit de placer UNION entre les deux
requtes.
UNION est quivalent UNION DISTINCT. Si lon ne veut pas liminer les doublons, il faut
utiliser UNION ALL.
Il est possible de limiter les rsultats dun UNION (avec LIMIT), mais galement de limiter
les rsultats de chaque requte composant lUNION.
Par contre, ORDER BY naura dinfluence que sil est utilis sur le rsultat final dun UNION.

3.7 Options des cls trangres


Lorsque je vous ai parl des cls trangres, et que je vous ai donn la syntaxe pour les crer, jai
omis de vous parler des deux options fort utiles :

ON DELETE, qui permet de dterminer le comportement de MySQL en cas de suppression


dune rfrence ;
ON UPDATE, qui permet de dterminer le comportement de MySQL en cas de modification
dune rfrence.

Nous allons maintenant examiner ces options.

3.7.1 Option sur suppression des cls trangres


3.7.1.1 Petits rappels

3.7.1.1.1 La syntaxe Voici comment on ajoute une cl trangre une table dj existante :

ALTER TABLE nom_table


ADD [CONSTRAINT fk_col_ref] -- On donne un nom la cl (facultatif)
FOREIGN KEY colonne -- La colonne sur laquelle on ajoute la cl
REFERENCES table_ref(col_ref); -- La table et la colonne de rfrence

[[information]] | Le principe expliqu ici est exactement le mme si lon cre la cl en mme
temps que la table. La commande ALTER TABLE est simplement plus courte, cest la raison pour
laquelle je lutilise dans mes exemples plutt que CREATE TABLE.

3.7.1.1.2 Le principe Dans notre table Animal, nous avons par exemple mis une cl trangre
sur la colonne race_id, rfrenant la colonne id de la table Race. Cela implique que chaque fois
quune valeur est insre dans cette colonne (soit en ajoutant une ligne, soit en modifiant une
ligne existante), MySQL va vrifier que cette valeur existe bien dans la colonne id de la table Race.
Aucun animal ne pourra donc avoir un race_id qui ne correspond rien dans notre base.

3.7.1.2 Suppression dune rfrence

Que se passe-t-il si lon supprime la race des Boxers ? Certains animaux rfrencent cette espce
dans leur colonne race_id. On risque donc davoir des donnes incohrentes. Or, viter cela est
prcisment la raison dtre de notre cl trangre.

Essayons :

143
3 Index, jointures et sous-requtes

DELETE FROM Race WHERE nom = 'Boxer' ;

ERROR1451(23000):Cannotdeleteorupdateaparentrow:aforeignkeyconstraintfail

Ouf ! Visiblement, MySQL vrifie la contrainte de cl trangre lors dune suppression aussi, et
empche de supprimer une ligne si elle contient une rfrence utilise ailleurs dans la base (ici,
lid de la ligne est donc utilis par certaines lignes de la table Animal).

Mais a veut donc dire que chaque fois quon veut supprimer des lignes de la table Race, il faut
dabord supprimer toutes les rfrences ces races. Dans notre base, a va encore, il ny a pas
normment de cls trangres, mais imaginez si lid de Race servait de rfrence des cls tran-
gres dans ne serait-ce que cinq ou six tables. Pour supprimer une seule race, il faudrait faire
jusqu six ou sept requtes.

Cest donc ici quintervient notre option ON DELETE, qui permet de changer la manire dont la
cl trangre gre la suppression dune rfrence.

3.7.1.2.1 Syntaxe Voici comment on ajoute cette option la cl trangre :

ALTER TABLE nom_table


ADD [CONSTRAINT fk_col_ref]
FOREIGN KEY (colonne)
REFERENCES table_ref(col_ref)
ON DELETE {RESTRICT | NO ACTION | SET NULL | CASCADE} ; -- <-- Nouvelle option !

Il y a donc quatre comportements possibles, que je vais vous dtailler tout de suite (bien que leurs
noms soient plutt clairs) : RESTRICT, NO ACTION, SET NULL et CASCADE.

3.7.1.2.2 RESTRICT ou NO ACTION RESTRICT est le comportement par dfaut. Si lon


essaye de supprimer une valeur rfrence par une cl trangre, laction est avorte et on obtient
une erreur. NO ACTION a exactement le mme effet.

[[attention]] | Cette quivalence de RESTRICT et NO ACTION est propre MySQL. Dans dautres
SGBD, ces deux options nauront pas le mme effet (RESTRICT tant gnralement plus strict
que NO ACTION).

3.7.1.2.3 SET NULL Si on choisit SET NULL, alors tout simplement, NULL est substitu aux
valeurs dont la rfrence est supprime. Pour reprendre notre exemple, en supprimant la race des
Boxers, tous les animaux auxquels on a attribu cette race verront la valeur de leur race_id passer
NULL.

Dailleurs, a me semble plutt intressant comme comportement dans cette situation ! Nous
allons donc modifier notre cl trangre fk_race_id. Cest--dire que nous allons supprimer la cl,
puis la recrer avec le bon comportement :

ALTER TABLE Animal DROP FOREIGN KEY fk_race_id;

ALTER TABLE Animal


ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race(id) ON DELETE SET NULL

144
3.7 Options des cls trangres

Dornavant, si vous supprimez une race, tous les animaux auxquels vous avez attribu cette race
auparavant auront NULL comme race_id.

Vrifions en supprimant les Boxers, depuis le temps quon essaye !

-- Affichons d'abord tous les animaux, avec leur race --


-- -----------------------------------------------------
SELECT Animal.nom, Animal.race_id, Race.nom as race FROM Animal
LEFT JOIN Race ON Animal.race_id = Race.id
ORDER BY race;

-- Supprimons ensuite la race 'Boxer' --


-- -------------------------------------
DELETE FROM Race WHERE nom = 'Boxer' ;

-- Raffichons les animaux --


-- --------------------------
SELECT Animal.nom, Animal.race_id, Race.nom as race FROM Animal
LEFT JOIN Race ON Animal.race_id = Race.id
ORDER BY race;

Les ex-boxers existent toujours dans la table Animal, mais ils nappartiennent plus aucune race.

3.7.1.2.4 CASCADE Ce dernier comportement est le plus risqu (et le plus violent ! :pirate :
). En effet, cela supprime purement et simplement toutes les lignes qui rfrenaient la valeur
supprime ! Donc, si on choisit ce comportement pour la cl trangre sur la colonne espece_id de
la table Animal, vous supprimez lespce Perroquet amazone et POUF !, quatre lignes de votre
table Animal (les quatre perroquets) sont supprimes en mme temps. Il faut donc tre bien sr
de ce que lon fait si lon choisit ON DELETE CASCADE. Il y a cependant de nombreuses situations
dans lesquelles cest utile. Prenez par exemple un forum sur un site internet. Vous avez une table
Sujet, et une table Message, avec une colonne sujet_id. Avec ON DELETE CASCADE, il vous suffit de
supprimer un sujet pour que tous les messages de ce sujet soient galement supprims. Plutt
pratique non ?

[[attention]] | Je le rpte : soyez bien srs de ce que vous faites ! Je dcline toute responsabilit
en cas de perte de donnes cause par un ON DELETE CASCADE inconsidrment utilis !

*SGBD : Systme de Gestion de Bases de Donnes

3.7.2 Option sur modification des cls trangres


On peut galement rencontrer des problmes de cohrence des donnes en cas de modification.
En effet, si lon change par exemple lid de la race Singapura, tous les animaux qui ont lan-
cien id dans leur colonne race_id rfrenceront une ligne qui nexiste plus. Les modifications de
rfrences de cls trangres sont donc soumises aux mmes restrictions que la suppression.

Exemple : essayons de modifier lid de la race Singapura.

UPDATE Race SET id = 3 WHERE nom = 'Singapura' ;

ERROR1451(23000):Cannotdeleteorupdateaparentrow:aforeignkeyconstraintfail

145
3 Index, jointures et sous-requtes

Loption permettant de dfinir le comportement en cas de modification est donc ON UPDATE


{RESTRICT | NO ACTION | SET NULL | CASCADE}. Les quatre comportements possibles
sont exactement les mmes que pour la suppression.

RESTRICT et NO ACTION : empche la modification si elle casse la contrainte (comporte-


ment par dfaut).
SET NULL : met NULL partout o la valeur modifie tait rfrence.
CASCADE : modifie galement la valeur l o elle est rfrence.

3.7.2.0.1 Petite explication propos de CASCADE CASCADE signifie que lvnement


est rpt sur les tables qui rfrencent la valeur. Pensez des ractions en cascade. Ainsi,
une suppression provoquera dautres suppressions, tandis quune modification provoquera
dautres modifications ! :soleil :

Modifions par exemple la cl trangre sur Animal.race_id, avant de modifier lid de la race Singa-
pura (jetez dabord un il aux donnes des tables Race et Animal, afin de voir les diffrences).

-- Suppression de la cl --
-- ------------------------
ALTER TABLE Animal DROP FOREIGN KEY fk_race_id;

-- Recration de la cl avec les bonnes options --


-- -----------------------------------------------
ALTER TABLE Animal
ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race(id)
ON DELETE SET NULL -- N'oublions pas de remettre le ON DELETE !
ON UPDATE CASCADE ;

-- Modification de l'id des Singapura --


-- -------------------------------------
UPDATE Race SET id = 3 WHERE nom = 'Singapura' ;

Les animaux nots comme tant des Singapura ont dsormais leur race_id 3. Parfait ! :magi-
cien :

[[attention]] | En rgle gnrale (dans 99,99 % des cas), cest une trs trs mauvaise ide de chan-
ger la valeur dun id (ou de votre cl primaire auto-incrmente, quel que soit le nom que vous lui
donnez). En effet, vous risquez des problmes avec lauto-incrment, si vous donnez une valeur
non encore atteinte par auto-incrmentation par exemple. Soyez bien conscients de ce que vous
faites. Je ne lai montr ici que pour illustrer le ON UPDATE, parce que toutes nos cls trangres
rfrencent des cls primaires. Mais ce nest pas le cas partout. Une cl trangre pourrait par-
faitement rfrencer un simple index, dpourvu de toute auto-incrmentation, auquel cas vous
pouvez vous amuser en changer la valeur autant de fois que vous le voudrez.

Modifions une dernire fois cette cl trangre pour remettre loption ON UPDATE par dfaut.

ALTER TABLE Animal DROP FOREIGN KEY fk_race_id;

ALTER TABLE Animal


ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race(id) ON DELETE SET NULL

146
3.7 Options des cls trangres

3.7.3 Utilisation de ces options dans notre base


Appliquons maintenant ce que nous avons appris notre base de donnes elevage. Celle-ci com-
porte 5 cls trangres :

3.7.3.0.1 Sur la table Animal


race_id rfrence Race.id
espece_id rfrence Espece.id
mere_id rfrence Animal.id
pere_id rfrence Animal.id

3.7.3.0.2 Sur la table Race


espece_id rfrence Espece.id

3.7.3.1 Modifications

Pour les modifications, le mieux ici est de laisser le comportement par dfaut (RESTRICT)
pour toutes nos cls trangres. Les colonnes rfrences sont chaque fois des colonnes auto-
incrmentes, on ne devrait donc pas modifier leurs valeurs.

3.7.3.2 Suppressions

Le problme est plus dlicat pour les suppressions. On a dj dfini ON DELETE SET NULL pour
la cl sur Animal.race_id. Prenons les autres cls une une.

3.7.3.2.1 Cl sur Animal.espece_id Si lon supprime une espce de la base de donnes,


cest quon ne lutilise plus dans notre levage, donc a priori, on na plus besoin non plus des ani-
maux de cette espce. Sachant cela, on serait sans doute tents de mettre ON DELETE CASCADE.
Ainsi, en une seule requte, tout est fait. Cependant, les animaux sont quand mme le point cen-
tral de notre base de donnes. Cela me parat donc un peu violent de les supprimer automatique-
ment de cette manire, en cas de suppression despce. Par consquent, je vous propose plutt de
laisser le ON DELETE RESTRICT. Supprimer une espce nest pas anodin, et supprimer de nom-
breux animaux dun coup non plus. En empchant la suppression des espces tant quil existe des
animaux de celle-ci, on oblige lutilisateur supprimer dabord tous ces animaux. Pas de risque
de fausse manuvre donc. Attention au fait que le ON DELETE SET NULL nest bien sr pas envi-
sageable, puisque la colonne espece_id de la table Animal ne peut pas tre NULL. Pas de changement
pour cette cl trangre !

[[information]] | Il sagit de mon point de vue personnel bien sr. Si vous pensez que cest mieux
de mettre ON DELETE CASCADE, faites-le. On peut certainement trouver des arguments en fa-
veur des deux possibilits.

3.7.3.2.2 Cls sur Animal.mere_id et Animal.pere_id Ce nest pas parce quon sup-
prime un animal que tous ses enfants doivent tre supprims galement. Par contre, mettre
NULL semble une bonne ide. ON DELETE SET NULL donc !

147
3 Index, jointures et sous-requtes

3.7.3.2.3 Cl sur Race.espece_id Si une espce est finalement supprime, et donc que
tous les animaux de cette espce ont galement t supprims auparavant (puisquon a laiss
ON DELETE RESTRICT pour la cl sur Animal.espece_id), alors les races de cette espce deviennent
caduques. On peut donc utiliser un ON DELETE CASCADE ici.

3.7.3.3 Les requtes

Vous avez toutes les informations ncessaires pour crire ces requtes, je vous encourage donc
les crire vous-mmes avant de regarder mon code.

[[secret]] | sql | -- Animal.mere_id -- | -- ----------------- | ALTER TABLE


Animal DROP FOREIGN KEY fk_mere_id; | | ALTER TABLE Animal | ADD CONSTRAINT
fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal(id) ON DELETE SET NULL;
| | -- Animal.pere_id -- | -- ----------------- | ALTER TABLE Animal DROP
FOREIGN KEY fk_pere_id; | | ALTER TABLE Animal | ADD CONSTRAINT fk_pere_id
FOREIGN KEY (pere_id) REFERENCES Animal(id) ON DELETE SET NULL; | | --
Race.espece_id -- | -- ----------------- | ALTER TABLE Race DROP FOREIGN
KEY fk_race_espece_id; | | ALTER TABLE Race | ADD CONSTRAINT fk_race_espece_id
FOREIGN KEY (espece_id) REFERENCES Espece(id) ON DELETE CASCADE; |

3.7.3.4 En rsum

Lorsque lon cre (ou modifie) une cl trangre, on peut lui dfinir deux options : ON
DELETE, qui sert en cas de suppression de la rfrence ; et ON UPDATE, qui sert en cas de
modification de la rfrence.
RESTRICT et NO ACTION dsignent le comportement par dfaut : la rfrence ne peut tre
ni supprime, ni modifie si cela entrane des donnes incohrentes vis--vis de la cl
trangre.
SET NULL fait en sorte que les donnes de la cl trangre ayant perdu leur rfrence (suite
une modification ou une suppression) soient mises NULL.
CASCADE rpercute la modification ou la suppression dune rfrence de cl trangre sur
les lignes impactes.

3.8 Violation de contrainte dunicit


Lorsque vous insrez ou modifiez une ligne dans une table, diffrents vnements en relation
avec les cls et les index peuvent se produire.

Linsertion/la modification peut russir, cest videmment le mieux.


Linsertion/la modification peut chouer parce quune contrainte de cl secondaire nest
pas respecte.
Linsertion/la modification peut chouer parce quune contrainte dunicit (cl primaire
ou index UNIQUE) nest pas respecte.

Nous allons ici nous intresser la troisime possibilit : ce qui arrive en cas de non-respect
dune contrainte dunicit. Pour linstant, dans ce cas-l, une erreur est dclenche. la fin de
ce chapitre, vous serez capables de modifier ce comportement :

148
3.8 Violation de contrainte dunicit

vous pourrez laisser linsertion/la modification chouer, mais sans dclencher derreur ;
vous pourrez remplacer la(les) ligne(s) qui existe(nt) dj par la ligne que vous essayez
dinsrer (ne concerne que les requtes dinsertion) ;
enfin vous pourrez modifier la ligne qui existe dj au lieu den insrer une nouvelle (ne
concerne que les requtes dinsertion).

3.8.1 Ignorer les erreurs


Les commandes dinsertion et modification possdent une option : IGNORE, qui permet dignorer
(tiens donc ! o_O ) linsertion ou la modification si elle viole une contrainte dunicit.

3.8.1.1 Insertion

Nous avons mis une contrainte dunicit (sous la forme dun index UNIQUE) sur la colonne
nom_latin de la table Espece. Donc, si lon essaye dinsrer la ligne suivante, une erreur sera
dclenche puisquil existe dj une espce dont le nom latin est Canis canis.

INSERT INTO Espece (nom_courant, nom_latin, description)


VALUES ('Chien en peluche', 'Canis canis', 'Tout doux, propre et silencieux');

ERROR1062(23000):Duplicateentry'Caniscanis'forkey'nom_latin'

Par contre, si lon utilise le mot-cl IGNORE :

INSERT IGNORE INTO Espece (nom_courant, nom_latin, description)


VALUES ('Chien en peluche', 'Canis canis', 'Tout doux, propre et silencieux');

QueryOK,0rowsaffected(0.01sec)

Plus derreur, la ligne na simplement pas t insre.

3.8.1.2 Modification

Si lon essaye de modifier lespce des chats, pour lui donner comme nom latin Canis canis, une
erreur sera dclenche, sauf si lon ajoute loption IGNORE.

UPDATE Espece SET nom_latin = 'Canis canis' WHERE nom_courant = 'Chat' ;

ERROR1062(23000):Duplicateentry'Caniscanis'forkey'nom_latin'

UPDATE IGNORE Espece SET nom_latin = 'Canis canis' WHERE nom_courant = 'Chat' ;

QueryOK,0rowsaffected(0.01sec)

Les chats sont toujours des Felix silvestris !

149
3 Index, jointures et sous-requtes

3.8.1.3 LOAD DATA INFILE

La mme option est disponible avec la commande LOAD DATA INFILE, ce qui est plutt pratique
si vous voulez viter de devoir traficoter votre fichier suite une insertion partielle due une ligne
qui ne respecte pas une contrainte dunicit.

3.8.1.3.1 Syntaxe
LOAD DATA [LOCAL] INFILE 'nom_fichier' IGNORE -- IGNORE se place juste avant INTO, c
INTO TABLE nom_table
[FIELDS
[TERMINATED BY '\t']
[ENCLOSED BY '']
[ESCAPED BY '\\' ]
]
[LINES
[STARTING BY '']
[TERMINATED BY '\n']
]
[IGNORE nombre LINES]
[(nom_colonne,...)];

3.8.2 Remplacer lancienne ligne


Lorsque vous voulez insrer une ligne dans une table, vous pouvez utiliser la commande bien
connue INSERT INTO, ou vous pouvez utiliser REPLACE INTO. La diffrence entre ces deux re-
qutes est la faon quelles ont de grer les contraintes dunicit (a tombe bien, cest le sujet de
ce chapitre !).

Dans le cas dune insertion qui enfreint une contrainte dunicit, REPLACE ne va ni renvoyer une
erreur, ni ignorer linsertion (comme INSERT INTO [IGNORE]). REPLACE va, purement, simple-
ment et violemment, remplacer lancienne ligne par la nouvelle.

[[question]] | Mais que veut dire remplacer lancienne ligne par la nouvelle ?

Prenons par exemple Spoutnik la tortue, qui se trouve dans notre table Animal.

SELECT id, sexe, date_naissance, nom, espece_id FROM Animal WHERE nom = 'Spoutnik' ;

id sexe date_naissance nom espece_id

53 M 2007-04-02 01 :45 :00 Spoutnik 3

tant donn que nous avons mis un index UNIQUE sur (nom, espece_id), il est absolument impos-
sible davoir une autre tortue du nom de Spoutnik dans notre table. La requte suivante va donc
chouer lamentablement.

INSERT INTO Animal (sexe, nom, date_naissance, espece_id)


VALUES ('F', 'Spoutnik', '2010-08-06 15:05:00', 3);

ERROR1062(23000):Duplicateentry'Spoutnik-3'forkey'ind_uni_nom_espece_id'

150
3.8 Violation de contrainte dunicit

Par contre, si on utilise REPLACE au lieu de INSERT :

REPLACE INTO Animal (sexe, nom, date_naissance, espece_id)


VALUES ('F', 'Spoutnik', '2010-08-06 15:05:00', 3);

QueryOK,2rowsaffected(0.06sec)

Pas derreur, mais vous pouvez voir que deux lignes ont t affectes par la commande. En effet,
Spoutnik est mort, vive Spoutnik !

SELECT id, sexe, date_naissance, nom, espece_id FROM Animal WHERE nom = 'Spoutnik' ;

id sexe date_naissance nom espece_id

63 F 2010-08-06 15 :05 :00 Spoutnik 3

Comme vous voyez, nous navons toujours quune seule tortue du nom de Spoutnik, mais il ne
sagit plus du mle n le 2 avril 2007 que nous avions prcdemment, mais bien de la femelle ne
le 6 aot 2010 que nous venons dinsrer avec REPLACE INTO. Lautre Spoutnik a t purement
et simplement remplac.

Attention cependant, quand je dis que lancien Spoutnik a t remplac, jutilise le terme rem-
plac, car il sagit de la traduction de REPLACE. Il sagit cependant dun abus de langage. En ra-
lit, la ligne de lancien Spoutnik a t supprime, et ensuite seulement, le nouveau Spoutnik
a t insr. Cest dailleurs pour cela que les deux Spoutnik nont pas du tout le mme id.

3.8.2.1 Remplacement de plusieurs lignes

Pourquoi ai-je bien prcis quil ne sagissait pas vraiment dun remplacement, mais dune sup-
pression suivie dune insertion ? Parce que ce comportement a des consquences quil ne faut pas
ngliger !

Prenons par exemple une table sur laquelle existent plusieurs contraintes dunicit. Cest le cas
dAnimal, puisquon a cet index UNIQUE (nom, espece_id), ainsi que la cl primaire (lid doit donc
tre unique aussi).

Nous allons insrer avec REPLACE une ligne qui viole les deux contraintes dunicit :

REPLACE INTO Animal (id, sexe, nom, date_naissance, espece_id) -- Je donne moi-mme un
VALUES (32, 'M', 'Spoutnik', '2009-07-26 11:52:00', 3); -- Et Spoutnik est mon s

QueryOK,3rowsaffected(0.05sec)

Cette fois-ci, trois lignes ont t affectes. Trois ! Tout simplement, les deux lignes qui emp-
chaient linsertion cause des contraintes dunicit ont t supprimes. La ligne qui avait lid 32,
ainsi que lancien Spoutnik ont t supprims. Le nouveau Spoutnik a ensuite t insr.

[[attention]] | Je lai fait ici pour vous donner un exemple, mais je rappelle que cest une trs
mauvaise ide de donner soi-mme un id lorsque la colonne est auto-incrmente (ce qui sera
presque toujours le cas).

151
3 Index, jointures et sous-requtes

3.8.2.2 LOAD DATA INFILE

REPLACE est galement disponible avec LOAD DATA INFILE. Le comportement est exactement
le mme.

Bien entendu, IGNORE et REPLACE ne peuvent pas tre utiliss en mme temps. Cest lun ou
lautre.

3.8.2.2.1 Syntaxe
LOAD DATA [LOCAL] INFILE 'nom_fichier' REPLACE -- se place au mme endroit que IGNORE
INTO TABLE nom_table
[FIELDS
[TERMINATED BY '\t']
[ENCLOSED BY '']
[ESCAPED BY '\\' ]
]
[LINES
[STARTING BY '']
[TERMINATED BY '\n']
]
[IGNORE nombre LINES]
[(nom_colonne,...)];

3.8.3 Modifier lancienne ligne


REPLACE supprime lancienne ligne (ou les anciennes lignes), puis insre la nouvelle. Mais par-
fois, ce quon veut, cest bien modifier la ligne dj existante. Cest possible grce la clause ON
DUPLICATE KEY UPDATE de la commande INSERT.

3.8.3.1 Syntaxe

Voici donc la syntaxe de INSERT INTO avec cette fameuse clause :

INSERT INTO nom_table [(colonne1, colonne2, colonne3)]


VALUES (valeur1, valeur2, valeur3)
ON DUPLICATE KEY UPDATE colonne2 = valeur2 [, colonne3 = valeur3];

Donc, si une contrainte dunicit est viole par la requte dinsertion, la clause ON DUPLICATE
KEY va aller modifier les colonnes spcifies dans la ligne dj existante.

Exemple : revoici notre petit Spoutnik !

SELECT id, sexe, date_naissance, nom, espece_id, mere_id, pere_id


FROM Animal
WHERE nom = 'Spoutnik' ;

152
3.8 Violation de contrainte dunicit

mere_id
id sexe date_naissance nom espece_id pere_id

32 M 2009-07-26 Spoutnik 3 NULL NULL


11 :52 :00

Essayons dinsrer une autre tortue du nom de Spoutnik, mais cette fois-ci avec la nouvelle
clause.

INSERT INTO Animal (sexe, date_naissance, espece_id, nom, mere_id)


VALUES ('M', '2010-05-27 11:38:00', 3, 'Spoutnik', 52) -- date_naissance et mere_id son
ON DUPLICATE KEY UPDATE mere_id = 52 ;

SELECT id, sexe, date_naissance, nom, espece_id, mere_id, pere_id


FROM Animal
WHERE nom = 'Spoutnik' ;

mere_id
id sexe date_naissance nom espece_id pere_id

32 M 2009-07-26 Spoutnik 3 52 NULL


11 :52 :00

Spoutnik est toujours l, mais il a dsormais une mre ! Et son id na pas t modifi. Il sagit donc
bien dune modification de la ligne, et non dune suppression suivie dune insertion. De mme,
sa date de naissance est reste la mme puisque lUPDATE ne portait que sur mere_id.

En fait, ce que nous avons fait tait quivalent la requte de modification suivante :

UPDATE Animal
SET mere_id = 52
WHERE nom = 'Spoutnik'
AND espece_id = 3 ;

3.8.3.2 Attention : plusieurs contraintes dunicit sur la mme table

Souvenez-vous, avec REPLACE, nous avons vu que sil existait plusieurs contraintes dunicit sur
la mme table, et que plusieurs lignes faisaient chouer linsertion cause de ces contraintes,
REPLACE supprimait autant de lignes que ncessaire pour faire linsertion.
Le comportement de ON DUPLICATE KEY est diffrent ! Dans le cas o plusieurs lignes seraient
impliques, seule une de ces lignes est modifie (et impossible de prdire laquelle). Il faut donc
tout prix viter dutiliser cette clause quand plusieurs contraintes dunicit pourraient tre vio-
les par linsertion.

3.8.3.3 En rsum

Le mot-cl IGNORE, utilis dans des requtes INSERT, UPDATE ou LOAD DATA, permet de
ne pas dclencher derreur en cas de violation dune contrainte dunicit : la ligne posant

153
3 Index, jointures et sous-requtes

problme ne sera simplement pas insre/modifie.


Utiliser REPLACE au lieu de INSERT (ou dans LOAD DATA) supprime les lignes existantes
qui provoquent une violation de la contrainte dunicit linsertion, puis insre la nouvelle
ligne.
Il est possible, en ajoutant une clause ON DUPLICATE KEY UPDATE une requte INSERT
INTO, de provoquer soit une insertion (si aucune contrainte dunicit nest viole), soit une
modification de certaines valeurs de la ligne dj existante (dans le cas contraire).

On commence faire des choses plutt sympathiques avec nos donnes, nest-ce pas ? Cette par-
tie a pu vous paratre un peu plus complique. Du coup, pour les prochaines, je vous prpare
quelque chose de simple, et pourtant extrmement utile !

154
4 Fonctions : nombres, chanes et
agrgats
Maintenant que vous savez faire les oprations basiques sur vos donnes (insrer, lire, modifier,
supprimer - les oprations du CRUD), les deux prochaines parties seront consacres la ma-
nipulation de ces donnes, en particulier grce lutilisation des fonctions. Cette partie-ci sera
consacre aux nombres et aux chanes de caractres. Aprs une brve introduction sur les fonc-
tions, vous dcouvrirez quelques fonctions bien utiles. Les fonctions dagrgat cltureront en-
suite ce chapitre.

*[CRUD] : Create, Read, Update, Delete

4.1 Rappels et introduction


Pour commencer en douceur, voici un chapitre dintroduction. On commence avec quelques rap-
pels et astuces. Ensuite, on entre dans le vif du sujet avec la dfinition dune fonction, et la
diffrence entre une fonction scalaire et une fonction dagrgation. Et pour finir, nous verrons
quelques fonctions permettant dobtenir des renseignements sur votre environnement, sur la
dernire requte effectue, et permettant de convertir des valeurs.

Que du bonheur, que du facile, que du super utile !

4.1.0.1 Etat actuel de la base de donnes

Note : les tables de test ne sont pas reprises

[[secret]] | sql | SET NAMES utf8; | | DROP TABLE IF EXISTS Animal; | DROP
TABLE IF EXISTS Race; | DROP TABLE IF EXISTS Espece; | | | CREATE TABLE
Espece ( | id smallint(6) unsigned NOT NULL AUTO_INCREMENT, | nom_courant
varchar(40) NOT NULL, | nom_latin varchar(40) NOT NULL, | description
text, | PRIMARY KEY (id), | UNIQUE KEY nom_latin (nom_latin) | ) ENGINE=InnoDB
AUTO_INCREMENT=7 DEFAULT CHARSET=latin1; | | LOCK TABLES Espece WRITE;
| INSERT INTO Espece VALUES (1,'Chien','Canis canis','Bestiole quatre
pattes qui aime les caresses et tire souvent la langue'),(2,'Chat','Felis
silvestris','Bestiole quatre pattes qui saute trs haut et grimpe aux
arbres'),(3,'Tortue d''Hermann','Testudo hermanni','Bestiole avec une carapace
trs dure'), | (4,'Perroquet amazone','Alipiopsitta xanthops','Joli oiseau
parleur vert et jaune'); | UNLOCK TABLES; | | | CREATE TABLE Race ( |
id smallint(6) unsigned NOT NULL AUTO_INCREMENT, | nom varchar(40) NOT
NULL, | espece_id smallint(6) unsigned NOT NULL, | description text, |
PRIMARY KEY (id) | ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;
| | LOCK TABLES Race WRITE; | INSERT INTO Race VALUES (1,'Berger allemand',1,'Chien

155
4 Fonctions : nombres, chanes et agrgats

sportif et lgant au pelage dense, noir-marron-fauve, noir ou gris.'),(2,'Berger


blanc suisse',1,'Petit chien au corps compact, avec des pattes courtes
mais bien proportionnes et au pelage tricolore ou bicolore.'), | (3,'Singapura',2,'Cha
de petite taille aux grands yeux en amandes.'),(4,'Bleu russe',2,'Chat aux
yeux verts et la robe paisse et argente.'),(5,'Maine coon',2,'Chat de
grande taille, poils mi-longs.'), | (7,'Sphynx',2,'Chat sans poils.'),(8,'Nebelung',2
bleu russe, mais avec des poils longs...'); | UNLOCK TABLES; | | | CREATE
TABLE Animal ( | id smallint(6) unsigned NOT NULL AUTO_INCREMENT, | sexe
char(1) DEFAULT NULL, | date_naissance datetime NOT NULL, | nom varchar(30)
DEFAULT NULL, | commentaires text, | espece_id smallint(6) unsigned
NOT NULL, | race_id smallint(6) unsigned DEFAULT NULL, | mere_id smallint(6)
unsigned DEFAULT NULL, | pere_id smallint(6) unsigned DEFAULT NULL, |
PRIMARY KEY (id), | UNIQUE KEY ind_uni_nom_espece_id (nom,espece_id) |
) ENGINE=InnoDB AUTO_INCREMENT=65 DEFAULT CHARSET=utf8; | | LOCK TABLES
Animal WRITE; | INSERT INTO Animal VALUES (1,'M','2010-04-05 13:43:00','Rox','Mordille
beaucoup',1,1,18,22), | (2,NULL,'2010-03-24 02:23:00','Roucky',NULL,2,NULL,40,30),(3,'F
15:02:00','Schtroumpfette',NULL,2,4,41,31),(4,'F','2009-08-03 05:12:00',NULL,'Bestiole
avec une carapace trs dure',3,NULL,NULL,NULL), | (5,NULL,'2010-10-03 16:44:00','Choupi
sans oreille gauche',2,NULL,NULL,NULL),(6,'F','2009-06-13 08:17:00','Bobosse','Carapace
bizarre',3,NULL,NULL,NULL),(7,'F','2008-12-06 05:18:00','Caroline',NULL,1,2,NULL,NULL),
| (8,'M','2008-09-11 15:38:00','Bagherra',NULL,2,5,NULL,NULL),(9,NULL,'2010-08-23
05:18:00',NULL,'Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),(10,'M','2010-0
15:41:00','Bobo',NULL,1,NULL,7,21), | (11,'F','2008-02-20 15:45:00','Canaille',NULL,1,N
08:54:00','Cali',NULL,1,2,NULL,NULL),(13,'F','2007-04-24 12:54:00','Rouquine',NULL,1,1,
| (14,'F','2009-05-26 08:56:00','Fila',NULL,1,2,NULL,NULL),(15,'F','2008-02-20
15:47:00','Anya',NULL,1,NULL,NULL,NULL),(16,'F','2009-05-26 08:50:00','Louya',NULL,1,NU
| (17,'F','2008-03-10 13:45:00','Welva',NULL,1,NULL,NULL,NULL),(18,'F','2007-04-24
12:59:00','Zira',NULL,1,1,NULL,NULL),(19,'F','2009-05-26 09:02:00','Java',NULL,1,2,NULL
| (20,'M','2007-04-24 12:45:00','Balou',NULL,1,1,NULL,NULL),(21,'F','2008-03-10
13:43:00','Pataude',NULL,1,NULL,NULL,NULL),(22,'M','2007-04-24 12:42:00','Bouli',NULL,1
| (24,'M','2007-04-12 05:23:00','Cartouche',NULL,1,NULL,NULL,NULL),(25,'M','2006-05-14
15:50:00','Zambo',NULL,1,1,NULL,NULL),(26,'M','2006-05-14 15:48:00','Samba',NULL,1,1,NU
| (27,'M','2008-03-10 13:40:00','Moka',NULL,1,NULL,NULL,NULL),(28,'M','2006-05-14
15:40:00','Pilou',NULL,1,1,NULL,NULL),(29,'M','2009-05-14 06:30:00','Fiero',NULL,2,3,NU
| (30,'M','2007-03-12 12:05:00','Zonko',NULL,2,5,NULL,NULL),(31,'M','2008-02-20
15:45:00','Filou',NULL,2,4,NULL,NULL),(32,'M','2009-07-26 11:52:00','Spoutnik',NULL,3,N
| (33,'M','2006-05-19 16:17:00','Caribou',NULL,2,4,NULL,NULL),(34,'M','2008-04-20
03:22:00','Capou',NULL,2,5,NULL,NULL),(35,'M','2006-05-19 16:56:00','Raccou','Pas
de queue depuis la naissance',2,4,NULL,NULL), | (36,'M','2009-05-14 06:42:00','Boucan',
16:06:00','Callune',NULL,2,8,NULL,NULL),(38,'F','2009-05-14 06:45:00','Boule',NULL,2,3,
| (39,'F','2008-04-20 03:26:00','Zara',NULL,2,5,NULL,NULL),(40,'F','2007-03-12
12:00:00','Milla',NULL,2,5,NULL,NULL),(41,'F','2006-05-19 15:59:00','Feta',NULL,2,4,NUL
| (42,'F','2008-04-20 03:20:00','Bilba','Sourde de l''oreille droite
80%',2,5,NULL,NULL),(43,'F','2007-03-12 11:54:00','Cracotte',NULL,2,5,NULL,NULL),(44,'F
16:16:00','Cawette',NULL,2,8,NULL,NULL), | (45,'F','2007-04-01 18:17:00','Nikki','Besti
avec une carapace trs dure',3,NULL,NULL,NULL),(46,'F','2009-03-24 08:23:00','Tortilla'
avec une carapace trs dure',3,NULL,NULL,NULL),(47,'F','2009-03-26 01:24:00','Scroupy',
avec une carapace trs dure',3,NULL,NULL,NULL), | (48,'F','2006-03-15 14:56:00','Lulla'

156
4.1 Rappels et introduction

avec une carapace trs dure',3,NULL,NULL,NULL),(49,'F','2008-03-15 12:02:00','Dana','Be


avec une carapace trs dure',3,NULL,NULL,NULL),(50,'F','2009-05-25 19:57:00','Cheli','B
avec une carapace trs dure',3,NULL,NULL,NULL), | (51,'F','2007-04-01 03:54:00','Chicac
avec une carapace trs dure',3,NULL,NULL,NULL),(52,'F','2006-03-15 14:26:00','Redbul','
08:20:00','Bubulle','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),
| (55,'M','2008-03-15 18:45:00','Relou','Surpoids',3,NULL,NULL,NULL),(56,'M','2009-05-2
18:54:00','Bulbizard','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),(57,'M',
19:36:00','Safran','Coco veut un gteau !',4,NULL,NULL,NULL), | (58,'M','2008-02-20
02:50:00','Gingko','Coco veut un gteau !',4,NULL,NULL,NULL),(59,'M','2009-03-26
08:28:00','Bavard','Coco veut un gteau !',4,NULL,NULL,NULL),(60,'F','2009-03-26
07:55:00','Parlotte','Coco veut un gteau !',4,NULL,NULL,NULL), | (61,'M','2010-11-09
00:00:00','Yoda',NULL,2,5,NULL,NULL); | UNLOCK TABLES; | | | ALTER TABLE
Race ADD CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES
Espece (id) ON DELETE CASCADE; | | ALTER TABLE Animal ADD CONSTRAINT fk_pere_id
FOREIGN KEY (pere_id) REFERENCES Animal (id) ON DELETE SET NULL; | ALTER
TABLE Animal ADD CONSTRAINT fk_espece_id FOREIGN KEY (espece_id) REFERENCES
Espece (id); | ALTER TABLE Animal ADD CONSTRAINT fk_mere_id FOREIGN KEY
(mere_id) REFERENCES Animal (id) ON DELETE SET NULL; | ALTER TABLE Animal
ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race (id) ON
DELETE SET NULL; |

4.1.1 Rappels et manipulation simple de nombres

4.1.1.1 Rappels

Nous avons vu quil tait possible dafficher des nombres ou des chanes de caractres avec un
simple SELECT :

Exemple : affichage simple de nombres et de chanes de caractres.

SELECT 3, 'Bonjour !' ;

3 Bonjour !

3 Bonjour !

Vous savez galement quil est possible de faire diverses oprations mathmatiques de la mme
manire, et que la priorit des oprations est respecte :

Exemple : quelques oprations mathmatiques.

SELECT 3+5, 8/3, 10+2/2, (10+2)/2 ;

3+5 8/3 10+2/2 (10+2)/2

8 2.6667 11.0000 6.0000

4.1.1.1.1 Oprateurs mathmatiques Six oprateurs mathmatiques sont utilisables avec


MySQL.

157
4 Fonctions : nombres, chanes et agrgats

Symbole Opration

addition

soustraction

multiplication

/ division
DIV division entire
% (ou MOD) modulo

Pour ceux qui ne le sauraient pas, le modulo de a par b est le reste de la division entire de a par
b (par exemple, le modulo de 13 par 10 vaut 3).

Exemple : les six oprateurs mathmatiques.

SELECT 1+1, 4-2, 3*6, 5/2, 5 DIV 2, 5 % 2, 5 MOD 2 ;

4-2 3*6
1+1 5/2 5 DIV 2 5%2 5 MOD 2

2 2 18 2.5000 2 1 1

4.1.1.2 Combiner les donnes avec des oprations mathmatiques

Jusquici, rien de plus simple. Cependant, si lon veut juste faire 3+6, pas besoin de MySQL, une
calculette (ou un cerveau) suffit. Aprs tout, dans une base de donnes, limportant, ce sont les
donnes. Et, il est videmment possible de faire des oprations mathmatiques (et bien dautres
choses) sur les donnes.

4.1.1.2.1 Modification de notre base de donnes Mais avant de rentrer dans le vif du
sujet, je vais vous demander dajouter quelque chose notre base de donnes. En effet, pour
linstant nous navons pas de donnes numriques, si ce nest les diffrents id. Or, il nest pas
trs intressant (et a peut mme tre dangereux) de manipuler les id. Par consquent, nous al-
lons ajouter une colonne prix nos tables Espece et Race, qui contiendra donc le prix payer pour
adopter un animal de telle espce ou telle race. Cette colonne sera du type DECIMAL, avec deux
chiffres aprs la virgule. Elle ne pourra pas contenir de nombres ngatifs, et sera donc galement
UNSIGNED.
Voici donc les commandes excuter :

ALTER TABLE Race


ADD COLUMN prix DECIMAL(7,2) UNSIGNED;

ALTER TABLE Espece


ADD COLUMN prix DECIMAL(7,2) UNSIGNED;

158
4.1 Rappels et introduction

-- Remplissage des colonnes "prix"


UPDATE Espece SET prix = 200 WHERE id = 1 ;
UPDATE Espece SET prix = 150 WHERE id = 2 ;
UPDATE Espece SET prix = 140 WHERE id = 3 ;
UPDATE Espece SET prix = 700 WHERE id = 4 ;
UPDATE Espece SET prix = 10 WHERE id = 5 ;
UPDATE Espece SET prix = 75 WHERE id = 6 ;

UPDATE Race SET prix = 450 WHERE id = 1 ;


UPDATE Race SET prix = 900 WHERE id = 2 ;
UPDATE Race SET prix = 950 WHERE id = 3 ;
UPDATE Race SET prix = 800 WHERE id = 4 ;
UPDATE Race SET prix = 700 WHERE id = 5 ;
UPDATE Race SET prix = 1200 WHERE id = 7 ;
UPDATE Race SET prix = 950 WHERE id = 8 ;
UPDATE Race SET prix = 600 WHERE id = 9 ;

Le prix de la race sera prioritaire sur le prix de lespce. Donc pour un Berger allemand, on ira
chercher le prix dans la table Race, tandis que pour un btard ou une tortue dHermann, on prendra
le prix de la table Espece.

4.1.1.2.2 Oprations sur donnes slectionnes Exemple 1 : Imaginons que nous vou-
lions savoir le prix payer pour acheter 3 individus de la mme espce (sans considrer la race).
Facile, il suffit de multiplier le prix de chaque espce par 3.

SELECT nom_courant, prix*3 AS prix_trio


FROM Espece;

nom_courant prix_trio

Chien 600.00
Chat 450.00
Tortue dHermann 420.00
Perroquet amazone 2100.00

Exemple 2 : toutes les oprations courantes peuvent bien entendu tre utilises.

SELECT nom_courant, prix,


prix+100 AS addition, prix/2 AS division,
prix-50.5 AS soustraction, prix%3 AS modulo
FROM Espece;

modulo
nom_courant prix addition division soustraction

Chien 200.00 300.00 100.000000 149.50 2.00


Chat 150.00 250.00 75.000000 99.50 0.00
Tortue dHermann 140.00 240.00 70.000000 89.50 2.00
Perroquet amazone 700.00 800.00 350.000000 649.50 1.00

159
4 Fonctions : nombres, chanes et agrgats

4.1.1.2.3 Modification de donnes grce des oprations mathmatiques Il est tout


fait possible, et souvent fort utile, de modifier des donnes grce des oprations.

Exemple : la commande suivante va augmenter le prix de toutes les races de 35 :

UPDATE Race
SET prix = prix + 35 ;

Cest quand mme plus lgant que de devoir faire une requte SELECT pour savoir le prix actuel
des races, calculer ces prix +35, puis faire un UPDATE pour chaque race avec le rsultat.

Bien entendu, on peut faire ce genre de manipulation galement dans une requte de type INSERT
INTO ... SELECT par exemple. En fait, on peut faire ce genre de manipulation partout.

4.1.2 Dfinition dune fonction


Nous avons dj utilis quelques fonctions dans ce cours. Par exemple, dans le chapitre sur les
sous-requtes, je vous proposais la requte suivante :

SELECT MIN(date_naissance) -- On utilise ici une fonction !


FROM (
SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.espece_id
FROM Animal
INNER JOIN Espece
ON Espece.id = Animal.espece_id
WHERE Espece.nom_courant IN ('Tortue d''Hermann', 'Perroquet amazone')
) AS tortues_perroquets;

Lorsque lon fait MIN(date_naissance), on appelle la fonction MIN(), en lui donnant en para-
mtre la colonne date_naissance (ou plus prcisment, les lignes de la colonne date_naissance slec-
tionnes par la requte).

Dtaillons un peu tout a !

4.1.2.0.1 Une fonction Une fonction est un code qui effectue une srie dinstructions bien
prcises (dans le cas de MIN(), ces instructions visent donc chercher la valeur minimale), et
renvoie le rsultat de ces instructions (la valeur minimale en question). Une fonction est dfinie
par son nom (exemple : MIN) et ses paramtres.

4.1.2.0.2 Un paramtre Un paramtre de fonction est une donne (ou un ensemble de don-
nes) que lon fournit la fonction afin quelle puisse effectuer son action. Par exemple, pour
MIN(), il faut passer un paramtre : les donnes parmi lesquelles on souhaite rcuprer la valeur
minimale. Une fonction peut avoir un ou plusieurs paramtres, ou nen avoir aucun. Dans le cas
dune fonction ayant plusieurs paramtres, lordre dans lequel on donne ces paramtres est trs
important.

On parle aussi des arguments dune fonction.

4.1.2.0.3 Appeler une fonction Lorsque lon utilise une fonction, on dit quon fait appel
celle-ci. Pour appeler une fonction, il suffit donc de donner son nom, suivi des paramtres ven-
tuels entre parenthses (lesquelles sont obligatoires, mme sil ny a aucun paramtre).

160
4.1 Rappels et introduction

4.1.2.0.4 Exemples
-- Fonction sans paramtre
SELECT PI(); -- renvoie le nombre Pi, avec 5 dcimales

-- Fonction avec un paramtre


SELECT MIN(prix) AS minimum -- il est bien sr possible d'utiliser les alias !
FROM Espece;

-- Fonction avec plusieurs paramtres


SELECT REPEAT('fort ! Trop ', 4); -- rpte une chane (ici : 'fort ! Trop ', rpt 4

-- Mme chose qu'au-dessus, mais avec les paramtres dans le mauvais ordre
SELECT REPEAT(4, 'fort ! Trop '); -- la chane de caractres 'fort ! Trop ' va tre conv

PI()

3.141593

minimum

140.00

REPEAT(fort ! Trop, 4)

fort ! Trop fort ! Trop fort ! Trop fort ! Trop

REPEAT(4, fort ! Trop)

[[attention]] | Il ny a pas despace entre le nom de la fonction et la parenthse ouvrante !

4.1.2.1 Fonctions scalaires vs fonctions dagrgation

On peut distinguer deux types de fonctions : les fonctions scalaires et les fonctions dagrgation
(ou fonctions de groupement). Les fonctions scalaires sappliquent chaque ligne indpendam-
ment, tandis que les fonctions dagrgation regroupent les lignes (par dfaut, elles regroupent
toutes les lignes en une seule). Un petit exemple rendra cette explication lumineuse.

4.1.2.1.1 Fonction scalaire La fonction ROUND(X) arrondit X lentier le plus proche. Il


sagit dune fonction scalaire.

SELECT nom, prix, ROUND(prix)


FROM Race;

161
4 Fonctions : nombres, chanes et agrgats

nom prix ROUND(prix)

Berger allemand 485.00 485


Berger blanc suisse 935.00 935
Singapura 985.00 985
Bleu russe 835.00 835
Maine coon 735.00 735
Sphynx 1235.00 1235
Nebelung 985.00 985

Il y a sept races dans ma table, et lorsquon applique la fonction ROUND(X) la colonne prix, on
rcupre bien sept lignes.

4.1.2.1.2 Fonction dagrgation La fonction MIN(X) par contre, est une fonction dagrga-
tion.

SELECT MIN(prix)
FROM Race;

MIN(prix)

485.00

On ne rcupre quune seule ligne de rsultat. Les sept races ont t regroupes (a naurait
dailleurs pas de sens davoir une ligne par race). Cette particularit des fonctions dagrgation
les rend un peu plus dlicates utiliser, mais offre des possibilits vraiment intressantes. Nous
commencerons donc en douceur avec les fonctions scalaires pour consacrer ensuite plusieurs cha-
pitres lutilisation des fonctions de groupement.

4.1.3 Quelques fonctions gnrales

4.1.3.0.1 Petite mise au point Le but de cette partie nest videmment pas de rfren-
cer toutes les fonctions existantes. De mme, les fonctions prsentes seront dcrites avec des
exemples, mais nous ne verrons pas les petits cas particuliers, les exceptions, ni les ventuels
comportements tranges et imprvisibles. Pour a, la doc est, et restera, votre meilleur compa-
gnon. Le but ici est de vous montrer un certain nombre de fonctions que, selon mon exprience,
je juge utile que vous connaissiez. Par consquent, et on ne le rptera jamais assez, nhsitez
pas faire un tour sur la documentation officielle de MySQL si vous ne trouvez pas votre bonheur
parmi les fonctions cites ici.

4.1.3.1 Informations sur lenvironnement actuel

4.1.3.1.1 Version de MySQL La fonction classique parmi les classiques : VERSION() vous
permettra de savoir sous quelle version de MySQL tourne votre serveur.

SELECT VERSION();

162
4.1 Rappels et introduction

VERSION()

5.5.16

4.1.3.1.2 O suis-je ? Qui suis-je ? Vous avez cr plusieurs utilisateurs diffrents pour
grer votre base de donnes, et prsentement vous ne savez plus avec lequel vous tes connects ?
Pas de panique, il existe les fonctions CURRENT_USER() et USER(). Ces deux fonctions ne font
pas exactement la mme chose. Par consquent, il nest pas impossible quelles vous renvoient
deux rsultats diffrents.

CURRENT_USER() : renvoie lutilisateur (et lhte) qui a t utilis lors de lidentification


au serveur ;
USER() : renvoie lutilisateur (et lhte) qui a t spcifi lors de lidentification au serveur.

Mais comment cela pourrait-il tre diffrent ? Tout simplement, parce que si vous vous connectez
avec un utilisateur qui na aucun droit (droits que lon donne avec la commande GRANT, mais nous
verrons a dans une prochaine partie), vous arriverez vous connecter, mais le serveur vous iden-
tifiera avec un utilisateur anonyme. USER() vous renverra alors votre utilisateur sans droit,
tandis que CURRENT_USER() vous donnera lutilisateur anonyme.

Dans notre cas, lutilisateur sdz (ou nimporte quel user que vous avez cr) ayant des droits,
les deux fonctions renverront exactement la mme chose.

SELECT CURRENT_USER(), USER();

CURRENT_USER() USER()

sdz@localhost sdz@localhost

4.1.3.2 Informations sur la dernire requte

4.1.3.2.1 Dernier ID gnr par auto-incrmentation Dans une base de donnes rela-
tionnelle, il arrive trs souvent que vous deviez insrer plusieurs lignes en une fois dans la base
de donnes, et que certaines de ces nouvelles lignes doivent contenir une rfrence dautres
nouvelles lignes. Par exemple, vous voulez ajouter Pipo le rottweiller dans votre base. Pour ce
faire, vous devez insrer une nouvelle race (rottweiller), et un nouvel animal (Pipo) pour lequel
vous avez besoin de lid de la nouvelle race. Plutt que de faire un SELECT sur la table Race une
fois la nouvelle race insre, il est possible dutiliser LAST_INSERT_ID(). Cette fonction ren-
voie le dernier id cr par auto-incrmentation, pour la connexion utilise (donc si quelquun se
connecte au mme serveur, avec un autre client, il ninfluera pas sur le LAST_INSERT_ID() que
vous recevez).

INSERT INTO Race (nom, espece_id, description, prix)


VALUES ('Rottweiller', 1, 'Chien d''apparence solide, bien muscl, la robe noire avec

INSERT INTO Animal (sexe, date_naissance, nom, espece_id, race_id)


VALUES ('M', '2010-11-05', 'Pipo', 1, LAST_INSERT_ID()); -- LAST_INSERT_ID() renverra

4.1.3.2.2 Nombre de lignes renvoyes par la requte La fonction FOUND_ROWS() vous


permet dafficher le nombre de lignes que votre dernire requte a ramenes.

163
4 Fonctions : nombres, chanes et agrgats

SELECT id, nom, espece_id, prix


FROM Race;

SELECT FOUND_ROWS();

id nom espece_id prix

1 Berger allemand 1 485.00


2 Berger blanc suisse 1 935.00
3 Singapura 2 985.00
4 Bleu russe 2 835.00
5 Maine coon 2 735.00
7 Sphynx 2 1235.00
8 Nebelung 2 985.00
9 Rottweiller 1 600.00

FOUND_ROWS()

Jusque-l, rien de bien extraordinaire. Cependant, utilise avec LIMIT, FOUND_ROWS() peut avoir
un comportement trs intressant. En effet, moyennant lajout dune option dans la requte
SELECT dorigine, FOUND_ROWS() nous donnera le nombre de lignes que la requte aurait rame-
nes en labsence de LIMIT. Loption ajouter dans le SELECT est SQL_CALC_FOUND_ROWS, et se
place juste aprs le mot-cl SELECT.

SELECT id, nom, espece_id, prix -- Sans option


FROM Race
LIMIT 3 ;

SELECT FOUND_ROWS() AS sans_option;

SELECT SQL_CALC_FOUND_ROWS id, nom, espece_id, prix -- Avec option


FROM Race
LIMIT 3 ;

SELECT FOUND_ROWS() AS avec_option;

sans_option

avec_option

164
4.1 Rappels et introduction

4.1.3.3 Convertir le type de donnes

Dans certaines situations, vous allez vouloir convertir le type de votre donne (une chane de
caractres '45' en entier 45 par exemple). Il faut savoir que dans la majorit de ces situations,
MySQL est assez souple et permissif pour faire la conversion lui-mme, automatiquement, et
sans que vous ne vous en rendiez vraiment compte (attention, ce nest pas le cas de la plupart des
SGBDR).

Exemple : conversions automatiques

SELECT *
FROM Espece
WHERE id = '3' ;

INSERT INTO Espece (nom_latin, nom_courant, description, prix)


VALUES ('Rattus norvegicus', 'Rat brun', 'Petite bestiole avec de longues moustaches et

La colonne id est de type INT, pourtant la comparaison avec une chane de caractres renvoie bien
un rsultat. De mme, la colonne prix est de type DECIMAL, mais linsertion dune valeur sous
forme de chane de caractres na pos aucun problme. MySQL a converti automatiquement.

Dans les cas o la conversion automatique nest pas possible, vous pouvez utiliser la fonction
CAST(expr AS type). expr reprsente la donne que vous voulez convertir, et type est bien sr
le type vers lequel vous voulez convertir votre donne.

Ce type peut tre : BINARY, CHAR, DATE, DATETIME, TIME, UNSIGNED (sous-entendu INT), SIGNED
(sous-entendu INT), DECIMAL.

Exemple : conversion dune chane de caractre en date

SELECT CAST('870303' AS DATE);

CAST(870303 AS DATE)

1987-03-03

*SGBDR : Systme de Gestion de Base de Donnes Relationnelles

4.1.3.4 En rsum

MySQL permet de faire de nombreuses oprations mathmatiques, que ce soit directement


sur des valeurs entres par lutilisateur, ou sur des donnes de la base.
MySQL permet lutilisation des oprateurs mathmatiques et des fonctions dans tous les
types de requtes (insertion, slection, modification, etc.).
Une fonction est un code qui effectue une srie dinstructions bien dfinie et renvoie un
rsultat.
Les paramtres dune fonction sont des valeurs ou des donnes fournies la fonction lors
de son appel.
Une fonction scalaire, applique une colonne de donnes agit sur chaque ligne indpen-
damment.

165
4 Fonctions : nombres, chanes et agrgats

Une fonction dagrgation regroupe les diffrentes lignes.

4.2 Fonctions scalaires


Comme prvu, ce chapitre sera consacr aux fonctions scalaires permettant la manipulation de
nombres et de chanes de caractres. Nous verrons entre autres comment arrondir un nombre ou
tirer un nombre au hasard, et comment connatre la longueur dun texte ou en extraire une partie.
La fin de ce chapitre est constitue dexercices afin de pouvoir mettre les fonctions en pratique.

[[information]] | Il existe aussi des fonctions permettant de manipuler les dates en SQL, mais une
partie entire leur sera consacre.

nouveau, si vous ne trouvez pas votre bonheur parmi les fonctions prsentes ici, nhsitez sur-
tout pas faire un tour sur la documentation officielle. Nessayez pas de retenir par cur toutes
ces fonctions bien sr. Si vous savez quelles existent, il vous sera facile de retrouver leur syntaxe
exacte en cas de besoin, que ce soit ici ou dans la documentation.

4.2.1 Manipulation de nombres


Voici donc quelques fonctions scalaires qui vont vous permettre de manipuler les nombres : faire
des calculs, des arrondis, prendre un nombre au hasard, etc.

[[information]] | Toutes les fonctions de cette partie retournent NULL en cas derreur !

4.2.1.1 Arrondis

Arrondir un nombre, cest trouver une valeur proche de ce nombre avec une prcision donne et
selon certains critres. La prcision est en gnral reprsente par le nombre de dcimales dsi-
res. Par exemple, pour un prix, on travaille rarement avec plus de deux dcimales. Pour un ge,
on prfrera gnralement un nombre entier (cest--dire aucune dcimale). Quant aux critres,
il sagit de dcider si lon veut arrondir au plus proche (ex. : 4,3 arrondi lentier le plus proche
vaut 4), arrondir au suprieur (ex. : 4,3 arrondi lentier suprieur vaut 5) ou arrondir linfrieur
(ex. : 4,3 arrondi lentier infrieur vaut 4).

Voici quatre fonctions permettant darrondir les nombres, selon ces diffrents critres. Pour les
paramtres, n reprsente le nombre arrondir, d le nombre de dcimales dsires.

4.2.1.1.1 CEIL() CEIL(n) ou CEILING(n) arrondit au nombre entier suprieur.


SELECT CEIL(3.2), CEIL(3.7);

CEIL(3.2) CEIL(3.7)

4 4

4.2.1.1.2 FLOOR() FLOOR(n) arrondit au nombre entier infrieur.


SELECT FLOOR(3.2), FLOOR(3.7);

166
4.2 Fonctions scalaires

FLOOR(3.2) FLOOR(3.7)

3 3

4.2.1.1.3 ROUND() ROUND(n, d) arrondit au nombre d dcimales le plus proche.


ROUND(n) quivaut crire ROUND(n, 0), donc arrondit lentier le plus proche.

SELECT ROUND(3.22, 1), ROUND(3.55, 1), ROUND(3.77, 1);

SELECT ROUND(3.2), ROUND(3.5), ROUND(3.7);

ROUND(3.22, 1) ROUND(3.55, 1) ROUND(3.77, 1)

3.2 3.6 3.8

ROUND(3.2) ROUND(3.5) ROUND(3.7)

3 4 4

Si le nombre se trouve juste entre larrondi suprieur et larrondi infrieur (par exemple si on
arrondit 3,5 un nombre entier), certaines implmentations vont arrondir vers le haut, dautres
vers le bas. Testez donc pour savoir dans quel cas est votre serveur (comme vous voyez, ici, cest
vers le haut).

4.2.1.1.4 TRUNCATE() TRUNCATE(n, d) arrondit en enlevant purement et simplement


les dcimales en trop (donc arrondi linfrieur pour les nombres positifs, au suprieur pour les
nombres ngatifs).

SELECT TRUNCATE(3.2, 0), TRUNCATE(3.5, 0), TRUNCATE(3.7, 0);

SELECT TRUNCATE(3.22, 1), TRUNCATE(3.55, 1), TRUNCATE(3.77, 1);

TRUNCATE(3.2, 0) TRUNCATE(3.5, 0) TRUNCATE(3.7, 0)

3 3 3

TRUNCATE(3.22, 1) TRUNCATE(3.55, 1) TRUNCATE(3.77, 1)

3.2 3.5 3.7

4.2.1.2 Exposants et racines

4.2.1.2.1 Exposants POWER(n, e) (ou POW(n, e)) retourne le rsultat de n exposant e (ne ).
Pour rappel, n exposant e (ne ) veut dire que lon multiplie n par lui-mme, e fois. Donc par
exemple, 23 =222=8
SELECT POW(2, 5), POWER(5, 2);

167
4 Fonctions : nombres, chanes et agrgats

POW(2, 5) POWER(5, 2)

32 25


4.2.1.2.2 Racines Prendre la racine nime dun nombre x ( n
x), cest trouver le (ou les)
n
nombre(s)
y qui rpond(ent) la condition suivante :
y = x. Donc la racine cinquime de 32
( 32) vaut 2, puisque 25 = 2 2 2 2 2 = 32
5


SQRT(n) donne la racine carre positive de n ( n = 2 n).

SELECT SQRT(4);

SQRT(4)

Il nexiste pas de fonction particulire pour obtenir la racine nime dun nombre pour n > 2. Ce-
1
pendant, pour ceux qui ne le sauraient pas : n
x = x n . Donc, pour obtenir par exemple la racine
cinquime de 32, il suffit de faire :

SELECT POW(32, 1/5);

POW(32, 1/5)

4.2.1.3 Hasard

Le vrai hasard nexiste pas en informatique. Il est cependant possible de simuler le hasard, avec
par exemple un gnrateur de nombres alatoires. MySQL implmente un gnrateur de nombres
alatoires auquel vous pouvez faire appel en utilisant la fonction RAND(), qui retourne un nombre
alatoire entre 0 et 1.

SELECT RAND();

RAND()

0.08678611469155748

Cette fonction peut par exemple tre utile pour trier des rsultats de manire alatoire. Un
nombre diffrent va en effet tre gnr pour chaque ligne de rsultat. Le tri se fera alors sur ce
nombre gnr au hasard. Notez que ce nest pas trs bon en termes de performances, tant
donn quun nombre doit tre gnr pour chaque ligne.

SELECT *
FROM Race
ORDER BY RAND();

4.2.1.4 Divers

168
4.2 Fonctions scalaires

4.2.1.4.1 SIGN() SIGN(n) renvoie le signe du nombre n. Ou plus exactement, SIGN(n) ren-
voie -1 si n est ngatif, 0 si n vaut 0, et 1 si n est positif.

SELECT SIGN(-43), SIGN(0), SIGN(37);

SIGN(-43) SIGN(0) SIGN(37)

-1 0 1

4.2.1.4.2 ABS() ABS(n) retourne la valeur absolue de n, donc sa valeur sans le signe.
SELECT ABS(-43), ABS(0), ABS(37);

ABS(-43) ABS(0) ABS(37)

43 0 37

4.2.1.4.3 MOD() La fonction MOD(n, div) retourne le modulo, donc le reste de la division
entire de n par div (comme loprateur % ou MOD)

SELECT MOD(56, 10);

MOD(56, 10)

4.2.2 Manipulation de chanes de caractres


Nous allons maintenant manipuler, triturer, examiner des chanes de caractres. toutes fins
utiles, je rappelle en passant quen SQL, les chanes de caractres sont entoures de guillemets
(simples, mais les guillemets doubles fonctionnent avec MySQL galement).

4.2.2.1 Longueur et comparaison

4.2.2.1.1 Connatre la longueur dune chane Trois fonctions permettent davoir des in-
formations sur la longueur dune chane de caractres. Chaque fonction calcule la longueur dune
manire particulire.

BIT_LENGTH(chaine) : retourne le nombre de bits de la chane. Chaque caractre est en-


cod sur un ou plusieurs octets, chaque octet reprsente huit bits.
CHAR_LENGTH(chaine) : (ou CHARACTER_LENGTH()) retourne le nombre de caractres de
la chane.
LENGTH(chaine) : retourne le nombre doctets de la chane. Chaque caractre tant repr-
sent par un ou plusieurs octets (rappel : les caractres que vous pouvez utiliser dpendent
de lencodage choisi).

SELECT BIT_LENGTH('levage'),
CHAR_LENGTH('levage'),
LENGTH('levage'); -- Les caractres accentus sont cods sur 2 octets en UTF-8

169
4 Fonctions : nombres, chanes et agrgats

BIT_LENGTH(levage) CHAR_LENGTH(levage) LENGTH(levage)

64 7 8

A priori, dans neuf cas sur dix au moins, la fonction qui vous sera utile est donc CHAR_LENGTH().

4.2.2.1.2 Comparer deux chanes La fonction STRCMP(chaine1, chaine2) compare les


deux chanes passes en paramtres et retourne 0 si les chanes sont les mmes, -1 si la premire
chane est classe avant dans lordre alphabtique et 1 dans le cas contraire.

SELECT STRCMP('texte', 'texte') AS 'texte=texte',


STRCMP('texte','texte2') AS 'texte<texte2',
STRCMP('chaine','texte') AS 'chaine<texte',
STRCMP('texte', 'chaine') AS 'texte>chaine',
STRCMP('texte3','texte24') AS 'texte3>texte24' ; -- 3 est aprs 24 dans l'ordre

texte=texte texte>chainetexte3>texte24
texte<texte2 chaine<texte

0 -1 -1 1 1

4.2.2.2 Retrait et ajout de caractres

4.2.2.2.1 Rpter une chane REPEAT(c, n) retourne le texte c, n fois.


SELECT REPEAT('Ok ', 3);

REPEAT(Ok, 3)

Ok Ok Ok

4.2.2.2.2 Complter/rduire une chane Les fonctions LPAD() et RPAD() appliques une
chane de caractres retournent cette chane en lui donnant une longueur particulire, donne en
paramtre. Si la chane de dpart est trop longue, elle sera raccourcie, si elle est trop courte, des
caractres seront ajouts, gauche de la chane pour LPAD(), droite pour RPAD(). Ces fonc-
tions ncessitent trois paramtres : la chane transformer (texte), la longueur dsire (long), et
le caractre ajouter si la chane est trop courte (caract).

LPAD(texte, long, caract)


RPAD(texte, long, caract)

SELECT LPAD('texte', 3, '@') AS '3_gauche_@',


LPAD('texte', 7, '$') AS '7_gauche_$',
RPAD('texte', 5, 'u') AS '5_droite_u',
RPAD('texte', 7, '*') AS '7_droite_*',
RPAD('texte', 3, '-') AS '3_droite_-' ;

170
4.2 Fonctions scalaires

3_gauche_@
7_gauche_$ 5_droite_u 7_droite_* 3_droite_-

tex $$texte texte texte** tex

4.2.2.2.3 ter les caractres inutiles Il peut arriver que certaines de vos donnes aient des
caractres inutiles ajouts avant et/ou aprs le texte intressant. Dans ce cas, il vous est possible
dutiliser la fonction TRIM(), qui va supprimer tous ces caractres. Cette fonction a une syntaxe
un peu particulire que voici :

TRIM([[BOTH | LEADING | TRAILING] [caract] FROM] texte);

Je rappelle que ce qui est entre crochets est facultatif. On peut donc choisir entre trois options :
BOTH, LEADING et TRAILING.

BOTH : les caractres seront limins lavant, et larrire du texte


LEADING : seuls les caractres lavant de la chane seront supprims
TRAILING : seuls les caractres larrire de la chane seront supprims

Si aucune option nest prcise, cest BOTH qui est utilis par dfaut.

caract est la chane de caractres (ou le caractre unique) liminer en dbut et/ou fin de chane.
Ce paramtre est facultatif : par dfaut les espaces blancs seront supprimes. Et texte est bien sr
la chane de caractres traiter.

SELECT TRIM(' Tralala ') AS both_espace,


TRIM(LEADING FROM ' Tralala ') AS lead_espace,
TRIM(TRAILING FROM ' Tralala ') AS trail_espace,

TRIM('e' FROM 'eeeBouHeee') AS both_e,


TRIM(LEADING 'e' FROM 'eeeBouHeee') AS lead_e,
TRIM(BOTH 'e' FROM 'eeeBouHeee') AS both_e,

TRIM('123' FROM '1234ABCD4321') AS both_123;

both_espace lead_espace
trail_espace both_e lead_e both_e both_123

Tralala Tralala Tralala BouH BouHeee BouH 4ABCD4321

4.2.2.2.4 Rcuprer une sous-chane La fonction SUBSTRING() retourne une partie dune
chane de caractres. Cette partie est dfinie par un ou deux paramtres : pos (obligatoire), qui
donne la position de dbut de la sous-chane, et long (facultatif) qui donne la longueur de la sous-
chane dsire (si ce paramtre nest pas prcis, toute la fin de la chane est prise). Quatre syn-
taxes sont possibles :

SUBSTRING(chaine, pos)
SUBSTRING(chaine FROM pos)
SUBSTRING(chaine, pos, long)
SUBSTRING(chaine FROM pos FOR long)

171
4 Fonctions : nombres, chanes et agrgats

SELECT SUBSTRING('texte', 2) AS from2,


SUBSTRING('texte' FROM 3) AS from3,
SUBSTRING('texte', 2, 3) AS from2long3,
SUBSTRING('texte' FROM 3 FOR 1) AS from3long1;

from2 from3 from2long3 from3long1

exte xte ext x

4.2.2.3 Recherche et remplacement

4.2.2.3.1 Rechercher une chane de caractres INSTR(), LOCATE() et POSITION() re-


tournent la position de la premire occurrence dune chane de caractres rech dans une chane de
caractres chaine. Ces trois fonctions ont chacune une syntaxe particulire :

INSTR(chaine, rech)
LOCATE(rech, chaine) - les paramtres sont inverss par rapport INSTR()
POSITION(rech IN chaine)

Si la chane de caractres rech nest pas trouve dans chaine, ces fonctions retournent 0. Par cons-
quent, la premire lettre dune chane de caractres est la position 1 (alors que dans beaucoup
de langages de programmation, on commence toujours la position 0).

LOCATE() peut aussi accepter un paramtre supplmentaire : pos, qui dfinit la position dans la
chane partir de laquelle il faut rechercher rech : LOCATE(rech, chaine, pos).

SELECT INSTR('tralala', 'la') AS fct_INSTR,


POSITION('la' IN 'tralala') AS fct_POSITION,
LOCATE('la', 'tralala') AS fct_LOCATE,
LOCATE('la', 'tralala', 5) AS fct_LOCATE2;

fct_INSTR fct_POSITION fct_LOCATE fct_LOCATE2

4 4 4 6

4.2.2.3.2 Changer la casse des chanes Les fonctions LOWER(chaine) et LCASE(chaine)


mettent toutes les lettres de chaine en minuscules, tandis que UPPER(chaine) et UCASE(chaine)
mettent toutes les lettres en majuscules.

SELECT LOWER('AhAh') AS minuscule,


LCASE('AhAh') AS minuscule2,
UPPER('AhAh') AS majuscule,
UCASE('AhAh') AS majuscule2;

minuscule minuscule2 majuscule majuscule2

ahah ahah AHAH AHAH

4.2.2.3.3 Rcuprer la partie gauche ou droite LEFT(chaine, long) retourne les long
premiers caractres de chaine en partant de la gauche, et RIGHT(chaine, long) fait la mme

172
4.2 Fonctions scalaires

chose en partant de la droite.

SELECT LEFT('123456789', 5), RIGHT('123456789', 5);

LEFT(123456789, 5) RIGHT(123456789, 5)

12345 56789

4.2.2.3.4 Inverser une chane REVERSE(chaine) renvoie chaine en inversant les caractres.
SELECT REVERSE('abcde');

REVERSE(abcde)

edcba

4.2.2.3.5 Remplacer une partie par autre chose Deux fonctions permettent de remplacer
une partie dune chane de caractres : INSERT() et REPLACE().

INSERT(chaine, pos, long, nouvCaract) : le paramtre chaine est la chane de ca-


ractres dont on veut remplacer une partie, pos est la position du premier caractre rem-
placer, long le nombre de caractres remplacer, et nouvCaract est la chane de caractres
qui viendra remplacer la portion de chaine choisie.
REPLACE(chaine, ancCaract, nouvCaract) : tous les caractres (ou sous-chanes)
ancCaract seront remplacs par nouvCaract.

SELECT INSERT('texte', 3, 2, 'blabla') AS fct_INSERT,


REPLACE('texte', 'e', 'a') AS fct_REPLACE,
REPLACE('texte', 'ex', 'ou') AS fct_REPLACE2;

fct_INSERT fct_REPLACE fct_REPLACE2

teblablae taxta toute

4.2.2.4 Concatnation

Concatner deux chanes de caractres signifie les mettre bout bout pour nen faire quune
seule. Deux fonctions scalaires permettent la concatnation : CONCAT() et CONCAT_WS(). Ces
deux fonctions permettent de concatner autant de chanes que vous voulez, il suffit de toutes
les passer en paramtres. Par consquent, ces deux fonctions nont pas un nombre de paramtres
dfini.

CONCAT(chaine1, chaine2,) : renvoie simplement une chane de caractres, rsultat


de la concatnation de toutes les chanes passes en paramtres.
CONCAT_WS(separateur, chaine1, chaine2) : mme chose que CONCAT(), sauf que
la premire chane passe sera utilise comme sparateur, donc place entre chacune des
autres chanes passes en paramtres.

SELECT CONCAT('My', 'SQL', ' !'), CONCAT_WS('-', 'My', 'SQL', ' !');

173
4 Fonctions : nombres, chanes et agrgats

CONCAT(My, SQL, !) CONCAT_WS(-, My, SQL, !)

MySQL ! My-SQL- !

4.2.2.5 FIELD(), une fonction bien utile pour le tri

La fonction FIELD(rech, chaine1, chaine2, chaine3,) recherche le premier argument


(rech) parmi les arguments suivants (chaine1, chaine2, chaine3,) et retourne lindex auquel rech est
trouve (1 si rech = chaine1, 2 si rech = chaine2,). Si rech nest pas trouve parmi les arguments, 0
est renvoy.

SELECT FIELD('Bonjour', 'Bonjour !', 'Au revoir', 'Bonjour', 'Au revoir !') AS field_bon

field_bonjour

Par consquent, FIELD peut tre utilise pour dfinir un ordre arbitraire dans une clause ORDER
BY.

Exemple : ordonnons les espces selon un ordre arbitraire. La fonction FIELD() dans la clause
SELECT nest l que pour illustrer la faon dont ce tri fonctionne.

SELECT nom_courant, nom_latin, FIELD(nom_courant, 'Rat brun', 'Chat', 'Tortue d''Herman


FROM Espece
ORDER BY FIELD(nom_courant, 'Rat brun', 'Chat', 'Tortue d''Hermann', 'Chien', 'Perroque

nom_courant nom_latin resultat_field

Rat brun Rattus norvegicus 1


Chat Felis silvestris 2
Tortue dHermann Testudo hermanni 3
Chien Canis canis 4
Perroquet amazone Alipiopsitta xanthops 5

[[information]] | Si vous ne mettez pas toutes les valeurs existantes de la colonne en argument
de FIELD(), les lignes ayant les valeurs non mentionnes seront classes en premier (puisque
FIELD() renverra 0).

4.2.2.6 Code ASCII

Les deux dernires fonctions que nous allons voir sont ASCII() et CHAR(), qui sont complmen-
taires. ASCII(chaine) renvoie le code ASCII du premier caractre de la chane passe en para-
mtre, tandis que CHAR(ascii1, ascii2,) retourne les caractres correspondant aux codes
ASCII passs en paramtres (autant de paramtres quon veut). Les arguments passs CHAR()
seront convertis en entiers par MySQL.

SELECT ASCII('T'), CHAR(84), CHAR('84', 84+32, 84.2);

174
4.2 Fonctions scalaires

ASCII(T) CHAR(84) CHAR(84, 84+32, 84.2)

84 T TtT

4.2.3 Exemples dapplication et exercices


Ce chapitre a t fort thorique jusqu maintenant. Donc pour changer un peu, et vous rveiller,
je vous propose de passer la pratique, en utilisant les donnes de notre base elevage. Ces quelques
exercices sont faisables en utilisant uniquement les fonctions et oprateurs mathmatiques que
je vous ai dcrits dans ce chapitre.

4.2.3.1 On commence par du facile

4.2.3.1.1 1. Afficher une phrase donnant le prix de lespce, pour chaque espce
Par exemple, afficher Un chat cote 100 euros., ou une autre phrase du genre, et ce pour les
cinq espces enregistres.

[[secret]]| sql | SELECT CONCAT('Un(e) ', nom_courant, ' cote ', prix, '
euros.') AS Solution | FROM Espece; | | -- OU | | SELECT CONCAT_WS('
','Un(e)', nom_courant, 'cote', prix, 'euros.') AS Solution | FROM Espece;
|

4.2.3.1.2 2. Afficher les chats dont la deuxime lettre du nom est un a [[secret]]
| sql | SELECT Animal.nom, Espece.nom_courant | FROM Animal | INNER JOIN
Espece ON Animal.espece_id = Espece.id | WHERE Espece.nom_courant = 'Chat'
| AND SUBSTRING(nom, 2, 1) = 'a' ; |

4.2.3.2 Puis on corse un peu

4.2.3.2.1 1. Afficher les noms des perroquets en remplaant les a par @


et les e par 3 pour en faire des perroquets Kikoolol [[secret]] | sql |
SELECT REPLACE(REPLACE(nom, 'a', '@'), 'e', '3') AS Solution | FROM Animal
| INNER JOIN Espece ON Animal.espece_id = Espece.id | WHERE Espece.nom_courant
LIKE 'Perroquet%' ; | | | | Une petite explication simpose avant de vous laisser continuer.
Comme vous voyez, il est tout fait possible dimbriquer plusieurs fonctions. Le tout est de le
faire correctement, et pour cela, il faut procder par tape. Ici, vous voulez faire deux remplace-
ments successifs dans une chane de caractres (en loccurrence, le nom des perroquets). Donc,
vous effectuez un premier remplacement, en changeant les a par les @ : REPLACE(nom,
'a', '@'). Ensuite, sur la chane rsultant de ce premier remplacement, vous effectuez le
second : REPLACE(REPLACE(nom, 'a', '@'), 'e', '3'). Logique, non ?

4.2.3.2.2 2. Afficher les chiens dont le nom a un nombre pair de lettres [[se-
cret]] | sql | SELECT nom, nom_courant | FROM Animal | INNER JOIN Espece ON
Animal.espece_id = Espece.id | WHERE nom_courant = 'Chien' | AND CHAR_LENGTH(nom)%2
= 0; | | -- OU | | SELECT nom, nom_courant | FROM Animal | INNER JOIN
Espece ON Animal.espece_id = Espece.id | WHERE nom_courant = 'Chien' |
AND CHAR_LENGTH(nom) MOD 2 = 0; | | -- OU | | SELECT nom, nom_courant |

175
4 Fonctions : nombres, chanes et agrgats

FROM Animal | INNER JOIN Espece ON Animal.espece_id = Espece.id | WHERE


nom_courant = 'Chien' | AND MOD(CHAR_LENGTH(nom),2) = 0; | | | | Le nombre de
lettres, ctait facile, il suffisait dutiliser CHAR_LENGTH(). | Pour savoir si un nombre est pair, il
faut utiliser les modulos : lorsquun nombre est pair, le reste dune division entire de ce nombre
par 2 est 0, donc ce nombre modulo 2 vaut 0.

4.2.3.3 En rsum

Concatner deux chanes de caractres signifie les mettre bout bout.


Il ne faut pas hsiter, pour obtenir le rsultat voulu, combiner plusieurs fonctions en-
semble.
Pour savoir si un nombre est multiple dun autre, on peut utiliser le modulo.

4.3 Fonctions dagrgation


Les fonctions dagrgation, ou de groupement, sont des fonctions qui vont regrouper les lignes.
Elles agissent sur une colonne, et renvoient un rsultat unique pour toutes les lignes slection-
nes (ou pour chaque groupe de lignes, mais nous verrons cela plus tard).

Elles servent majoritairement faire des statistiques, comme nous allons le voir dans la premire
partie de ce chapitre (compter des lignes, connatre une moyenne, trouver la valeur maximale
dune colonne,). Nous verrons ensuite la fonction GROUP_CONCAT() qui, comme son nom lin-
dique, est une fonction de groupement qui sert concatner des valeurs.

4.3.1 Fonctions statistiques


La plupart des fonctions dagrgation vont vous permettre de faire des statistiques sur vos don-
nes.

4.3.1.1 Nombre de lignes

La fonction COUNT() permet de savoir combien de lignes sont slectionnes par la requte.

-- Combien de races avons-nous ? --


-- ---------------------------------
SELECT COUNT(*) AS nb_races
FROM Race;

-- Combien de chiens avons-nous ? --


-- ---------------------------------
SELECT COUNT(*) AS nb_chiens
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE Espece.nom_courant = 'Chien' ;

176
4.3 Fonctions dagrgation

nb_races

nb_chiens

21

4.3.1.1.1 COUNT(*) ou COUNT(colonne) Vous lavez vu, jai utilis COUNT(*) dans les
exemples ci-dessus. Cela signifie que lon compte tout simplement les lignes, sans se soucier de
ce quelles contiennent.

Par contre, si on utilise COUNT(colonne), seules les lignes dont la valeur de colonne nest pas
NULL seront prises en compte.

Exemple : comptons les lignes de la table Animal, avec COUNT(*) et COUNT(race_id).

SELECT COUNT(race_id), COUNT(*)


FROM Animal;

COUNT(race_id) COUNT(*)

31 60

Il ny a donc que 31 animaux sur nos 60 pour lesquels la race est dfinie.

4.3.1.1.2 Doublons Comme dans une requte SELECT tout fait banale, il est possible duti-
liser le mot-cl DISTINCT pour ne pas prendre en compte les doublons.

Exemple : comptons le nombre de races distinctes dfinies dans la table Animal.

SELECT COUNT(DISTINCT race_id)


FROM Animal;

COUNT(DISTINCT race_id)

Parmi nos 31 animaux dont la race est dfinie, on trouve donc 7 races diffrentes.

4.3.1.2 Minimum et maximum

Nous avons dj eu loccasion de rencontrer la fonction MIN(x), qui retourne la plus petite valeur
de x. Il existe galement une fonction MAX(x), qui renvoie la plus grande valeur de x.

SELECT MIN(prix), MAX(prix)


FROM Race;

MIN(prix) MAX(prix)

485.00 1235.00

177
4 Fonctions : nombres, chanes et agrgats

Notez que MIN() et MAX() ne sutilisent pas uniquement sur des donnes numriques. Si vous lui
passez des chanes de caractres, MIN() rcuprera la premire chane dans lordre alphabtique,
MAX() la dernire ; avec des dates, MIN() renverra la plus vieille et MAX() la plus rcente.

Exemple :

SELECT MIN(nom), MAX(nom), MIN(date_naissance), MAX(date_naissance)


FROM Animal;

MIN(nom) MAX(nom)
MIN(date_naissance) MAX(date_naissance)

Anya Zonko 2006-03-15 14 :26 :00 2010-11-09 00 :00 :00

4.3.1.3 Somme et moyenne

4.3.1.3.1 Somme La fonction SUM(x) renvoie la somme de x.

SELECT SUM(prix)
FROM Espece;

SUM(prix)

1200.00

4.3.1.3.2 Moyenne La fonction AVG(x) (du mot anglais average) renvoie la valeur moyenne de
x.

SELECT AVG(prix)
FROM Espece;

AVG(prix)

240.000000

4.3.2 Concatnation
4.3.2.1 Principe

Avec les fonctions dagrgation, on regroupe plusieurs lignes. Les fonctions statistiques
nous permettent davoir des informations fort utiles sur le rsultat dune requte, mais
parfois, il est intressant davoir galement les valeurs concernes. Ceci est faisable avec
GROUP_CONCAT(nom_colonne). Cette fonction concatne les valeurs de nom_colonne pour
chaque groupement ralis.

Exemple : on rcupre la somme des prix de chaque espce, et on affiche les espces concernes
par la mme occasion.

SELECT SUM(prix), GROUP_CONCAT(nom_courant)


FROM Espece;

178
4.3 Fonctions dagrgation

SUM(prix) GROUP_CONCAT(nom_courant)

1200.00 Chien,Chat,Tortue dHermann,Perroquet amazone,Rat brun

4.3.2.2 Syntaxe

Voici la syntaxe de cette fonction :

GROUP_CONCAT(
[DISTINCT] col1 [, col2, ...]
[ORDER BY col [ASC | DESC]]
[SEPARATOR sep]
)

DISTINCT : sert comme dhabitude liminer les doublons.


col1 : est le nom de la colonne dont les valeurs doivent tre concatnes. Cest le seul
argument obligatoire.
col2, : sont les ventuelles autres colonnes (ou chanes de caractres) concatner.
ORDER BY : permet de dterminer dans quel ordre les valeurs seront concatnes.
SEPARATOR : permet de spcifier une chane de caractres utiliser pour sparer les diff-
rentes valeurs. Par dfaut, cest une virgule.

4.3.2.3 Exemples

-- --------------------------------------
-- CONCATENATION DE PLUSIEURS COLONNES --
-- --------------------------------------
SELECT SUM(Race.prix), GROUP_CONCAT(Race.nom, Espece.nom_courant)
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id;

-- ---------------------------------------------------
-- CONCATENATION DE PLUSIEURS COLONNES EN PLUS JOLI --
-- ---------------------------------------------------
SELECT SUM(Race.prix), GROUP_CONCAT(Race.nom, ' (', Espece.nom_courant, ')')
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id;

-- ---------------------------
-- ELIMINATION DES DOUBLONS --
-- ---------------------------
SELECT SUM(Espece.prix), GROUP_CONCAT(DISTINCT Espece.nom_courant) -- Essayez sans le D
FROM Espece
INNER JOIN Race ON Race.espece_id = Espece.id;

-- --------------------------
-- UTILISATION DE ORDER BY --
-- --------------------------
SELECT SUM(Race.prix), GROUP_CONCAT(Race.nom, ' (', Espece.nom_courant, ')' ORDER BY Ra

179
4 Fonctions : nombres, chanes et agrgats

FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id;

-- ----------------------------
-- CHANGEMENT DE SEPARATEUR --
-- ----------------------------
SELECT SUM(Race.prix), GROUP_CONCAT(Race.nom, ' (', Espece.nom_courant, ')' SEPARATOR '
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id;

4.3.2.4 En rsum

La plupart des fonctions dagrgation permettent de faire des statistiques sur les donnes :
nombre de lignes, moyenne dune colonne,
COUNT(*) compte toutes les lignes quel que soit leur contenu, tandis que COUNT(colonne_x)
compte les lignes pour lesquelles colonne_x nest pas NULL.
GROUP_CONCAT(colonne_x) permet de concatner les valeurs de colonne_x dont les lignes
ont t groupes par une autre fonction dagrgation.

4.4 Regroupement
Vous savez donc que les fonctions dagrgation groupent plusieurs lignes ensemble. Jusqu main-
tenant, toutes les lignes taient chaque fois regroupes en une seule. Mais ce qui est particulire-
ment intressant avec ces fonctions, cest quil est possible de regrouper les lignes en fonction
dun critre, et davoir ainsi plusieurs groupes distincts. Par exemple, avec la fonction COUNT(*),
vous pouvez compter le nombre de lignes que vous avez dans la table Animal. Mais que diriez-vous
de faire des groupes par espces, et donc de savoir en une seule requte combien vous avez de
chats, chiens, etc. ? Un simple groupement, et cest fait !

Au programme de ce chapitre :

les rgles et la syntaxe appliquer pour regrouper des lignes ;


le groupement sur plusieurs critres ;
les super-agrgats ;
la slection de certains groupes sur la base de critres.

4.4.1 Regroupement sur un critre


Pour regrouper les lignes selon un critre, il faut utiliser GROUP BY, qui se place aprs lventuelle
clause WHERE (sinon directement aprs FROM), suivi du nom de la colonne utiliser comme critre
de regroupement.

SELECT ...
FROM nom_table
[WHERE condition]
GROUP BY nom_colonne;

180
4.4 Regroupement

Exemple 1 : comptons les lignes dans la table Animal, en regroupant sur le critre de lespce (donc
avec la colonne espece_id).

SELECT COUNT(*) AS nb_animaux


FROM Animal
GROUP BY espece_id;

nb_animaux

21
20
15
4

Exemple 2 : Mme chose, mais on ne prend que les mles cette fois-ci.

SELECT COUNT(*) AS nb_males


FROM Animal
WHERE sexe = 'M'
GROUP BY espece_id;

nb_males

10
9
4
3

Cest dj intressant, mais nous nallons pas en rester l. En effet, il serait quand mme mieux
de savoir quelle espce correspond quel nombre !

4.4.1.1 Voir dautres colonnes

Pour savoir quoi correspond chaque nombre, il suffit dafficher galement le critre qui a permis
de regrouper les lignes. Dans notre cas, espece_id.

SELECT espece_id, COUNT(*) AS nb_animaux


FROM Animal
GROUP BY espece_id;

espece_id nb_animaux

1 21
2 20
3 15
4 4

Cest dj mieux, mais lidal serait davoir le nom des espces directement. Qu cela ne tienne,
il suffit de faire une jointure ! Sans oublier de changer le critre et de mettre nom_courant la place

181
4 Fonctions : nombres, chanes et agrgats

de espece_id.

SELECT nom_courant, COUNT(*) AS nb_animaux


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant;

nom_courant nb_animaux

Chat 20
Chien 21
Perroquet amazone 4
Tortue dHermann 15

4.4.1.2 Colonnes slectionnes

4.4.1.2.1 La rgle SQL Lorsque lon fait un groupement dans une requte, avec GROUP BY,
on ne peut slectionner que deux types dlments dans la clause SELECT :

une ou des colonnes ayant servi de critre pour le regroupement ;


une fonction dagrgation (agissant sur nimporte quelle colonne).

Cette rgle est dailleurs logique. Imaginez la requte suivante :

SELECT nom_courant, COUNT(*) AS nb_animaux, date_naissance


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant;

Que vient faire la date de naissance dans cette histoire ? Et surtout, quelle date de naissance
espre-t-on slectionner ? Chaque ligne reprsente une espce puisque lon a regroup les
lignes sur la base de Espece.nom_courant. Donc date_naissance na aucun sens par rapport aux
groupes forms, une espce nayant pas de date de naissance. Il en est de mme pour les
colonnes sexe ou commentaires par exemple.

Quen est-il des colonnes Espece.id, Animal.espece_id, ou Espece.nom_latin ? En groupant sur le nom
courant de lespce, ces diffrentes colonnes ont un sens, et pourraient donc tre utiles. Il a ce-
pendant t dcid que par scurit, la slection de colonnes ntant pas dans les critres de
groupement serait interdite. Cela afin dviter les situations comme au-dessus, o les colonnes
slectionnes nont aucun sens par rapport au groupement fait. Par consquent, si vous voulez
afficher galement lid de lespce et son nom latin, il vous faudra grouper sur les trois colonnes :
Espece.nom_latin, Espece.nom_courant et Espece.id. Les groupes crs seront les mmes quen groupant
uniquement sur Espece.nom_courant, mais votre requte respectera les standards SQL.

SELECT Espece.id, nom_courant, nom_latin, COUNT(*) AS nb_animaux


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant, Espece.id, nom_latin;

id nom_courant nom_latin nb_animaux

2 Chat Felis silvestris 20

182
4.4 Regroupement

id nom_courant nom_latin nb_animaux

1 Chien Canis canis 21


4 Perroquet amazone Alipiopsitta xanthops 4
3 Tortue dHermann Testudo hermanni 15

4.4.1.2.2 Le cas MySQL On ne le rptera jamais assez : MySQL est un SGBD extrmement
permissif. Dans certains cas, cest bien pratique, mais cest toujours dangereux. Et notamment
en ce qui concerne GROUP BY, MySQL ne sera pas perturb pour un sou si vous slectionnez une
colonne qui nest pas dans les critres de regroupement. Reprenons la requte qui slectionne la
colonne date_naissance alors que le regroupement se fait sur la base de lespece_id. Jinsiste, cette
requte ne respecte pas la norme SQL, et na aucun sens. La plupart des SGBD vous renverront
une erreur si vous tentez de lexcuter.

SELECT nom_courant, COUNT(*) AS nb_animaux, date_naissance


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY espece_id;

Pourtant, loin de rouspter, MySQL donne le rsultat suivant :

nom_courant nb_animaux date_naissance

Chat 20 2010-03-24 02 :23 :00


Chien 21 2010-04-05 13 :43 :00
Perroquet amazone 4 2007-03-04 19 :36 :00
Tortue dHermann 15 2009-08-03 05 :12 :00

MySQL a tout simplement pris nimporte quelle valeur parmi celles du groupe pour la date de
naissance. Dailleurs, il est tout fait possible que vous ayez obtenu des valeurs diffrentes des
miennes. Soyez donc trs prudents lorsque vous utilisez GROUP BY. Vous faites peut-tre des
requtes qui nont aucun sens, et MySQL ne vous en avertira pas !

4.4.1.3 Tri des donnes

Par dfaut dans MySQL, les donnes sont tries sur la base du critre de regroupement. Cest la
raison pour laquelle, dans la requte prcdente, la colonne nom_courant est trie par ordre alpha-
btique : cest le premier critre de regroupement. MySQL permet dutiliser les mots-cls ASC et
DESC dans une clause GROUP BY pour choisir un tri ascendant (par dfaut) ou descendant.

SELECT Espece.id, nom_courant, nom_latin, COUNT(*) AS nb_animaux


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant DESC, Espece.id, nom_latin; -- On trie par ordre anti-alphabtiqu

id nom_courant nom_latin nb_animaux

3 Tortue dHermann Testudo hermanni 15


4 Perroquet amazone Alipiopsitta xanthops 4
1 Chien Canis canis 21

183
4 Fonctions : nombres, chanes et agrgats

id nom_courant nom_latin nb_animaux

2 Chat Felis silvestris 20

Mais rien nempche dutiliser une clause ORDER BY classique, aprs la clause GROUP BY.
LORDER BY sera prioritaire sur lordre dfini par la clause de regroupement.

Dans le mme ordre dides, il nest possible de faire un tri des donnes qu partir dune colonne
qui fait partie des critres de regroupement, ou partir dune fonction dagrgation. a na pas
plus de sens de trier les espces par date de naissance que de slectionner une date de naissance
par espce.

Vous pouvez par contre parfaitement crire ceci :

SELECT Espece.id, nom_courant, nom_latin, COUNT(*) AS nb_animaux


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant, Espece.id, nom_latin
ORDER BY nb_animaux;

id nom_courant nom_latin nb_animaux

4 Perroquet amazone Alipiopsitta xanthops 4


3 Tortue dHermann Testudo hermanni 15
2 Chat Felis silvestris 20
1 Chien Canis canis 21

Notez que la norme SQL veut que lon nutilise pas dexpressions (fonction, opration ma-
thmatique,) dans GROUP BY ou ORDER BY. Cest la raison pour laquelle jai mis ORDER BY
nb_animaux et non pas ORDER BY COUNT(*), bien quavec MySQL les deux fonctionnent.
Pensez donc utiliser des alias pour ces situations.

4.4.1.4 Et les autres espces ?

La requte suivante nous donne le nombre danimaux quon possde pour chaque espce dont
on possde au moins un animal. Comment peut-on faire pour afficher galement les autres es-
pces ?

SELECT Espece.nom_courant, COUNT(*) AS nb_animaux


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant;

Essayons donc avec une jointure externe, puisquil faut tenir compte de toutes les espces, mme
celles qui nont pas de correspondance dans la table Animal.

SELECT Espece.nom_courant, COUNT(*) AS nb_animaux


FROM Animal
RIGHT JOIN Espece ON Animal.espece_id = Espece.id -- RIGHT puisque la table Espece est
GROUP BY nom_courant;

184
4.4 Regroupement

nom_courant nb_animaux

Chat 20
Chien 21
Perroquet amazone 4
Rat brun 1
Tortue dHermann 15

Les rats bruns apparaissent bien. En revanche, ce nest pas 1 quon attend, mais 0, puisquon na
pas de rats dans notre levage. Cela dit, ce rsultat est logique : avec la jointure externe, on aura
une ligne correspondant aux rats bruns, avec NULL dans toutes les colonnes de la table Animal.
Donc ce quil faudrait, cest avoir les cinq espces, mais ne compter que lorsquil y a un animal
correspondant. Pour ce faire, il suffit de faire COUNT(Animal.espece_id) par exemple.

SELECT Espece.nom_courant, COUNT(Animal.espece_id) AS nb_animaux


FROM Animal
RIGHT JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY nom_courant;

nom_courant nb_animaux

Chat 20
Chien 21
Perroquet amazone 4
Rat brun 0
Tortue dHermann 15

Cest pas magique a ? :magicien :

*SGBD : Systme de Gestion de Base de Donnes

4.4.2 Regroupement sur plusieurs critres


Jai mentionn le fait quil tait possible de grouper sur plusieurs colonnes, mais jusqu mainte-
nant, cela na servi qu pouvoir afficher correctement les colonnes voulues, sans que a ninflue
sur les groupes. On navait donc en fait quun seul critre, reprsent par plusieurs colonnes.
Voyons maintenant un exemple avec deux critres diffrents (qui ne crent pas les mmes
groupes).

Les deux requtes suivantes permettent de savoir combien danimaux de chaque espce vous avez
dans la table Animal, ainsi que combien de mles et de femelles, toutes espces confondues.

SELECT nom_courant, COUNT(*) as nb_animaux


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant;

SELECT sexe, COUNT(*) as nb_animaux


FROM Animal
GROUP BY sexe;

185
4 Fonctions : nombres, chanes et agrgats

nom_courant nb_animaux

Chat 20
Chien 21
Perroquet amazone 4
Tortue dHermann 15

sexe nb_animaux

NULL 3
F 31
M 26

En faisant un regroupement multicritre, il est possible de savoir facilement combien de mles et


de femelles par espce il y a dans la table Animal. Notez que lordre des critres a son importance.

Exemple 1 : on regroupe dabord sur lespce, puis sur le sexe.

SELECT nom_courant, sexe, COUNT(*) as nb_animaux


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant, sexe;

nom_courant sexe nb_animaux

Chat NULL 2

Chat F 9
Chat M 9
Chien F 11
Chien M 10
Perroquet amazone F 1
Perroquet amazone M 3
Tortue dHermann NULL 1

Tortue dHermann F 10
Tortue dHermann M 4

Exemple 2 : on regroupe dabord sur le sexe, puis sur lespce.

SELECT nom_courant, sexe, COUNT(*) as nb_animaux


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY sexe,nom_courant;

nom_courant sexe nb_animaux

Chat NULL 2

186
4.4 Regroupement

nom_courant sexe nb_animaux

Tortue dHermann NULL 1

Chat F 9
Chien F 11
Perroquet amazone F 1
Tortue dHermann F 10
Chat M 9
Chien M 10
Perroquet amazone M 3
Tortue dHermann M 4

tant donn que le regroupement par sexe donnait trois groupes diffrents, et le regroupement
par espce donnait quatre groupes diffrents, il peut y avoir jusqu douze (3 x 4) groupes lorsque
lon regroupe en se basant sur les deux critres. Ici, il y en aura moins puisque le sexe de tous les
chiens et de tous les perroquets est dfini (pas de NULL).

4.4.3 Super-agrgats
Parlons maintenant de loption WITH ROLLUP de GROUP BY. Cette option va afficher des lignes
supplmentaires dans la table de rsultats. Ces lignes reprsenteront des super-groupes (ou
super-agrgats), donc des groupes de groupes. Deux petits exemples, et vous aurez compris !

4.4.3.0.1 Exemple avec un critre de regroupement


SELECT nom_courant, COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant WITH ROLLUP ;

nom_courant nb_animaux

Chat 20
Chien 21
Perroquet amazone 4
Tortue dHermann 15
NULL 60

Nous avons donc 20 chats, 21 chiens, 4 perroquets et 15 tortues. Et combien font 20 + 21 + 4 + 15 ?


60 ! Exactement. La ligne supplmentaire reprsente donc le regroupement de nos quatre groupes
bas sur le critre GROUP BY nom_courant.

4.4.3.0.2 Exemple avec deux critres de regroupement


SELECT nom_courant, sexe, COUNT(*) as nb_animaux
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id

187
4 Fonctions : nombres, chanes et agrgats

WHERE sexe IS NOT NULL


GROUP BY nom_courant, sexe WITH ROLLUP ;

nom_courant sexe nb_animaux

Chat F 9
Chat M 9
Chat NULL 18

Chien F 11
Chien M 10
Chien NULL 21

Perroquet amazone F 1
Perroquet amazone M 3
Perroquet amazone NULL 4

Tortue dHermann F 10
Tortue dHermann M 4
Tortue dHermann NULL 14

NULL NULL 57

Les deux premires lignes correspondent aux nombres de chats mles et femelles. Jusque-l, rien
de nouveau. Par contre, la troisime ligne est une ligne insre par WITH ROLLUP, et contient le
nombre de chats (mles et femelles). Nous avons fait des groupes en sparant les espces et les
sexes, et WITH ROLLUP a cr des super-groupes en regroupant les sexes mais gardant les es-
pces spares. Nous avons donc galement le nombre de chiens la sixime ligne, de perroquets
la neuvime, et de tortues la douzime. Quant la toute dernire ligne, cest un super-super-
groupe qui runit tous les groupes ensemble.

Cest en utilisant WITH ROLLUP quon se rend compte que lordre des critres est vraiment im-
portant. En effet, voyons ce qui se passe si on change les deux critres nom_courant et sexe.

SELECT nom_courant, sexe, COUNT(*) as nb_animaux


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE sexe IS NOT NULL
GROUP BY sexe, nom_courant WITH ROLLUP ;

nom_courant sexe nb_animaux

Chat F 9
Chien F 11
Perroquet amazone F 1
Tortue dHermann F 10
NULL F 31
Chat M 9
Chien M 10

188
4.4 Regroupement

nom_courant sexe nb_animaux

Perroquet amazone M 3
Tortue dHermann M 4
NULL M 26
NULL NULL 57

Cette fois-ci, les super-groupes ne correspondent pas aux espces, mais aux sexes, cest--dire
au premier critre. Le regroupement se fait bien dans lordre donn par les critres.

[[information]] | Jai ajout la condition WHERE sexe IS NOT NULL pour des raisons de lisibilit
uniquement, tant donn que sans cette condition, dautres NULL seraient apparus, rendant plus
complique lexplication des super-agrgats.

4.4.3.0.3 NULL, cest pas joli Il est possible dviter davoir ces NULL dans les lignes des
super-groupes. Pour cela, on peut utiliser la fonction COALESCE(). Cette fonction prend autant
de paramtres que lon veut, et renvoie le premier paramtre non NULL.

Exemples :

SELECT COALESCE(1, NULL, 3, 4); -- 1


SELECT COALESCE(NULL, 2); -- 2
SELECT COALESCE(NULL, NULL, 3); -- 3

Voici comment lutiliser dans le cas des super-agrgats.

SELECT COALESCE(nom_courant, 'Total'), COUNT(*) as nb_animaux


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant WITH ROLLUP ;

COALESCE(nom_courant, Total) nb_animaux

Chat 20
Chien 21
Perroquet amazone 4
Tortue dHermann 15
Total 60

Tant quil sagit de simples groupes, nom_courant contient bien le nom de lespce. COALESCE()
renvoie donc celui-ci. Par contre, quand il sagit du super-groupe, la colonne nom_courant du r-
sultat contient NULL, et donc COALESCE() va renvoyer Total.

[[attention]] | Si vous utilisez COALESCE() dans ce genre de situation, il est impratif que vos
critres de regroupement ne contiennent pas NULL (ou que vous liminiez ces lignes-l). Sinon,
vous aurez Total des lignes qui ne sont pas des super-groupes.

Exemple : groupons sur le sexe, sans liminer les lignes pour lesquelles le sexe nest pas dfini.

SELECT COALESCE(sexe, 'Total'), COUNT(*) as nb_animaux


FROM Animal

189
4 Fonctions : nombres, chanes et agrgats

INNER JOIN Espece ON Espece.id = Animal.espece_id


GROUP BY sexe WITH ROLLUP ;

COALESCE(sexe, Total) nb_animaux

Total 3
F 31
M 26
Total 60

4.4.4 Conditions sur les fonctions dagrgation


Il nest pas possible dutiliser la clause WHERE pour faire des conditions sur une fonction dagr-
gation. Donc, si lon veut afficher les espces dont on possde plus de 15 individus, la requte
suivante ne fonctionnera pas.

SELECT nom_courant, COUNT(*)


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE COUNT(*) > 15
GROUP BY nom_courant;

ERROR1111(HY000):Invaliduseofgroupfunction

Il faut utiliser une clause spciale : HAVING. Cette clause se place juste aprs le GROUP BY.

SELECT nom_courant, COUNT(*)


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant
HAVING COUNT(*) > 15 ;

nom_courant COUNT(*)

Chat 20
Chien 21

Il est galement possible dutiliser un alias dans une condition HAVING :

SELECT nom_courant, COUNT(*) as nombre


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant
HAVING nombre > 15 ;

nom_courant nombre

Chat 20
Chien 21

190
4.5 Exercices sur les agrgats

4.4.4.1 Optimisation

Les conditions donnes dans la clause HAVING ne doivent pas ncessairement comporter une
fonction dagrgation. Les deux requtes suivantes donneront par exemple des rsultats qui-
valents :

SELECT nom_courant, COUNT(*) as nombre


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
GROUP BY nom_courant
HAVING nombre > 6 AND SUBSTRING(nom_courant, 1, 1) = 'C' ; -- Deux conditions dans HAVI

SELECT nom_courant, COUNT(*) as nombre


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE SUBSTRING(nom_courant, 1, 1) = 'C' -- Une condition dans WHERE
GROUP BY nom_courant
HAVING nombre > 6 ; -- Et une dans HAVING

Il est cependant prfrable, et de loin, dutiliser la clause WHERE autant que possible, cest--dire
pour toutes les conditions, sauf celles utilisant une fonction dagrgation. En effet, les conditions
HAVING ne sont absolument pas optimises, au contraire des conditions WHERE.

4.4.4.2 En rsum

Utiliser GROUP BY en combinaison avec une fonction dagrgation permet de regrouper les
lignes selon un ou plusieurs critres.
Si lon groupe sur un seul critre, on aura autant de groupes (donc de lignes) que de valeurs
diffrentes dans la colonne utilise comme critre.
Si lon groupe sur plusieurs critres, le nombre maximum de groupes rsultant est ob-
tenu en multipliant le nombre de valeurs diffrentes dans chacune des colonnes utilises
comme critre.
Selon la norme SQL, si lon utilise GROUP BY, on ne peut avoir dans la clause SELECT que
des fonctions dagrgation, ou des colonnes utilises comme critre dans le GROUP BY.
Loption WITH ROLLUP permet de crer des super-groupes (ou groupes de groupe).
Une condition sur le rsultat dune fonction dagrgation se place dans une clause HAVING,
et non dans la clause WHERE.

4.5 Exercices sur les agrgats


Jusqu maintenant, tout a t trs thorique. Or, la meilleure faon dapprendre, cest la pratique.
Voici donc quelques exercices que je vous conseille de faire. Sil vaut mieux que vous essayiez par
vous-mmes avant de regarder la solution, ne restez cependant pas bloqus trop longtemps sur
un exercice, et prenez toujours le temps de bien comprendre la solution.

191
4 Fonctions : nombres, chanes et agrgats

4.5.1 Du simple

4.5.1.1 1. Combien de races avons-nous dans la table Race ?

[[secret]] | sql | SELECT COUNT(*) | FROM Race; | | | | Simple chauffement.

4.5.1.2 2. De combien de chiens connaissons-nous le pre ?

[[secret]] | sql | SELECT COUNT(pere_id) | FROM Animal | INNER JOIN Espece ON


Espece.id = Animal.espece_id | WHERE Espece.nom_courant = 'Chien' ; | | | |
Lastuce ici tait de ne pas oublier de donner la colonne pere_id en paramtre COUNT(), pour ne
compter que les lignes o pere_id est non NULL. Si vous avez fait directement WHERE espece_id
= 1 au lieu dutiliser une jointure pour slectionner les chiens, ce nest pas bien grave.

4.5.1.3 3. Quelle est la date de naissance de notre plus jeune femelle ?

[[secret]] | sql | SELECT MAX(date_naissance) | FROM Animal | WHERE sexe =


'F' ; |

4.5.1.4 4. En moyenne, quel est le prix dun chien ou dun chat de race, par
espce, et en gnral ?

[[secret]] | sql | SELECT nom_courant AS Espece, AVG(Race.prix) AS prix_moyen


| FROM Race | INNER JOIN Espece ON Race.espece_id = Espece.id | WHERE Espece.nom_couran
IN ('Chat', 'Chien') | GROUP BY Espece.nom_courant WITH ROLLUP; | | | | Ne pas
oublier WITH ROLLUP pour avoir le rsultat gnral.

4.5.1.5 5. Combien avons-nous de perroquets mles et femelles, et quels sont


leurs noms (en une seule requte bien sr) ?

[[secret]] |sql | SELECT sexe, COUNT(*), GROUP_CONCAT(nom SEPARATOR ', ') |


FROM Animal | INNER JOIN Espece ON Animal.espece_id = Espece.id | WHERE
nom_courant = 'Perroquet amazone' | GROUP BY sexe; | | | | Il suffisait de se souve-
nir de la mthode GROUP_CONCAT() pour pouvoir raliser simplement cette requte. Peut-tre
avez-vous group sur lespce aussi (avec nom_courant ou autre). Ce ntait pas ncessaire puis-
quon avait restreint une seule espce avec la clause WHERE. Cependant, cela ninflue pas sur le
rsultat, mais sur la rapidit de la requte.

4.5.2 Vers le complexe

4.5.2.1 1. Quelles sont les races dont nous ne possdons aucun individu ?

[[secret]] | sql | SELECT Race.nom, COUNT(Animal.race_id) AS nombre | FROM


Race | LEFT JOIN Animal ON Animal.race_id = Race.id | GROUP BY Race.nom
| HAVING nombre = 0; | | | | Il fallait ici ne pas oublier de faire une jointure externe (LEFT
ou RIGHT, selon votre requte), ainsi que de mettre la colonne Animal.race_id (ou Animal.id, ou
Animal.espece_id mais cest moins intuitif) en paramtre de la fonction COUNT().

192
4.5 Exercices sur les agrgats

4.5.2.2 2. Quelles sont les espces (tries par ordre alphabtique du nom latin)
dont nous possdons moins de cinq mles ?

[[secret]] | sql | SELECT Espece.nom_latin, COUNT(espece_id) AS nombre | FROM


Espece | LEFT JOIN Animal ON Animal.espece_id = Espece.id | WHERE sexe =
'M' OR Animal.id IS NULL | GROUP BY Espece.nom_latin | HAVING nombre < 5;
| | | | nouveau, une jointure externe et espece_id en argument de COUNT(), mais il y avait ici
une petite subtilit en plus. | Puisquon demandait des informations sur les mles uniquement,
il fallait une condition WHERE sexe = 'M'. Mais cette condition fait que les lignes de la jointure
provenant de la table Espece nayant aucune correspondance dans la table Animal sont limines
galement (puisque forcment, toutes les colonnes de la table Animal, dont sexe, seront NULL
pour ces lignes). Par consquent, il fallait ajouter une condition permettant de garder ces fa-
meuses lignes (les espces pour lesquelles on na aucun individu, donc aucun mle). Il fallait
donc ajouter OR Animal.id IS NULL, ou faire cette condition sur toute autre colonne dAnimal
ayant la contrainte NOT NULL, et qui donc ne sera NULL que lors dune jointure externe, en cas
de non-correspondance avec lautre table. | Il ny a plus alors qu ajouter la clause HAVING pour
slectionner les espces ayant moins de cinq mles.

4.5.2.3 3. Combien de mles et de femelles de chaque race avons-nous, avec un


compte total intermdiaire pour les races (mles et femelles confondues)
et pour les espces ? Afficher le nom de la race, et le nom courant de
lespce.

[[secret]] | sql | SELECT Animal.sexe, Race.nom, Espece.nom_courant, COUNT(*)


AS nombre | FROM Animal | INNER JOIN Espece ON Animal.espece_id = Espece.id
| INNER JOIN Race ON Animal.race_id = Race.id | WHERE Animal.sexe IS NOT
NULL | GROUP BY Espece.nom_courant, Race.nom, sexe WITH ROLLUP; | | | | Deux
jointures sont ncessaires pour pouvoir afficher les noms des races et des espces. Il suffit alors
de ne pas oublier loption WITH ROLLUP et de mettre les critres de regroupement dans le bon
ordre pour avoir les super-agrgats voulus.

4.5.2.4 4. Quel serait le cot, par espce et au total, de ladoption de Parlotte,


Spoutnik, Caribou, Cartouche, Cali, Canaille, Yoda, Zambo et Lulla ?

[[information]] | Petit indice, pour avoir le prix dun animal selon que sa race soit dfinie ou non,
vous pouvez utiliser une fonction que nous venons de voir au chapitre prcdent.

[[secret]] | sql | SELECT Espece.nom_courant, SUM(COALESCE(Race.prix, Espece.prix))


AS somme | FROM Animal | INNER JOIN Espece ON Espece.id = Animal.espece_id
| LEFT JOIN Race ON Race.id = Animal.race_id | WHERE Animal.nom IN ('Parlotte',
'Spoutnik', 'Caribou', 'Cartouche', 'Cali', 'Canaille', 'Yoda', 'Zambo',
'Lulla') | GROUP BY Espece.nom_courant WITH ROLLUP; | | | | Cest ici la fonction
SUM() quil fallait utiliser, puisquon veut le prix total par groupe. Sans oublier le WITH ROLLUP
pour avoir galement le prix total tous groupes confondus. | Quant au prix de chaque animal,
cest typiquement une situation o lon peut utiliser COALESCE() !

193
4 Fonctions : nombres, chanes et agrgats

Et voil pour les nombres et les chanes de caractres ! Notez que les fonctions dagrgat sont
parmi les plus utilises donc soyez bien srs davoir compris comment elles fonctionnent, cou-
ples GROUP BY ou non.

194
5 Fonctions : manipuler les dates
Cette partie sera entirement consacre aux donnes temporelles, et plus particulirement la
manipulation de celles-ci laide de fonctions. Il en existe beaucoup, et leur utilisation nest
pas bien complique. Et pourtant, cest quelque chose qui semble rebuter les dbutants, tel
point que beaucoup prfrent reprsenter leurs donnes temporelles sous forme de chanes de
caractres, perdant ainsi lopportunit dutiliser les outils spcifiques existants.

Cest la raison pour laquelle jai dcid de consacrer toute une partie lutilisation de ces fonc-
tions, mme si 90 % de ce que je vais crire ici se trouve dans la documentation officielle. Jes-
pre quune prsentation plus structure, dote de nombreux exemples et termine par une srie
dexercices rendra ces fonctions plus attractives aux nouveaux venus dans le monde de MySQL.

5.1 Obtenir la date/lheure actuelle


Avant de plonger tte la premire dans les fonctions temporelles, il convient de faire un bref rap-
pel sur les diffrents types de donnes temporelles, qui sont au nombre de cinq : DATE, TIME,
DATETIME, TIMESTAMP et YEAR. Ensuite, nous verrons avec quelles fonctions il est possible dob-
tenir la date actuelle, lheure actuelle, ou les deux.

5.1.0.1 Etat actuel de la base de donnes

Note : les tables de test ne sont pas reprises.

[[secret]] | sql | SET NAMES utf8; | | | DROP TABLE IF EXISTS Animal; | DROP
TABLE IF EXISTS Race; | DROP TABLE IF EXISTS Espece; | | | CREATE TABLE
Espece ( | id smallint(6) unsigned NOT NULL AUTO_INCREMENT, | nom_courant
varchar(40) NOT NULL, | nom_latin varchar(40) NOT NULL, | description
text, | prix decimal(7,2) unsigned DEFAULT NULL, | PRIMARY KEY (id),
| UNIQUE KEY nom_latin (nom_latin) | ) ENGINE=InnoDB AUTO_INCREMENT=6
DEFAULT CHARSET=latin1; | | LOCK TABLES Espece WRITE; | INSERT INTO Espece
VALUES (1,'Chien','Canis canis','Bestiole quatre pattes qui aime les
caresses et tire souvent la langue',200.00),(2,'Chat','Felis silvestris','Bestiole
quatre pattes qui saute trs haut et grimpe aux arbres',150.00),(3,'Tortue
d''Hermann','Testudo hermanni','Bestiole avec une carapace trs dure',140.00),
| (4,'Perroquet amazone','Alipiopsitta xanthops','Joli oiseau parleur vert
et jaune',700.00),(5,'Rat brun','Rattus norvegicus','Petite bestiole avec
de longues moustaches et une longue queue sans poils',10.00); | UNLOCK
TABLES; | | | CREATE TABLE Race ( | id smallint(6) unsigned NOT NULL
AUTO_INCREMENT, | nom varchar(40) NOT NULL, | espece_id smallint(6)
unsigned NOT NULL, | description text, | prix decimal(7,2) unsigned
DEFAULT NULL, | PRIMARY KEY (id) | ) ENGINE=InnoDB AUTO_INCREMENT=10

195
5 Fonctions : manipuler les dates

DEFAULT CHARSET=latin1; | | LOCK TABLES Race WRITE; | INSERT INTO Race


VALUES (1,'Berger allemand',1,'Chien sportif et lgant au pelage dense,
noir-marron-fauve, noir ou gris.',485.00),(2,'Berger blanc suisse',1,'Petit
chien au corps compact, avec des pattes courtes mais bien proportionnes
et au pelage tricolore ou bicolore.',935.00),(3,'Singapura',2,'Chat de
petite taille aux grands yeux en amandes.',985.00), | (4,'Bleu russe',2,'Chat
aux yeux verts et la robe paisse et argente.',835.00),(5,'Maine coon',2,'Chat
de grande taille, poils mi-longs.',735.00),(7,'Sphynx',2,'Chat sans poils.',1235.00),
| (8,'Nebelung',2,'Chat bleu russe, mais avec des poils longs...',985.00),(9,'Rottweill
d''apparence solide, bien muscl, la robe noire avec des taches feu bien
dlimites.',600.00); | UNLOCK TABLES; | | | CREATE TABLE Animal ( |
id smallint(6) unsigned NOT NULL AUTO_INCREMENT, | sexe char(1) DEFAULT
NULL, | date_naissance datetime NOT NULL, | nom varchar(30) DEFAULT
NULL, | commentaires text, | espece_id smallint(6) unsigned NOT NULL,
| race_id smallint(6) unsigned DEFAULT NULL, | mere_id smallint(6)
unsigned DEFAULT NULL, | pere_id smallint(6) unsigned DEFAULT NULL, |
PRIMARY KEY (id), | UNIQUE KEY ind_uni_nom_espece_id (nom,espece_id) |
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8; | | LOCK TABLES
Animal WRITE; | INSERT INTO Animal VALUES (1,'M','2010-04-05 13:43:00','Rox','Mordille
beaucoup',1,1,18,22),(2,NULL,'2010-03-24 02:23:00','Roucky',NULL,2,NULL,40,30),(3,'F','
15:02:00','Schtroumpfette',NULL,2,4,41,31), | (4,'F','2009-08-03 05:12:00',NULL,'Bestio
avec une carapace trs dure',3,NULL,NULL,NULL),(5,NULL,'2010-10-03 16:44:00','Choupi','
sans oreille gauche',2,NULL,NULL,NULL),(6,'F','2009-06-13 08:17:00','Bobosse','Carapace
bizarre',3,NULL,NULL,NULL), | (7,'F','2008-12-06 05:18:00','Caroline',NULL,1,2,NULL,NUL
15:38:00','Bagherra',NULL,2,5,NULL,NULL),(9,NULL,'2010-08-23 05:18:00',NULL,'Bestiole
avec une carapace trs dure',3,NULL,NULL,NULL), | (10,'M','2010-07-21 15:41:00','Bobo',
15:45:00','Canaille',NULL,1,NULL,NULL,NULL),(12,'F','2009-05-26 08:54:00','Cali',NULL,1
| (13,'F','2007-04-24 12:54:00','Rouquine',NULL,1,1,NULL,NULL),(14,'F','2009-05-26
08:56:00','Fila',NULL,1,2,NULL,NULL),(15,'F','2008-02-20 15:47:00','Anya',NULL,1,NULL,N
| (16,'F','2009-05-26 08:50:00','Louya',NULL,1,NULL,NULL,NULL),(17,'F','2008-03-10
13:45:00','Welva',NULL,1,NULL,NULL,NULL),(18,'F','2007-04-24 12:59:00','Zira',NULL,1,1,
| (19,'F','2009-05-26 09:02:00','Java',NULL,1,2,NULL,NULL),(20,'M','2007-04-24
12:45:00','Balou',NULL,1,1,NULL,NULL),(21,'F','2008-03-10 13:43:00','Pataude',NULL,1,NU
| (22,'M','2007-04-24 12:42:00','Bouli',NULL,1,1,NULL,NULL),(24,'M','2007-04-12
05:23:00','Cartouche',NULL,1,NULL,NULL,NULL),(25,'M','2006-05-14 15:50:00','Zambo',NULL
| (26,'M','2006-05-14 15:48:00','Samba',NULL,1,1,NULL,NULL),(27,'M','2008-03-10
13:40:00','Moka',NULL,1,NULL,NULL,NULL),(28,'M','2006-05-14 15:40:00','Pilou',NULL,1,1,
| (29,'M','2009-05-14 06:30:00','Fiero',NULL,2,3,NULL,NULL),(30,'M','2007-03-12
12:05:00','Zonko',NULL,2,5,NULL,NULL),(31,'M','2008-02-20 15:45:00','Filou',NULL,2,4,NU
| (32,'M','2009-07-26 11:52:00','Spoutnik',NULL,3,NULL,52,NULL),(33,'M','2006-05-19
16:17:00','Caribou',NULL,2,4,NULL,NULL),(34,'M','2008-04-20 03:22:00','Capou',NULL,2,5,
| (35,'M','2006-05-19 16:56:00','Raccou','Pas de queue depuis la naissance',2,4,NULL,NU
06:42:00','Boucan',NULL,2,3,NULL,NULL),(37,'F','2006-05-19 16:06:00','Callune',NULL,2,8
| (38,'F','2009-05-14 06:45:00','Boule',NULL,2,3,NULL,NULL),(39,'F','2008-04-20
03:26:00','Zara',NULL,2,5,NULL,NULL),(40,'F','2007-03-12 12:00:00','Milla',NULL,2,5,NUL
| (41,'F','2006-05-19 15:59:00','Feta',NULL,2,4,NULL,NULL),(42,'F','2008-04-20
03:20:00','Bilba','Sourde de l''oreille droite 80%',2,5,NULL,NULL),(43,'F','2007-03-1
11:54:00','Cracotte',NULL,2,5,NULL,NULL), | (44,'F','2006-05-19 16:16:00','Cawette',NUL

196
5.1 Obtenir la date/lheure actuelle

18:17:00','Nikki','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),(46,'F','200


08:23:00','Tortilla','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),
| (47,'F','2009-03-26 01:24:00','Scroupy','Bestiole avec une carapace trs
dure',3,NULL,NULL,NULL),(48,'F','2006-03-15 14:56:00','Lulla','Bestiole
avec une carapace trs dure',3,NULL,NULL,NULL),(49,'F','2008-03-15 12:02:00','Dana','Be
avec une carapace trs dure',3,NULL,NULL,NULL), | (50,'F','2009-05-25 19:57:00','Cheli'
avec une carapace trs dure',3,NULL,NULL,NULL),(51,'F','2007-04-01 03:54:00','Chicaca',
avec une carapace trs dure',3,NULL,NULL,NULL),(52,'F','2006-03-15 14:26:00','Redbul','
| (54,'M','2008-03-16 08:20:00','Bubulle','Bestiole avec une carapace trs
dure',3,NULL,NULL,NULL),(55,'M','2008-03-15 18:45:00','Relou','Surpoids',3,NULL,NULL,NU
18:54:00','Bulbizard','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),
| (57,'M','2007-03-04 19:36:00','Safran','Coco veut un gteau !',4,NULL,NULL,NULL),(58,
02:50:00','Gingko','Coco veut un gteau !',4,NULL,NULL,NULL),(59,'M','2009-03-26
08:28:00','Bavard','Coco veut un gteau !',4,NULL,NULL,NULL), | (60,'F','2009-03-26
07:55:00','Parlotte','Coco veut un gteau !',4,NULL,NULL,NULL),(61,'M','2010-11-09
00:00:00','Yoda',NULL,2,5,NULL,NULL),(62,'M','2010-11-05 00:00:00','Pipo',NULL,1,9,NULL
| UNLOCK TABLES; | | | ALTER TABLE Race ADD CONSTRAINT fk_race_espece_id
FOREIGN KEY (espece_id) REFERENCES Espece (id) ON DELETE CASCADE; | |
ALTER TABLE Animal ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES
Race (id) ON DELETE SET NULL; | ALTER TABLE Animal ADD CONSTRAINT fk_espece_id
FOREIGN KEY (espece_id) REFERENCES Espece (id); | ALTER TABLE Animal ADD
CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal (id) ON DELETE
SET NULL; | ALTER TABLE Animal ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id)
REFERENCES Animal (id) ON DELETE SET NULL; |

5.1.1 Rappels

Nous allons rapidement revoir les cinq types de donnes temporelles disponibles pour MySQL.
Pour plus de dtails, je vous renvoie au chapitre consacr ceux-ci, ou la documentation offi-
cielle.

5.1.1.1 Date

On peut manipuler une date (jour, mois, anne) avec le type DATE. Ce type reprsente la date sous
forme de chane de caractres 'AAAA-MM-JJ' (A = anne, M = mois, J = jour). Par exemple : le 21
octobre 2011 sera reprsent '2011-10-21'.

Lorsque lon cre une donne de type DATE, on peut le faire avec une multitude de formats dif-
frents, que MySQL convertira automatiquement. Il suffit de donner lanne (en deux ou quatre
chiffres), suivie du mois (deux chiffres) puis du jour (deux chiffres). Avec une chane de carac-
tres, nimporte quel caractre de ponctuation (ou aucun caractre) peut tre utilis pour sparer
lanne du mois et le mois du jour. On peut aussi utiliser un nombre entier pour initialiser une
date (pour autant quil ait du sens en tant que date bien sr).

MySQL supporte des DATE allant de '1001-01-01' '9999-12-31'.

197
5 Fonctions : manipuler les dates

5.1.1.2 Heure

Pour une heure, ou une dure, on utilise le type TIME, qui utilise galement une chane de carac-
tres pour reprsenter lheure : '[H]HH:MM:SS' (H = heures, M = minutes, S = secondes).

MySQL supporte des TIME allant de '-838:59:59' '838:59:59'. Ce nest en effet pas limit
24h, puisquil est possible de stocker des dures.

Pour crer un TIME, on donne dabord les heures, puis les minutes, puis les secondes, avec || :||
entre chaque donne. On peut ventuellement aussi spcifier un nombre de jours avant les heures
(suivi cette fois dune espace, et non dun || :||) : 'J HH:MM:SS'.

5.1.1.3 Date et heure

Sans surprise, DATETIME est le type de donnes reprsentant une date et une heure, toujours
stockes sous forme de chane de caractres : 'AAAA-MM-JJ HH:MM:SS'. Les heures doivent ici
tre comprises entre 00 et 23, puisquil ne peut plus sagir dune dure.

Comme pour DATE, limportant dans DATETIME est lordre des donnes : anne, mois, jour,
heures, minutes, secondes ; chacune avec deux chiffres, sauf lanne pour laquelle on peut aussi
donner quatre chiffres. Cela peut tre un nombre entier, ou une chane de caractres, auquel cas
les signes de ponctuation entre chaque partie du DATETIME importent peu.

MySQL supporte des DATETIME allant de '1001-01-01 00:00:00' '9999-12-31 23:59:59'.

5.1.1.4 Timestamp

Le timestamp dune date est le nombre de secondes coules depuis le 1er janvier 1970, 0h0min0s
(TUC) et la date en question. Mais attention, ce qui est stock par MySQL dans une donne de
type TIMESTAMP nest pas ce nombre de secondes, mais bien la date, sous format numrique :
AAAAMMJJHHMMSS (contrairement DATE, TIME et DATETIME qui utilisent des chanes de carac-
tres).

Un timestamp est limit aux dates allant du 1er janvier 1970 00h00min00s au 19 janvier 2038
03h14min07s.

5.1.1.5 Anne

Le dernier type temporel est YEAR, qui stocke une anne sous forme dentier. Nous nen parlerons
pas beaucoup dans cette partie.

YEAR peut contenir des annes comprises entre 1901 et 2155.

5.1.2 Date actuelle


Il existe deux fonctions permettant davoir la date actuelle (juste la date, sans lheure, donc au
format DATE) :

CURDATE() ;
CURRENT_DATE(), qui peut galement sutiliser sans parenthses, comme plusieurs
autres fonctions temporelles.

198
5.1 Obtenir la date/lheure actuelle

SELECT CURDATE(), CURRENT_DATE(), CURRENT_DATE ;

199
5 Fonctions : manipuler les dates

CURRENT_DATE()
CURDATE() CURRENT_DATE

2011-10-25 2011-10-25 2011-10-25

5.1.3 Heure actuelle


nouveau, deux fonctions existent, extrmement similaires aux fonctions permettant davoir la
date actuelle. Il suffit en effet de remplacer DATE par TIME dans le nom de la fonction.

SELECT CURTIME(), CURRENT_TIME(), CURRENT_TIME;

CURRENT_TIME()
CURTIME() CURRENT_TIME

18 :04 :20 18 :04 :20 18 :04 :20

5.1.4 Date et heure actuelles

5.1.4.1 Les fonctions

Pour obtenir la date et lheure actuelles (format DATETIME), cest Byzance : vous avez le choix
entre cinq fonctions diffrentes !

5.1.4.1.1 NOW() et SYSDATE() NOW() est sans doute la fonction MySQL la plus utilise
pour obtenir la date du jour. Cest aussi la plus facile retenir (bien que les noms des fonctions
soient souvent explicites en SQL), puisque now veut dire maintenant en anglais. SYSDATE()
(system date) est aussi pas mal utilise.

SELECT NOW(), SYSDATE();

NOW() SYSDATE()

2011-10-26 09 :40 :18 2011-10-26 09 :40 :18

5.1.4.1.2 Et les autres Les trois autres fonctions peuvent sutiliser avec ou sans parenthses.

CURRENT_TIMESTAMP ou CURRENT_TIMESTAMP()
LOCALTIME ou LOCALTIME()
LOCALTIMESTAMP ou LOCALTIMESTAMP()

SELECT LOCALTIME, CURRENT_TIMESTAMP(), LOCALTIMESTAMP ;

CURRENT_TIMESTAMP()
LOCALTIME LOCALTIMESTAMP

2011-10-26 10 :02 :31 2011-10-26 10 :02 :31 2011-10-26 10 :02 :31

200
5.1 Obtenir la date/lheure actuelle

5.1.4.2 Qui peut le plus, peut le moins

Il est tout fait possible dutiliser une des fonctions donnant lheure et la date pour remplir une
colonne de type DATE, ou de type TIME. MySQL convertira simplement le DATETIME en DATE, ou
en TIME, en supprimant la partie inutile.

5.1.4.2.1 Exemple Crons une table de test simple, avec juste trois colonnes. Une de type
DATE, une de type TIME, une de type DATETIME. On peut voir que linsertion dune ligne en utili-
sant NOW() pour les trois colonnes donne le rsultat attendu.

-- Cration d'une table de test toute simple


CREATE TABLE testDate (
dateActu DATE,
timeActu TIME,
datetimeActu DATETIME
);

INSERT INTO testDate VALUES (NOW(), NOW(), NOW());

SELECT *
FROM testDate;

dateActu timeActu datetimeActu

2011-10-26 11 :22 :10 2011-10-26 11 :22 :10

5.1.4.3 Timestamp Unix

Il existe encore une fonction qui peut donner des informations sur la date et lheure actuelle,
sous forme de timestamp Unix ; ce qui est donc le nombre de secondes coules depuis le premier
janvier 1970, 00 :00 :00. Il sagit de UNIX_TIMESTAMP().

Je vous la donne au cas o, mais jespre que vous ne vous en servirez pas pour stocker vos dates
sous forme dINT avec un timestamp Unix !

SELECT UNIX_TIMESTAMP();

UNIX_TIMESTAMP()

1319621754

5.1.4.4 En rsum

La date du jour peut sobtenir avec CURDATE() et CURRENT_DATE().


Lheure actuelle peut sobtenir avec CURTIME() et CURRENT_TIME().
Lheure et la date actuelles peuvent sobtenir avec NOW(), SYSDATE(), LOCALTIME(),
CURRENT_TIMESTAMP(), LOCALTIMESTAMP().

201
5 Fonctions : manipuler les dates

On peut insrer un DATETIME dans une colonne DATE ou TIME. MySQL tera la partie in-
utile.

5.2 Formater une donne temporelle


Lorsque lon tombe sur quelquun qui a fait le (mauvais) choix de stocker ses dates sous forme
de chanes de caractres, et quon lui demande les raisons de son choix, celle qui revient le plus
souvent est quil ne veut pas afficher ses dates sous la forme 'AAAA-MM-JJ'. Donc il les stocke
sous forme de CHAR ou VARCHAR 'JJ/MM/AAAA' par exemple, ou nimporte quel format de son
choix. Malheureusement, en faisant a, il se prive des nombreux avantages des formats temporels
SQL (en particulier toutes les fonctions que nous voyons dans cette partie), et cela pour rien, car
SQL dispose de puissantes fonctions permettant de formater une donne temporelle. Cest donc
ce que nous allons voir dans ce chapitre.

5.2.1 Extraire une information prcise


Commenons en douceur avec des fonctions permettant dextraire une information dune donne
temporelle. Par exemple, le jour de la semaine, le nom du mois, lanne, etc.

5.2.1.1 Informations sur la date

5.2.1.1.1 Extraire la partie DATE La fonction DATE(datetime) permet dextraire la partie


DATE dune donne de type DATETIME (ou DATE mais cest moins utile).

SELECT nom, date_naissance,


DATE(date_naissance) AS uniquementDate
FROM Animal
WHERE espece_id = 4 ;

nom date_naissance uniquementDate

Safran 2007-03-04 19 :36 :00 2007-03-04


Gingko 2008-02-20 02 :50 :00 2008-02-20
Bavard 2009-03-26 08 :28 :00 2009-03-26
Parlotte 2009-03-26 07 :55 :00 2009-03-26

5.2.1.1.2 Le jour Les fonctions suivantes donnent des informations sur le jour :

DAY(date) ou DAYOFMONTH(date) : donne le jour du mois (sous forme de nombre entier


de 1 31) ;
DAYOFWEEK(date) : donne lindex du jour de la semaine (nombre de 1 7 avec 1 = di-
manche, 2 = lundi,7 = samedi) ;
WEEKDAY(date) : donne aussi lindex du jour de la semaine, de manire un peu diffrente
(nombre de 0 6 avec 0 = lundi, 1 = mardi,6 = dimanche) ;
DAYNAME(date) : donne le nom du jour de la semaine ;
DAYOFYEAR(date) : retourne le numro du jour par rapport lanne (de 1 366 donc).

Exemples :

202
5.2 Formater une donne temporelle

SELECT nom, DATE(date_naissance) AS date_naiss,


DAY(date_naissance) AS jour,
DAYOFMONTH(date_naissance) AS jour,
DAYOFWEEK(date_naissance) AS jour_sem,
WEEKDAY(date_naissance) AS jour_sem2,
DAYNAME(date_naissance) AS nom_jour,
DAYOFYEAR(date_naissance) AS jour_annee
FROM Animal
WHERE espece_id = 4 ;

jour_sem jour_sem2 jour_annee


nom date_naiss jour jour nom_jour

Safran 2007-03- 4 4 1 6 Sunday 63


04

Gingko 2008-02- 20 20 4 2 Wednesday 51


20

Bavard 2009-03- 26 26 5 3 Thursday 85


26

Parlotte 2009-03- 26 26 5 3 Thursday 85


26

Tout a fonctionne trs bien, mais ce serait encore mieux si lon pouvait avoir le nom des jours en
franais plutt quen anglais. Aucun problme, il suffit de le demander, en excutant la requte
suivante :

SET lc_time_names = 'fr_FR' ;

Et voil le travail :

SELECT nom, date_naissance,


DAYNAME(date_naissance) AS jour_semaine
FROM Animal
WHERE espece_id = 4 ;

nom date_naissance jour_semaine

Safran 2007-03-04 19 :36 :00 dimanche


Gingko 2008-02-20 02 :50 :00 mercredi
Bavard 2009-03-26 08 :28 :00 jeudi
Parlotte 2009-03-26 07 :55 :00 jeudi

5.2.1.1.3 La semaine partir dune date, il est possible de calculer la semaine laquelle cor-
respond celle-ci. Sagit-il de la premire semaine de lanne ? De la quinzime ? Ceci peut tre
obtenu grce trois fonctions : WEEK(date), WEEKOFYEAR(date) et YEARWEEK(date).

203
5 Fonctions : manipuler les dates

WEEK(date) : donne uniquement le numro de la semaine (un nombre entre 0 et 53,


puisque 7 x 52 = 364, donc en un an, il y a 52 semaines et 1 ou 2 jours dune 53e semaine).
WEEKOFYEAR(date) : donne uniquement le numro de la semaine (un nombre entre 1 et
53).
YEARWEEK(date) : donne galement lanne.

SELECT nom, date_naissance, WEEK(date_naissance) AS semaine, WEEKOFYEAR(date_naissance)


FROM Animal
WHERE espece_id = 4 ;

semaine_annee
nom date_naissance semaine semaine2

Safran 2007-03-04 19 :36 :00 9 9 200709


Gingko 2008-02-20 02 :50 :00 7 8 200807

Bavard 2009-03-26 08 :28 :00 12 13 200912


Parlotte 2009-03-26 07 :55 :00 12 13 200912

WEEK() et YEARWEEK() peuvent galement accepter un deuxime argument, qui sert spcifier
si la semaine doit commencer le lundi ou le dimanche, et ce quon considre comme la premire
semaine de lanne. Selon loption utilise par WEEK(), le rsultat de cette fonction peut donc
diffrer de celui de WEEKOFYEAR(). Si ces options vous intressent, je vous invite aller vous
renseigner dans la documentation officielle.

5.2.1.1.4 Le mois Pour le mois, il existe deux fonctions : MONTH(date) qui donne le numro
du mois (nombre de 1 12) et MONTHNAME(date) qui donne le nom du mois.

SELECT nom, date_naissance, MONTH(date_naissance) AS numero_mois, MONTHNAME(date_naissa


FROM Animal
WHERE espece_id = 4 ;

numero_mois nom_mois
nom date_naissance

Safran 2007-03-04 19 :36 :00 3 mars


Gingko 2008-02-20 02 :50 :00 2 fvrier
Bavard 2009-03-26 08 :28 :00 3 mars
Parlotte 2009-03-26 07 :55 :00 3 mars

5.2.1.1.5 Lanne Enfin, la fonction YEAR(date) extrait lanne.

SELECT nom, date_naissance, YEAR(date_naissance)


FROM Animal
WHERE espece_id = 4 ;

nom date_naissance YEAR(date_naissance)

Safran 2007-03-04 19 :36 :00 2007

204
5.2 Formater une donne temporelle

nom date_naissance YEAR(date_naissance)

Gingko 2008-02-20 02 :50 :00 2008


Bavard 2009-03-26 08 :28 :00 2009
Parlotte 2009-03-26 07 :55 :00 2009

5.2.1.2 Informations sur lheure

En ce qui concerne lheure, voici quatre fonctions intressantes (et faciles retenir) qui sap-
pliquent une donne de type DATETIME ou TIME :

TIME(datetime) : qui extrait lheure complte (le TIME) ;


HOUR(heure) : qui extrait lheure ;
MINUTE(heure) : qui extrait les minutes ;
SECOND(heure) : qui extrait les secondes.

SELECT nom, date_naissance,


TIME(date_naissance) AS time_complet,
HOUR(date_naissance) AS heure,
MINUTE(date_naissance) AS minutes,
SECOND(date_naissance) AS secondes
FROM Animal
WHERE espece_id = 4 ;

time_complet minutes
nom date_naissance heure secondes

Safran 2007-03-04 19 :36 :00 19 36 0


19 :36 :00
Gingko 2008-02-20 02 :50 :00 2 50 0
02 :50 :00
Bavard 2009-03-26 08 :28 :00 8 28 0
08 :28 :00
Parlotte 2009-03-26 07 :55 :00 7 55 0
07 :55 :00

5.2.2 Formater une date facilement


Avec les fonctions que nous venons de voir, vous tes maintenant capables dafficher une date
dans un joli format, par exemple le lundi 8 novembre 1987.

SELECT nom, date_naissance, CONCAT_WS(' ', 'le', DAYNAME(date_naissance), DAY(date_nais


FROM Animal
WHERE espece_id = 4 ;

nom date_naissance jolie_date

Safran 2007-03-04 19 :36 :00 le dimanche 4 mars 2007


Gingko 2008-02-20 02 :50 :00 le mercredi 20 fvrier 2008
Bavard 2009-03-26 08 :28 :00 le jeudi 26 mars 2009

205
5 Fonctions : manipuler les dates

nom date_naissance jolie_date

Parlotte 2009-03-26 07 :55 :00 le jeudi 26 mars 2009

Cependant, il faut bien avouer que cest un peu long crire. Heureusement, il existe une fonc-
tion qui va nous permettre de faire la mme chose, en bien plus court : DATE_FORMAT(date,
format).

Cette fonction DATE_FORMAT() a donc deux paramtres :

date : la date formater (DATE, TIME ou DATETIME) ;


format : le format voulu.

5.2.2.1 Format

Le format utiliser doit tre donn sous forme de chane de caractres. Cette chane peut contenir
un ou plusieurs spcificateurs dont les plus courants sont lists dans le tableau suivant.

Spcificateur Description

%d Jour du mois (nombre deux chiffres, de 00 31)


%e Jour du mois (nombre un ou deux chiffres, de 0 31)
%D Jour du mois, avec suffixe (1rst, 2nd,, 31th) en anglais
%w Numro du jour de la semaine (dimanche = 0,, samedi = 6)
%W Nom du jour de la semaine
%a Nom du jour de la semaine en abrg
%m Mois (nombre de deux chiffres, de 00 12)
%c Mois (nombre de un ou deux chiffres, de 0 12)
%M Nom du mois
%b Nom du mois en abrg
%y Anne, sur deux chiffres
%Y Anne, sur quatre chiffres
%r Heure complte, format 12h (hh :mm :ss AM/PM)
%T Heure complte, format 24h (hh :mm :ss)
%h Heure sur deux chiffres et sur 12 heures (de 00 12)
%H Heure sur deux chiffres et sur 24 heures (de 00 23)
%l Heure sur un ou deux chiffres et sur 12 heures (de 0 12)
%k Heure sur un ou deux chiffres et sur 24 heures (de 0 23)
%i Minutes (de 00 59)
%s ou %S Secondes (de 00 59)
%p AM/PM

Tous les caractres ne faisant pas partie dun spcificateur sont simplement recopis tels quels.

5.2.2.2 Exemples

5.2.2.2.1 Mme rsultat que prcdemment Avec une requte bien plus courte :

SELECT nom, date_naissance, DATE_FORMAT(date_naissance, 'le %W %e %M %Y') AS jolie_date


FROM Animal

206
5.2 Formater une donne temporelle

WHERE espece_id = 4 ;

nom date_naissance jolie_date

Safran 2007-03-04 19 :36 :00 le dimanche 4 mars 2007


Gingko 2008-02-20 02 :50 :00 le mercredi 20 fvrier 2008
Bavard 2009-03-26 08 :28 :00 le jeudi 26 mars 2009
Parlotte 2009-03-26 07 :55 :00 le jeudi 26 mars 2009

5.2.2.2.2 Autres exemples [[attention]] | Attention bien chapper les guillemets ventuels
dans la chane de caractres du format.

SELECT DATE_FORMAT(NOW(), 'Nous sommes aujourd''hui le %d %M de l''anne %Y. Il est act

SELECT DATE_FORMAT(NOW(), '%d %b. %y - %r') AS Top_date_courte;

Top_date_longue

Nous sommes aujourdhui le 27 octobre de lanne 2011. Il est actuellement 3 heures et 17


minutes.

Top_date_courte

27 oct. 11 - 03 :34 :40 PM

5.2.2.3 Fonction supplmentaire pour lheure

DATE_FORMAT() peut sutiliser sur des donnes de type DATE, TIME ou DATETIME. Mais il existe
galement une fonction TIME_FORMAT(heure, format), qui ne sert qu formater les heures
(et ne doit donc pas sutiliser sur une DATE). Elle sutilise exactement de la mme manire, sim-
plement il faut y utiliser des spcificateurs ayant du sens pour une donne TIME, sinon NULL
ou 0 est renvoy. Si un mauvais format de TIME ou DATETIME est donn TIME_FORMAT() (par
exemple, si on lui donne une DATE), MySQL va tenter dinterprter la donne et renverra bien un
rsultat, mais celui-ci naura peut-tre pas beaucoup de sens (pour vous du moins).

Exemples :

-- Sur une DATETIME


SELECT TIME_FORMAT(NOW(), '%r') AS sur_datetime,
TIME_FORMAT(CURTIME(), '%r') AS sur_time,
TIME_FORMAT(NOW(), '%M %r') AS mauvais_specificateur,
TIME_FORMAT(CURDATE(), '%r') AS sur_date;

sur_datetime sur_time mauvais_specificateur sur_date

10 :58 :47 AM 10 :58 :47 AM NULL 12 :20 :12 AM

Lapplication de TIME_FORMAT() sur CURDATE() a renvoy un avertissement :

207
5 Fonctions : manipuler les dates

Level Code Message

Warning 1292 Truncated incorrect time value : 2012-04-30

5.2.2.4 Formats standards

Il existe un certain nombre de formats de date et dheure standards, prdfinis, que lon peut
utiliser dans la fonction DATE_FORMAT(). Pour obtenir ces formats, il faut appeler la fonction
GET_FORMAT(type, standard).

Le paramtre type doit tre choisi entre les trois types de donnes : DATE, TIME et DATETIME.

Il existe cinq formats standards :

'EUR'
'USA'
'ISO'
'JIS'
'INTERNAL'

Voici un tableau reprenant les diffrentes possibilits :

Fonction Format Exemple

GET_FORMAT(DATE,'USA') %m.%d.%Y 10.30.1988


GET_FORMAT(DATE,'JIS') %Y-%m-%d 1988-10-30
GET_FORMAT(DATE,'ISO') %Y-%m-%d 1988-10-30
GET_FORMAT(DATE,'EUR') %d.%m.%Y 30.10.1988
GET_FORMAT(DATE,'INTERNAL') %Y%m%d 19881031
GET_FORMAT(DATETIME,'USA') %Y-%m-%d %H.%i.%s 1988-10-30 13.44.33

GET_FORMAT(DATETIME,'JIS') %Y-%m-%d 1988-10-30 13 :44 :33


%H :%i :%s
GET_FORMAT(DATETIME,'ISO') %Y-%m-%d 1988-10-30 13 :44 :33
%H :%i :%s
GET_FORMAT(DATETIME,'EUR') %Y-%m-%d %H.%i.%s 1988-10-30 13.44.33

GET_FORMAT(DATETIME,'INTERNAL') %Y%m%d%H%i%s 19881030134433

GET_FORMAT(TIME,'USA') %h :%i :%s %p 1 :44 :33 PM


GET_FORMAT(TIME,'JIS') %H :%i :%s 13 :44 :33
GET_FORMAT(TIME,'ISO') %H :%i :%s 13 :44 :33
GET_FORMAT(TIME,'EUR') %H.%i.%S 13.44.33
GET_FORMAT(TIME,'INTERNAL') %H%i%s 134433

Exemples :

SELECT DATE_FORMAT(NOW(), GET_FORMAT(DATE, 'EUR')) AS date_eur,


DATE_FORMAT(NOW(), GET_FORMAT(TIME, 'JIS')) AS heure_jis,
DATE_FORMAT(NOW(), GET_FORMAT(DATETIME, 'USA')) AS date_heure_usa;

208
5.2 Formater une donne temporelle

date_eur heure_jis date_heure_usa

29.04.2012 11 :20 :55 2012-04-29 11.20.55

5.2.3 Crer une date partir dune chane de caractres


Voici une dernire fonction ayant trait au format des dates : STR_TO_DATE(date, format).
Cette fonction est lexact contraire de DATE_FORMAT() : elle prend une chane de caractres re-
prsentant une date suivant le format donn, et renvoie la DATETIME correspondante.

Exemples :

SELECT STR_TO_DATE('03/04/2011 09h17', '%d/%m/%Y %Hh%i') AS StrDate,


STR_TO_DATE('15blabla', '%Hblabla') StrTime;

StrDate StrTime

2011-04-03 09 :17 :00 15 :00 :00

Il est bien sr possible dutiliser GET_FORMAT() aussi avec STR_TO_DATE().

SELECT STR_TO_DATE('11.21.2011', GET_FORMAT(DATE, 'USA')) AS date_usa,


STR_TO_DATE('12.34.45', GET_FORMAT(TIME, 'EUR')) AS heure_eur,
STR_TO_DATE('20111027133056', GET_FORMAT(TIMESTAMP, 'INTERNAL')) AS date_heure_i

date_usa heure_eur date_heure_int

2011-11-21 12 :34 :45 2011-10-27 13 :30 :56

5.2.3.1 En rsum

De nombreuses fonctions permettent dextraire un lment prcis partir dune donne


temporelle : HOUR() permet dextraire lheure, YEAR() extrait lanne,
On peut formater une donne temporelle facilement en utilisant DATE_FORMAT() et
TIME_FORMAT() avec des spcificateurs : %M reprsente le nom du mois, %i les
minutes,
Il existe cinq formats de date standard : EUR, ISO, USA, JIS et INTERNAL.
Pour que les noms des jours et des mois soient donns en franais, il faut excuter cette
commande : SET lc_time_names = 'fr_FR' ;
Lorsque lon a une donne temporelle dans un format particulier, on peut la transformer
en DATE, TIME ou DATETIME avec STR_TO_FORMAT(), qui utilise les mmes spcificateurs
que DATE_FORMAT() et TIME_FORMAT().

209
5 Fonctions : manipuler les dates

5.3 Calculs sur les donnes temporelles


Il est frquent de vouloir faire des calculs sur des donnes temporelles. Par exemple, pour calculer
le nombre de jours ou dheures entre deux dates, pour ajouter une certaine dure une donne
en cas de calcul dchance, etc. Pour ce faire, on peut soit se lancer dans des calculs compliqus
en convertissant des jours en heures, des heures en jours, des minutes en secondes ; soit utiliser
les fonctions SQL prvues cet effet. Je vous laisse deviner quelle solution est la meilleure

Nous allons donc voir dans ce chapitre comment :

calculer la diffrence entre deux donnes temporelles ;


ajouter un intervalle de temps une donne temporelle ;
convertir une donne horaire en un nombre de secondes ;
et dautres petites choses bien utiles.

5.3.1 Diffrence entre deux dates/heures


Trois fonctions permettent de calculer le temps coul entre deux donnes temporelles :

DATEDIFF() : qui donne un rsultat en nombre de jours ;


TIMEDIFF() : qui donne un rsultat sous forme de TIME ;
TIMESTAMPDIFF() : qui donne le rsultat dans lunit de temps souhaite (heure, se-
condes, mois,).

5.3.1.0.1 DATEDIFF() DATEDIFF(date1, date2) peut sutiliser avec des donnes de type
DATE ou DATETIME (dans ce dernier cas, seule la partie date est utilise).

Les trois requtes suivantes donnent donc le mme rsultat.

SELECT DATEDIFF('2011-12-25','2011-11-10') AS nb_jours;


SELECT DATEDIFF('2011-12-25 22:12:18','2011-11-10 12:15:41') AS nb_jours;
SELECT DATEDIFF('2011-12-25 22:12:18','2011-11-10') AS nb_jours;

nb_jours

45

5.3.1.0.2 TIMEDIFF() La fonction TIMEDIFF(expr1, expr2) calcule la dure entre expr1


et expr2. Les deux arguments doivent tre de mme type, soit TIME, soit DATETIME.

-- Avec des DATETIME


SELECT '2011-10-08 12:35:45' AS datetime1, '2011-10-07 16:00:25' AS datetime2, TIMEDIFF

-- Avec des TIME


SELECT '12:35:45' AS time1, '00:00:25' AS time2, TIMEDIFF('12:35:45', '00:00:25') as di

datetime1 datetime2 difference

2011-10-08 12 :35 :45 2011-10-07 16 :00 :25 20 :35 :20

210
5.3 Calculs sur les donnes temporelles

time1 time2 difference

12 :35 :45 00 :00 :25 12 :35 :20

5.3.1.0.3 TIMESTAMPDIFF() La fonction TIMESTAMPDIFF() prend, quant elle, un pa-


ramtre supplmentaire : lunit de temps dsire pour le rsultat. Les units autorises com-
prennent : SECOND (secondes), MINUTE (minutes), HOUR (heures), DAY (jours), WEEK (semaines),
MONTH (mois), QUARTER (trimestres) et YEAR (annes).

TIMESTAMPDIFF(unite, date1, date2) sutilise galement avec des donnes de type DATE
ou DATETIME. Si vous demandez un rsultat comprenant une unit infrieure au jour (heure ou
minute par exemple) et que lune de vos donnes est de type DATE, MySQL compltera cette date
avec lheure par dfaut '00:00:00'.

SELECT TIMESTAMPDIFF(DAY, '2011-11-10', '2011-12-25') AS nb_jours,


TIMESTAMPDIFF(HOUR,'2011-11-10', '2011-12-25 22:00:00') AS nb_heures_def,
TIMESTAMPDIFF(HOUR,'2011-11-10 14:00:00', '2011-12-25 22:00:00') AS nb_heures,
TIMESTAMPDIFF(QUARTER,'2011-11-10 14:00:00', '2012-08-25 22:00:00') AS nb_trimes

nb_jours nb_heures_def nb_heures nb_trimestres

45 1102 1088 3

5.3.2 Ajout et retrait dun intervalle de temps

5.3.2.0.1 Intervalle Certaines des fonctions et oprations suivantes utilisent le mot-cl


INTERVAL, permettant de dfinir un intervalle de temps ajouter ou soustraire dune date.

Un intervalle de temps est dfini par une quantit et une unit (3 jours par exemple, 3 tant la
quantit, jour lunit). En MySQL, il existe une vingtaine dunits possibles pour un INTERVAL,
dont une partie est liste dans le tableau ci-dessous.

Unit Format

SECOND

MINUTE

HOUR

DAY

WEEK

211
5 Fonctions : manipuler les dates

Unit Format

MONTH

YEAR

MINUTE_SECOND 'm:S'
HOUR_SECOND 'HH:mm:SS'
HOUR_MINUTE 'HH:mm'
DAY_SECOND 'J HH:mm:SS'
DAY_MINUTE 'J HH:mm'
DAY_HOUR 'J HH'
YEAR_MONTH 'A-M'

Notez que, comme pour DATE, DATETIME et TIME, les signes de ponctuation sparant les diff-
rentes parties dun intervalle ne doivent pas ncessairement tre '-' pour la partie date et ' :'
pour la partie heure. Il ne sagit que de suggestions. Nimporte quel signe de ponctuation (ou
aucun) sera accept.

5.3.2.1 Ajout dun intervalle de temps

Trois fonctions permettent dajouter un intervalle de temps une date (de type DATE ou
DATETIME) :

ADDDATE() : qui sutilise avec un INTERVAL ou un nombre de jours.


DATE_ADD() : qui sutilise avec un INTERVAL.
TIMESTAMPADD() : qui nutilise pas dINTERVAL mais un nombre plus limit dunits de
temps.

5.3.2.1.1 ADDDATE() Cette fonction peut sutiliser de deux manires : soit en prcisant un
intervalle de temps avec le mot-cl INTERVAL, soit en donnant un nombre de jours ajouter la
date.

ADDDATE(date, INTERVAL quantite unite)


ADDDATE(date, nombreJours)

Exemples :

SELECT ADDDATE('2011-05-21', INTERVAL 3 MONTH) AS date_interval,


ADDDATE('2011-05-21 12:15:56', INTERVAL '3 02:10:32' DAY_SECOND) AS datetime_int
ADDDATE('2011-05-21', 12) AS date_nombre_jours,
ADDDATE('2011-05-21 12:15:56', 42) AS datetime_nombre_jours;

datetime_nombre_jours
date_interval datetime_interval date_nombre_jours

2011-08-21 2011-05-24 14 :26 :28 2011-06-02 2011-07-02 12 :15 :56

212
5.3 Calculs sur les donnes temporelles

5.3.2.1.2 DATE_ADD() DATE_ADD(date, INTERVAL quantite unite) sutilise exac-


tement de la mme manire que ADDDATE(date, INTERVAL quantite unite).

Exemples :

SELECT DATE_ADD('2011-05-21', INTERVAL 3 MONTH) AS avec_date,


DATE_ADD('2011-05-21 12:15:56', INTERVAL '3 02:10:32' DAY_SECOND) AS avec_dateti

avec_date avec_datetime

2011-08-21 2011-05-24 14 :26 :28

5.3.2.1.3 Oprateur + Il est galement possible dajouter un intervalle de temps une date
en utilisant simplement loprateur + et un INTERVAL. Lintervalle peut se trouver droite ou
gauche du signe +.

Exemples :

SELECT '2011-05-21' + INTERVAL 5 DAY AS droite, -- Avec DATE et inte


INTERVAL '3 12' DAY_HOUR + '2011-05-21 12:15:56' AS gauche; -- Avec DATETIME et

droite gauche

2011-05-26 2011-05-25 00 :15 :56

5.3.2.1.4 TIMESTAMPADD() TIMESTAMPADD(unite, quantite, date) est un peu


plus restreint que DATE_ADD() et ADDDATE(). En effet, cette fonction nutilise pas dINTERVAL.
Il faut cependant dfinir une unit parmi les suivantes : FRAC_SECOND, SECOND, MINUTE, HOUR,
DAY, WEEK, MONTH, QUARTER, et YEAR.

Exemple :

SELECT TIMESTAMPADD(DAY, 5, '2011-05-21') AS avec_date, -- Avec DATE


TIMESTAMPADD(MINUTE, 34, '2011-05-21 12:15:56') AS avec_datetime; -- Avec DATET

avec_date avec_datetime

2011-05-26 2011-05-21 12 :49 :56

5.3.2.1.5 ADDTIME() La fonction ADDTIME(expr1, expr2) permet dajouter expr2 (de


type TIME) expr1 (de type DATETIME ou TIME). Le rsultat sera du mme type que expr1.

Exemples :

SELECT NOW() AS Maintenant, ADDTIME(NOW(), '01:00:00') AS DansUneHeure,


CURRENT_TIME() AS HeureCourante, ADDTIME(CURRENT_TIME(), '03:20:02') AS PlusTard

Maintenant DansUneHeure HeureCourante PlusTard

2012-04-29 12 :08 :33 2012-04-29 13 :08 :33 12 :08 :33 15 :28 :35

213
5 Fonctions : manipuler les dates

5.3.2.2 Soustraction dun intervalle de temps

5.3.2.2.1 SUBDATE(), DATE_SUB() et SUBTIME() SUBDATE(), DATE_SUB() et


SUBTIME() sont les quivalents de ADDDATE(), DATE_ADD() et ADDTIME() pour la soustraction.
Elles sutilisent exactement de la mme manire.

Exemples :

SELECT SUBDATE('2011-05-21 12:15:56', INTERVAL '3 02:10:32' DAY_SECOND) AS SUBDATE1,


SUBDATE('2011-05-21', 12) AS SUBDATE2,
DATE_SUB('2011-05-21', INTERVAL 3 MONTH) AS DATE_SUB;

SELECT SUBTIME('2011-05-21 12:15:56', '18:35:15') AS SUBTIME1,


SUBTIME('12:15:56', '8:35:15') AS SUBTIME2;

SUBDATE1 SUBDATE2 DATE_SUB

2011-05-18 10 :05 :24 2011-05-09 2011-02-21

SUBTIME1 SUBTIME2

2011-05-20 17 :40 :41 03 :40 :41

5.3.2.2.2 Oprateur - Tout comme loprateur + peut sutiliser pour ajouter un intervalle de
temps, il est possible dutiliser loprateur - pour en soustraire un. Cependant, pour la soustrac-
tion, la date doit imprativement se trouver gauche du signe -, et lintervalle droite. Il nest
en effet pas possible de soustraire une date dun intervalle.

Exemple :

SELECT '2011-05-21' - INTERVAL 5 DAY ;

2011-05-21 - INTERVAL 5 DAY

2011-05-16

5.3.2.2.3 Soustraire, cest ajouter un ngatif Un INTERVAL peut tre dfini avec une
quantit ngative, et ajouter un intervalle ngatif, cest soustraire un intervalle positif. De mme
soustraire un intervalle ngatif revient ajouter un intervalle positif.

Par consquent, dans les requtes suivantes, les deux parties du SELECT sont quivalentes.

SELECT ADDDATE(NOW(), INTERVAL -3 MONTH) AS ajout_negatif, SUBDATE(NOW(), INTERVAL 3 MO


SELECT DATE_ADD(NOW(), INTERVAL 4 HOUR) AS ajout_positif, DATE_SUB(NOW(), INTERVAL -4 H
SELECT NOW() + INTERVAL -15 MINUTE AS ajout_negatif, NOW() - INTERVAL 15 MINUTE AS retr

ajout_negatif retrait_positif

2011-09-01 16 :04 :26 2011-09-01 16 :04 :26

214
5.3 Calculs sur les donnes temporelles

ajout_positif retrait_negatif

2011-12-01 20 :04 :26 2011-12-01 20 :04 :26

ajout_negatif retrait_positif

2011-12-01 15 :49 :28 2011-12-01 15 :49 :28

5.3.3 Divers

5.3.3.1 Crer une date/heure partir dautres informations

5.3.3.1.1 partir dun timestamp Unix La fonction FROM_UNIXTIME(ts) renvoie un


DATETIME partir du timestamp Unix ts.

SELECT FROM_UNIXTIME(1325595287);

FROM_UNIXTIME(1325595287)

2012-01-03 13 :54 :47

Notez que la fonction UNIX_TIMESTAMP(), que nous avons vue lors dun chapitre prcdent et
qui donne le timestamp actuel, peut galement sutiliser avec un DATETIME en paramtre ; dans
ce cas, elle fait linverse de la fonction FROM_UNIXTIME(ts) : elle renvoie le timestamp Unix du
DATETIME pass en paramtre.

SELECT UNIX_TIMESTAMP('2012-01-03 13:54:47');

UNIX_TIMESTAMP(2012-01-03 13 :54 :47)

1325595287

5.3.3.1.2 partir de diffrents lments dune date/heure La fonction MAKEDATE()


cre une DATE partir dune anne et dun numro de jour (1 tant le premier janvier, 32 le premier
fvrier, etc.). Quant la fonction MAKETIME(), elle cre un TIME partir dune heure et dun
nombre de minutes et de secondes.

SELECT MAKEDATE(2012, 60) AS 60eJour2012, MAKETIME(3, 45, 34) AS heureCree;

60eJour2012 heureCree

2012-02-29 03 :45 :34

5.3.3.2 Convertir un TIME en secondes, et vice versa

Il est toujours utile de connatre la fonction SEC_TO_TIME()qui convertit un nombre de secondes


en une donne de type TIME, et son oppos TIME_TO_SEC()qui convertit un TIME en un nombre
de secondes.

215
5 Fonctions : manipuler les dates

Exemples :

SELECT SEC_TO_TIME(102569), TIME_TO_SEC('01:00:30');

SEC_TO_TIME(102569) TIME_TO_SEC(01 :00 :30)

28 :29 :29 3630

[[information]] | Je vous rappelle quil est normal davoir un nombre dheures suprieur 24
dans un TIME, puisque TIME sert stocker une heure ou une dure, et peut par consquent aller
de '-838:59:59' '838:59:59'.

5.3.3.3 Dernier jour du mois

Enfin, voici la dernire fonction que nous verrons dans ce chapitre : LAST_DAY(date). Cette fonc-
tion donne le dernier jour du mois de la date passe en paramtre. Cela permet par exemple de
voir que 2012 est une anne bissextile, contrairement lan 2100.

SELECT LAST_DAY('2012-02-03') AS fevrier2012, LAST_DAY('2100-02-03') AS fevrier2100;

fevrier2012 fevrier2100

2012-02-29 2100-02-28

5.3.3.4 En rsum

DATEDIFF(), TIMEDIFF() et TIMESTAMPDIFF() sont trois fonctions permettant de cal-


culer une diffrence entre deux donnes temporelles.
Un INTERVAL SQL reprsente un intervalle de temps ; il est compos dune quantit et
dune unit de temps (ex. : INTERVAL 3 DAY reprsente un intervalle de trois jours).
ADDDATE() et DATE_ADD() permettent dajouter un intervalle de temps une DATE ou
une DATETIME. ADDTIME() permet dajouter une dure un TIME ou une DATETIME.
SUBDATE() et DATE_SUB() permettent de soustraire un intervalle de temps une DATE
ou une DATETIME. SUBTIME() permet de soustraire une dure un TIME ou une DATETIME.
On peut galement utiliser les oprateurs + et - pour ajouter ou soustraire un intervalle de
temps une donne temporelle.

5.4 Exercices
Nous avons maintenant vu une bonne partie des fonctions MySQL relatives aux donnes tem-
porelles. Noubliez pas de consulter la documentation officielle au besoin, car celle-ci contient
plus de dtails et dautres fonctions. Je vous propose maintenant quelques exercices pour passer
de la thorie la pratique. Cela va vous permettre de retenir dj une partie des fonctions, mais
surtout, cela va replacer ces fonctions dans un vritable contexte puisque nous allons bien sr tra-
vailler sur notre table Animal. Bien entendu, certaines questions ont plusieurs rponses possibles,

216
5.4 Exercices

mais je ne donnerai quune seule solution. Ne soyez donc pas tonns davoir trouv une requte
faisant ce qui est demand mais diffrente de la rponse indique.

5.4.1 Commenons par le format

5.4.1.0.1 1. Slectionner tous les animaux ns en juin. [[secret]] | sql | SELECT id,
date_naissance, nom | FROM Animal | WHERE MONTH(date_naissance) = 6; |

5.4.1.0.2 2. Slectionner tous les animaux ns dans les huit premires semaines
dune anne. [[secret]] | sql | SELECT id, date_naissance, nom | FROM Animal |
WHERE WEEKOFYEAR(date_naissance) < 9; |

5.4.1.0.3 3. Afficher le jour (en chiffres) et le mois de naissance (en toutes lettres)
des tortues et des chats ns avant 2007 (en deux colonnes). [[secret]] | sql | SELECT
DAY(date_naissance), MONTHNAME(date_naissance) | FROM Animal | INNER
JOIN Espece ON Animal.espece_id = Espece.id | WHERE Espece.nom_courant IN
('Chat', 'Tortue d''Hermann') | AND YEAR(date_naissance) < 2007; |

5.4.1.0.4 4. Mme chose qu la question prcdente, mais en une seule


colonne. [[secret]] | sql | SELECT DATE_FORMAT(date_naissance, '%e %M') |
FROM Animal | INNER JOIN Espece ON Animal.espece_id = Espece.id | WHERE
Espece.nom_courant IN ('Chat', 'Tortue d''Hermann') | AND YEAR(date_naissance)
< 2007; |

5.4.1.0.5 5. Slectionner tous les animaux ns en avril, mais pas un 24 avril, tris
par heure de naissance dcroissante (heure dans le sens commun du terme, donc
heure, minutes, secondes) et afficher leur date de naissance suivant le mme format
que lexemple ci-dessous. Format : 8 janvier, 6h30PM, en lan 2010 aprs J.C.
[[secret]] |
sql | SELECT DATE_FORMAT(date_naissance, '%e %M, %lh%i%p, en
l''an %Y aprs J.C.') AS jolie_date | FROM Animal | WHERE MONTH(date_naissance)
= 4 | AND DAY(date_naissance) <> 24 | ORDER BY TIME(date_naissance) DESC;
|

5.4.2 Passons aux calculs

5.4.2.0.1 1. Moka tait cens natre le 27 fvrier 2008. Calculer le nombre de


jours de retard de sa naissance. [[secret]] | sql | SELECT DATEDIFF(date_naissance,
'2008-02-27') AS retard | FROM Animal | WHERE nom = 'Moka' ; |

5.4.2.0.2 2. Afficher la date laquelle chaque perroquet (espece_id = 4) f-


tera son 25e anniversaire. [[secret]] | sql | SELECT DATE(ADDDATE(date_naissance,
INTERVAL 25 YEAR)) AS Anniversaire | FROM Animal | WHERE espece_id = 4;
| | | | On ne demandait que la date (on fte rarement son anniversaire lheure pile de sa
naissance), do lutilisation de la fonction DATE().

217
5 Fonctions : manipuler les dates

5.4.2.0.3 3. Slectionner les animaux ns dans un mois contenant exactement 29


jours. [[secret]] | sql | SELECT id, date_naissance, nom | FROM Animal | WHERE
DAY(LAST_DAY(date_naissance)) = 29; |

5.4.2.0.4 4. Aprs douze semaines, un chaton est sevr (sauf exception bien sr).
Afficher la date partir de laquelle les chats (espece_id = 2) de llevage peuvent
tre adopts (quil sagisse dune date dans le pass ou dans le futur). [[secret]]
| sql | SELECT id, nom, DATE(DATE_ADD(date_naissance, INTERVAL 12 WEEK)) AS
sevrage | FROM Animal | WHERE espece_id = 2; |

5.4.2.0.5 5. Rouquine, Zira, Bouli et Balou (id 13, 18, 20 et 22 respective-


ment) font partie de la mme porte. Calculer combien de temps, en minutes,
Balou est n avant Zira. [[secret]] | sql | SELECT TIMESTAMPDIFF(MINUTE, |
(SELECT date_naissance | FROM Animal | WHERE
nom = 'Balou'), | (SELECT date_naissance | FROM
Animal | WHERE nom = 'Zira')) | AS nb_minutes; |
| | | Il fallait ici penser aux sous-requtes, afin dobtenir les dates de naissance de Balou et Zira
pour les utiliser dans la mme fonction TIMESTAMPDIFF().

5.4.3 Et pour finir, mlangeons le tout


Et quand on dit tout, cest tout ! Par consquent, il est possible (et mme fort probable) que vous
ayez besoin de notions et fonctions vues dans les chapitres prcdents (regroupements, sous-
requtes, etc.) pour rsoudre ces exercices.

5.4.3.0.1 1. Rouquine, Zira, Bouli et Balou (id 13, 18, 20 et 22 respective-


ment) font partie de la mme porte. Calculer combien de temps, en minutes,
sest coul entre le premier n et le dernier n de la porte. [[secret]] | sql
| SELECT TIMESTAMPDIFF(MINUTE, | ( | SELECT
MIN(date_naissance) | FROM Animal | WHERE id IN (13,
18, 20, 22) | ), | ( | SELECT MAX(date_naissance)
| FROM Animal | WHERE id IN (13, 18, 20, 22) | )
| ) AS nb_minutes; | | | | Presque le mme exercice quau-dessus, ceci prs
quil fallait utiliser les fonctions dagrgation.

5.4.3.0.2 2. Calculer combien danimaux sont ns durant un mois pendant lequel les
moules sont les plus consommables (cest--dire les mois finissant en bre). [[se-
cret]] | sql | SELECT COUNT(*) | FROM Animal | WHERE MONTHNAME(date_naissance)
LIKE '%bre' ; | | | | Il faut bien sr avoir pralablement dfini que le nom des mois et des
jours doit tre exprim en franais. Je rappelle la requte utiliser : SET lc_time_names =
'fr_FR' ;

5.4.3.0.3 3. Pour les chiens et les chats (espece_id = 1 et espece_id = 2 respec-


tivement), afficher les diffrentes dates de naissance des portes dau moins deux
individus (format JJ/MM/AAAA), ainsi que le nombre dindividus pour chacune
de ces portes. Attention, il nest pas impossible quune porte de chats soit

218
5.4 Exercices

ne le mme jour quune porte de chiens (il nest pas non plus impossible que
deux portes de chiens naissent le mme jour, mais on considre que ce nest
pas le cas). [[secret]] | sql | SELECT DATE_FORMAT(date_naissance, '%d/%m/%Y'),
COUNT(*) as nb_individus | FROM Animal | WHERE espece_id IN (1, 2) | GROUP
BY DATE(date_naissance), espece_id | HAVING nb_individus > 1; | | |
| Il faut regrouper sur la date (et uniquement la date, pas lheure) puis sur lespce pour viter de
mlanger chiens et chats. Une simple clause HAVING permet ensuite de slectionner les portes
de deux individus ou plus.

5.4.3.0.4 4. Calculer combien de chiens (espece_id = 1) sont ns en moyenne


chaque anne entre 2006 et 2010 (sachant quon a eu au moins une naissance
chaque anne). [[secret]] | sql | SELECT AVG(nb) | FROM ( | SELECT COUNT(*)
AS nb | FROM Animal | WHERE espece_id = 1 | AND YEAR(date_naissance)
>= 2006 | AND YEAR(date_naissance) <= 2010 | GROUP BY YEAR(date_naissance)
| ) AS tableIntermedaire; | | | | Ici, il fallait penser faire une sous-requte dans la
clause FROM. Si vous navez pas trouv, rappelez-vous quil faut penser par tapes. Vous voulez la
moyenne du nombre de chiens ns chaque anne ? Commencez par obtenir le nombre de chiens
ns chaque anne, puis seulement demandez-vous comment faire la moyenne.

5.4.3.0.5 5. Afficher la date au format ISO du 5e anniversaire des animaux dont on


connat soit le pre, soit la mre. [[secret]] | sql | SELECT DATE_FORMAT(DATE_ADD(date_naissance
INTERVAL 5 YEAR), GET_FORMAT(DATE, 'ISO')) AS dateIso | FROM Animal | WHERE
pere_id IS NOT NULL | OR mere_id IS NOT NULL; | | | | Pour cette dernire question, il
fallait juste imbriquer plusieurs fonctions diffrentes. nouveau, si vous navez pas russi, cest
sans doute parce que vous ne dcomposez pas le problme correctement.

partir de maintenant, si jen vois encore un stocker ses dates sous forme de chane de caractres,
ou sous forme de INT pour stocker un timestamp Unix, je le mords ! Avec cette partie sur les dates
sachvent les parties basiques de ce tutoriel. Vous devriez maintenant avoir les connaissances
suffisantes pour grer la base de donnes dun petit site web ou dune application toute simple,
sans toutefois exploiter vraiment les possibilits de MySQL. Les parties suivantes aborderont des
notions un peu plus avances.

219
6 Scuriser et automatiser ses actions
Dans cette partie, nous allons aborder trois notions qui permettent de scuriser une base de don-
nes :

les transactions ;
les verrous ;
et les requtes prpares.

Nous passerons ensuite aux procdures stockes et aux triggers (ou dclencheurs) qui permettent
dautomatiser des actions complexes.

Toutes ces notions ne sont pas indpendantes les unes des autres. Par exemple, automatiser un
traitement peut permettre de scuriser une application.

6.1 Transactions
Pour commencer cette partie, nous allons voir ce que sont les transactions, quoi elles servent
exactement, et comment les utiliser avec MySQL.

Les transactions sont une fonctionnalit absolument indispensable, permettant de scuriser une
application utilisant une base de donnes. Sans transactions, certaines oprations risqueraient
dtre moiti ralises, et la moindre erreur, la moindre interruption pourrait avoir des cons-
quences normes. En effet, les transactions permettent de regrouper des requtes dans des blocs,
et de faire en sorte que tout le bloc soit excut en une seule fois, cela afin de prserver lintgrit
des donnes de la base.

Les transactions ont t implmentes assez tard dans MySQL, et qui plus est, elles ne sont pas
utilisables pour tous les types de tables. Cest dailleurs un des principaux arguments des dtrac-
teurs de MySQL.

6.1.0.1 Etat actuel de la base de donnes

Note : les tables de test ne sont pas reprises.

[[secret]] | sql | SET NAMES utf8; |


| | DROP TABLE IF EXISTS Animal; | DROP
TABLE IF EXISTS Race; | DROP TABLE IF EXISTS Espece; | | | CREATE TABLE
Espece ( | id smallint(6) unsigned NOT NULL AUTO_INCREMENT, | nom_courant
varchar(40) NOT NULL, | nom_latin varchar(40) NOT NULL, | description
text, | prix decimal(7,2) unsigned DEFAULT NULL, | PRIMARY KEY (id),
| UNIQUE KEY nom_latin (nom_latin) | ) ENGINE=InnoDB AUTO_INCREMENT=6
DEFAULT CHARSET=latin1; | | LOCK TABLES Espece WRITE; | INSERT INTO Espece
VALUES (1,'Chien','Canis canis','Bestiole quatre pattes qui aime les
caresses et tire souvent la langue',200.00),(2,'Chat','Felis silvestris','Bestiole
quatre pattes qui saute trs haut et grimpe aux arbres',150.00),(3,'Tortue

221
6 Scuriser et automatiser ses actions

d''Hermann','Testudo hermanni','Bestiole avec une carapace trs dure',140.00),


| (4,'Perroquet amazone','Alipiopsitta xanthops','Joli oiseau parleur vert
et jaune',700.00),(5,'Rat brun','Rattus norvegicus','Petite bestiole avec
de longues moustaches et une longue queue sans poils',10.00); | UNLOCK
TABLES; | | | CREATE TABLE Race ( | id smallint(6) unsigned NOT NULL
AUTO_INCREMENT, | nom varchar(40) NOT NULL, | espece_id smallint(6)
unsigned NOT NULL, | description text, | prix decimal(7,2) unsigned
DEFAULT NULL, | PRIMARY KEY (id) | ) ENGINE=InnoDB AUTO_INCREMENT=10
DEFAULT CHARSET=latin1; | | LOCK TABLES Race WRITE; | INSERT INTO Race
VALUES (1,'Berger allemand',1,'Chien sportif et lgant au pelage dense,
noir-marron-fauve, noir ou gris.',485.00),(2,'Berger blanc suisse',1,'Petit
chien au corps compact, avec des pattes courtes mais bien proportionnes
et au pelage tricolore ou bicolore.',935.00),(3,'Singapura',2,'Chat de
petite taille aux grands yeux en amandes.',985.00), | (4,'Bleu russe',2,'Chat
aux yeux verts et la robe paisse et argente.',835.00),(5,'Maine coon',2,'Chat
de grande taille, poils mi-longs.',735.00),(7,'Sphynx',2,'Chat sans poils.',1235.00),
| (8,'Nebelung',2,'Chat bleu russe, mais avec des poils longs...',985.00),(9,'Rottweill
d''apparence solide, bien muscl, la robe noire avec des taches feu bien
dlimites.',600.00); | UNLOCK TABLES; | | | CREATE TABLE Animal ( |
id smallint(6) unsigned NOT NULL AUTO_INCREMENT, | sexe char(1) DEFAULT
NULL, | date_naissance datetime NOT NULL, | nom varchar(30) DEFAULT
NULL, | commentaires text, | espece_id smallint(6) unsigned NOT NULL,
| race_id smallint(6) unsigned DEFAULT NULL, | mere_id smallint(6)
unsigned DEFAULT NULL, | pere_id smallint(6) unsigned DEFAULT NULL, |
PRIMARY KEY (id), | UNIQUE KEY ind_uni_nom_espece_id (nom,espece_id) |
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8; | | LOCK TABLES
Animal WRITE; | INSERT INTO Animal VALUES (1,'M','2010-04-05 13:43:00','Rox','Mordille
beaucoup',1,1,18,22),(2,NULL,'2010-03-24 02:23:00','Roucky',NULL,2,NULL,40,30),(3,'F','
15:02:00','Schtroumpfette',NULL,2,4,41,31), | (4,'F','2009-08-03 05:12:00',NULL,'Bestio
avec une carapace trs dure',3,NULL,NULL,NULL),(5,NULL,'2010-10-03 16:44:00','Choupi','
sans oreille gauche',2,NULL,NULL,NULL),(6,'F','2009-06-13 08:17:00','Bobosse','Carapace
bizarre',3,NULL,NULL,NULL), | (7,'F','2008-12-06 05:18:00','Caroline',NULL,1,2,NULL,NUL
15:38:00','Bagherra',NULL,2,5,NULL,NULL),(9,NULL,'2010-08-23 05:18:00',NULL,'Bestiole
avec une carapace trs dure',3,NULL,NULL,NULL), | (10,'M','2010-07-21 15:41:00','Bobo',
15:45:00','Canaille',NULL,1,NULL,NULL,NULL),(12,'F','2009-05-26 08:54:00','Cali',NULL,1
| (13,'F','2007-04-24 12:54:00','Rouquine',NULL,1,1,NULL,NULL),(14,'F','2009-05-26
08:56:00','Fila',NULL,1,2,NULL,NULL),(15,'F','2008-02-20 15:47:00','Anya',NULL,1,NULL,N
| (16,'F','2009-05-26 08:50:00','Louya',NULL,1,NULL,NULL,NULL),(17,'F','2008-03-10
13:45:00','Welva',NULL,1,NULL,NULL,NULL),(18,'F','2007-04-24 12:59:00','Zira',NULL,1,1,
| (19,'F','2009-05-26 09:02:00','Java',NULL,1,2,NULL,NULL),(20,'M','2007-04-24
12:45:00','Balou',NULL,1,1,NULL,NULL),(21,'F','2008-03-10 13:43:00','Pataude',NULL,1,NU
| (22,'M','2007-04-24 12:42:00','Bouli',NULL,1,1,NULL,NULL),(24,'M','2007-04-12
05:23:00','Cartouche',NULL,1,NULL,NULL,NULL),(25,'M','2006-05-14 15:50:00','Zambo',NULL
| (26,'M','2006-05-14 15:48:00','Samba',NULL,1,1,NULL,NULL),(27,'M','2008-03-10
13:40:00','Moka',NULL,1,NULL,NULL,NULL),(28,'M','2006-05-14 15:40:00','Pilou',NULL,1,1,
| (29,'M','2009-05-14 06:30:00','Fiero',NULL,2,3,NULL,NULL),(30,'M','2007-03-12
12:05:00','Zonko',NULL,2,5,NULL,NULL),(31,'M','2008-02-20 15:45:00','Filou',NULL,2,4,NU
| (32,'M','2009-07-26 11:52:00','Spoutnik',NULL,3,NULL,52,NULL),(33,'M','2006-05-19

222
6.1 Transactions

16:17:00','Caribou',NULL,2,4,NULL,NULL),(34,'M','2008-04-20 03:22:00','Capou',NULL,2,5,
| (35,'M','2006-05-19 16:56:00','Raccou','Pas de queue depuis la naissance',2,4,NULL,NU
06:42:00','Boucan',NULL,2,3,NULL,NULL),(37,'F','2006-05-19 16:06:00','Callune',NULL,2,8
| (38,'F','2009-05-14 06:45:00','Boule',NULL,2,3,NULL,NULL),(39,'F','2008-04-20
03:26:00','Zara',NULL,2,5,NULL,NULL),(40,'F','2007-03-12 12:00:00','Milla',NULL,2,5,NUL
| (41,'F','2006-05-19 15:59:00','Feta',NULL,2,4,NULL,NULL),(42,'F','2008-04-20
03:20:00','Bilba','Sourde de l''oreille droite 80%',2,5,NULL,NULL),(43,'F','2007-03-1
11:54:00','Cracotte',NULL,2,5,NULL,NULL), | (44,'F','2006-05-19 16:16:00','Cawette',NUL
18:17:00','Nikki','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),(46,'F','200
08:23:00','Tortilla','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),
| (47,'F','2009-03-26 01:24:00','Scroupy','Bestiole avec une carapace trs
dure',3,NULL,NULL,NULL),(48,'F','2006-03-15 14:56:00','Lulla','Bestiole
avec une carapace trs dure',3,NULL,NULL,NULL),(49,'F','2008-03-15 12:02:00','Dana','Be
avec une carapace trs dure',3,NULL,NULL,NULL), | (50,'F','2009-05-25 19:57:00','Cheli'
avec une carapace trs dure',3,NULL,NULL,NULL),(51,'F','2007-04-01 03:54:00','Chicaca',
avec une carapace trs dure',3,NULL,NULL,NULL),(52,'F','2006-03-15 14:26:00','Redbul','
| (54,'M','2008-03-16 08:20:00','Bubulle','Bestiole avec une carapace trs
dure',3,NULL,NULL,NULL),(55,'M','2008-03-15 18:45:00','Relou','Surpoids',3,NULL,NULL,NU
18:54:00','Bulbizard','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL),
| (57,'M','2007-03-04 19:36:00','Safran','Coco veut un gteau !',4,NULL,NULL,NULL),(58,
02:50:00','Gingko','Coco veut un gteau !',4,NULL,NULL,NULL),(59,'M','2009-03-26
08:28:00','Bavard','Coco veut un gteau !',4,NULL,NULL,NULL), | (60,'F','2009-03-26
07:55:00','Parlotte','Coco veut un gteau !',4,NULL,NULL,NULL),(61,'M','2010-11-09
00:00:00','Yoda',NULL,2,5,NULL,NULL),(62,'M','2010-11-05 00:00:00','Pipo',NULL,1,9,NULL
| UNLOCK TABLES; | | | ALTER TABLE Race ADD CONSTRAINT fk_race_espece_id
FOREIGN KEY (espece_id) REFERENCES Espece (id) ON DELETE CASCADE; | |
ALTER TABLE Animal ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES
Race (id) ON DELETE SET NULL; | ALTER TABLE Animal ADD CONSTRAINT fk_espece_id
FOREIGN KEY (espece_id) REFERENCES Espece (id); | ALTER TABLE Animal ADD
CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal (id) ON DELETE
SET NULL; | ALTER TABLE Animal ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id)
REFERENCES Animal (id) ON DELETE SET NULL; |

6.1.1 Principe

Une transaction, cest un ensemble de requtes qui sont excutes en un seul bloc. Ainsi, si
une des requtes du bloc choue, on peut dcider dannuler tout le bloc de requtes (ou de quand
mme valider les requtes qui ont russi).

[[question]] | quoi a sert ?

Imaginez que Monsieur Durant fasse un virement de 300 euros Monsieur Dupont via sa banque
en ligne. Il remplit toutes les petites cases du virement, puis valide. Lapplication de la banque
commence traiter le virement quand soudain, une violente panne de courant provoque larrt
des serveurs de la banque.

Deux jours plus tard, Monsieur Durant reoit un coup de fil de Monsieur Dupont, trs nerv, qui
lui demande pourquoi le paiement convenu na toujours pas t fait. Intrigu, Monsieur Durant
va vrifier son compte, et constate quil a bien t dbit de 300 euros.

223
6 Scuriser et automatiser ses actions

[[question]] | Mais que sest-il donc pass ?

Normalement, le traitement dun virement est plutt simple, deux tapes suffisent :

tape 1 : on retire le montant du virement du compte du donneur dordre ;


tape 2 : on ajoute le montant du virement au compte du bnficiaire.

Seulement voil, pas de chance pour Monsieur Durant, la panne de courant qui a teint les ser-
veurs est survenue pile entre ltape 1 et ltape 2. Du coup, son compte a t dbit, mais le
compte de Monsieur Dupont na jamais t crdit.

La banque de Monsieur Durant nutilisait pas les transactions. Si ctait le cas, la seconde requte
du traitement nayant jamais t excute, la premire requte naurait jamais t valide.

6.1.1.0.1 Comment se droule une transaction ? Voici un schma qui devrait vous clai-
rer sur le principe des transactions.

On dmarre une transaction.


On excute les requtes dsires une une.
Si une des requtes choue, on annule toutes les requtes, et on termine la transaction.
Par contre, si la fin des requtes, tout sest bien pass, on valide tous les changements,
et on termine la transaction.
Si le traitement est interrompu (entre deux requtes par exemple), les changements ne
sont jamais valids, et donc les donnes de la base restent les mmes quavant la transac-
tion.

6.1.1.1 Support des transactions

Il nest pas possible dutiliser les transactions sur nimporte quelle table. Pour les supporter, une
table doit tre transactionnelle, ce qui, avec MySQL, est dfini par le moteur de stockage utilis
pour la table.

Rappelez-vous, nous avons vu dans le chapitre sur la cration des tables quil existait diffrents
moteurs de stockage possibles avec MySQL, dont les plus connus sont MyISAM et InnoDB.

MyISAM ne supportant pas les contraintes de cls trangres, nos tables ont t cres avec le
moteur InnoDB, ce qui tombe plutt bien pour la suite de ce chapitre. En effet :

les tables MyISAM sont non-transactionnelles, donc ne supportent pas les transactions ;
les tables InnoDB sont transactionnelles, donc supportent les transactions.

6.1.2 Syntaxe et utilisation


6.1.2.0.1 Vocabulaire Lorsque lon valide les requtes dune transaction, on dit aussi que lon
commite les changements. linverse, lannulation des requtes sappelle un rollback.

6.1.2.0.2 Comportement par dfaut Vous laurez compris, par dfaut MySQL ne travaille
pas avec les transactions. Chaque requte effectue est directement commite (valide). On
ne peut pas revenir en arrire. On peut donc en fait considrer que chaque requte constitue
une transaction, qui est automatiquement commite. Par dfaut, MySQL est donc en mode
autocommit.

Pour quitter ce mode, il suffit de lancer la requte suivante :

224
6.1 Transactions

Figure 6.1 Schma dune transaction

225
6 Scuriser et automatiser ses actions

SET autocommit=0 ;

Une fois que vous ntes plus en autocommit, chaque modification de donne devra tre com-
mite pour prendre effet. Tant que vos modifications ne sont pas valides, vous pouvez tout
moment les annuler (faire un rollback).

6.1.2.1 Valider/annuler les changements

Les commandes pour commiter et faire un rollback sont relativement faciles retenir :

COMMIT ; -- pour valider les requtes


ROLLBACK ; -- pour annuler les requtes

6.1.2.1.1 Exemples de transactions en mode non-autocommit Si ce nest pas dj fait,


changez le mode par dfaut de MySQL grce la commande que nous venons de voir.

Premire exprience : annulation des requtes.

Excutez ces quelques requtes :

INSERT INTO Animal (nom, espece_id, date_naissance, sexe)


VALUES ('Baba', 5, '2012-02-13 15:45:00', 'F');
INSERT INTO Animal (nom, espece_id, date_naissance, sexe)
VALUES ('Bibo', 5, '2012-02-13 15:48:00', 'M');
INSERT INTO Animal (nom, espece_id, date_naissance, sexe)
VALUES ('Buba', 5, '2012-02-13 18:32:00', 'F'); -- Insertion de 3 rats bruns

UPDATE Espece
SET prix = 20
WHERE id = 5 ; -- Les rats bruns cotent maintenant 20 euros au lieu de 10

Faites maintenant un SELECT sur les tables Espece et Animal.

SELECT *
FROM Animal
WHERE espece_id = 5 ;

SELECT *
FROM Espece
WHERE id = 5 ;

Les changements faits sont bien visibles. Les rats bruns valent maintenant 20 euros, et nos trois
nouvelles bestioles ont bien t insres. Cependant, un simple rollback va annuler ces change-
ments.

ROLLBACK ;

Nos rats cotent nouveau 10 euros et Baba, Bibo et Buba ont disparu.

Deuxime exprience : Interruption de la transaction.

Excutez nouveau les trois requtes INSERT et la requte UPDATE. Ensuite, quittez votre client
MySQL (fermez simplement la fentre, ou tapez quit ou exit).

226
6.1 Transactions

Reconnectez-vous et vrifiez vos donnes : les rats valent 10 euros, et Baba, Bibo et Buba
nexistent pas. Les changements nont pas t commits, cest comme sil ne stait rien pass !

[[attention]] | Le mode autocommit est de nouveau activ ! Le fait de faire SET autocommit =
0; nest valable que pour la session courante. Or, en ouvrant une nouvelle connexion, vous avez
cr une nouvelle session. Dsactivez donc nouveau ce mode.

Troisime exprience : validation et annulation.

Excutez la squence de requtes suivante :

INSERT INTO Animal (nom, espece_id, date_naissance, sexe)


VALUES ('Baba', 5, '2012-02-13 15:45:00', 'F');
INSERT INTO Animal (nom, espece_id, date_naissance, sexe)
VALUES ('Bibo', 5, '2012-02-13 15:48:00', 'M');
INSERT INTO Animal (nom, espece_id, date_naissance, sexe)
VALUES ('Buba', 5, '2012-02-13 18:32:00', 'F'); -- Insertion de 3 rats bruns

COMMIT ;

UPDATE Espece
SET prix = 20
WHERE id = 5 ; -- Les rats valent 20 euros

ROLLBACK ;

Si vous navez pas oubli de ractiver le mode non-autocommit, vous avez maintenant trois nou-
veaux rats bruns (les requtes dinsertion ayant t valides), et ils ne valent toujours que 10 euros
chacun (la modification de lespce ayant t annule).

Quatrime exprience : visibilit des changements non-commits.

Excutez la requte suivante :

UPDATE Animal
SET commentaires = 'Queue coupe'
WHERE nom = 'Bibo' AND espece_id = 5 ;

Ensuite, tout en laissant ce client MySQL ouvert, ouvrez-en un deuxime. Connectez-vous


comme dhabitude la base de donnes elevage. Vous avez maintenant deux sessions ouvertes,
connectes votre base de donnes. Slectionnez les rats bruns.

SELECT id, sexe, nom, commentaires, espece_id, race_id


FROM Animal
WHERE espece_id = 5 ;

commentaires
id sexe nom espece_id race_id

69 F Baba NULL 5 NULL

70 M Bibo NULL 5 NULL

71 F Buba NULL 5 NULL

227
6 Scuriser et automatiser ses actions

Les commentaires de Bibo sont toujours vides. Les changements non-commits ne sont donc pas
visibles lextrieur de la transaction qui les a faits. En particulier, une autre session na pas accs
ces changements.

Annulez la modification de Bibo dans la premire session avec un ROLLBACK. Vous pouvez fermer
la seconde session.

6.1.2.2 Dmarrer explicitement une transaction

En dsactivant le mode autocommit, en ralit, on dmarre une transaction. Et chaque fois que
lon fait un rollback ou un commit (ce qui met fin la transaction), une nouvelle transaction est
cre automatiquement, et ce tant que la session est ouverte.

Il est galement possible de dmarrer explicitement une transaction, auquel cas on peut laisser
le mode autocommit activ, et dcider au cas par cas des requtes qui doivent tre faites dans une
transaction.

Repassons donc en mode autocommit :

SET autocommit=1 ;

Pour dmarrer une transaction, il suffit de lancer la commande suivante :

START TRANSACTION ;

[[information]] | Avec MySQL, il est galement possible de dmarrer une transaction avec BEGIN
ou BEGIN WORK. Cependant, il est conseill dutiliser plutt START TRANSACTION, car il sagit
de la commande SQL standard.

Une fois la transaction ouverte, les requtes devront tre valides pour prendre effet. Attention
au fait quun COMMIT ou un ROLLBACK met fin automatiquement la transaction, donc les com-
mandes suivantes seront nouveau commites automatiquement si une nouvelle transaction
nest pas ouverte.

6.1.2.2.1 Exemples de transactions en mode autocommit


-- Insertion d'un nouveau rat brun, plus vieux
INSERT INTO Animal (nom, espece_id, date_naissance, sexe)
VALUES ('Momy', 5, '2008-02-01 02:25:00', 'F');

-- Ouverture d'une transaction


START TRANSACTION ;

-- La nouvelle rate est la mre de Buba et Baba


UPDATE Animal
SET mere_id = LAST_INSERT_ID()
WHERE espece_id = 5
AND nom IN ('Baba', 'Buba');

-- On annule les requtes de la transaction, ce qui termine celle-ci


ROLLBACK ;

228
6.1 Transactions

-- La nouvelle rate est la mre de Bibo


UPDATE Animal
SET mere_id = LAST_INSERT_ID()
WHERE espece_id = 5
AND nom = 'Bibo' ;

-- Nouvelle transaction
START TRANSACTION ;

-- Suppression de Buba
DELETE FROM Animal
WHERE espece_id = 5
AND nom = 'Buba' ;

-- On valide les requtes de la transaction, ce qui termine celle-ci


COMMIT ;

Si vous avez bien suivi, vous devriez savoir les changements qui ont t faits.

[[secret]] | - On a insr Momy (insertion hors transaction) | - Momy nest pas la mre de Baba
(modification dans une transaction dont les requtes ont t annules) | - Momy est la mre de
Bibo (modification hors transaction) | - Buba a t supprime (suppression dans une transaction
dont les requtes ont t commites) | | | | sql | SELECT id, nom, espece_id, mere_id
| FROM Animal | WHERE espece_id = 5; | | | |

id nom espece_id mere_id

69 Baba 5 NULL
70 Bibo 5 72
72 Momy 5 NULL

6.1.2.3 Jalon de transaction

Lorsque lon travaille dans une transaction, et que lon constate que certaines requtes posent
problme, on na pas toujours envie de faire un rollback depuis le dbut de la transaction, an-
nulant toutes les requtes alors quune partie aurait pu tre valide. Il nest pas possible de d-
marrer une transaction lintrieur dune transaction. Par contre, on peut poser des jalons de
transaction. Il sagit de points de repre, qui permettent dannuler toutes les requtes excutes
depuis ce jalon, et non toutes les requtes de la transaction.

6.1.2.3.1 Syntaxe Trois nouvelles commandes suffisent pour pouvoir utiliser pleinement les
jalons :

SAVEPOINT nom_jalon; -- Cre un jalon avec comme nom "nom_jalon"

ROLLBACK [WORK] TO [SAVEPOINT] nom_jalon; -- Annule les requtes excutes depuis le ja

RELEASE SAVEPOINT nom_jalon; -- Retire le jalon "nom_jalon" (sans annuler, ni valider l

229
6 Scuriser et automatiser ses actions

Exemple : excutez les requtes suivantes.

START TRANSACTION ;

INSERT INTO Animal (nom, espece_id, date_naissance, sexe)


VALUES ('Popi', 5, '2007-03-11 12:45:00', 'M');

SAVEPOINT jalon1;

INSERT INTO Animal (nom, espece_id, date_naissance, sexe)


VALUES ('Momo', 5, '2007-03-12 05:23:00', 'M');

ROLLBACK TO SAVEPOINT jalon1;

INSERT INTO Animal (nom, espece_id, date_naissance, sexe)


VALUES ('Mimi', 5, '2007-03-12 22:03:00', 'F');

COMMIT ;

On nutilise quune seule transaction, on valide la fin, et pourtant la seconde insertion na pas
t faite au final, puisquelle a t annule grce au jalon. Seuls Popi et Mimi existent.

SELECT id, sexe, date_naissance, nom, espece_id, mere_id, pere_id


FROM Animal
WHERE espece_id = 5 ;

mere_id
id sexe date_naissance nom espece_id pere_id

69 F 2012-02-13 Baba 5 NULL NULL


15 :45 :00
70 M 2012-02-13 Bibo 5 72 NULL
15 :48 :00
72 F 2008-02-01 Momy 5 NULL NULL
02 :25 :00
73 M 2007-03-11 Popi 5 NULL NULL
12 :45 :00
75 F 2007-03-12 Mimi 5 NULL NULL
22 :03 :00

6.1.3 Validation implicite et commandes non-annulables

Vous savez dj que pour terminer une transaction, il faut utiliser les commandes COMMIT ou
ROLLBACK, selon que lon veut valider les requtes ou les annuler.

a, cest la manire classique et recommande. Mais il faut savoir quun certain nombre dautres
commandes auront aussi pour effet de clturer une transaction. Et pas seulement la clturer,
mais galement valider toutes les requtes qui ont t faites dans cette transaction. Exactement
comme si vous utilisiez COMMIT.

230
6.1 Transactions

Par ailleurs, ces commandes ne peuvent pas tre annules par un ROLLBACK.

6.1.3.0.1 Commandes DDL Toutes les commandes qui crent, modifient, suppriment des
objets dans la base de donnes valident implicitement les transactions.

[[information]] | Ces commandes forment ce quon appelle les requtes DDL, pour Data Denition
Langage.

Cela comprend donc :

la cration et suppression de bases de donnes : CREATE DATABASE, DROP DATABASE ;


la cration, modification, suppression de tables : CREATE TABLE, ALTER TABLE, RENAME
TABLE, DROP TABLE ;
la cration, modification, suppression dindex : CREATE INDEX, DROP INDEX ;
la cration dobjets comme les procdures stockes, les vues, etc., dont nous parlerons plus
tard.

De manire gnrale, tout ce qui influe sur la structure de la base de donnes, et non sur les
donnes elles-mmes.

6.1.3.0.2 Utilisateurs La cration, la modification et la suppression dutilisateurs (voir partie


7) provoquent aussi une validation implicite.

6.1.3.0.3 Transactions et verrous Je vous ai signal quil ntait pas possible dimbriquer
des transactions, donc davoir une transaction lintrieur dune transaction. En fait, la com-
mande START TRANSACTION provoque galement une validation implicite si elle est excute
lintrieur dune transaction. Le fait dactiver le mode autocommit (sil ntait pas dj activ) a le
mme effet.

La cration et suppression de verrous de table clturent aussi une transaction en la validant im-
plicitement (voir chapitre suivant).

6.1.3.0.4 Chargements de donnes Enfin, le chargement de donnes avec LOAD DATA pro-
voque galement une validation implicite.

*[DDL] : Data Definition Langage

6.1.4 ACID

Derrire ce titre mystrieux se cache un concept trs important !

[[question]] | Quels sont les critres quun systme utilisant les transactions doit respecter pour
tre fiable ?

Il a t dfini que ces critres sont au nombre de quatre : Atomicit, Cohrence, Isolation et
Durabilit. Soit, si on prend la premire lettre de chaque critre : ACID. Voyons donc en dtail
ces quatre critres.

231
6 Scuriser et automatiser ses actions

6.1.4.1 A pour Atomicit

Atome signifie tymologiquement qui ne peut tre divis. Une transaction doit tre atomique,
cest--dire quelle doit former une entit complte et indivisible. Chaque lment de la tran-
saction, chaque requte effectue, ne peut exister que dans la transaction.

Si lon reprend lexemple du virement bancaire, en utilisant les transactions, les deux tapes (d-
bit du compte donneur dordre, crdit du compte bnficiaire) ne peuvent exister indpendam-
ment lune de lautre. Si lune est excute, lautre doit ltre galement. Il sagit dun tout.

[[question]] | Peut-on dire que nos transactions sont atomiques ?

Oui. Si une transaction en cours est interrompue, aucune des requtes excutes ne sera valide.
De mme, en cas derreur, il suffit de faire un ROLLBACK pour annuler toute la transaction. Et si
tout se passe bien, un COMMIT validera lintgralit de la transaction en une fois.

6.1.4.2 C pour cohrence

Les donnes doivent rester cohrentes dans tous les cas : que la transaction se termine sans en-
combre, quune erreur survienne, ou que la transaction soit interrompue. Un virement dont seule
ltape de dbit du donneur dordre est excute produit des donnes incohrentes (la disparition
de 300 euros jamais arrivs chez le bnficiaire). Avec une transaction, cette incohrence nap-
parat jamais. Tant que la totalit des tapes na pas t ralise avec succs, les donnes restent
dans leur tat initial.

[[question]] | Nos transactions permettent-elles dassurer la cohrence des donnes ?

Oui, les changements de donnes ne sont valids quune fois que toutes les tapes ont t excu-
tes. De lextrieur de la transaction, le moment entre les deux tapes dun virement nest jamais
visible.

6.1.4.3 I pour Isolation

Chaque transaction doit tre isole, donc ne pas interagir avec une autre transaction.

[[question]] | Nos transactions sont-elles isoles ?

6.1.4.3.1 Test Dans votre client MySQL, excutez les requtes suivantes (ne commitez pas)
pour modifier le pere_id du rat Bibo :

[[attention]] | Copiez-collez tout le bloc dans votre client MySQL

START TRANSACTION ; -- On ouvre une transaction

UPDATE Animal -- On modifie Bibo


SET pere_id = 73
WHERE espece_id = 5 AND nom = 'Bibo' ;

SELECT id, nom, commentaires, pere_id, mere_id


FROM Animal
WHERE espece_id = 5 ;

232
6.1 Transactions

nouveau, ouvrez une deuxime session, tout en laissant la premire ouverte (dmarrez un
deuxime client SQL et connectez-vous votre base de donnes). Excutez les requtes sui-
vantes, pour modifier les commentaires de Bibo.

[[attention]] | nouveau, prenez bien tout le bloc dun coup, vous suivrez plus facilement les
explications qui suivent.

START TRANSACTION ; -- On ouvre une transaction

SELECT id, nom, commentaires, pere_id, mere_id


FROM Animal
WHERE espece_id = 5 ;

UPDATE Animal -- On modifie la perruche Bibo


SET commentaires = 'Agressif'
WHERE espece_id = 5 AND nom = 'Bibo' ;

SELECT id, nom, commentaires, pere_id, mere_id


FROM Animal
WHERE espece_id = 5 ;

Le rsultat nest pas du tout le mme dans les deux sessions. En effet, dans la premire, on a la
confirmation que la requte UPDATE a t effectue :

QueryOK,1rowaffected(0.00sec)
Rowsmatched:1Changed:1Warnings:0

Et le SELECT renvoie bien les donnes modifies (pere_id nest plus NULL pour Bibo) :

id nom commentaires pere_id mere_id

69 Baba NULL NULL NULL


70 Bibo NULL 73 72
72 Momy NULL NULL NULL

73 Popi NULL NULL NULL


75 Mimi NULL NULL NULL

Par contre, dans la deuxime session, on a dabord fait un SELECT, et Bibo na toujours pas de
pre (puisque a na pas t commit dans la premire session). Donc on sattendrait ce que la
requte UPDATE laisse pere_id NULL et modifie commentaires.

id nom commentaires pere_id mere_id

69 Baba NULL NULL NULL


70 Bibo NULL NULL 72
72 Momy NULL NULL NULL

73 Popi NULL NULL NULL


75 Mimi NULL NULL NULL

233
6 Scuriser et automatiser ses actions

Seulement voil, la requte UPDATE ne fait rien ! La session semble bloque : pas de message de
confirmation aprs la requte UPDATE, et le second SELECT na pas t effectu.

mysql>
mysql>UPDATEAnimal--OnmodifieBibo
->SETcommentaires='Agressif'
->WHEREespece_id=5ANDnom='Bibo' ;
_

Commitez maintenant les changements dans la premire session (celle qui nest pas bloque).
Retournez voir dans la seconde session : elle sest dbloque et indique maintenant un message
de confirmation aussi :

QueryOK,1rowaffected(5.17sec)
Rowsmatched:1Changed:1Warnings:0

Qui plus est, le SELECT a t excut (vous devrez peut-tre appuyer sur ||Entre|| pour que ce
soit envoy au serveur) et les modifications ayant t faites par la session 1 ont t prises en
compte : commentaires vaut 'Agressif' et pere_id vaut 73 !

id nom commentaires pere_id mere_id

69 Baba NULL NULL NULL


70 Bibo Agressif 73 72
72 Momy NULL NULL NULL

73 Popi NULL NULL NULL


75 Mimi NULL NULL NULL

[[information]] | Il est possible que votre seconde session indique ceci : ERROR 1205 (HY000): Lock wait tim
Cela signifie que la session est reste bloque trop longtemps et que par consquent la transac-
tion a t automatiquement ferme (avec un rollback des requtes effectues). Dans ce cas,
recommencez lopration.

Il ny a plus qu commiter les changements faits par la deuxime session, et cest termin ! Si
vous ne commitez pas, commentaires restera NULL. Par contre, pere_id vaudra toujours 73 puisque
ce changement-l a t commit par la premire session.

6.1.4.3.2 Conclusion La deuxime session na pas interagi avec les changements faits par la
premire session, chaque transaction est bien isole.

[[question]] | Et la premire session qui bloque la seconde, ce nest pas une interaction a ?

Pas dans le cadre des critres ACID. Oui, la premire session provoque un retard dans lexcu-
tion des requtes de la deuxime session, mais les critres de fiabilit que nous examinons ici
concernent les donnes impactes par les transactions, et non le droulement de celles-ci (qui
importe peu finalement). Ce blocage a pour effet dempcher la deuxime session dcraser un
changement fait par la premire. Donc, ce blocage a bien pour effet lisolation des transactions.

234
6.2 Verrous

6.1.4.3.3 Verrous Le blocage de la deuxime session vient en fait de ce que la premire session,
en faisant sa requte UPDATE, a automatiquement pos un verrou sur la ligne contenant Bobi
le rat, empchant toute modification tant que la transaction tait en cours. Les verrous faisant
lobjet du prochain chapitre, je nen dis pas plus pour linstant.

6.1.4.3.4 Utilit Je vous laccorde, vous nallez pas vous amuser tous les jours ouvrir deux
sessions MySQL. Par contre, pour une application pouvant tre utilise par plusieurs personnes en
mme temps (qui toutes travaillent sur la mme base de donnes), il est impratif que ce critre
soit respect. Prenons lexemple simple dun jeu par navigateur : de nombreux joueurs peuvent
tre connects en mme temps, et effectuer des oprations diffrentes. Si les transactions ne
sont pas isoles, une partie des actions des joueurs risquerait de se voir annules. On isole donc
les transactions grce aux verrous (qui sont ici automatiquement poss mais ce nest pas toujours
le cas).

6.1.4.4 D pour Durabilit

Une fois la transaction termine, les donnes rsultant de cette transaction doivent tre stockes
de manire durable, et pouvoir tre rcupres, en cas de crash du serveur par exemple.

[[question]] | Nos transactions modifient-elles les donnes de manire durable ?

Oui, une fois les changements commits, ils sont stocks dfinitivement (jusqu modification
par une nouvelle transaction).

*ACID : Atomicit Cohrence Isolation Durabilit

6.1.4.5 En rsum

Les transactions permettent de grouper plusieurs requtes, lesquelles seront valides


(COMMIT) ou annules (ROLLBACK) toutes en mme temps.
Tous les changements de donnes (insertion, suppression, modification) faits par les re-
qutes lintrieur dune transaction sont invisibles pour les autres sessions tant que la
transaction nest pas valide.
Les transactions permettent dexcuter un traitement ncessitant plusieurs requtes en
une seule fois, ou de lannuler compltement si une des requtes pose problme ou si la
transaction est interrompue.
Certaines commandes SQL provoquent une validation implicite des transactions, notam-
ment toutes les commandes DDL, cest--dire les commandes qui crent, modifient ou
suppriment des objets dans la base de donnes (tables, index,).
Les critres ACID sont les critres quun systme appliquant les transactions doit respecter
pour tre fiable : Atomicit, Cohrence, Isolation, Durabilit.

6.2 Verrous
Complment indispensable des transactions, les verrous permettent de scuriser les requtes
en bloquant ponctuellement et partiellement laccs aux donnes.

235
6 Scuriser et automatiser ses actions

Il sagit dun gros chapitre, avec beaucoup dinformations. Il y a par consquent un maximum
dexemples pour vous aider comprendre le comportement des verrous selon les situations.

Au sommaire de ce chapitre :

Quest-ce quun verrou ?


Quel est le comportement par dfaut de MySQL par rapport aux verrous ?
Quand et comment poser un verrou de table ?
Quand et comment poser un verrou de ligne ?
Comment modifier le comportement par dfaut de MySQL ?

6.2.1 Principe
Lorsquune session MySQL pose un verrou sur un lment de la base de donnes, cela veut dire
quil restreint, voire interdit, laccs cet lment aux autres sessions MySQL qui voudraient
y accder.

6.2.1.1 Verrous de table et verrous de ligne

Il est possible de poser un verrou sur une table entire, ou seulement sur une ou plusieurs lignes
dune table. tant donn quun verrou empche laccs dautres sessions, il est en gnral plus
intressant de poser un verrou sur la plus petite partie de la base possible.

Par exemple, si lon travaille avec les chiens de la table Animal.

On peut poser un verrou sur toute la table Animal. Dans ce cas, les autres sessions nauront
pas accs cette table, tant que le verrou sera pos. Quelles veuillent en utiliser les chiens,
les chats, ou autre, tout leur sera refus.
On peut aussi poser un verrou uniquement sur les lignes de la table qui contiennent des
chiens. De cette manire, les autres sessions pourront accder aux chats, aux perroquets,
etc. Elles pourront toujours travailler, tant quelles nutilisent pas les chiens.

Cette notion daccs simultan aux donnes par plusieurs sessions diffrentes sappelle la
concurrence. Plus la concurrence est possible, donc plus le nombre de sessions pouvant accder
aux donnes simultanment est grand, mieux cest. En effet, prenons lexemple dun site web.
En gnral, on prfre permettre plusieurs utilisateurs de surfer en mme temps, sans devoir
attendre entre chaque action de pouvoir accder aux informations chacun son tour. Or, chaque
utilisateur cre une session chaque fois quil se connecte la base de donnes (pour lire les infor-
mations ou les modifier). Prfrez donc (autant que possible) les verrous de ligne aux verrous de
table !

6.2.1.2 Avertissements

Les informations donnes dans ce chapitre concernent exclusivement MySQL, et en particulier


les tables utilisant les moteurs MyISAM ou InnoDB (selon le type de verrou utilis). En effet, les
verrous sont implments diffremment selon les SGDB, et mme selon le moteur de table en ce
qui concerne MySQL. Si le principe gnral reste toujours le mme, certains comportements et
certaines options peuvent diffrer dune implmentation lautre. Nhsitez pas vous rensei-
gner plus avant.

236
6.2 Verrous

Par ailleurs, je vous prsente ici les principes gnraux et les principales options, mais il faut
savoir quil y a normment dire sur les verrous, et que jai donc d faire un srieux tri des
informations avant de rdiger ce chapitre. nouveau, en cas de doute, ou si vous avez besoin
dinformations prcises, je vous conseille vraiment de consulter la documentation officielle (si
possible en anglais, car elle est infiniment plus complte quen franais).

Enfin, dernier avertissement : de nombreux changements dans limplmentation des verrous


sont advenus lors du dveloppement des dernires versions de MySQL. Aussi, la diffrence entre
les verrous dans la version 5.0 et la version 5.5 est assez importante. Tout ce que je prsente dans
ce chapitre concerne la version 5.5. Vrifiez bien votre version, et si vous consultez la documen-
tation officielle, prenez bien celle qui concerne votre propre version.

6.2.1.3 Modification de notre base de donnes

Nous allons ajouter deux tables notre base de donnes, afin dillustrer au mieux lintrt et
lutilisation des verrous : une table Client, qui contiendra les coordonnes des clients de notre
levage, et une table Adoption, qui contiendra les renseignements concernant les adoptions faites
par nos clients. Dornavant, certains animaux prsents dans notre table Animal ne seront plus
disponibles, car ils auront t adopts. Nous les garderons cependant dans notre base de donnes.
Avant toute adoption, il nous faudra donc vrifier la disponibilit de lanimal.

Voici les requtes effectuer pour faire ces changements.

[[secret]] |
sql | -- Table Client | CREATE TABLE Client ( | id SMALLINT
UNSIGNED AUTO_INCREMENT NOT NULL, | nom VARCHAR(100) NOT NULL, | prenom
VARCHAR(60) NOT NULL, | adresse VARCHAR(200), | code_postal VARCHAR(6),
| ville VARCHAR(60), | pays VARCHAR(60), | email VARBINARY(100),
| PRIMARY KEY (id), | UNIQUE INDEX ind_uni_email (email) | ) ENGINE
= InnoDB; | | -- Table Adoption | CREATE TABLE Adoption ( | client_id
SMALLINT UNSIGNED NOT NULL, | animal_id SMALLINT UNSIGNED NOT NULL, |
date_reservation DATE NOT NULL, | date_adoption DATE, | prix
DECIMAL(7,2) UNSIGNED NOT NULL, | paye TINYINT(1) NOT NULL DEFAULT 0, |
PRIMARY KEY (client_id, animal_id), | CONSTRAINT fk_client_id FOREIGN
KEY (client_id) REFERENCES Client(id), | CONSTRAINT fk_adoption_animal_id
FOREIGN KEY (animal_id) REFERENCES Animal(id), | UNIQUE INDEX ind_uni_animal_id
(animal_id) | ) ENGINE = InnoDB; | | -- Insertion de quelques clients |
INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email)
VALUES ('Jean', 'Dupont', 'Rue du Centre, 5', '45810', 'Houtsiplou', 'France',
'jean.dupont@email.com'); | INSERT INTO Client (prenom, nom, adresse, code_postal,
ville, pays, email) VALUES ('Marie', 'Boudur', 'Place de la Gare, 2', '35840',
'Troudumonde', 'France', 'marie.boudur@email.com'); | INSERT INTO Client
(prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Fleur',
'Trachon', 'Rue haute, 54b', '3250', 'Belville', 'Belgique', 'fleurtrachon@email.com');
| INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email)
VALUES ('Julien', 'Van Piperseel', NULL, NULL, NULL, NULL, 'jeanvp@email.com');
| INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email)
VALUES ('Johan', 'Nouvel', NULL, NULL, NULL, NULL, 'johanetpirlouit@email.com');
| INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email)
VALUES ('Frank', 'Germain', NULL, NULL, NULL, NULL, 'francoisgermain@email.com');
| INSERT INTO Client (prenom, nom, adresse, code_postal, ville, pays, email)

237
6 Scuriser et automatiser ses actions

VALUES ('Maximilien', 'Antoine', 'Rue Moineau, 123', '4580', 'Trocoul',


'Belgique', 'max.antoine@email.com'); | INSERT INTO Client (prenom, nom,
adresse, code_postal, ville, pays, email) VALUES ('Hector', 'Di Paolo',
NULL, NULL, NULL, NULL, 'hectordipao@email.com'); | INSERT INTO Client
(prenom, nom, adresse, code_postal, ville, pays, email) VALUES ('Anaelle',
'Corduro', NULL, NULL, NULL, NULL, 'ana.corduro@email.com'); | INSERT
INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES
('Eline', 'Faluche', 'Avenue circulaire, 7', '45870', 'Garduche', 'France',
'elinefaluche@email.com'); | INSERT INTO Client (prenom, nom, adresse,
code_postal, ville, pays, email) VALUES ('Carine', 'Penni', 'Boulevard
Haussman, 85', '1514', 'Plasse', 'Suisse', 'cpenni@email.com'); | INSERT
INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES
('Virginie', 'Broussaille', 'Rue du Fleuve, 18', '45810', 'Houtsiplou',
'France', 'vibrousaille@email.com'); | INSERT INTO Client (prenom, nom,
adresse, code_postal, ville, pays, email) VALUES ('Hannah', 'Durant', 'Rue
des Pendus, 66', '1514', 'Plasse', 'Suisse', 'hhdurant@email.com'); | INSERT
INTO Client (prenom, nom, adresse, code_postal, ville, pays, email) VALUES
('Elodie', 'Delfour', 'Rue de Flore, 1', '3250', 'Belville', 'Belgique',
'e.delfour@email.com'); | INSERT INTO Client (prenom, nom, adresse, code_postal,
ville, pays, email) VALUES ('Joel', 'Kestau', NULL, NULL, NULL, NULL, 'joel.kestau@emai
| | -- Insertion de quelques adoptions | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (1, 39,
'2008-08-17', '2008-08-17', 735.00, 1); | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (1, 40,
'2008-08-17', '2008-08-17', 735.00, 1); | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (2, 18,
'2008-06-04', '2008-06-04', 485.00, 1); | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (3, 27,
'2009-11-17', '2009-11-17', 200.00, 1); | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (4, 26,
'2007-02-21', '2007-02-21', 485.00, 1); | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (4, 41,
'2007-02-21', '2007-02-21', 835.00, 1); | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (5, 21,
'2009-03-08', '2009-03-08', 200.00, 1); | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (6, 16,
'2010-01-27', '2010-01-27', 200.00, 1); | INSERT INTO Adoption (client_id,
animal_id, date_reservation, date_adoption, prix, paye) VALUES (7, 5, '2011-04-05',
'2011-04-05', 150.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (8, 42, '2008-08-16',
'2008-08-16', 735.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (9, 55, '2011-02-13',
'2011-02-13', 140.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (9, 54, '2011-02-13',
'2011-02-13', 140.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (10, 49, '2010-08-17',
'2010-08-17', 140.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (11, 62, '2011-03-01',

238
6.2 Verrous

'2011-03-01', 630.00, 1); | INSERT INTO Adoption (client_id, animal_id,


date_reservation, date_adoption, prix, paye) VALUES (12, 69, '2007-09-20',
'2007-09-20', 10.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (13, 57, '2012-01-10',
'2012-01-10', 700.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (14, 58, '2012-02-25',
'2012-02-25', 700.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (15, 30, '2008-08-17',
'2008-08-17', 735.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (11, 32, '2008-08-17',
'2010-03-09', 140.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (9, 33, '2007-02-11',
'2007-02-11', 835.00, 1); | INSERT INTO Adoption (client_id, animal_id,
date_reservation, date_adoption, prix, paye) VALUES (2, 3, '2011-03-12',
'2011-03-12', 835.00, 1); |

La table Adoption ne contient pas de colonne id auto-incrmente. Par contre, on a bien dfini une
cl primaire, mais une cl primaire composite (sur plusieurs colonnes). En effet, une adoption
est dfinie par un client adoptant un animal. Il nest pas ncessaire dajouter une colonne suppl-
mentaire pour dfinir individuellement chaque ligne ; le couple (client_id, animal_id) fait trs bien
laffaire (il est compos de deux SMALLINT, donc les recherches sur cette cl seront rapides). No-
tez que nous dfinissons galement un index UNIQUE sur la colonne animal_id. Par consquent, on
aurait mme pu dfinir directement animal_id comme tant la cl primaire. Je trouvais cependant
plus logique dinclure le client dans la dfinition dune adoption. Cest un choix plutt arbitraire,
qui a surtout comme avantage de vous montrer un exemple de cl composite.

6.2.2 Syntaxe et utilisation : verrous de table


Les verrous de table sont les seuls supports par MyISAM. Ils sont dailleurs principalement
utiliss pour pallier en partie labsence de transactions dans MyISAM. Les tables InnoDB peuvent
galement utiliser ce type de verrou.

Pour verrouiller une table, il faut utiliser la commande LOCK TABLES :

LOCK TABLES nom_table [AS alias_table] [READ | WRITE] [, ...];

En utilisant READ, un verrou de lecture sera pos ; cest--dire que les autres sessions pour-
ront toujours lire les donnes des tables verrouilles, mais ne pourront plus les modifier.
En utilisant WRITE, un verrou dcriture sera pos. Les autres sessions ne pourront plus ni
lire ni modifier les donnes des tables verrouilles.

Pour dverrouiller les tables, on utilise UNLOCK TABLES. Cela dverrouille toutes les tables ver-
rouilles. Il nest pas possible de prciser les tables dverrouiller. Tous les verrous de table dune
session sont relchs en mme temps.

6.2.2.0.1 Session ayant obtenu le verrou Lorsquune session acquiert un ou plusieurs ver-
rous de table, cela a plusieurs consquences pour cette session :

elle ne peut plus accder quaux tables sur lesquelles elle a pos un verrou ;
elle ne peut accder ces tables quen utilisant les noms quelle a donns lors du ver-
rouillage (soit le nom de la table, soit le/les alias donn(s)) ;

239
6 Scuriser et automatiser ses actions

sil sagit dun verrou de lecture (READ), elle peut uniquement lire les donnes, pas les
modifier.

Exemples : on pose deux verrous, lun READ, lautre WRITE, lun en donnant un alias au nom de
la table, lautre sans.

LOCK TABLES Espece READ, -- On pose un verrou de lecture sur Espece


Adoption AS adopt WRITE ; -- et un verrou d'criture sur Adoption avec l'a

Voyons maintenant le rsultat de ces diffrentes requtes.

1. Slection dans Espece, sans alias.

SELECT id, nom_courant FROM Espece;

id nom_courant

1 Chien
2 Chat
3 Tortue dHermann
4 Perroquet amazone
5 Rat brun

Pas de problme, on a bien un verrou sur Espece, sans alias.

2. Slection dans Espece, avec alias.

SELECT id, nom_courant


FROM Espece AS table_espece;

ERROR1100(HY000):Table'table_espece'wasnotlockedwithLOCKTABLES

Par contre, si lon essaye dutiliser un alias, cela ne fonctionne pas. Le verrou est pos sur Espece,
pas sur Espece AS table_espece.

3. Modification dans Espece, sans alias.

UPDATE Espece
SET description = 'Petit piaf bruyant'
WHERE id = 4 ;

ERROR1099(HY000):Table'Espece'waslockedwithaREADlockandcan'tbeupdated

Avec ou sans alias, impossible de modifier la table Espece, puisque le verrou que lon possde des-
sus est un verrou de lecture.

4. Slection dans Adoption, sans alias.

SELECT client_id, animal_id


FROM Adoption;

ERROR1100(HY000):Table'Adoption'wasnotlockedwithLOCKTABLES

240
6.2 Verrous

Cette fois, cest le contraire, sans alias, a ne passe pas.

5. Slection dans Adoption, avec alias.

SELECT client_id, animal_id


FROM Adoption AS adopt
WHERE client_id = 4 ;

client_id animal_id

4 26
4 41

6. Modification dans Adoption, sans alias.

UPDATE Adoption
SET paye = 0
WHERE client_id = 10 AND animal_id = 49 ;

ERROR1100(HY000):Table'Adoption'wasnotlockedwithLOCKTABLES

Idem pour la modification, lalias est indispensable.

7. Modification dans Adoption, avec alias.

UPDATE Adoption AS adopt


SET paye = 0
WHERE client_id = 10 AND animal_id = 49 ;

QueryOK,1rowaffected(0.03sec)

Il faut donc penser acqurir tous les verrous ncessaires aux requtes excuter. De plus, il faut
les obtenir en une seule requte LOCK TABLES. En effet, LOCK TABLES commence par enlever
tous les verrous de table de la session avant den acqurir de nouveaux. Il est bien entendu pos-
sible de poser plusieurs verrous sur la mme table en une seule requte afin de verrouiller son
nom ainsi quun ou plusieurs alias.

Exemples : on pose un verrou de lecture sur Adoption, puis avec une seconde requte, on pose deux
verrous de lecture sur la table Espece, lun avec alias, lautre sans.

UNLOCK TABLES ; -- On relche d'abord les deux verrous prcdents

LOCK TABLES Adoption READ ;


LOCK TABLES Espece READ, Espece AS table_espece READ ;

Une fois ces deux requtes effectues, nous aurons donc bien deux verrous de lecture sur la table
Espece : un avec son nom, lautre avec un alias. Par contre, le verrou sur Adoption nexistera plus
puisquil aura t relch par lexcution de la seconde requte LOCK TABLES.

1. Slection dans Espece, sans alias.

SELECT id, nom_courant FROM Espece;

241
6 Scuriser et automatiser ses actions

id nom_courant

1 Chien
2 Chat
3 Tortue dHermann
4 Perroquet amazone
5 Rat brun

2. Slection dans Espece, avec alias.

SELECT id, nom_courant FROM Espece AS table_espece;

id nom_courant

1 Chien
2 Chat
3 Tortue dHermann
4 Perroquet amazone
5 Rat brun

Avec ou sans alias, on peut slectionner les donnes de la table Espece, puisque lon a un verrou
sur Espece et sur Espece AS table_espece.

3. Slection dans Espece, avec mauvais alias.

SELECT id, nom_courant FROM Espece AS table_esp;

ERROR1100(HY000):Table'table_esp'wasnotlockedwithLOCKTABLES

Bien entendu, cela ne fonctionne que pour lalias que lon a donn lors du verrouillage.

4. Slection dans Adoption, sans alias.

SELECT * FROM Adoption;

ERROR1100(HY000):Table'Adoption'wasnotlockedwithLOCKTABLES

Le verrou sur Adoption a t relch lorsque lon a pos les verrous sur Espece. On ne peut donc pas
lire les donnes dAdoption (avec ou sans alias).

6.2.2.0.2 Consquences pour les autres sessions Si une session a obtenu un verrou de
lecture sur une table, les autres sessions :

peuvent lire les donnes de la table ;


peuvent galement acqurir un verrou de lecture sur cette table ;
ne peuvent pas modifier les donnes, ni acqurir un verrou dcriture sur cette table.

Si par contre une session a obtenu un verrou dcriture, les autres sessions ne peuvent absolu-
ment pas accder cette table tant que ce verrou existe.

242
6.2 Verrous

Exemples : ouvrez un deuxime client MySQL et connectez-vous votre base de donnes, afin
davoir deux sessions ouvertes.

1. Slection sur des tables verrouilles partir dune autre session.

Session 1 :

LOCK TABLES Client READ, -- Verrou de lecture sur Client


Adoption WRITE ; -- Verrou d'criture sur Adoption

Session 2 :

SELECT id, nom, prenom, ville, email


FROM Client
WHERE ville = 'Houtsiplou' ;

id nom prenom ville email

1 Dupont Jean Houtsiplou jean.dupont@email.com


12 Broussaille Virginie Houtsiplou vibrousaille@email.com

La slection sur Client se fait sans problme.

Session 2 :

SELECT *
FROM Adoption
WHERE client_id = 4 ;

Par contre, la slection sur Adoption ne passe pas. La session se bloque, jusqu ce que la session 1
dverrouille les tables avec UNLOCK TABLES.

2. Modification sur des tables verrouilles partir dune autre session.

Reverrouillez les tables avec la session 1 :

LOCK TABLES Client READ, -- Verrou de lecture sur Client


Adoption WRITE ; -- Verrou d'criture sur Adoption

Session 2 :

UPDATE Client
SET pays = 'Suisse'
WHERE id = 5 ;

La modification sur Client, contrairement la slection, est bloque jusquau dverrouillage. D-


verrouillez puis verrouillez nouveau avec la session 1.

Session 2 :

UPDATE Adoption
SET paye = 1
WHERE client_id = 3 ;

Bien entendu, la modification sur la table Adoption attend galement que les verrous soient rel-
chs par la session 1.

243
6 Scuriser et automatiser ses actions

En ce qui concerne la pose de verrous de table par les autres sessions, faites vos propres tests,
mais simplement : si une session peut lire les donnes dune table, elle peut galement poser un
verrou de lecture. Si une session peut modifier les donnes dune table, elle peut galement poser
un verrou dcriture.

6.2.2.0.3 Interaction avec les transactions Si lon utilise des tables MyISAM, il ny a vi-
demment aucune prcaution particulire prendre par rapport aux transactions lorsquon utilise
des verrous de table (les tables MyISAM tant non-transactionnelles). Par contre, si on utilise des
tables InnoDB, il convient dtre prudent. En effet :

START TRANSACTION te les verrous de table ;


les commandes LOCK TABLES et UNLOCK TABLES provoquent une validation implicite si
elles sont excutes lintrieur dune transaction.

Pour utiliser la fois les transactions et les verrous de table, il faut renoncer dmarrer explicite-
ment les transactions, et donc utiliser le mode non-autocommit. Lorsque lon est dans ce mode, il
est facile de contourner la validation implicite provoque par LOCK TABLES et UNLOCK TABLES :
il suffit dappeler LOCK TABLES avant toute modification de donnes, et de commiter/annuler
les modifications avant dexcuter UNLOCK TABLES.

Exemple :

SET autocommit = 0 ;
LOCK TABLES Adoption WRITE ; -- La validation implicite ne commite rien puisque aucun c

UPDATE Adoption SET date_adoption = NOW() WHERE client_id = 9 AND animal_id = 54 ;


SELECT client_id, animal_id, date_adoption FROM Adoption WHERE client_id = 9 ;

ROLLBACK ;
UNLOCK TABLES ; -- On a annul les changements juste avant donc la validation implicite
SELECT client_id, animal_id, date_adoption FROM Adoption WHERE client_id = 9 ;
SET autocommit = 1 ;

6.2.3 Syntaxe et utilisation : verrous de ligne


[[attention]] | Ces verrous ne peuvent pas tre poss sur une table utilisant le moteur MyISAM !
Tout ce qui est dit ici concerne les tables InnoDB uniquement.

Comme les verrous de table, les verrous de ligne peuvent tre de deux types :

Les verrous partags : permettent aux autres sessions de lire les donnes, mais pas de les
modifier (quivalents aux verrous de table de lecture) ;
Les verrous exclusifs : ne permettent ni la lecture ni la modification des donnes (quiva-
lents aux verrous dcriture).

6.2.3.1 Requtes de modification, insertion et suppression

Les requtes de modification et suppression des donnes posent automatiquement un


verrou exclusif sur les lignes concernes, savoir les lignes slectionnes par la clause
WHERE, ou toutes les lignes sil ny a pas de clause WHERE (ou sil ny a pas dindex, sur les
colonnes utilises comme nous verrons plus loin).

244
6.2 Verrous

Les requtes dinsertion quant elles posent un verrou exclusif sur la ligne insre.

6.2.3.2 Requtes de slection

Les requtes de slection, par dfaut, ne posent pas de verrous. Il faut donc en poser explicitement
au besoin.

6.2.3.2.1 Verrou partag Pour poser un verrou partag, on utilise LOCK IN SHARE MODE
la fin de la requte SELECT.

SELECT * FROM Animal WHERE espece_id = 5 LOCK IN SHARE MODE ;

Cette requte pose donc un verrou partag sur les lignes de la table Animal pour lesquelles espece_id
vaut 5.

Ce verrou signifie en fait, pour les autres sessions : Je suis en train de lire ces donnes. Vous
pouvez venir les lire aussi, mais pas les modifier tant que je nai pas termin..

6.2.3.2.2 Verrou exclusif Pour poser un verrou exclusif, on utilise FOR UPDATE la fin de la
requte SELECT.

SELECT * FROM Animal WHERE espece_id = 5 FOR UPDATE ;

Ce verrou signifie aux autres sessions : Je suis en train de lire ces donnes dans le but probable
de faire une modification. Ne les lisez pas avant que jaie fini (et bien sr, ne les modifiez pas)..

6.2.3.3 Transactions et fin dun verrou de ligne

Les verrous de ligne ne sont donc pas poss par des commandes spcifiques, mais par des requtes
de slection, insertion ou modification. Ces verrous existent donc uniquement tant que la requte
qui les a poss interagit avec les donnes.

Par consquent, ce type de verrou sutilise en conjonction avec les transactions. En effet, hors
transaction, ds quune requte est lance, elle est effectue et les ventuelles modifications des
donnes sont immdiatement valides. Par contre, dans le cas dune requte faite dans une tran-
saction, les changements ne sont pas valids tant que la transaction na pas t commite. Donc,
partir du moment o une requte a t excute dans une transaction, et jusqu la fin de la
transaction (COMMIT ou ROLLBACK), la requte a potentiellement un effet sur les donnes. Cest
ce moment-l (quand une requte a t excute mais pas valide ou annule) quil est intres-
sant de verrouiller les donnes qui vont potentiellement tre modifies (ou supprimes) par la
transaction.

Un verrou de ligne est donc li la transaction dans laquelle il est pos. Ds que lon fait un COMMIT
ou un ROLLBACK de la transaction, le verrou est lev.

6.2.3.4 Exemples

6.2.3.4.1 Verrou pos par une requte de modification Session 1 :

245
6 Scuriser et automatiser ses actions

START TRANSACTION ;

UPDATE Client SET pays = 'Suisse'


WHERE id = 8 ; -- un verrou exclusif sera pos sur la ligne avec id = 8

Session 2 :

START TRANSACTION ;

SELECT * FROM Client


WHERE id = 8 ; -- pas de verrou

SELECT * FROM Client


WHERE id = 8
LOCK IN SHARE MODE ; -- on essaye de poser un verrou partag

La premire session a donc pos un verrou exclusif automatiquement en faisant un UPDATE.

La seconde session fait dabord une simple slection, sans poser de verrou. Pas de problme, la
requte passe.

[[question]] | Ah ? La requte passe ? Et cest normal ? Et le verrou exclusif alors ?

Oui, cest normal et cest important de comprendre pourquoi. En fait, lorsquune session dmarre
une transaction, elle prend en quelque sorte une photo des tables dans leur tat actuel (les
modifications non commites ntant pas visibles). La transaction va alors travailler sur la base
de cette photo, tant quon ne lui demande pas daller vrifier que les donnes nont pas chang.
Donc le SELECT ne voit pas les changements, et ne se heurte pas au verrou, puisque celui-ci est
pos sur les lignes de la table, et non pas sur la photo de cette table que dtient la session.

[[question]] | Et comment fait-on pour demander la session dactualiser sa photo ?

On lui demande de poser un verrou ! Lorsquune session pose un verrou sur une table, elle est
oblige de travailler vraiment avec la table, et pas sur sa photo. Elle va donc aller chercher les
dernires infos disponibles, et actualiser sa photo par la mme occasion. On le voit bien avec la
seconde requte, qui tente de poser un verrou partag (qui vise donc uniquement la lecture). Elle
va dabord chercher les lignes les plus jour et tombe sur le verrou pos par la premire session ;
elle se retrouve alors bloque jusqu ce que la premire session te le verrou exclusif.

Session 1 :

COMMIT ;

En committant les changements de la session 1, le verrou exclusif pos par la requte de modifi-
cation est relch. La session 2 est donc libre de poser son tour un verrou partag.

On peut galement essayer la mme manuvre, avec cette fois-ci un UPDATE plutt quun
SELECT ... LOCK IN SHARE MODE (donc une requte qui va tenter de poser un verrou exclusif
plutt quun verrou partag).

Session 1 :

START TRANSACTION ;

UPDATE Adoption SET paye = 0


WHERE client_id = 11 ;

246
6.2 Verrous

Session 2 :

START TRANSACTION ;

UPDATE Adoption SET paye = 1


WHERE animal_id = 32 ; -- l'animal 32 a t adopt par le client 11

Comme prvu, la seconde session est bloque, jusqu ce que la premire session termine sa tran-
saction. Validez la transaction de la premire session, puis de la seconde. Le comportement sera
le mme si la deuxime session fait un DELETE sur les lignes verrouilles, ou un SELECT ... FOR
UPDATE.

6.2.3.4.2 Verrou pos par une requte dinsertion Session 1 :

START TRANSACTION ;

INSERT INTO Adoption (client_id, animal_id, date_reservation, prix)


VALUES (12, 75, NOW(), 10.00);

Session 2 :

SELECT * FROM Adoption


WHERE client_id > 13
LOCK IN SHARE MODE ;

SELECT * FROM Adoption


WHERE client_id < 13
LOCK IN SHARE MODE ;

La premire session insre une adoption pour le client 12 et pose un verrou exclusif sur cette
ligne. La seconde session fait deux requtes SELECT en posant un verrou partag : lune qui s-
lectionne les adoptions des clients avec un id suprieur 13 ; lautre qui slectionne les adoptions
des clients avec un id infrieur 13. Seule la seconde requte SELECT se heurte au verrou pos par
la premire session, puisquelle tente de rcuprer notamment les adoptions du client 12, dont
une est verrouille.

Ds que la session 1 commite linsertion, la slection se fait dans la session 2.

Session 1 :

COMMIT ;

6.2.3.4.3 Verrou pos par une requte de slection Voyons dabord le comportement
dun verrou partag, pos par SELECT ... LOCK IN SHARE MODE.

Session 1 :

START TRANSACTION ;

SELECT * FROM Client


WHERE id < 5
LOCK IN SHARE MODE ;

247
6 Scuriser et automatiser ses actions

Session 2 :

START TRANSACTION ;

SELECT * FROM Client


WHERE id BETWEEN 3 AND 8 ;

SELECT * FROM Client


WHERE id BETWEEN 3 AND 8
LOCK IN SHARE MODE ;

SELECT * FROM Client


WHERE id BETWEEN 3 AND 8
FOR UPDATE ;

La premire session pose un verrou partag sur les clients 1, 2, 3 et 4. La seconde session fait trois
requtes de slection. Toutes les trois concernent les clients 3 8 (dont les deux premiers sont
verrouills).

Requte 1 : ne pose aucun verrou (travaille sur une photo de la table et pas sur les vraies
donnes) donc seffectue sans souci.
Requte 2 : pose un verrou partag, ce qui est faisable sur une ligne verrouille par un verrou
partag. Elle seffectue galement.
Requte 3 : tente de poser un verrou exclusif, ce qui lui est refus.

Bien entendu, des requtes UPDATE ou DELETE (posant des verrous exclusifs) faites par la
deuxime session se verraient, elles aussi, bloques.

Terminez les transactions des deux sessions (par un rollback ou un commit).

Quant aux requtes SELECT ... FOR UPDATE posant un verrou exclusif, elles provoqueront
exactement les mmes effets quune requte UPDATE ou DELETE (aprs tout, un verrou exclusif,
cest un verrou exclusif).

Session 1 :

START TRANSACTION ;

SELECT * FROM Client


WHERE id < 5
FOR UPDATE ;

Session 2 :

START TRANSACTION ;

SELECT * FROM Client


WHERE id BETWEEN 3 AND 8 ;

SELECT * FROM Client


WHERE id BETWEEN 3 AND 8
LOCK IN SHARE MODE ;

248
6.2 Verrous

Cette fois-ci, mme la requte SELECT ... LOCK IN SHARE MODE de la seconde session est
bloque (comme le serait une requte SELECT ... FOR UPDATE, ou une requte UPDATE, ou une
requte DELETE).

6.2.3.5 En rsum

On pose un verrou partag lorsquon fait une requte dans le but de lire des donnes.
On pose un verrou exclusif lorsquon fait une requte dans le but (immdiat ou non) de
modifier des donnes.
Un verrou partag sur les lignes x va permettre aux autres sessions dobtenir galement un
verrou partag sur les lignes x, mais pas dobtenir un verrou exclusif.
Un verrou exclusif sur les lignes x va empcher les autres sessions dobtenir un verrou sur
les lignes x, quil soit partag ou exclusif.

[[information]] | En fait, ils portent plutt bien leurs noms ces verrous !

6.2.3.6 Rle des index

Tentons une nouvelle exprience.

Session 1 :

START TRANSACTION ;
UPDATE Animal
SET commentaires = CONCAT_WS(' ', 'Animal fondateur.', commentaires) -- On ajoute une p
WHERE date_naissance < '2007-01-01' ; -- tous les ani

Session 2 :

START TRANSACTION ;
UPDATE Animal
SET commentaires = 'Aveugle' -- On modifie les commentaires
WHERE date_naissance = '2008-03-10 13:40:00' ; -- De l'animal n le 10 mars 2008 13h4

Dans la session 1, on fait un UPDATE sur les animaux ns avant 2007. On sattend donc pouvoir
utiliser les animaux ns aprs dans une autre session, puisque InnoDB pose des verrous sur les
lignes et pas sur toute la table. Pourtant, la session 2 semble bloque lorsque lon fait un UPDATE
sur un animal n en 2008. Faites un rollback sur la session 1 ; ceci dbloque la session 2. Annulez
galement la requte de cette session.

[[question]] | Ce comportement est donc en contradiction avec ce quon obtenait prcdemment.


Quelle est la diffrence ?

Le sous-titre vous a videmment souffl la rponse : la diffrence se trouve au niveau des index.
Voyons donc a ! Voici une commande qui va vous afficher les index prsents sur la table Animal :

SHOW INDEX FROM Animal;

Non_unique Column_name
Table Key_name Null

Animal 0 PRIMARY id

249
6 Scuriser et automatiser ses actions

Non_unique Column_name
Table Key_name Null

Animal 0 ind_uni_nom_espece_id nom YES

Animal 0 ind_uni_nom_espece_id espece_id

Animal 1 fk_race_id race_id YES

Animal 1 fk_espece_id espece_id

Animal 1 fk_mere_id mere_id YES

Animal 1 fk_pere_id pere_id YES

[[information]] | Une partie des colonnes du rsultat montr ici a t retire pour des raisons de
clart.

Nous avons donc des index sur les colonnes suivantes : id, nom, mere_id, pere_id, espece_id et race_id.
Mais aucun index sur la colonne date_naissance.

Il semblerait donc que lorsque lon pose un verrou, avec dans la clause WHERE de la requte une
colonne indexe (espece_id), le verrou est bien pos uniquement sur les lignes pour lesquelles es-
pece_id vaut la valeur recherche. Par contre, si dans la clause WHERE on utilise une colonne non-
indexe (date_naissance), MySQL nest pas capable de dterminer quelles lignes doivent tre blo-
ques, donc on se retrouve avec toutes les lignes bloques.

[[question]] | Pourquoi faut-il un index pour pouvoir poser un verrou efficacement ?

Cest trs simple ! Vous savez que lorsquune colonne est indexe (que ce soit un index simple,
unique, ou une cl primaire ou trangre), MySQL stocke les valeurs de cette colonne en les triant.
Du coup, lors dune recherche sur lindex, pas besoin de parcourir toutes les lignes, il peut utiliser
des algorithmes de recherche performants et trouver facilement les lignes concernes. Sil ny a
pas dindex par contre, toutes les lignes doivent tre parcourues chaque fois que la recherche est
faite, et il ny a donc pas moyen de verrouiller simplement une partie de lindex (donc une partie
des lignes). Dans ce cas, MySQL verrouille toutes les lignes.

Cela fait une bonne raison de plus de mettre des index sur les colonnes qui servent frquemment
dans vos clauses WHERE !

Encore une petite exprience pour illustrer le rle des index dans le verrouillage de lignes :

Session 1 :

START TRANSACTION ;
UPDATE Animal -- Modification de tous les rats
SET commentaires = CONCAT_WS(' ', 'Trs intelligent.', commentaires)
WHERE espece_id = 5 ;

Session 2 :

START TRANSACTION ;
UPDATE Animal

250
6.2 Verrous

SET commentaires = 'Aveugle'


WHERE id = 34 ; -- Modification de l'animal 34 (un chat)
UPDATE Animal
SET commentaires = 'Aveugle'
WHERE id = 72 ; -- Modification de l'animal 72 (un rat)

La session 1 se sert de lindex sur espece_id pour verrouiller les lignes contenant des rats bruns.
Pendant ce temps, la session 2 veut modifier deux animaux : un chat et un rat, en se basant sur leur
id. La modification du chat se fait sans problme, par contre, la modification du rat est bloque,
tant que la transaction de la session 1 est ouverte. Faites un rollback des deux transactions.

On peut conclure de cette exprience que, bien que MySQL utilise les index pour verrouiller les
lignes, il nest pas ncessaire dutiliser le mme index pour avoir des accs concurrents.

6.2.3.7 Lignes fantmes et index de cl suivante

[[question]] | Quest-ce quune ligne fantme ?

Dans une session, dmarrons une transaction et slectionnons toutes les adoptions faites par les
clients dont lid dpasse 13, avec un verrou exclusif.

Session 1 :

START TRANSACTION ;

SELECT * FROM Adoption WHERE client_id > 13 FOR UPDATE ; -- ne pas oublier le FOR UPDAT

La requte va poser un verrou exclusif sur toutes les lignes dont client_id vaut 14 ou plus.

client_id animal_id date_reservation date_adoption prix paye

14 58 2012-02-25 2012-02-25 700.00 1


15 30 2008-08-17 2008-08-17 735.00 1

Imaginons maintenant quune seconde session dmarre une transaction ce moment-l, insre
et commite une ligne dans Adoption pour le client 15. Si, par la suite, la premire session refait la
mme requte de slection avec verrou exclusif, elle va faire apparatre une troisime ligne de
rsultat : ladoption nouvellement insre (tant donn que pour poser le verrou, la session va
aller chercher les donnes les plus jour, prenant en compte le commit de la seconde session).

Cette ligne nouvellement apparue malgr les verrous est une ligne fantme.

Pour pallier ce problme, qui est contraire au principe disolation, les verrous poss par des re-
qutes de lecture, de modification et de suppression sont des verrous dits de cl suivante ;
ils empchent linsertion dune ligne dans les espaces entre les lignes verrouilles, ainsi que dans
lespace juste aprs les lignes verrouilles.

[[question]] | Lespace entre ? Lespace juste aprs ?

Nous avons vu que les verrous se basent sur les index pour verrouiller uniquement les lignes n-
cessaires. Voici un petit schma qui vous expliquera ce quest cet index de cl suivante.

On peut reprsenter lindex sur client_id de la table Adoption de la manire suivante (je ne mets que

251
6 Scuriser et automatiser ses actions

les client_id < 10) :

Si lon insre une adoption avec 4 pour client_id, lindex va tre rorganis de la manire suivante :

Mais, si lon pose un verrou de cl suivante sur lindex, sur les lignes dont client_id vaut 4, on va
alors verrouiller les lignes, les espaces entre les lignes et les espaces juste aprs. Ceci va bloquer

linsertion de la nouvelle ligne :

Dmonstration

On a toujours un verrou exclusif (grce notre SELECT ... FOR UPDATE) sur les client_id sup-
rieurs 14 dans la session 1 (sinon, reposez-le).

Session 2 :

START TRANSACTION ;

INSERT INTO Adoption (client_id, animal_id, date_reservation, prix)


VALUES (15, 61, NOW(), 735.00);

Linsertion est bloque ! Pas de risque de voir apparatre une ligne fantme. Annulez les deux
transactions.

6.2.3.7.1 Exception Si la clause WHERE concerne un index UNIQUE (cela inclut bien sr les cls
primaires) et recherche une seule valeur (exemple : WHERE id = 4), alors seule la ligne concer-
ne (si elle existe) est verrouille, et pas lespace juste aprs dans lindex. Forcment, sil sagit
dun index UNIQUE, linsertion dune nouvelle valeur ne changera rien : WHERE id = 4 ne ren-
verra jamais quune seule ligne.

6.2.3.8 Pourquoi poser un verrou exclusif avec une requte SELECT ?

Aprs tout, une requte SELECT ne fait jamais que lire des donnes. Que personne ne puisse les
modifier pendant quon est en train de les lire, cest tout fait comprhensible. Mais pourquoi
carrment interdire aux autres de les lire aussi ?

252
6.2 Verrous

Tout simplement parce que certaines donnes sont lues dans le but prvisible et avou de les
modifier immdiatement aprs.

Lexemple typique est la vrification de stock dans un magasin (ou dans un levage danimaux).
Un client arrive et veut adopter un chat, on vrifie donc les chats disponibles pour ladoption, en
posant un verrou partag :

Session 1 :

START TRANSACTION ;

SELECT Animal.id, Animal.nom, Animal.date_naissance, Race.nom as race, COALESCE(Race.pr


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
LEFT JOIN Race ON Animal.race_id = Race.id -- Jointure externe, on ne veut
WHERE Espece.nom_courant = 'Chat' -- Uniquement les chats...
AND Animal.id NOT IN (SELECT animal_id FROM Adoption) -- ... qui n'ont pas encore t
LOCK IN SHARE MODE ;

id nom date_naissance race prix

2 Roucky 2010-03-24 02 :23 :00 NULL 150.00


8 Bagherra 2008-09-11 15 :38 :00 Maine coon 735.00
29 Fiero 2009-05-14 06 :30 :00 Singapura 985.00
31 Filou 2008-02-20 15 :45 :00 Bleu russe 835.00
34 Capou 2008-04-20 03 :22 :00 Maine coon 735.00
35 Raccou 2006-05-19 16 :56 :00 Bleu russe 835.00
36 Boucan 2009-05-14 06 :42 :00 Singapura 985.00
37 Callune 2006-05-19 16 :06 :00 Nebelung 985.00
38 Boule 2009-05-14 06 :45 :00 Singapura 985.00
43 Cracotte 2007-03-12 11 :54 :00 Maine coon 735.00
44 Cawette 2006-05-19 16 :16 :00 Nebelung 985.00
61 Yoda 2010-11-09 00 :00 :00 Maine coon 735.00

[[information]] | Je rappelle que la fonction COALESCE() prend un nombre illimit de paramtres,


et renvoie le premier paramtre non NULL quelle rencontre. Donc ici, sil sagit dun chat de race,
Race.prix ne sera pas NULL et sera donc renvoy. Par contre, sil ny a pas de race, Race.prix sera
NULL, mais pas Espece.prix, qui sera alors slectionn.

Si, pendant que le premier client fait son choix, un second client arrive, qui veut adopter un chat
Maine Coon, il va galement chercher la liste des chats disponibles. Et vu quon travaille pour
linstant en verrous partags, il va pouvoir lobtenir.

Session 2 :

START TRANSACTION ;

SELECT Animal.id, Animal.nom, Animal.date_naissance, Race.nom as race, COALESCE(Race.pr


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
INNER JOIN Race ON Animal.race_id = Race.id -- Jointure interne cette fois

253
6 Scuriser et automatiser ses actions

WHERE Race.nom = 'Maine Coon' -- Uniquement les Maine Coon...


AND Animal.id NOT IN (SELECT animal_id FROM Adoption) -- ... qui n'ont pas encore t
LOCK IN SHARE MODE ;

id nom date_naissance race prix

8 Bagherra 2008-09-11 15 :38 :00 Maine coon 735.00


34 Capou 2008-04-20 03 :22 :00 Maine coon 735.00
43 Cracotte 2007-03-12 11 :54 :00 Maine coon 735.00
61 Yoda 2010-11-09 00 :00 :00 Maine coon 735.00

Cest alors que le premier client, M. Dupont, dcide de craquer pour Bagherra.

INSERT INTO Adoption (client_id, animal_id, date_reservation, prix, paye)


SELECT id, 8, NOW(), 735.00, 1
FROM Client
WHERE email = 'jean.dupont@email.com' ;

COMMIT ;

Et M. Durant jette galement son dvolu sur Bagherra (qui est dcidment trs trs mignon) !

INSERT INTO Client (nom, prenom, email)


VALUES ('Durant', 'Philippe', 'phidu@email.com');

INSERT INTO Adoption (client_id, animal_id, date_reservation, prix, paye)


VALUES (LAST_INSERT_ID(), 8, NOW(), 735.00, 0);

Linsertion dans Client fonctionne mais linsertion dans Adoption pose problme :

ERROR1062(23000):Duplicateentry'8'forkey'ind_uni_animal_id'

Et pour cause : Bagherra vient dtre adopt, linstant. Furieux, M. Durant sen va, et llevage
a perdu un client. Il ne reste plus qu annuler sa transaction.

Cest pour viter ce genre de situation quil vaut parfois mieux mettre un verrou exclusif sur une
slection. Si lon sait que cette slection sert dterminer quels changements vont tre faits, ce
nest pas la peine de laisser quelquun dautre lire des informations qui cesseront dtre justes
incessamment sous peu.

6.2.4 Niveaux disolation


Nous avons vu que par dfaut :

lorsque lon dmarre une transaction, la session prend une photo des tables, et travaille
uniquement sur cette photo (donc sur des donnes potentiellement primes) tant quelle
ne pose pas un verrou ;
les requtes SELECT ne posent pas de verrous si lon ne le demande pas explicitement ;
les requtes SELECT ... LOCK IN SHARE MODE, SELECT ... FOR UPDATE, DELETE et
UPDATE posent un verrou de cl suivante (sauf dans le cas dune recherche sur index unique,
avec une valeur unique).

Ce comportement est dfini par le niveau disolation des transactions.

254
6.2 Verrous

6.2.4.1 Syntaxe

Pour dfinir le niveau disolation des transactions, on utilise la requte suivante :

SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED

Le mot-cl GLOBAL dfinit le niveau disolation pour toutes les sessions MySQL qui seront
cres dans le futur. Les sessions existantes ne sont pas affectes.
SESSION dfinit le niveau disolation pour la session courante.
Si lon ne prcise ni GLOBAL, ni SESSION, le niveau disolation dfini ne concernera que la
prochaine transaction que lon ouvrira dans la session courante.

6.2.4.2 Les diffrents niveaux

6.2.4.2.1 REPEATABLE READ Il sagit du niveau par dfaut, celui avec lequel vous tra-
vaillez depuis le dbut. Repeatable read signifie lecture rptable, cest--dire que si lon fait
plusieurs requtes de slection (non-verrouillantes) de suite, elles donneront toujours le mme
rsultat, quels que soient les changements effectus par dautres sessions. Si lon pense bien
utiliser les verrous l o cest ncessaire, cest un niveau disolation tout fait suffisant.

6.2.4.2.2 READ COMMITTED Avec ce niveau disolation, chaque requte SELECT (non-
verrouillante) va reprendre une photo jour de la base de donnes, mme si plusieurs SELECT
se font dans la mme transaction. Ainsi, un SELECT verra toujours les derniers changements
commits, mme sils ont t faits dans une autre session, aprs le dbut de la transaction.

6.2.4.2.3 READ UNCOMMITTED Le niveau READ UNCOMMITTED fonctionne comme


READ COMMITTED, si ce nest quil autorise la lecture sale. Cest--dire quune session sera
capable de lire des changements encore non commits par dautres sessions.

Exemple

Session 1 :

START TRANSACTION ;

UPDATE Race
SET prix = 0
WHERE id = 7 ;

Session 2 :

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;


START TRANSACTION ;

SELECT id, nom, espece_id, prix FROM Race;

id nom espece_id prix

1 Berger allemand 1 485.00


2 Berger blanc suisse 1 935.00
3 Singapura 2 985.00

255
6 Scuriser et automatiser ses actions

id nom espece_id prix

4 Bleu russe 2 835.00


5 Maine coon 2 735.00
7 Sphynx 2 0.00
8 Nebelung 2 985.00
9 Rottweiller 1 600.00

La modification faite par la session 1 na pas t commite. Elle ne sera donc potentiellement
jamais valide, auquel cas, elle naffectera jamais les donnes. Pourtant, la session 2 voit ce chan-
gement de donnes non-commites. Lecture sale na pas une connotation ngative par hasard,
bien entendu ! Aussi, vitez de travailler avec ce niveau disolation. Annulez la modification de
donnes ralise par la session 1 et terminez la transaction de la seconde session.

6.2.4.2.4 SERIALIZABLE Ce niveau disolation se comporte comme REPEATABLE READ,


sauf que lorsque le mode autocommit est dsactiv, tous les SELECT simples sont implicitement
convertis en SELECT ... LOCK IN SHARE MODE.

6.2.4.3 En rsum

Les verrous permettent de restreindre, voire interdire laccs, une partie des donnes.
Les verrous de table peuvent sutiliser sur des tables transactionnelles et non-transactionnelles,
contrairement aux verrous de ligne qui ne sont disponibles que pour des tables transac-
tionnelles.
Les verrous de lecture (tables) et partags (lignes) permettent aux autres sessions de
lire les donnes verrouilles, mais pas de les modifier. Les verrous dcriture (tables) et
exclusif (lignes) par contre, ne permettent aux autres sessions ni de lire, ni de modifier
les donnes verrouilles.
Les verrous de ligne sutilisent avec les transactions, et dpendent des index.
Les requtes de suppression, modification et insertion posent automatiquement un ver-
rou de ligne exclusif de cl suivante sur les lignes concernes par la requte. Les requtes de
slection par contre, ne posent pas de verrou par dfaut, il faut en poser un explicitement.
Le comportement par dfaut des verrous de ligne est dfini par le niveau disolation des
transactions, qui est modifiable.

6.3 Requtes prpares


Aprs les verrous et les transactions, voici une troisime notion importante pour la scurisation
des requtes : les requtes prpares.

Une requte prpare, cest en quelque sorte une requte stocke en mmoire (pour la session
courante), et que lon peut excuter loisir. Mais avant dentrer dans le vif du sujet, nous allons
faire un dtour par les variables utilisateur, qui sont indispensables aux requtes prpares.

Dans ce chapitre, nous apprendrons donc :

256
6.3 Requtes prpares

ce que sont les variables utilisateur et comment les dfinir ;


ce quest une requte prpare ;
comment crer, excuter et supprimer une requte prpare ;
la manire dutiliser les requtes prpares ;
en quoi les requtes prpares sont utiles pour la scurisation dune application ;
comment et dans quel cas on peut gagner en performance en utilisant les requtes prpa-
res.

6.3.1 Variables utilisateur

6.3.1.1 Dfinitions

6.3.1.1.1 Variable Une variable est une information stocke, constitue dun nom et dune
valeur. On peut par exemple avoir la variable age (son nom), ayant pour valeur 24.

6.3.1.1.2 Variable utilisateur Une variable utilisateur est une variable, dfinie par lutilisa-
teur. Les variables utilisateur MySQL doivent toujours tre **prcdes du signe @**.

Une variable utilisateur peut contenir quatre types de valeur :

un entier (ex. : 24) ;


un rel (ex. : 7,8) ;
une chane de caractres (ex. : 'Hello World !') ;
une chane binaire, auquel cas il faut faire prcder la chane du caractre b (ex. :
b'011001'). Nous nen parlerons pas dans ce cours.

[[attention]] | Pour les noms de vos variables utilisateur utilisez uniquement des lettres, des
chiffres, des ||_||, des ||$|| et des ||.||. Attention au fait que les noms des variables ne sont pas
sensibles la casse : @A est la mme variable utilisateur que @a.

Il existe galement ce quon appelle des variables systme, qui sont des variables prdfinies par
MySQL, et des variables locales, que nous verrons avec les procdures stockes.

6.3.1.2 Crer et modifier une variable utilisateur

6.3.1.2.1 SET La manire la plus classique de crer ou modifier une variable utilisateur est
dutiliser la commande SET.

SET @age = 24 ; -- Ne pas oublier le @


SET @salut = 'Hello World !', @poids = 7.8 ; -- On peut crer plusieurs variables en

Si la variable utilisateur existe dj, sa valeur sera modifie, sinon, elle sera cre.

SELECT @age, @poids, @salut;

@age
@poids @salut

24 7.8 Hello World !

257
6 Scuriser et automatiser ses actions

6.3.1.2.2 Oprateur dassignation Il est galement possible dassigner une valeur une va-
riable utilisateur directement dans une requte, en utilisant loprateur dassignation := (nou-
bliez pas les || :||, sinon il sagit de loprateur de comparaison de valeurs).

SELECT @age := 32, @poids := 48.15, @perroquet := 4 ;

@age := 32 @poids := 48.15 @perroquet := 4

32 48.15 4

[[information]] | On peut utiliser loprateur dassignation := dans une commande SET gale-
ment : SET @chat := 2;

6.3.1.3 Utilisation dune variable utilisateur

6.3.1.3.1 Ce quon peut faire Une fois votre variable utilisateur cre, vous pouvez bien
sr lafficher avec SELECT. Mais vous pouvez galement lutiliser dans des requtes ou faire des
calculs avec.

SELECT id, sexe, nom, commentaires, espece_id


FROM Animal
WHERE espece_id = @perroquet; -- On slectionne les perroquets

SET @conversionDollar = 1.31564 ; -- On cre une variable contenant le taux de co


SELECT prix AS prix_en_euros, -- On slectionne le prix des races, en euros et
ROUND(prix * @conversionDollar, 2) AS prix_en_dollars, -- En arrondissant d
nom FROM Race;

[[information]] | Si vous utilisez une variable utilisateur qui na pas t dfinie, vous nobtiendrez
aucune erreur. Simplement, la variable aura comme valeur NULL.

6.3.1.3.2 Ce quon ne peut pas faire Avec les variables utilisateur, on peut donc dynami-
ser un peu nos requtes. Cependant, il nest pas possible dutiliser les variables utilisateur pour
stocker un nom de table ou de colonne quon introduirait ensuite directement dans la requte. Ni
pour stocker une partie de commande SQL. Les variables utilisateur stockent des donnes.

Exemples

SET @table_clients = 'Client' ;

SELECT * FROM @table_clients;

ERROR1064(42000):YouhaveanerrorinyourSQLsyntax;checkthemanualthatcorresp

SET @colonnes = 'id, nom, description' ;

SELECT @colonnes FROM Race WHERE espece_id = 1 ;

258
6.3 Requtes prpares

@colonnes

id, nom, description


id, nom, description
id, nom, description

Cest logique, dans une requte, les noms de tables/colonnes et les commandes SQL ne peuvent
pas tre reprsentes comme des chanes de caractres : on ne les entoure pas de guillemets.

6.3.1.4 Porte des variables utilisateurs

Une variable utilisateur nexiste que pour la session dans laquelle elle a t dfinie. Lorsque vous
vous dconnectez, toutes vos variables utilisateurs sont automatiquement rinitialises. De plus,
deux sessions diffrentes ne partagent pas leurs variables utilisateur.

Exemple

Session 1 :

SET @essai = 3 ;

Session 2 :

SELECT @essai;

@essai

NULL

De mme, si vous assignez une valeur @essai_ dans la session 2, @essai_ vaudra toujours 3 pour la
session 1.

6.3.2 Principe et syntaxe des requtes prpares

6.3.2.1 Principe

Une requte prpare, cest en fait un modle de requte que lon enregistre et auquel on donne
un nom. On va ensuite pouvoir lexcuter en lappelant grce son nom, et en lui passant ven-
tuellement un ou plusieurs paramtres.

Par exemple, si vous avez rgulirement besoin daller chercher des informations sur vos clients
partir de leur adresse email dans une session, plutt que de faire :

SELECT * FROM Client WHERE email = 'truc@email.com' ;


SELECT * FROM Client WHERE email = 'machin@email.com' ;
SELECT * FROM Client WHERE email = 'bazar@email.com' ;
SELECT * FROM Client WHERE email = 'brol@email.com' ;

Vous pouvez prparer une requte modle :

SELECT * FROM Client WHERE email = ?

259
6 Scuriser et automatiser ses actions

O le || ?|| reprsente un paramtre. Ensuite, il suffit de lappeler par son nom en lui passant
'truc@email.com', ou 'machin@email.com', selon les besoins du moment.

[[information]] | Bien entendu, on parle dun cas o lon na quune seule adresse email la
fois. Typiquement, le cas o lon soccupe dun client la fois. Sinon, bien entendu, une simple
clause email IN ('truc@email.com', 'machin@email.com', 'bazar@email.com',
'brol@email.com') suffirait.

6.3.2.1.1 Porte Tout comme les variables utilisateur, une requte prpare nexiste que
pour la session qui la cre.

6.3.2.2 Syntaxe

Voyons comment faire tout a !

6.3.2.2.1 Prparation dune requte Pour prparer une requte, il faut renseigner deux
lments :

le nom quon donne la requte prpare ;


la requte elle-mme, avec un ou plusieurs paramtres (reprsents par un || ?||).

Voici la syntaxe utiliser :

PREPARE nom_requete
FROM 'requete_preparable' ;

Exemples

-- Sans paramtre
PREPARE select_race
FROM 'SELECT * FROM Race' ;

-- Avec un paramtre
PREPARE select_client
FROM 'SELECT * FROM Client WHERE email = ?' ;

-- Avec deux paramtres


PREPARE select_adoption
FROM 'SELECT * FROM Adoption WHERE client_id = ? AND animal_id = ?' ;

Plusieurs choses importantes :

Le nom de la requte prpare ne doit pas tre entre guillemets. Par contre la requte
prparer, si. La requte prparer doit tre passe comme une chane de caractres.
Que le paramtre soit un nombre (client_id = ?) ou une chane de caractres (email = ?), cela
ne change rien. On ne met pas de guillemets autour du || ?|| lintrieur de la requte
prparer.
La chane de caractres contenant la requte prparer ne peut contenir quune seule re-
qute (et non plusieurs spares par un || ;||).
Les paramtres ne peuvent reprsenter que des donnes, des valeurs, pas des noms de
tables ou de colonnes, ni des morceaux de commandes SQL.

260
6.3 Requtes prpares

Comme la requte prparer est donne sous forme de chane de caractres, il est galement
possible dutiliser une variable utilisateur, dans laquelle on enregistre tout ou partie de la requte
prparer.

Exemples

SET @req = 'SELECT * FROM Race' ;


PREPARE select_race
FROM @req;

SET @colonne = 'nom' ;


SET @req_animal = CONCAT('SELECT ', @colonne, ' FROM Animal WHERE id = ?');
PREPARE select_col_animal
FROM @req_animal;

Par contre, il nest pas possible de mettre directement la fonction CONCAT() dans la clause FROM.

[[information]] | Si vous donnez une requte prpare le nom dune requte prpare dj exis-
tante, cette dernire sera supprime et remplace par la nouvelle.

6.3.2.2.2 Excution dune requte prpare Pour excuter une requte prpare, on uti-
lise la commande suivante :

EXECUTE nom_requete [USING @parametre1, @parametre2, ...];

Si la requte prpare contient un ou plusieurs paramtres, on doit leur donner une valeur avec la
clause USING, en utilisant une variable utilisateur. Il nest pas possible de donner directement
une valeur. Par ailleurs, il faut donner exactement autant de variables utilisateur quil y a de pa-
ramtres dans la requte.

Exemples

EXECUTE select_race;

SET @id = 3 ;
EXECUTE select_col_animal USING @id;

SET @client = 2 ;
EXECUTE select_adoption USING @client, @id;

SET @email = 'jean.dupont@email.com' ;


EXECUTE select_client USING @email;

SET @email = 'marie.boudur@email.com' ;


EXECUTE select_client USING @email;

SET @email = 'fleurtrachon@email.com' ;


EXECUTE select_client USING @email;

SET @email = 'jeanvp@email.com' ;


EXECUTE select_client USING @email;

261
6 Scuriser et automatiser ses actions

SET @email = 'johanetpirlouit@email.com' ;


EXECUTE select_client USING @email;

6.3.2.2.3 Suppression dune requte prpare Pour supprimer une requte prpare, on
utilise DEALLOCATE PREPARE, suivi du nom de la requte prpare.

Exemple

DEALLOCATE PREPARE select_race;

6.3.3 Usage et utilit

6.3.3.1 Usage

La syntaxe que nous venons de voir, avec PREPARE, EXECUTE et DEALLOCATE est en fait trs rare-
ment utilise. En effet, vous le savez, MySQL est rarement utilis seul. La plupart du temps, il est
utilis en conjonction avec un langage de programmation, comme Java, PHP, python, etc. Celui-
ci permet de grer un programme, un site web, , et cre des requtes SQL permettant de grer
la base de donnes de lapplication. Or, il existe des API (interfaces de programmation) pour plu-
sieurs langages, qui permettent de faire des requtes prpares sans excuter vous-mmes les
commandes SQL PREPARE, EXECUTE et DEALLOCATE. Exemples : lAPI C MySQL pour le langage
C, MySQL Connector/J pour le Java, ou MySQL Connector/Net pour le .Net.

Voici un exemple de code C utilisant lAPI MySQL pour prparer et excuter une requte dinser-
tion :

MYSQL_STMT *req_prep;
MYSQL_BIND param[3];

int client = 2 ;
int animal = 56 ;
MYSQL_TIME date_reserv;

char *req_texte = "INSERT INTO Adoption (client_id, animal_id, date_reservation)

// On prpare la requte
if (mysql_stmt_prepare(req_prep, req_texte, strlen(req_texte)) != 0)
{
printf("Impossible de prparer la requte");
exit(0);
}

// On initialise un tableau (param, qui contiendra les paramtres) 0


memset((void*) param, 0, sizeof(param));

// On dfinit le premier paramtre (pour client_id)


param[0].buffer_type = MYSQL_TYPE_INT;
param[0].buffer = (void*) &client;
param[0].is_unsigned = 0 ;

262
6.3 Requtes prpares

param[0].is_null = 0 ;

// On dfinit le deuxime paramtre (pour animal_id)


param[1].buffer_type = MYSQL_TYPE_INT;
param[1].buffer = (void*) &animal;
param[1].is_unsigned = 0 ;
param[1].is_null = 0 ;

// On dfinit le troisime paramtre (pour date_reservation)


param[2].buffer_type = MYSQL_TYPE_DATE;
param[2].buffer = (void*) &date_reserv;
param[2].is_null = 0 ;

// On lie les paramtres


if (mysql_stmt_bind_param(req_prep, param) != 0)
{
printf("Impossible de lier les paramtres la requte");
exit(0);
}

// On dfinit la date
date_reserv.year = 2012 ;
date_reserv.month = 3 ;
date_reserv.day = 20 ;

// On excute la requte
if (mysql_stmt_execute(req_prep) != 0)
{
printf("Impossible d'excuter la requte");
exit(0);
}

Pour dautres langages, des extensions ont t cres, qui sont en gnral elles-mmes bases
sur lAPI C MySQL, comme par exemple PDO pour le PHP.

Exemple de code PHP utilisant lextension PDO pour prparer et excuter une requte de slec-
tion :

<?php
try
{
$email = 'jean.dupont@email.com' ;

// On se connecte
$bdd = new PDO('mysql:host=localhost;dbname=elevage', 'sdz', '', array(PDO ::ATTR_E

// On prpare la requte
$requete = $bdd->prepare("SELECT * FROM Client WHERE email = :email");

// On lie la variable $email dfinie au-dessus au paramtre :email de la requte pr

263
6 Scuriser et automatiser ses actions

$requete->bindValue(' :email', $email, PDO ::PARAM_STR);

//On excute la requte


$requete->execute();

// On rcupre le rsultat
if ($requete->fetch())
{
echo 'Le client existe !' ;
}
} catch (Exception $e)
{
die('Erreur : ' . $e->getMessage());
}

[[question]] | Pourquoi nous avoir fait apprendre la syntaxe SQL si on ne sen sert jamais ?

Dabord parce quil est toujours intressant de savoir comment fonctionnent les requtes prpa-
res. Ensuite, parce quil pourrait arriver quil nexiste aucune API ni extension permettant de
faire des requtes prpares pour le langage dans lequel vous programmez, auquel cas, bien en-
tendu, il faudrait construire vos requtes prpares vous-mmes. Ou vous pourriez tomber sur
lun des rares cas o il vous serait ncessaire de prparer une requte directement en SQL. Enfin,
vous aurez peut-tre besoin de faire quelques tests impliquant des requtes prpares directe-
ment dans MySQL.

Cependant, si une API ou une extension existe et rpond vos besoins, utilisez-la ! Elle sera g-
nralement plus performante et plus scurise que ce que vous pourriez faire vous-mmes.

6.3.3.2 Utilit

Les requtes prpares sont principalement utilises pour deux raisons :

protger son application des injections SQL ;


gagner en performance dans le cas dune requte excute plusieurs fois par la mme ses-
sion.

6.3.3.2.1 Empcher les injections SQL En gnral, quand on cre une application, luti-
lisateur peut interagir avec celle-ci. Lutilisateur peut crer un membre sur un site web commu-
nautaire, un personnage sur un jeu vido, etc. Les actions de lutilisateur vont donc avoir une
incidence sur la base de donnes de lapplication. Il va envoyer certaines informations, qui vont
tre traites, puis une partie va tre envoye sous forme de requtes la base de donnes. Il existe
un adage bien connu en programmation : Never trust user input traduit en franais par Ne jamais
faire confiance aux donnes fournies par lutilisateur.

Lorsque lon traite des donnes qui viennent de lextrieur, il est absolument impratif de tou-
jours vrifier celles-ci, et de protger les requtes construites partir de ces donnes. Ceci vite
que lutilisateur, volontairement ou non, fasse ce quon appelle une injection SQL et provoque un
comportement inattendu et souvent indsirable, voire dangereux, pour les donnes.

[[attention]] | Les injections SQL sont un type de failles exploitables par lutilisateur. Il existe de
nombreux autres types de failles.

264
6.3 Requtes prpares

Passons maintenant la question qui doit vous brler les lvres.

[[question]] | Mais quest-ce quune injection SQL ?

On appelle injection SQL le fait quun utilisateur fournisse des donnes contenant des mots-cls
SQL ou des caractres particuliers qui vont dtourner ou modifier le comportement des requtes
construites sur la base de ces donnes.

Imaginons que vous criez un site web avec un espace membre. Vos membres ont accs une
page profil grce laquelle ils peuvent grer leurs informations, ou supprimer leur compte.
Pour supprimer leur compte, ils ont simplement appuyer sur un bouton qui envoie leur numro
did.

Dun ct on a donc une requte incomplte :

DELETE FROM Membre WHERE id =

De lautre, lid du client, par exemple 4.

Avec votre langage de programmation web prfr, vous mettez les deux ensemble pour crer la
requte complte :

DELETE FROM Membre WHERE id = 4 ;

Et maintenant, un mchant pirate samuse modifier la donne envoye par le bouton Suppri-
mer compte (oui oui, cest faisable, et mme plutt facile). la suite du numro did, il ajoute
OR 1 = 1. Aprs construction de la requte, on obtient donc ceci :

DELETE FROM Membre WHERE id = 4 OR 1 = 1 ;

Et la seconde condition tant toujours remplie, cest lhorreur : toute votre table Membre est effa-
ce ! Et voil ! Vous avez t victimes dune injection SQL.

[[question]] | Comment une requte prpare peut-elle viter les injections SQL ?

En utilisant les requtes prpares, lorsque vous liez une valeur un paramtre de la requte pr-
pare grce la fonction correspondante dans le langage que vous utilisez, le type du paramtre
attendu est galement donn, explicitement ou implicitement. La plupart du temps, soit une er-
reur sera gnre si la donne de lutilisateur ne correspond pas au type attendu, soit la donne de
lutilisateur sera rendue inoffensive (par lajout de guillemets qui en feront une simple chane de
caractres par exemple). Par ailleurs, lorsque MySQL injecte les valeurs dans la requte, les mots-
cls SQL qui sy trouveraient pour une raison o une autre ne seront pas interprts (puisque les
paramtres nacceptent que des valeurs, et pas des morceaux de requtes).

6.3.3.2.2 Gagner en performance Lorsque lon excute une simple requte (sans la prpa-
rer), voici les tapes qui sont effectues :

1. La requte est envoye vers le serveur.


2. La requte est compile par le serveur (traduite du langage SQL comprhensible pour nous,
pauvres humains limits, vers un langage machine dont se sert le serveur).
3. Le serveur cre un plan dexcution de la requte (quelles tables utiliser ? quels index ? dans
quel ordre ?).
4. Le serveur excute la requte.
5. Le rsultat de la requte est renvoy au client.

265
6 Scuriser et automatiser ses actions

Voici maintenant les tapes lorsquil sagit dune requte prpare :

Prparation de la requte :

1. La requte est envoye vers le serveur, avec un identifiant.


2. La requte est compile par le serveur.
3. Le serveur cre un plan dexcution de la requte.
4. La requte compile et son plan dexcution sont stocks en mmoire par le serveur.
5. Le serveur envoie vers le client une confirmation que la requte est prte (en se servant de
lidentifiant de la requte).

Excution de la requte :

1. Lidentifiant de la requte excuter, ainsi que les paramtres utiliser sont envoys vers
le serveur.
2. Le serveur excute la requte demande.
3. Le rsultat de la requte est renvoy au client.

On a donc plus dtapes pour une requte prpare, que pour une requte non prpare (8 contre
5). Du moins, lorsque lon excute une seule fois la requte. Car si lon excute plusieurs fois
une requte, la tendance sinverse rapidement : dans le cas dune requte non prpare, toutes
les tapes doivent tre rptes chaque fois (sachant que la cration du plan dexcution est
ltape la plus longue), alors que pour une requte prpare, seule les tapes dexcution seront
rptes.

Il est donc intressant, dun point de vue des performances, de prparer une requte lorsquelle
va tre excute plusieurs fois pendant la mme session (je vous rappelle que les requtes pr-
pares sont supprimes la fin de la session).

[[information]] | Le fait que la requte soit compile lors de sa prparation et que le plan dex-
cution soit calcul galement lors de la prparation et non de lexcution, explique pourquoi les
paramtres peuvent uniquement tre des valeurs, et non des parties de requtes comme des noms
de tables ou des mots-cls. En effet, il est impossible de crer le plan si lon ne sait pas encore
sur quelles tables on travaillera, ni quelles colonnes (et donc quels index) seront utilises.

*[API] : Application Programming Interface

6.3.3.3 En rsum

Les variables utilisateur sont, comme leur nom lindique, des variables dfinies par luti-
lisateur.
Les variables utilisateur sont prcdes du caractre ||@|| et peuvent tre dfinies par la
commande SET ou loprateur dassignation :=.
Une requte prpare est un modle de requte auquel on donne un nom, pour pouvoir
lappeler loisir, en lui passant ventuellement des paramtres, reprsents dans la re-
qute prpare par le caractre || ?||.
Lorsque lon prpare une requte, celle-ci doit tre reprsente par une chane de carac-
tres, qui peut tre pralablement stocke dans une variable utilisateur.
Les requtes prpares permettent de se protger des injections SQL.

266
6.4 Procdures stockes

Lorsquune requte doit tre excute plusieurs fois, la prparer peut permettre de gagner
en performance.

6.4 Procdures stockes

Les procdures stockes sont disponibles depuis la version 5 de MySQL, et permettent dautoma-
tiser des actions, qui peuvent tre trs complexes.

Une procdure stocke est en fait une srie dinstructions SQL dsigne par un nom. Lorsque lon
cre une procdure stocke, on lenregistre dans la base de donnes que lon utilise, au mme
titre quune table par exemple. Une fois la procdure cre, il est possible dappeler celle-ci, par
son nom. Les instructions de la procdure sont alors excutes.

Contrairement aux requtes prpares, qui ne sont gardes en mmoire que pour la session cou-
rante, les procdures stockes sont, comme leur nom lindique, stockes de manire durable, et
font bien partie intgrante de la base de donnes dans laquelle elles sont enregistres.

6.4.1 Cration et utilisation dune procdure

Voyons tout de suite la syntaxe utiliser pour crer une procdure :

CREATE PROCEDURE nom_procedure ([parametre1 [, parametre2, ...]])


corps de la procdure;

Dcodons tout ceci.

CREATE PROCEDURE : sans surprise, il sagit de la commande excuter pour crer une
procdure. On fait suivre cette commande du nom que lon veut donner la nouvelle pro-
cdure.
([parametre1 [, parametre2, ...]]) : aprs le nom de la procdure viennent des
parenthses. Celles-ci sont obligatoires ! lintrieur de ces parenthses, on dfinit les
ventuels paramtres de la procdure. Ces paramtres sont des variables qui pourront tre
utilises par la procdure.
corps de la procdure : cest l que lon met le contenu de la procdure, ce qui va tre ex-
cut lorsquon lance la procdure. Cela peut tre soit une seule requte, soit un bloc dins-
tructions.

[[information]] | Les noms des procdures stockes ne sont pas sensibles la casse.

6.4.1.1 Procdure avec une seule requte

Voici une procdure toute simple, sans paramtres, qui va juste afficher toutes les races dani-
maux.

CREATE PROCEDURE afficher_races_requete() -- pas de paramtres dans les parenthses


SELECT id, nom, espece_id, prix FROM Race;

267
6 Scuriser et automatiser ses actions

6.4.1.2 Procdure avec un bloc dinstructions

Pour dlimiter un bloc dinstructions (qui peut donc contenir plus dune instruction), on utilise
les mots BEGIN et END.

BEGIN
-- Srie d'instructions
END ;

Exemple : reprenons la procdure prcdente, mais en utilisant un bloc dinstructions.

CREATE PROCEDURE afficher_races_bloc() -- pas de paramtres dans les parenthses


BEGIN
SELECT id, nom, espece_id, prix FROM Race;
END ;

Malheureusement

ERROR1064(42000):YouhaveanerrorinyourSQLsyntax;checkthemanualthatcorresp

[[question]] | Que sest-il pass ? La syntaxe semble correcte

Les mots-cls sont bons, il ny a pas de paramtres mais on a bien mis les parenthses, BEGIN et
END sont tous les deux prsents. Tout cela est correct, et pourtant, nous avons visiblement omis
un dtail.

Peut-tre aurez-vous compris que le problme se situe au niveau du caractre || ;|| : en effet,
un || ;|| termine une instruction SQL. Or, on a mis un || ;|| la suite de SELECT * FROM Race;.
Cela semble logique, mais pose problme puisque cest le premier || ;|| rencontr par linstruc-
tion CREATE PROCEDURE, qui naturellement pense devoir sarrter l. Ceci dclenche une erreur
puisquen ralit, linstruction CREATE PROCEDURE nest pas termine : le bloc dinstructions
nest pas complet !

[[question]] | Comment faire pour crire des instructions lintrieur dune instruction alors ?

Il suffit de changer le dlimiteur !

6.4.1.3 Dlimiteur

Ce quon appelle dlimiteur, cest tout simplement (par dfaut), le caractre || ;||. Cest--dire
le caractre qui permet de dlimiter les instructions. Or, il est tout fait possible de dfinir le
dlimiteur manuellement, de manire ce que || ;|| ne signifie plus quune instruction se termine.
Auquel cas le caractre || ;|| pourra tre utilis lintrieur dune instruction, et donc pourra tre
utilis dans le corps dune procdure stocke.

Pour changer le dlimiteur, il suffit dutiliser cette commande :

DELIMITER |

partir de maintenant, vous devrez utiliser le caractre ||||| pour signaler la fin dune instruction.
|| ;|| ne sera plus compris comme tel par votre session.

SELECT 'test'|

268
6.4 Procdures stockes

test

test

[[information]] | DELIMITER nagit que pour la session courante.

Vous pouvez utiliser le (ou les) caractre(s) de votre choix comme dlimiteur. Bien entendu, il
vaut mieux choisir quelque chose qui ne risque pas dtre utilis dans une instruction. Bannissez
donc les lettres, chiffres, ||@|| (qui servent pour les variables utilisateurs) et les |||| (qui servent
chapper les caractres spciaux).

Les deux dlimiteurs suivants sont les plus couramment utiliss :

DELIMITER //
DELIMITER |

Bien ! Ceci tant rgl, reprenons !

6.4.1.4 Cration dune procdure stocke

DELIMITER | -- On change le dlimiteur


CREATE PROCEDURE afficher_races() -- toujours pas de paramtres, toujours des pare
BEGIN
SELECT id, nom, espece_id, prix
FROM Race; -- Cette fois, le ; ne nous embtera pas
END| -- Et on termine bien sr la commande CREATE PROC

Cette fois-ci, tout se passe bien. La procdure a t cre.

[[information]] | Lorsquon utilisera la procdure, quel que soit le dlimiteur dfini par
DELIMITER, les instructions lintrieur du corps de la procdure seront bien dlimites
par || ;||. En effet, lors de la cration dune procdure, celle-ci est interprte on dit aussi
parse par le serveur MySQL et le parseur des procdures stockes interprtera toujours || ;||
comme dlimiteur. Il nest pas influenc par la commande DELIMITER.

Les procdures stockes ntant que trs rarement composes dune seule instruction, on utilise
presque toujours un bloc dinstructions pour le corps de la procdure.

6.4.1.5 Utilisation dune procdure stocke

Pour appeler une procdure stocke, cest--dire dclencher lexcution du bloc dinstructions
constituant le corps de la procdure, il faut utiliser le mot-cl CALL, suivi du nom de la procdure
appele, puis de parenthses (avec ventuellement des paramtres).

CALL afficher_races()| -- le dlimiteur est toujours | !!!

id nom espece_id prix

1 Berger allemand 1 485.00


2 Berger blanc suisse 1 935.00
3 Singapura 2 985.00
4 Bleu russe 2 835.00

269
6 Scuriser et automatiser ses actions

id nom espece_id prix

5 Maine coon 2 735.00


7 Sphynx 2 1235.00
8 Nebelung 2 985.00
9 Rottweiller 1 600.00

Le bloc dinstructions a bien t excut (un simple SELECT dans ce cas).

6.4.2 Les paramtres dune procdure stocke


Maintenant que lon sait crer une procdure et lappeler, intressons-nous aux paramtres.

6.4.2.1 Sens des paramtres

Un paramtre peut tre de trois sens diffrents : entrant (IN), sortant (OUT), ou les deux (INOUT).

IN : cest un paramtre entrant. Cest--dire quil sagit dun paramtre dont la valeur
est fournie la procdure stocke. Cette valeur sera utilise pendant la procdure (pour un
calcul ou une slection par exemple).
OUT : il sagit dun paramtre sortant, dont la valeur va tre tablie au cours de la proc-
dure et qui pourra ensuite tre utilis en dehors de cette procdure.
INOUT : un tel paramtre sera utilis pendant la procdure, verra ventuellement sa valeur
modifie par celle-ci, et sera ensuite utilisable en dehors.

6.4.2.2 Syntaxe

Lorsque lon cre une procdure avec un ou plusieurs paramtres, chaque paramtre est dfini
par trois lments.

Son sens : entrant, sortant, ou les deux. Si aucun sens nest donn, il sagira dun paramtre
IN par dfaut.
Son nom : indispensable pour le dsigner lintrieur de la procdure.
Son type : INT, VARCHAR(10),

6.4.2.3 Exemples

6.4.2.3.1 Procdure avec un seul paramtre entrant Voici une procdure qui, selon lid
de lespce quon lui passe en paramtre, affiche les diffrentes races existant pour cette espce.

DELIMITER | -- Facultatif si votre


CREATE PROCEDURE afficher_race_selon_espece (IN p_espece_id INT) -- Dfinition du para
BEGIN
SELECT id, nom, espece_id, prix
FROM Race
WHERE espece_id = p_espece_id; -- Utilisation du para
END |
DELIMITER ; -- On remet le dlimit

270
6.4 Procdures stockes

[[information]] | Notez que, suite la cration de la procdure, jai remis le dlimiteur par dfaut
|| ;||. Ce nest absolument pas obligatoire, vous pouvez continuer travailler avec ||||| si vous
prfrez.

Pour lutiliser, il faut donc passer une valeur en paramtre de la procdure. Soit directement, soit
par lintermdiaire dune variable utilisateur.

CALL afficher_race_selon_espece(1);
SET @espece_id := 2 ;
CALL afficher_race_selon_espece(@espece_id);

id nom espece_id prix

1 Berger allemand 1 485.00


2 Berger blanc suisse 1 935.00
9 Rottweiller 1 600.00

id nom espece_id prix

3 Singapura 2 985.00
4 Bleu russe 2 835.00
5 Maine coon 2 735.00
7 Sphynx 2 1235.00
8 Nebelung 2 985.00

Le premier appel la procdure affiche bien toutes les races de chiens, et le second, toutes les
races de chats.

[[attention]] | Jai fait commencer le nom du paramtre par p_. Ce nest pas obligatoire, mais
je vous conseille de le faire systmatiquement pour vos paramtres afin de les distinguer facile-
ment. Si vous ne le faites pas, soyez extrmement prudents avec les noms que vous leur donnez.
Par exemple, dans cette procdure, si on avait nomm le paramtre espece_id, cela aurait pos
problme, puisque espece_id est aussi le nom dune colonne dans la table Race. Qui plus est, cest
le nom de la colonne dont on se sert dans la condition WHERE. En cas dambigut, MySQL inter-
prte llment comme tant le paramtre, et non la colonne. On aurait donc eu WHERE 1 = 1
par exemple, ce qui est toujours vrai.

6.4.2.3.2 Procdure avec deux paramtres, un entrant et un sortant Voici une pro-
cdure assez similaire la prcdente, si ce nest quelle naffiche pas les races existant pour une
espce, mais compte combien il y en a, puis stocke cette valeur dans un paramtre sortant.

DELIMITER |
CREATE PROCEDURE compter_races_selon_espece (p_espece_id INT, OUT p_nb_races INT)
BEGIN
SELECT COUNT(*) INTO p_nb_races
FROM Race
WHERE espece_id = p_espece_id;
END |
DELIMITER ;

271
6 Scuriser et automatiser ses actions

Aucun sens na t prcis pour p_espece_id, il est donc considr comme un paramtre entrant.

SELECT COUNT(*) INTO p_nb_races. Voila qui est nouveau ! Comme vous lavez sans doute
devin, le mot-cl INTO plac aprs la clause SELECT permet dassigner les valeurs slection-
nes par ce SELECT des variables, au lieu de simplement afficher les valeurs slectionnes. Dans
le cas prsent, la valeur du COUNT(*) est assigne p_nb_races.

Pour pouvoir lutiliser, il est ncessaire que le SELECT ne renvoie quune seule ligne, et il faut
que le nombre de valeurs slectionnes et le nombre de variables assigner soient gaux :

Exemple 1 : SELECT ... INTO correct avec deux valeurs

SELECT id, nom INTO @var1, @var2


FROM Animal
WHERE id = 7 ;
SELECT @var1, @var2;

@var1 @var2

7 Caroline

Le SELECT ... INTO na rien affich, mais a assign la valeur 7 @var1_, et la valeur 'Caroline'
@var2_, que nous avons ensuite affiches avec un autre SELECT.

Exemple 2 : SELECT ... INTO incorrect, car le nombre de valeurs slectionnes (deux) nest
pas le mme que le nombre de variables assigner (une).

SELECT id, nom INTO @var1


FROM Animal
WHERE id = 7 ;

ERROR1222(21000):TheusedSELECTstatementshaveadifferentnumberofcolumns

Exemple 3 : SELECT ... INTO incorrect, car il y a plusieurs lignes de rsultats.

SELECT id, nom INTO @var1, @var2


FROM Animal
WHERE espece_id = 5 ;

ERROR1172(42000):Resultconsistedofmorethanonerow

Revenons maintenant notre nouvelle procdure compter_races_selon_espece() et excutons-la.


Pour cela, il va falloir lui passer deux paramtres : p_espece_id et p_nb_races. Le premier ne pose
pas de problme, il faut simplement donner un nombre, soit directement soit par lintermdiaire
dune variable, comme pour la procdure acher_race_selon_espece(). Par contre, pour le second, il
sagit dun paramtre sortant. Il ne faut donc pas donner une valeur, mais quelque chose dont la
valeur sera dtermine par la procdure (grce au SELECT ... INTO), et quon pourra utiliser
ensuite : une variable utilisateur !

CALL compter_races_selon_espece (2, @nb_races_chats);

272
6.4 Procdures stockes

Et voil ! La variable _@nb_races_chats_ contient maintenant le nombre de races de chats. Il


suffit de lafficher pour vrifier.

SELECT @nb_races_chats;

273
6 Scuriser et automatiser ses actions

@nb_races_chats

6.4.2.3.3 Procdure avec deux paramtres, un entrant et un entrant-sortant Nous


allons crer une procdure qui va servir calculer le prix que doit payer un client. Pour cela,
deux paramtres sont ncessaires : lanimal achet (paramtre IN), et le prix payer (paramtre
INOUT). La raison pour laquelle le prix est un paramtre la fois entrant et sortant est quon veut
pouvoir, avec cette procdure, calculer simplement un prix total dans le cas o un client ach-
terait plusieurs animaux. Le principe est simple : si le client na encore achet aucun animal, le
prix est de 0. Pour chaque animal achet, on appelle la procdure, qui ajoute au prix total le prix
de lanimal en question. Une fois nest pas coutume, commenons par voir les requtes qui nous
serviront tester la procdure. Cela devrait clarifier le principe. Je vous propose dessayer ensuite
dcrire vous-mmes la procdure correspondante avant de regarder quoi elle ressemble.

SET @prix = 0 ; -- On initialise @prix 0

CALL calculer_prix (13, @prix); -- Achat de Rouquine


SELECT @prix AS prix_intermediaire;

CALL calculer_prix (24, @prix); -- Achat de Cartouche


SELECT @prix AS prix_intermediaire;

CALL calculer_prix (42, @prix); -- Achat de Bilba


SELECT @prix AS prix_intermediaire;

CALL calculer_prix (75, @prix); -- Achat de Mimi


SELECT @prix AS total;

On passe donc chaque animal achet tour tour la procdure, qui modifie le prix en consquence.
Voici quelques indices et rappels qui devraient vous aider crire vous-mmes la procdure.

Le prix nest pas un nombre entier.


Il est possible de faire des additions directement dans un SELECT.
Pour dterminer le prix, il faut utiliser la fonction COALESCE().

Rponse :

[[secret]] | sql | DELIMITER | |


| CREATE PROCEDURE calculer_prix (IN p_animal_id
INT, INOUT p_prix DECIMAL(7,2)) | BEGIN | SELECT p_prix + COALESCE(Race.prix,
Espece.prix) INTO p_prix | FROM Animal | INNER JOIN Espece ON Espece.id
= Animal.espece_id | LEFT JOIN Race ON Race.id = Animal.race_id | WHERE
Animal.id = p_animal_id; | END | | | DELIMITER ; |

Et voici ce quaffichera le code de test :

prix_intermediaire

485.00

274
6.4 Procdures stockes

prix_intermediaire

685.00

prix_intermediaire

1420.00

total

1430.00

Voil qui devrait nous simplifier la vie. Et nous nen sommes quau dbut des possibilits des
procdures stockes !

6.4.3 Suppression dune procdure


Vous commencez connatre cette commande : pour supprimer une procdure, on utilise DROP
(en prcisant quil sagit dune procdure).

Exemple :

DROP PROCEDURE afficher_races;

Pour rappel, les procdures stockes ne sont pas dtruites la fermeture de la session mais bien
enregistres comme un lment de la base de donnes, au mme titre quune table par exemple.

Notons encore quil nest pas possible de modifier une procdure directement. La seule faon de
modifier une procdure existante est de la supprimer puis de la recrer avec les modifications.

[[information]] | Il existe bien une commande ALTER PROCEDURE, mais elle ne permet de chan-
ger ni les paramtres, ni le corps de la procdure. Elle permet uniquement de changer certaines
caractristiques de la procdure, et ne sera pas couverte dans ce cours.

6.4.4 Avantages, inconvnients et usage des procdures stockes

6.4.4.1 Avantages

Les procdures stockes permettent de rduire les allers-retours entre le client et le serveur
MySQL. En effet, si lon englobe en une seule procdure un processus demandant lexcution de
plusieurs requtes, le client ne communique quune seule fois avec le serveur (pour demander
lexcution de la procdure) pour excuter la totalit du traitement. Cela permet donc un certain
gain en performance.

Elles permettent galement de scuriser une base de donnes. Par exemple, il est possible de res-
treindre les droits des utilisateurs de faon ce quils puissent uniquement excuter des proc-
dures. Finis les DELETE dangereux ou les UPDATE inconsidrs. Chaque requte excute par les
utilisateurs est cre et contrle par ladministrateur de la base de donnes par lintermdiaire
des procdures stockes.

275
6 Scuriser et automatiser ses actions

Cela permet ensuite de sassurer quun traitement est toujours excut de la mme manire,
quelle que soit lapplication/le client qui le lance. Il arrive par exemple quune mme base de don-
nes soit exploite par plusieurs applications, lesquelles peuvent tre crites avec diffrents lan-
gages. Si on laisse chaque application avoir son propre code pour un mme traitement, il est pos-
sible que des diffrences apparaissent (distraction, mauvaise communication, erreur ou autre).
Par contre, si chaque application appelle la mme procdure stocke, ce risque disparat.

6.4.4.2 Inconvnients

Les procdures stockes ajoutent videmment la charge sur le serveur de donnes. Plus on
implmente de logique de traitement directement dans la base de donnes, moins le serveur est
disponible pour son but premier : le stockage de donnes.

Par ailleurs, certains traitements seront toujours plus simples et plus courts crire (et donc
maintenir) sils sont dvelopps dans un langage informatique adapt. A fortiori lorsquil sagit
de traitements complexes. La logique quil est possible dimplmenter avec MySQL permet de
nombreuses choses, mais reste assez basique.

Enfin, la syntaxe des procdures stockes diffre beaucoup dun SGBD un autre. Par cons-
quent, si lon dsire en changer, il faudra procder un grand nombre de corrections et dajuste-
ments.

6.4.4.3 Conclusion et usage

Comme souvent, tout est question dquilibre. Il faut savoir utiliser des procdures quand cest
utile, quand on a une bonne raison de le faire. Il ne sert rien den abuser. Pour une base contenant
des donnes ultrasensibles, une bonne gestion des droits des utilisateurs couple lusage de
procdures stockes peut se rvler salutaire. Pour une base de donnes destine tre utilise
par plusieurs applications diffrentes, on choisira de crer des procdures pour les traitements
gnraux et/ou pour lesquels la moindre erreur peut poser de gros problmes. Pour un traitement
long, impliquant de nombreuses requtes et une logique simple, on peut srieusement gagner
en performance en le faisant dans une procdure stocke (a fortiori si ce traitement est souvent
lanc).

vous de voir quelles procdures sont utiles pour votre application et vos besoins.

*SGBD : Systme de Gestion de Base de Donnes

6.4.4.4 En rsum

Une procdure stocke est un ensemble dinstructions que lon peut excuter sur com-
mande.
Une procdure stocke est un objet de la base de donnes stock de manire durable, au
mme titre quune table. Elle nest pas supprime la fin de la session comme lest une
requte prpare.
On peut passer des paramtres une procdure stocke, qui peuvent avoir trois sens : IN
(entrant), OUT (sortant) ou INOUT (les deux).

276
6.5 Structurer ses instructions

SELECT ... INTO permet dassigner des donnes slectionnes des variables ou des
paramtres, condition que le SELECT ne renvoie quune seule ligne, et quil y ait autant
de valeurs slectionnes que de variables assigner.
Les procdures stockes peuvent permettre de gagner en performance en diminuant les
allers-retours entre le client et le serveur. Elles peuvent galement aider scuriser une
base de donnes et sassurer que les traitements sensibles soient toujours excuts de
la mme manire.
Par contre, elle ajoute la charge du serveur et sa syntaxe nest pas toujours portable
dun SGBD un autre.

*SGBD : Systme de Gestion de Base de Donnes

6.5 Structurer ses instructions


Lorsque lon crit une srie dinstructions, par exemple dans le corps dune procdure stocke,
il est ncessaire dtre capable de structurer ses instructions. Cela va permettre dinstiller de la
logique dans le traitement : excuter telles ou telles instructions en fonction des donnes que
lon possde, rpter une instruction un certain nombre de fois, etc.

Voici quelques outils indispensables la structuration des instructions :

les variables locales : qui vont permettre de stocker et modifier des valeurs pendant le
droulement dune procdure ;
les conditions : qui vont permettre dexcuter certaines instructions seulement si une cer-
taine condition est remplie ;
les boucles : qui vont permettre de rpter une instruction plusieurs fois.

Ces structures sont bien sr utilisables dans les procdures stockes, que nous avons vues au
chapitre prcdent, mais pas uniquement. Elles sont utilisables dans tout objet dfinissant une
srie dinstructions excuter. Cest le cas des fonctions stockes (non couvertes par ce cours et
qui forment avec les procdures stockes ce quon appelle les routines), des vnements (non
couverts), et galement des triggers, auxquels un chapitre est consacr la fin de cette partie.

6.5.1 Blocs dinstructions et variables locales

6.5.1.1 Blocs dinstructions

Nous avons vu quun bloc dinstructions tait dfini par les mots-cls BEGIN et END, entre les-
quels on met les instructions qui composent le bloc (de zro autant dinstructions que lon veut,
spares bien sr dun || ;||).

Il est possible dimbriquer plusieurs blocs dinstructions. De mme, lintrieur dun bloc dins-
tructions, plusieurs blocs dinstructions peuvent se suivre. Ceux-ci permettent donc de structu-
rer les instructions en plusieurs parties distinctes et sur plusieurs niveaux dimbrication diff-
rents.

BEGIN
SELECT 'Bloc d''instructions principal' ;

BEGIN

277
6 Scuriser et automatiser ses actions

SELECT 'Bloc d''instructions 2, imbriqu dans le bloc principal' ;

BEGIN
SELECT 'Bloc d''instructions 3, imbriqu dans le bloc d''instructions 2' ;
END ;
END ;

BEGIN
SELECT 'Bloc d''instructions 4, imbriqu dans le bloc principal' ;
END ;

END ;

[[information]] | Cet exemple montre galement limportance de lindentation pour avoir un


code lisible. Ici, toutes les instructions dun bloc sont au mme niveau et dcales vers la droite
par rapport la dclaration du bloc. Cela permet de voir en un coup dil o commence et o se
termine chaque bloc dinstructions.

6.5.1.2 Variables locales

Nous connaissons dj les variables utilisateur, qui sont des variables dsignes par ||@||. Jai
galement mentionn lexistence des variables systme, qui sont des variables prdfinies par
MySQL. Voyons maintenant les variables locales, qui peuvent tre dfinies dans un bloc dins-
tructions.

6.5.1.2.1 Dclaration dune variable locale La dclaration dune variable locale se fait avec
linstruction DECLARE :

DECLARE nom_variable type_variable [DEFAULT valeur_defaut];

Cette instruction doit se trouver au tout dbut du bloc dinstructions dans lequel la variable locale
sera utilise (donc directement aprs le BEGIN).

On a donc une structure gnrale des blocs dinstructions qui se dgage :

BEGIN
-- Dclarations (de variables locales par exemple)

-- Instructions (dont ventuels blocs d'instructions imbriqus)


END ;

[[information]] | Tout comme pour les variables utilisateur, le nom des variables locales nest pas
sensible la casse.

Si aucune valeur par dfaut nest prcise, la variable vaudra NULL tant que sa valeur nest pas
change. Pour changer la valeur dune variable locale, on peut utiliser SET ou SELECT ... INTO.

Exemple : voici une procdure stocke qui donne la date daujourdhui et de demain :

DELIMITER |
CREATE PROCEDURE aujourdhui_demain ()
BEGIN

278
6.5 Structurer ses instructions

DECLARE v_date DATE DEFAULT CURRENT_DATE(); -- On dclare une variabl

SELECT DATE_FORMAT(v_date, '%W %e %M %Y') AS Aujourdhui;

SET v_date = v_date + INTERVAL 1 DAY ; -- On change la valeur d


SELECT DATE_FORMAT(v_date, '%W %e %M %Y') AS Demain;
END|
DELIMITER ;

Testons-la :

SET lc_time_names = 'fr_FR' ;


CALL aujourdhui_demain();

Aujourdhui

mardi 1 mai 2012

Demain

mercredi 2 mai 2012

[[attention]] | Tout comme pour les paramtres, les variables locales peuvent poser problme si
lon ne fait pas attention au nom quon leur donne. En cas de conflit (avec un nom de colonne
par exemple), comme pour les paramtres, le nom sera interprt comme dsignant la variable
locale en priorit. Par consquent, toutes mes variables locales seront prfixes par v_.

6.5.1.2.2 Porte des variables locales dans un bloc dinstruction Les variables locales
nexistent que dans le bloc dinstructions dans lequel elles ont t dclares. Ds que le mot-cl
END est atteint, toutes les variables locales du bloc sont dtruites.

Exemple 1 :

DELIMITER |
CREATE PROCEDURE test_portee1()
BEGIN
DECLARE v_test1 INT DEFAULT 1 ;

BEGIN
DECLARE v_test2 INT DEFAULT 2 ;

SELECT 'Imbriqu' AS Bloc;


SELECT v_test1, v_test2;
END ;
SELECT 'Principal' AS Bloc;
SELECT v_test1, v_test2;

END|
DELIMITER ;

279
6 Scuriser et automatiser ses actions

CALL test_portee1();

Bloc

Imbriqu

v_test1 v_test2

1 2

Bloc

Principal

ERROR1054(42S22):Unknowncolumn'v_test2'in'fieldlist'

La variable locale v_test2 existe bien dans le bloc imbriqu, puisque cest l quelle est dfinie,
mais pas dans le bloc principal. v_test1 par contre existe dans le bloc principal (o elle est dfinie),
mais aussi dans le bloc imbriqu.

Exemple 2 :

DELIMITER |
CREATE PROCEDURE test_portee2()
BEGIN
DECLARE v_test1 INT DEFAULT 1 ;

BEGIN
DECLARE v_test2 INT DEFAULT 2 ;

SELECT 'Imbriqu 1' AS Bloc;


SELECT v_test1, v_test2;
END ;

BEGIN
SELECT 'imbriqu 2' AS Bloc;
SELECT v_test1, v_test2;
END ;

END|
DELIMITER ;

CALL test_portee2();

Bloc

Imbriqu 1

280
6.5 Structurer ses instructions

v_test1 v_test2

1 2

Bloc

imbriqu 2

ERROR1054(42S22):Unknowncolumn'v_test2'in'fieldlist'

nouveau, v_test1, dclare dans le bloc principal, existe dans les deux blocs imbriqus. Par contre,
v_test2 nexiste que dans le bloc imbriqu dans lequel elle est dclare.

[[attention]] | Attention cependant la subtilit suivante : si un bloc imbriqu dclare une va-
riable locale ayant le mme nom quune variable locale dclare dans un bloc dun niveau sup-
rieur, il sagira toujours de deux variables locales diffrentes, et seule la variable locale dclare
dans le bloc imbriqu sera visible dans ce mme bloc.

Exemple 3 :

DELIMITER |
CREATE PROCEDURE test_portee3()
BEGIN
DECLARE v_test INT DEFAULT 1 ;

SELECT v_test AS 'Bloc principal' ;

BEGIN
DECLARE v_test INT DEFAULT 0 ;

SELECT v_test AS 'Bloc imbriqu' ;


SET v_test = 2 ;
SELECT v_test AS 'Bloc imbriqu aprs modification' ;
END ;

SELECT v_test AS 'Bloc principal' ;


END |
DELIMITER ;

CALL test_portee3();

Bloc principal

Bloc imbriqu

281
6 Scuriser et automatiser ses actions

Bloc imbriqu aprs modification

Bloc principal

La variable locale v_test est dclare dans le bloc principal et dans le bloc imbriqu, avec deux va-
leurs diffrentes. Mais lorsquon revient dans le bloc principal aprs excution du bloc dinstruc-
tions imbriqu, v_test a toujours la valeur quelle avait avant lexcution de ce bloc et sa deuxime
dclaration. Il sagit donc bien de deux variables locales distinctes.

6.5.2 Structures conditionnelles


Les structures conditionnelles permettent de dclencher une action ou une srie dinstructions
lorsquune condition pralable est remplie.

MySQL propose deux structures conditionnelles : IF et CASE.

6.5.2.1 La structure IF

Voici la syntaxe de la structure IF :

IF condition THEN instructions


[ELSEIF autre_condition THEN instructions
[ELSEIF ...]]
[ELSE instructions]
END IF ;

6.5.2.1.1 Le cas le plus simple : si la condition est vraie, alors on excute ces
instructions Voici la structure minimale dun IF :

IF condition THEN
instructions
END IF ;

Soit on excute les instructions (si la condition est vraie), soit on ne les excute pas.

Exemple : la procdure suivante affiche 'J''ai dj t adopt !', si cest le cas, partir de
lid dun animal :

DELIMITER |
CREATE PROCEDURE est_adopte(IN p_animal_id INT)
BEGIN
DECLARE v_nb INT DEFAULT 0 ; -- On cre une variable locale

SELECT COUNT(*) INTO v_nb -- On met le nombre de lignes correspondant


FROM Adoption -- dans Adoption dans notre variable locale

282
6.5 Structurer ses instructions

WHERE animal_id = p_animal_id;

IF v_nb > 0 THEN -- On teste si v_nb est suprieur 0 (donc s


SELECT 'J''ai dj t adopt !' ;
END IF ; -- Et on n'oublie surtout pas le END IF et l
END |
DELIMITER ;

CALL est_adopte(3);
CALL est_adopte(28);

Seul le premier appel la procdure va afficher 'J''ai dj t adopt !', puisque lanimal
3 est prsent dans la table Adoption, contrairement lanimal 28.

6.5.2.1.2 Deuxime cas : si alors, sinon Grce au mot-cl ELSE, on peut dfinir une
srie dinstructions excuter si la condition est fausse.

[[attention]] | ELSE ne doit pas tre suivi de THEN.

Exemple : la procdure suivante affiche 'Je suis n avant 2010' ou 'Je suis n aprs
2010', selon la date de naissance de lanimal transmis en paramtre.

DELIMITER |
CREATE PROCEDURE avant_apres_2010(IN p_animal_id INT)
BEGIN
DECLARE v_annee INT ;

SELECT YEAR(date_naissance) INTO v_annee


FROM Animal
WHERE id = p_animal_id;

IF v_annee < 2010 THEN


SELECT 'Je suis n avant 2010' AS naissance;
ELSE -- Pas de THEN
SELECT 'Je suis n aprs 2010' AS naissance;
END IF ; -- Toujours obligatoire

END |
DELIMITER ;

CALL avant_apres_2010(34); -- N le 20/04/2008


CALL avant_apres_2010(69); -- N le 13/02/2012

6.5.2.1.3 Troisime et dernier cas : plusieurs conditions alternatives Enfin, le mot-


cl ELSEIF... THEN permet de vrifier dautres conditions (en dehors de la condition du IF),
chacune ayant une srie dinstructions dfinies excuter en cas de vracit. Si plusieurs condi-
tions sont vraies en mme temps, seule la premire rencontre verra ses instructions excutes.
On peut bien sr toujours (mais ce nest pas obligatoire) ajouter un ELSE pour le cas o aucune
condition ne serait vrifie.

283
6 Scuriser et automatiser ses actions

Exemple : cette procdure affiche un message diffrent selon le sexe de lanimal pass en para-
mtre.

DELIMITER |
CREATE PROCEDURE message_sexe(IN p_animal_id INT)
BEGIN
DECLARE v_sexe VARCHAR(10);

SELECT sexe INTO v_sexe


FROM Animal
WHERE id = p_animal_id;

IF (v_sexe = 'F') THEN -- Premire possibilit


SELECT 'Je suis une femelle !' AS sexe;
ELSEIF (v_sexe = 'M') THEN -- Deuxime possibilit
SELECT 'Je suis un mle !' AS sexe;
ELSE -- Dfaut
SELECT 'Je suis en plein questionnement existentiel...' AS sexe;
END IF ;
END|
DELIMITER ;

CALL message_sexe(8); -- Mle


CALL message_sexe(6); -- Femelle
CALL message_sexe(9); -- Ni l'un ni l'autre

Il peut bien sr y avoir autant de ELSEIF... THEN que lon veut (mais un seul ELSE).

6.5.2.2 La structure CASE

Deux syntaxes sont possibles pour utiliser CASE.

6.5.2.2.1 Premire syntaxe : conditions dgalit


CASE valeur_a_comparer
WHEN possibilite1 THEN instructions
[WHEN possibilite2 THEN instructions] ...
[ELSE instructions]
END CASE ;

Exemple : on reprend la procdure message_sexe(), et on ladapte pour utiliser CASE.

DELIMITER |
CREATE PROCEDURE message_sexe2(IN p_animal_id INT)
BEGIN
DECLARE v_sexe VARCHAR(10);

SELECT sexe INTO v_sexe


FROM Animal
WHERE id = p_animal_id;

284
6.5 Structurer ses instructions

CASE v_sexe
WHEN 'F' THEN -- Premire possibilit
SELECT 'Je suis une femelle !' AS sexe;
WHEN 'M' THEN -- Deuxime possibilit
SELECT 'Je suis un mle !' AS sexe;
ELSE -- Dfaut
SELECT 'Je suis en plein questionnement existentiel...' AS sexe;
END CASE ;
END|
DELIMITER ;

CALL message_sexe2(8); -- Mle


CALL message_sexe2(6); -- Femelle
CALL message_sexe2(9); -- Ni l'un ni l'autre

On dfinit donc v_sexe comme point de comparaison. Chaque WHEN donne alors un lment auquel
v_sexe doit tre compar. Les instructions excutes seront celles du WHEN dont llment est gal
v_sexe. Le ELSE sera excut si aucun WHEN ne correspond.

Ici, on compare une variable locale (v_sexe) des chanes de caractres ('F' et 'M'), mais on peut
utiliser diffrents types dlments. Voici les principaux :

des variables locales ;


des variables utilisateur ;
des valeurs constantes de tous types (0, 'chane', 5.67, '2012-03-23',) ;
des expressions (2 + 4, NOW(), CONCAT(nom, ' ', prenom),) ;

[[attention]] | Cette syntaxe ne permet pas de faire des comparaisons avec NULL, puisquelle uti-
lise une comparaison de type valeur1 = valeur2. Or cette comparaison est inutilisable dans
le cas de NULL. Il faudra donc utiliser la seconde syntaxe, avec le test IS NULL.

6.5.2.2.2 Seconde syntaxe : toutes conditions Cette seconde syntaxe ne compare pas un
lment diffrentes valeurs, mais utilise simplement des conditions classiques et permet donc
de faire des comparaisons de type plus grand que, diffrent de, etc. (bien entendu, elle peut
galement tre utilise pour des galits).

CASE
WHEN condition THEN instructions
[WHEN condition THEN instructions] ...
[ELSE instructions]
END CASE

Exemple : on reprend la procdure avant_apres_2010(), quon rcrit avec CASE, et en donnant une
possibilit en plus. De plus, on passe le message en paramtre OUT pour changer un peu.

DELIMITER |
CREATE PROCEDURE avant_apres_2010_case (IN p_animal_id INT, OUT p_message VARCHAR(100))
BEGIN
DECLARE v_annee INT ;

285
6 Scuriser et automatiser ses actions

SELECT YEAR(date_naissance) INTO v_annee


FROM Animal
WHERE id = p_animal_id;

CASE
WHEN v_annee < 2010 THEN
SET p_message = 'Je suis n avant 2010.' ;
WHEN v_annee = 2010 THEN
SET p_message = 'Je suis n en 2010.' ;
ELSE
SET p_message = 'Je suis n aprs 2010.' ;
END CASE ;
END |
DELIMITER ;

CALL avant_apres_2010_case(59, @message);


SELECT @message;
CALL avant_apres_2010_case(62, @message);
SELECT @message;
CALL avant_apres_2010_case(69, @message);
SELECT @message;

6.5.2.2.3 Comportement particulier : aucune correspondance trouve En labsence


de clause ELSE, si aucune des conditions poses par les diffrentes clauses WHEN nest remplie
(quelle que soit la syntaxe utilise), une erreur est dclenche.

Par exemple, cette procdure affiche une salutation diffrente selon la terminaison du nom de
lanimal pass en paramtre :

DELIMITER |
CREATE PROCEDURE salut_nom(IN p_animal_id INT)
BEGIN
DECLARE v_terminaison CHAR(1);

SELECT SUBSTRING(nom, -1, 1) INTO v_terminaison -- Une position ngative signifie


FROM Animal -- -1 est donc la dernire lettre
WHERE id = p_animal_id;

CASE v_terminaison
WHEN 'a' THEN
SELECT 'Bonjour !' AS Salutations;
WHEN 'o' THEN
SELECT 'Salut !' AS Salutations;
WHEN 'i' THEN
SELECT 'Coucou !' AS Salutations;
END CASE ;

END|

286
6.5 Structurer ses instructions

DELIMITER ;

CALL salut_nom(69); -- Baba


CALL salut_nom(5); -- Choupi
CALL salut_nom(29); -- Fiero
CALL salut_nom(54); -- Bubulle

Salutations

Bonjour !

Salutations

Coucou !

Salutations

Salut !

ERROR1339(20000):CasenotfoundforCASEstatement

Lappel de la procdure avec Bubulle prsente un cas qui nest pas couvert par les trois WHEN. Une
erreur est donc dclenche

Donc, si lon nest pas sr davoir couvert tous les cas possibles, il faut toujours ajouter une clause
ELSE pour viter les erreurs. Si lon veut quaucune instruction ne soit excute par le ELSE, il
suffit simplement de mettre un bloc dinstructions vide (BEGIN END;).

Exemple : reprenons la procdure salut_nom(), et ajoutons-lui une clause ELSE vide :

DROP PROCEDURE salut_nom;


DELIMITER |
CREATE PROCEDURE salut_nom(IN p_animal_id INT)
BEGIN
DECLARE v_terminaison CHAR(1);

SELECT SUBSTRING(nom, -1, 1) INTO v_terminaison


FROM Animal
WHERE id = p_animal_id;

CASE v_terminaison
WHEN 'a' THEN
SELECT 'Bonjour !' AS Salutations;
WHEN 'o' THEN
SELECT 'Salut !' AS Salutations;
WHEN 'i' THEN
SELECT 'Coucou !' AS Salutations;
ELSE
BEGIN -- Bloc d'instructions vide

287
6 Scuriser et automatiser ses actions

END ;
END CASE ;

END|
DELIMITER ;

CALL salut_nom(69); -- Baba


CALL salut_nom(5); -- Choupi
CALL salut_nom(29); -- Fiero
CALL salut_nom(54); -- Bubulle

Cette fois, pas derreur. Le dernier appel (avec Bubulle) naffiche simplement rien.

[[erreur]] | Il faut au minimum une instruction ou un bloc dinstructions par clause WHEN et par
clause ELSE. Un bloc vide BEGIN END; est donc ncessaire si lon ne veut rien excuter.

6.5.2.3 Utiliser une structure conditionnelle directement dans une requte

Jusquici, on a vu lusage des structures conditionnelles dans des procdures stockes. Il est ce-
pendant possible dutiliser une structure CASE dans une simple requte.

Par exemple, crivons une requte SELECT suivant le mme principe que la procdure mes-
sage_sexe() :

SELECT id, nom, CASE


WHEN sexe = 'M' THEN 'Je suis un mle !'
WHEN sexe = 'F' THEN 'Je suis une femelle !'
ELSE 'Je suis en plein questionnement existentiel...'
END AS message
FROM Animal
WHERE id IN (9, 8, 6);

id nom message

6 Bobosse Je suis une femelle !


8 Bagherra Je suis un mle !
9 NULL Je suis en plein questionnement existentiel

Quelques remarques :

On peut utiliser les deux syntaxes de CASE.


Il faut clturer le CASE par END, et non par END CASE (et bien sr ne pas mettre de || ;|| si
la requte nest pas finie).
Ce nest pas limit aux clauses SELECT, on peut tout fait utiliser un CASE dans une clause
WHERE par exemple.
Ce nest par consquent pas non plus limit aux requtes SELECT, on peut lutiliser dans
nimporte quelle requte.

Il nest par contre pas possible dutiliser une structure IF dans une requte. Cependant, il existe
une fonction IF(), beaucoup plus limite, dont la syntaxe est la suivante :

IF(condition, valeur_si_vrai, valeur_si_faux)

288
6.5 Structurer ses instructions

Exemple :

SELECT nom, IF(sexe = 'M', 'Je suis un mle', 'Je ne suis pas un mle') AS sexe
FROM Animal
WHERE espece_id = 5 ;

nom sexe

Baba Je ne suis pas un mle


Bibo Je suis un mle
Momy Je ne suis pas un mle

Popi Je suis un mle


Mimi Je ne suis pas un mle

6.5.3 Boucles

Une boucle est une structure qui permet de rpter plusieurs fois une srie dinstructions. Il existe
trois types de boucles en MySQL : WHILE, LOOP et REPEAT.

6.5.3.1 La boucle WHILE

La boucle WHILE permet de rpter une srie dinstructions tant que la condition donne reste
vraie.

WHILE condition DO -- Attention de ne pas oublier le DO, erreur classique


instructions
END WHILE ;

Exemple : la procdure suivante affiche les nombres entiers de 1 p_nombre (pass en paramtre).

DELIMITER |
CREATE PROCEDURE compter_jusque_while(IN p_nombre INT)
BEGIN
DECLARE v_i INT DEFAULT 1 ;

WHILE v_i <= p_nombre DO


SELECT v_i AS nombre;

SET v_i = v_i + 1 ; -- ne surtout pas oublier, sinon la condition restera


END WHILE ;
END |
DELIMITER ;

CALL compter_jusque_while(3);

[[attention]] | Vrifiez que votre condition devient bien fausse aprs un certain nombre ditra-
tions de la boucle. Sinon, vous vous retrouvez avec une boucle infinie (qui ne sarrte jamais).

289
6 Scuriser et automatiser ses actions

6.5.3.2 La boucle REPEAT

La boucle REPEAT travaille en quelque sorte de manire oppose WHILE, puisquelle excute des
instructions de la boucle jusqu ce que la condition donne devienne vraie.

Exemple : voici la mme procdure crite avec une boucle REPEAT.

DELIMITER |
CREATE PROCEDURE compter_jusque_repeat(IN p_nombre INT)
BEGIN
DECLARE v_i INT DEFAULT 1 ;

REPEAT
SELECT v_i AS nombre;

SET v_i = v_i + 1 ; -- ne surtout pas oublier, sinon la condition restera


UNTIL v_i > p_nombre END REPEAT;
END |
DELIMITER ;

CALL compter_jusque_repeat(3);

[[attention]] | Attention, comme la condition dune boucle REPEAT est vrifie aprs le bloc dins-
tructions de la boucle, on passe au moins une fois dans la boucle, mme si la condition est tout
de suite fausse !

Test

-- Condition fausse ds le dpart, on ne rentre pas dans la boucle


CALL compter_jusque_while(0);

-- Condition fausse ds le dpart, on rentre quand mme une fois dans la boucle
CALL compter_jusque_repeat(0);

6.5.3.3 Donner un label une boucle

Il est possible de donner un label (un nom) une boucle, ou un bloc dinstructions dfini par
BEGIN... END. Il suffit pour cela de faire prcder louverture de la boucle/du bloc par ce label,
suivi de || :||.

La fermeture de la boucle/du bloc peut alors faire rfrence ce label (mais ce nest pas obliga-
toire).

[[attention]] | Un label ne peut pas dpasser 16 caractres.

Exemples

-- Boucle WHILE
-- ------------
super_while: WHILE condition DO -- La boucle a pour label "super_while"
instructions
END WHILE super_while; -- On ferme en donnant le label de la boucle (facult

290
6.5 Structurer ses instructions

-- Boucle REPEAT
-- -------------
repeat_genial: REPEAT -- La boucle s'appelle "repeat_genial"
instructions
UNTIL condition END REPEAT; -- Cette fois, on choisit de ne pas faire rfrence

-- Bloc d'instructions
-- -------------------
bloc_extra: BEGIN -- Le bloc a pour label "bloc_extra"
instructions
END bloc_extra;

[[question]] | Mais en quoi cela peut-il tre utile ?

Dune part, cela peut permettre de clarifier le code lorsquil y a beaucoup de boucles et de blocs
dinstructions imbriqus. Dautre part, il est ncessaire de donner un label aux boucles et aux
blocs dinstructions pour lesquels on veut pouvoir utiliser les instructions ITERATE et LEAVE.

6.5.3.4 Les instructions LEAVE et ITERATE

6.5.3.4.1 LEAVE : quitter la boucle ou le bloc dinstructions Linstruction LEAVE


peut sutiliser dans une boucle ou un bloc dinstructions et dclenche la sortie immdiate de
la structure dont le label est donn.

LEAVE label_structure;

Exemple : cette procdure incrmente de 1, et affiche, un nombre entier pass en paramtre. Et


cela, 4 fois maximum. Mais si lon trouve un multiple de 10, la boucle sarrte.

DELIMITER |
CREATE PROCEDURE test_leave1(IN p_nombre INT)
BEGIN
DECLARE v_i INT DEFAULT 4 ;

SELECT 'Avant la boucle WHILE' ;

while1: WHILE v_i > 0 DO

SET p_nombre = p_nombre + 1 ; -- On incrmente le nombre de 1

IF p_nombre%10 = 0 THEN -- Si p_nombre est divisible par 10,


SELECT 'Stop !' AS 'Multiple de 10' ;
LEAVE while1; -- On quitte la boucle WHILE.
END IF ;

SELECT p_nombre; -- On affiche p_nombre


SET v_i = v_i - 1 ; -- Attention de ne pas l'oublier

END WHILE while1;

291
6 Scuriser et automatiser ses actions

SELECT 'Aprs la boucle WHILE' ;


END|
DELIMITER ;

CALL test_leave1(3); -- La boucle s'excutera 4 fois

Avant la boucle WHILE

Avant la boucle WHILE

p_nombre

p_nombre

p_nombre

p_nombre

Aprs la boucle WHILE

Aprs la boucle WHILE

CALL test_leave1(8); -- La boucle s'arrtera ds qu'on atteint 10

Avant la boucle WHILE

Avant la boucle WHILE

p_nombre

Multiple de 10

Stop !

292
6.5 Structurer ses instructions

Aprs la boucle WHILE

Aprs la boucle WHILE

Il est par consquent possible dutiliser LEAVE pour provoquer la fin de la procdure stocke.

Exemple : voici la mme procdure. Cette fois-ci un multiple de 10 provoque larrt de toute la
procdure, pas seulement de la boucle WHILE.

DELIMITER |
CREATE PROCEDURE test_leave2(IN p_nombre INT)
corps_procedure: BEGIN -- On donne un label au bloc d'instruc
DECLARE v_i INT DEFAULT 4 ;

SELECT 'Avant la boucle WHILE' ;


while1: WHILE v_i > 0 DO
SET p_nombre = p_nombre + 1 ; -- On incrmente le nombre de 1
IF p_nombre%10 = 0 THEN -- Si p_nombre est divisible par 10,
SELECT 'Stop !' AS 'Multiple de 10' ;
LEAVE corps_procedure; -- je quitte la procdure.
END IF ;

SELECT p_nombre; -- On affiche p_nombre


SET v_i = v_i - 1 ; -- Attention de ne pas l'oublier
END WHILE while1;

SELECT 'Aprs la boucle WHILE' ;


END|
DELIMITER ;

CALL test_leave2(8);

'Aprs la boucle WHILE' ne saffiche plus lorsque linstruction LEAVE est dclenche,
puisque lon quitte la procdure stocke avant darriver linstruction SELECT qui suit la boucle
WHILE.

En revanche, LEAVE ne permet pas de quitter directement une structure conditionnelle (IF ou
CASE). Il nest dailleurs pas non plus possible de donner un label ces structures. Cette restric-
tion est cependant aisment contournable en utilisant les blocs dinstructions.

Exemple : la procdure suivante affiche les nombres de 4 1, en prcisant sils sont pairs. Sauf
pour le nombre 2, pour lequel une instruction LEAVE empche laffichage habituel.

DELIMITER |
CREATE PROCEDURE test_leave3()
BEGIN
DECLARE v_i INT DEFAULT 4 ;

WHILE v_i > 0 DO

IF v_i%2 = 0 THEN

293
6 Scuriser et automatiser ses actions

if_pair: BEGIN
IF v_i = 2 THEN -- Si v_i vaut 2
LEAVE if_pair; -- On quitte le bloc "if_pair", ce qu
END IF ;
SELECT CONCAT(v_i, ' est pair') AS message;
END if_pair;
ELSE
if_impair: BEGIN
SELECT CONCAT(v_i, ' est impair') AS message;
END if_impair;
END IF ;

SET v_i = v_i - 1 ;


END WHILE ;
END|
DELIMITER ;

CALL test_leave3();

message

4 est pair

message

3 est impair

message

1 est impair

'2 est pair' nest pas affich, puisquon a quitt le IF avant cet affichage.

6.5.3.4.2 ITERATE : dclencher une nouvelle itration de la boucle Cette instruction


ne peut tre utilise que dans une boucle. Lorsquelle est excute, une nouvelle itration de la
boucle commence. Toutes les instructions suivant ITERATE dans la boucle sont ignores.

Exemple : la procdure suivante affiche les nombres de 1 3, avec un message avant le IF et aprs
le IF. Sauf pour le nombre 2, qui relance une itration de la boucle dans le IF.

DELIMITER |
CREATE PROCEDURE test_iterate()
BEGIN
DECLARE v_i INT DEFAULT 0 ;

boucle_while: WHILE v_i < 3 DO


SET v_i = v_i + 1 ;
SELECT v_i, 'Avant IF' AS message;

294
6.5 Structurer ses instructions

IF v_i = 2 THEN
ITERATE boucle_while;
END IF ;

SELECT v_i, 'Aprs IF' AS message; -- Ne sera pas excut pour v_i = 2
END WHILE ;
END |
DELIMITER ;

CALL test_iterate();

v_i message

1 Avant IF

v_i message

1 Aprs IF

v_i message

2 Avant IF

v_i message

3 Avant IF

v_i message

3 Aprs IF

[[attention]] | Attention ne pas faire de boucle infinie avec ITERATE, on oublie facilement que
cette instruction empche lexcution de toutes les instructions qui la suivent dans la boucle. Si
javais mis par exemple SET v_i = v_i + 1; aprs ITERATE et non avant, la boucle serait res-
te coince v_i = 2.

6.5.3.5 La boucle LOOP

On a gard la boucle LOOP pour la fin, parce quelle est un peu particulire. En effet, voici sa syn-
taxe :

[label:] LOOP
instructions
END LOOP [label]

Vous voyez bien : il nest question de condition nulle part. En fait, une boucle LOOP doit intgrer
dans ses instructions un lment qui va la faire sarrter : typiquement une instruction LEAVE.

295
6 Scuriser et automatiser ses actions

Sinon, cest une boucle infinie.

Exemple : nouveau une procdure qui affiche les nombres entiers de 1 p_nombre.

DELIMITER |
CREATE PROCEDURE compter_jusque_loop(IN p_nombre INT)
BEGIN
DECLARE v_i INT DEFAULT 1 ;

boucle_loop: LOOP
SELECT v_i AS nombre;

SET v_i = v_i + 1 ;

IF v_i > p_nombre THEN


LEAVE boucle_loop;
END IF ;
END LOOP ;
END |
DELIMITER ;

CALL compter_jusque_loop(3);

6.5.3.6 En rsum

Un bloc dinstructions est dlimit par BEGIN et END. Il est possible dimbriquer plusieurs
blocs dinstructions.
Une variable locale est dfinie dans un bloc dinstructions grce la commande DECLARE.
Une fois la fin du bloc dinstructions atteinte, toutes les variables locales qui y ont t d-
clares sont supprimes.
Une structure conditionnelle permet dexcuter une srie dinstructions si une condition
est respecte. Les deux structures conditionnelles de MySQL sont IF et CASE.
Une boucle est une structure qui permet de rpter une srie dinstructions un certain
nombre de fois. Il existe trois types de boucle pour MySQL : WHILE, REPEAT et LOOP.
Linstruction LEAVE permet de quitter un bloc dinstructions ou une boucle.
Linstruction ITERATE permet de relancer une itration dune boucle.

6.6 Gestionnaires derreurs, curseurs et utilisation


avance
Dans ce chapitre, nous verrons tout dabord deux structures utilisables dans les blocs dinstruc-
tions et qui vont vous ouvrir dnormes possibilits :

les gestionnaires derreur, qui permettent de grer les cas o une erreur se produirait pen-
dant lexcution dune srie dinstructions ;
les curseurs, qui permettent de parcourir les lignes de rsultat dune requte SELECT.

296
6.6 Gestionnaires derreurs, curseurs et utilisation avance

Ensuite, nous verrons quelques cas dutilisation avance des blocs dinstructions, utilisant non
seulement les structures dcrites dans ce chapitre et le prcdent, mais galement dautres no-
tions et objets (transaction, variables utilisateur, etc.).

6.6.1 Gestion des erreurs


Il arrive rgulirement quun traitement soit susceptible de gnrer une erreur SQL. Prenons la
procdure suivante, qui enregistre une adoption :

DELIMITER |
CREATE PROCEDURE ajouter_adoption(IN p_client_id INT, IN p_animal_id INT, IN p_date DAT
BEGIN
DECLARE v_prix DECIMAL(7,2);

SELECT COALESCE(Race.prix, Espece.prix) INTO v_prix


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.id = p_animal_id;

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix,


VALUES (p_animal_id, p_client_id, CURRENT_DATE(), p_date, v_prix, p_paye);

SELECT 'Adoption correctement ajoute' AS message;


END|
DELIMITER ;

Plusieurs erreurs sont susceptibles de se dclencher selon les paramtres passs cette proc-
dure.

Exemple 1 : le client nexiste pas.

SET @date_adoption = CURRENT_DATE() + INTERVAL 7 DAY ;

CALL ajouter_adoption(18, 6, @date_adoption, 1);

ERROR1452(23000):Cannotaddorupdateachildrow:aforeignkeyconstraintfails(`

Exemple 2 : lanimal a dj t adopt.

CALL ajouter_adoption(12, 21, @date_adoption, 1);

ERROR1062(23000):Duplicateentry'21'forkey'ind_uni_animal_id'

Exemple 3 : lanimal nexiste pas, v_prix est donc NULL.

CALL ajouter_adoption(12, 102, @date_adoption, 1);

ERROR1048(23000):Column'prix'cannotbenull

297
6 Scuriser et automatiser ses actions

Pour empcher ces erreurs intempestives, deux solutions :

vrifier chaque paramtre pouvant poser problme (p_animal_id et p_client_id ne sont pas
NULL et correspondent quelque chose dans les tables Animal et Client, p_animal_id ne cor-
respond pas un animal dj adopt, etc.) ;
utiliser un gestionnaire derreur : cest ce que nous allons apprendre faire ici.

6.6.1.1 Cration dun gestionnaire derreur

Voici la syntaxe utiliser pour crer un gestionnaire derreur :

DECLARE { EXIT | CONTINUE } HANDLER FOR { numero_erreur | { SQLSTATE identifiant_erreur


instruction ou bloc d'instructions
Un gestionnaire derreur dfinit une instruction (une seule !), ou un bloc dinstructions
(BEGIN ... END;), qui va tre excut en cas derreur correspondant au gestionnaire.
Tous les gestionnaires derreur doivent tre dclars au mme endroit : aprs la dclara-
tion des variables locales, mais avant les instructions de la procdure.
Un gestionnaire peut, soit provoquer larrt de la procdure (EXIT), soit faire reprendre
la procdure aprs avoir gr lerreur (CONTINUE).
On peut identifier le type derreur que le gestionnaire va reconnatre de trois manires
diffrentes : un numro derreur, un identifiant, ou une CONDITION.
Un gestionnaire tant dfini grce au mot-cl DECLARE, comme les variables locales, il a
exactement la mme porte que celles-ci.

Exemples : ces deux procdures enregistrent une adoption en grant les erreurs, lune arrtant
la procdure, lautre relanant celle-ci :

DELIMITER |
CREATE PROCEDURE ajouter_adoption_exit(IN p_client_id INT, IN p_animal_id INT, IN p_dat
BEGIN
DECLARE v_prix DECIMAL(7,2);
DECLARE EXIT HANDLER FOR SQLSTATE '23000'
BEGIN
SELECT 'Une erreur est survenue...' ;
SELECT 'Arrt prmatur de la procdure' ;
END ;

SELECT 'Dbut procdure' ;

SELECT COALESCE(Race.prix, Espece.prix) INTO v_prix


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.id = p_animal_id;

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix,


VALUES (p_animal_id, p_client_id, CURRENT_DATE(), p_date, v_prix, p_paye);

SELECT 'Fin procdure' AS message;


END|

298
6.6 Gestionnaires derreurs, curseurs et utilisation avance

CREATE PROCEDURE ajouter_adoption_continue(IN p_client_id INT, IN p_animal_id INT, IN p


BEGIN
DECLARE v_prix DECIMAL(7,2);
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SELECT 'Une erreur est survenue...' ;

SELECT 'Dbut procdure' ;

SELECT COALESCE(Race.prix, Espece.prix) INTO v_prix


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.id = p_animal_id;

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix,


VALUES (p_animal_id, p_client_id, CURRENT_DATE(), p_date, v_prix, p_paye);

SELECT 'Fin procdure' ;


END|
DELIMITER ;

SET @date_adoption = CURRENT_DATE() + INTERVAL 7 DAY ;

CALL ajouter_adoption_exit(18, 6, @date_adoption, 1);


CALL ajouter_adoption_continue(18, 6, @date_adoption, 1);

Les instructions dfinies par le gestionnaire sont bien excutes, mais 'Fin procdure' nest
affich que dans le cas de ajouter_adoption_continue(), qui fait reprendre la procdure une fois ler-
reur gre. La procdure ajouter_adoption_exit() utilise un bloc dinstructions et peut donc excuter
plusieurs instructions.

6.6.1.2 Dfinition de lerreur gre

6.6.1.2.1 Identifiant ou numro MySQL de lerreur Voici la dclaration du gestionnaire


dans la procdure ajouter_adoption_continue() :

DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SELECT 'Une erreur est survenue...' ;

Et voici une des erreurs qui peut tre intercepte par ce gestionnaire :

ERROR1062(23000):Duplicateentry'21'forkey'ind_uni_animal_id'

Le message derreur est constitu de trois lments importants :

1062 : le numro derreur MySQL (un nombre entier) ;


23000 : lidentifiant de ltat SQL (une chane de 5 caractres) ;
Duplicate entry 21 for key ind_uni_animal_id : un message donnant le dtail de lerreur.

Identifiant de ltat SQL

299
6 Scuriser et automatiser ses actions

Dans la procdure ajouter_adoption_continue(), cest lidentifiant de ltat SQL ('23000') qui a t


utilis. Il sagit dune chane de 5 caractres, renvoye par le serveur au client pour informer de
la russite ou de lchec dune instruction. Un identifiant commenant par '00' par exemple,
signifie que linstruction a russi. '23000' est lidentifiant renvoy lorsquune erreur concernant
une contrainte (NOT NULL, unicit, cl primaire ou secondaire,) a t dclenche. Pour utiliser
cet identifiant dans un gestionnaire derreur, il faut le faire prcder de SQLSTATE.

Numro derreur MySQL

Pour utiliser le numro derreur SQL, par contre, il suffit de lindiquer, comme un nombre entier :

DECLARE CONTINUE HANDLER FOR 1062 SELECT 'Une erreur est survenue...' ;

Ce sont des codes qui, contrairement aux identifiants SQLSTATE, sont propres MySQL. Ils sont
aussi en gnral plus prcis. Lidentifiant SQL 23000 par exemple, correspond une dizaine de
codes derreur MySQL diffrents.

Quelques exemples de codes souvent rencontrs

Code
MySQL SQLSTATE Description

1048 23000 La colonne x ne peut pas tre NULL


1169 23000 Violation de contrainte dunicit
1216 23000 Violation de cl secondaire : insertion ou modification
impossible (table avec la cl secondaire)
1217 23000 Violation de cl secondaire : suppression ou modification
impossible (table avec la rfrence de la cl secondaire)
1172 42000 Plusieurs lignes de rsultats alors quon ne peut en avoir quune
seule
1242 21000 La sous-requte retourne plusieurs lignes de rsultats alors
quon ne peut en avoir quune seule

Pour une liste plus complte, il suffit daller sur la documentation officielle.

Pour finir, notez quil est tout fait possible dintercepter des avertissements avec un gestion-
naire derreur, qui sont galement reprsents par un identifiant SQL et un code derreur MySQL.
Un avertissement, contrairement une erreur, ne fait pas chouer linstruction par dfaut, mais
en linterceptant dans une requte stocke avec un gestionnaire, vous pouvez dcider du compor-
tement adopter suite cet avertissement.

Vous aurez par exemple un avertissement si vous insrez un DATETIME dans une colonne DATE,
puisque la donne sera tronque pour correspondre au type de la colonne (lheure sera supprime
pour ne garder que la date) : code Mysql 1265, SQLSTATE '01000'.

6.6.1.2.2 Utilisation dune CONDITION Avec un numro derreur MySQL et un identi-


fiant dtat SQL, il existe une troisime manire didentifier les erreurs reconnues par un ges-
tionnaire : une CONDITION. Une CONDITION est en fait simplement un nom donn un numro
derreur MySQL ou un identifiant dtat SQL. Cela vous permet de travailler avec des erreurs
plus claires.

Voici la syntaxe utiliser pour nommer une erreur. Il sagit nouveau dun DECLARE. Les dcla-
rations de CONDITION doivent se trouver avant les dclarations de gestionnaires.

300
6.6 Gestionnaires derreurs, curseurs et utilisation avance

DECLARE nom_erreur CONDITION FOR { SQLSTATE identifiant_SQL | numero_erreur_MySQL } ;

Exemple : rcrivons la procdure ajouter_adoption_exit() en nommant lerreur :

DROP PROCEDURE ajouter_adoption_exit;


DELIMITER |
CREATE PROCEDURE ajouter_adoption_exit(IN p_client_id INT, IN p_animal_id INT, IN p_dat
BEGIN
DECLARE v_prix DECIMAL(7,2);

DECLARE violation_contrainte CONDITION FOR SQLSTATE '23000' ; -- On nomme l'erreu

DECLARE EXIT HANDLER FOR violation_contrainte -- Le gestionnaire s


BEGIN -- les erreurs de
SELECT 'Une erreur est survenue...' ;
SELECT 'Arrt prmatur de la procdure' ;
END ;

SELECT 'Dbut procdure' ;

SELECT COALESCE(Race.prix, Espece.prix) INTO v_prix


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.id = p_animal_id;

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix,


VALUES (p_animal_id, p_client_id, CURRENT_DATE(), p_date, v_prix, p_paye);

SELECT 'Fin procdure' ;


END|
DELIMITER ;

6.6.1.2.3 Conditions prdfinies Il existe trois conditions prdfinies dans MySQL :

SQLWARNING : tous les identifiants SQL commenant par 01, cest--dire les avertisse-
ments et les notes ;
NOT FOUND : tous les identifiants SQL commenant par 02, et que nous verrons plus en
dtail avec les curseurs ;
SQLEXCEPTION : tous les identifiants SQL ne commenant ni par 00, ni par 01, ni par
02, donc les erreurs.

Exemple : rcriture de la procdure ajouter_adoption_exit(), de faon ce que le gestionnaire inter-


cepte toutes les erreurs SQL.

DROP PROCEDURE ajouter_adoption_exit;


DELIMITER |
CREATE PROCEDURE ajouter_adoption_exit(IN p_client_id INT, IN p_animal_id INT, IN p_dat
BEGIN
DECLARE v_prix DECIMAL(7,2);

301
6 Scuriser et automatiser ses actions

DECLARE EXIT HANDLER FOR SQLEXCEPTION


BEGIN
SELECT 'Une erreur est survenue...' ;
SELECT 'Arrt prmatur de la procdure' ;
END ;

SELECT 'Dbut procdure' ;

SELECT COALESCE(Race.prix, Espece.prix) INTO v_prix


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.id = p_animal_id;

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix,


VALUES (p_animal_id, p_client_id, CURRENT_DATE(), p_date, v_prix, p_paye);

SELECT 'Fin procdure' ;


END|
DELIMITER ;

6.6.1.3 Dclarer plusieurs gestionnaires, grer plusieurs erreurs par gestionnaire

Un gestionnaire peut reconnatre plusieurs types derreurs diffrents. Par ailleurs, il est possible
de dclarer plusieurs gestionnaires dans un mme bloc dinstructions.

Exemple : toujours avec la procdure ajouter_adoption_exit(). On peut lcrire en dtaillant


diffrentes erreurs possibles, puis en ajoutant un gestionnaire gnral qui reconnatra les
SQLEXCEPTION et les SQLWARNING, pour tous les cas quon ne traite pas dans les autres gestion-
naires. Ce qui donne :

DROP PROCEDURE ajouter_adoption_exit;


DELIMITER |
CREATE PROCEDURE ajouter_adoption_exit(IN p_client_id INT, IN p_animal_id INT, IN p_dat
BEGIN
DECLARE v_prix DECIMAL(7,2);

DECLARE violation_cle_etrangere CONDITION FOR 1452 ; -- Dclaration des


DECLARE violation_unicite CONDITION FOR 1062 ;

DECLARE EXIT HANDLER FOR violation_cle_etrangere -- Dclaration du ge


BEGIN -- les erreurs de cl
SELECT 'Erreur : violation de cl trangre.' ;
END ;
DECLARE EXIT HANDLER FOR violation_unicite -- Dclaration du ge
BEGIN -- les erreurs d'ind
SELECT 'Erreur : violation de contrainte d''unicit.' ;
END ;

302
6.6 Gestionnaires derreurs, curseurs et utilisation avance

DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING -- Dclaration du ge


BEGIN -- toutes les autres
SELECT 'Une erreur est survenue...' ;
END ;

SELECT 'Dbut procdure' ;

SELECT COALESCE(Race.prix, Espece.prix) INTO v_prix


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.id = p_animal_id;

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix,


VALUES (p_animal_id, p_client_id, CURRENT_DATE(), p_date, v_prix, p_paye);

SELECT 'Fin procdure' ;


END|
DELIMITER ;

SET @date_adoption = CURRENT_DATE() + INTERVAL 7 DAY ;

CALL ajouter_adoption_exit(12, 3, @date_adoption, 1); -- Violation unicit (anim


CALL ajouter_adoption_exit(133, 6, @date_adoption, 1); -- Violation cl trangre
CALL ajouter_adoption_exit(NULL, 6, @date_adoption, 1); -- Violation de contrainte

Cette procdure montre galement que lorsque plusieurs gestionnaires derreur peuvent cor-
respondre lerreur dclenche (ou lavertissement), cest le plus prcis qui est utilis.
Cest la raison pour laquelle une violation de cl trangre dclenche le gestionnaire FOR
violation_cle_etrangere (numro MySQL 1062), et non le gestionnaire FOR SQLEXCEPTION.

6.6.2 Curseurs

Nous avons vu quil tait possible dexploiter le rsultat dun SELECT dans un bloc dinstructions,
en utilisant la commande SELECT colonne(s) INTO variable(s), qui assigne les valeurs s-
lectionnes des variables. Cependant, SELECT ... INTO ne peut tre utilis que pour des re-
qutes qui ne ramnent quune seule ligne de rsultats.

Les curseurs permettent de parcourir un jeu de rsultats dune requte SELECT, quel que soit le
nombre de lignes rcupres, et den exploiter les valeurs.

Quatre tapes sont ncessaires pour utiliser un curseur.

Dclaration du curseur : avec une instruction DECLARE.


Ouverture du curseur : on excute la requte SELECT du curseur et on stocke le rsultat
dans celui-ci.
Parcours du curseur : on parcourt une une les lignes.
Fermeture du curseur.

303
6 Scuriser et automatiser ses actions

6.6.2.1 Syntaxe

6.6.2.1.1 Dclaration du curseur Comme toutes les instructions DECLARE, la dclaration


dun curseur doit se faire au dbut du bloc dinstructions pour lequel celui-ci est dfini. Plus pr-
cisment, on dclare les curseurs aprs les variables locales et les conditions, mais avant les
gestionnaires derreur.

DECLARE nom_curseur CURSOR FOR requete_select;

Un curseur est donc compos dun nom, et dune requte SELECT.

Exemple :

DECLARE curseur_client CURSOR


FOR SELECT *
FROM Client;

6.6.2.1.2 Ouverture et fermeture du curseur En dclarant le curseur, on a donc associ


un nom et une requte SELECT. Louverture du curseur va provoquer lexcution de la requte
SELECT, ce qui va produire un jeu de rsultats.

Une fois quon aura parcouru les rsultats, il ny aura plus qu fermer le curseur. Si on ne le fait
pas explicitement, le curseur sera ferm la fin du bloc dinstructions.

OPEN nom_curseur;
-- Parcours du curseur et instructions diverses
CLOSE nom_curseur;

6.6.2.1.3 Parcours du curseur Une fois que le curseur a t ouvert et le jeu de rsultats r-
cupr, le curseur place un pointeur sur la premire ligne de rsultats. Avec la commande FETCH,
on rcupre la ligne sur laquelle pointe le curseur, et on fait avancer le pointeur vers la ligne de
rsultats suivante.

FETCH nom_curseur INTO variable(s);

Bien entendu, comme pour SELECT ... INTO, il faut donner autant de variables dans la clause
INTO quon a rcupr de colonnes dans la clause SELECT du curseur.

Exemple : la procdure suivante parcourt les deux premires lignes de la table Client avec un cur-
seur.

DELIMITER |
CREATE PROCEDURE parcours_deux_clients()
BEGIN
DECLARE v_nom, v_prenom VARCHAR(100);

DECLARE curs_clients CURSOR


FOR SELECT nom, prenom -- Le SELECT rcupre de
FROM Client
ORDER BY nom, prenom; -- On trie les clients p

OPEN curs_clients; -- Ouverture du curseur

304
6.6 Gestionnaires derreurs, curseurs et utilisation avance

FETCH curs_clients INTO v_nom, v_prenom; -- On rcupre la premi


SELECT CONCAT(v_prenom, ' ', v_nom) AS 'Premier client' ;

FETCH curs_clients INTO v_nom, v_prenom; -- On rcupre la second


SELECT CONCAT(v_prenom, ' ', v_nom) AS 'Second client' ;

CLOSE curs_clients; -- Fermeture du curseur


END|
DELIMITER ;

CALL parcours_deux_clients();

Premier client

Maximilien Antoine

Second client

Marie Boudur

6.6.2.2 Restrictions

FETCH est la seule commande permettant de rcuprer une partie dun jeu de rsultats dun cur-
seur, et elle ne permet quune chose : rcuprer la ligne de rsultats suivante. Il nest pas possible
de sauter une ou plusieurs lignes, ni daller rechercher une ligne prcdente. On ne peut que par-
courir les lignes une une, de la premire la dernire.

Ensuite, il nest pas possible de modifier une ligne directement partir dun curseur. Il sagit
dune restriction particulire MySQL. Dautres SGBD vous permettent des requtes dUPDATE
directement sur les curseurs.

Avec Oracle, la requte suivante modifiera la dernire ligne rcupre avec FETCH :

UPDATE nom_table
SET colonne = valeur
WHERE CURRENT OF nom_curseur;

[[erreur]] | Ce nest, du moins actuellement, absolument pas possible avec MySQL !!!

Il vaut dailleurs mieux viter tout UPDATE sur une table sur laquelle un curseur est ouvert, mme
sans faire de rfrence celui-ci. Le rsultat dun tel UPDATE serait imprvisible.

[[information]] | Ces restrictions sur les requtes UPDATE sont bien entendu galement valables
pour les suppressions (DELETE).

6.6.2.3 Parcourir intelligemment tous les rsultats dun curseur

Pour rcuprer une ligne de rsultats, on utilise donc FETCH. Dans la procdure parcours_deux_clients(),
on voulait rcuprer les deux premires lignes, on a donc utilis deux FETCH. Cependant, la plu-
part du temps, on ne veut pas seulement utiliser les deux premires lignes, mais toutes ! Or, sauf

305
6 Scuriser et automatiser ses actions

exception, on ne sait pas combien de lignes seront slectionnes.

On veut donc parcourir une une les lignes de rsultats, et leur appliquer un traitement, sans
savoir lavance combien de fois ce traitement devra tre rpt. Pour cela, on utilise une boucle !
WHILE, REPEAT ou LOOP. Il ny a plus qu trouver une condition pour arrter la boucle une fois
tous les rsultats parcourus.

6.6.2.3.1 Condition darrt Voyons ce qui se passe lorsque lon fait un FETCH alors quil ny a
plus, ou pas, de rsultats. Voici une procdure qui slectionne les clients selon une ville donne en
paramtre. Les lignes sont rcupres et affiches grce au FETCH, plac dans une boucle LOOP.
Je rappelle que cette boucle ne dfinit pas de condition darrt : il est ncessaire dajouter une
instruction LEAVE pour larrter. Ici, pour tester, on ne mettra pas dinstruction LEAVE.

DELIMITER |
CREATE PROCEDURE test_condition(IN p_ville VARCHAR(100))
BEGIN
DECLARE v_nom, v_prenom VARCHAR(100);

DECLARE curs_clients CURSOR


FOR SELECT nom, prenom
FROM Client
WHERE ville = p_ville;

OPEN curs_clients;

LOOP
FETCH curs_clients INTO v_nom, v_prenom;
SELECT CONCAT(v_prenom, ' ', v_nom) AS 'Client' ;
END LOOP ;

CLOSE curs_clients;
END|
DELIMITER ;

Voyons donc ce que a donne pour une ville dans laquelle quelques clients habitent.

CALL test_condition('Houtsiplou');

Client

Jean Dupont

Client

Virginie Broussaille

ERROR1329(02000):Nodata-zerorowsfetched,selected,orprocessed

Tentons ensuite lexprience avec une ville qui ne donnera aucun rsultat.

306
6.6 Gestionnaires derreurs, curseurs et utilisation avance

CALL test_condition('Bruxelles');

ERROR1329(02000):Nodata-zerorowsfetched,selected,orprocessed

On a la mme erreur dans les deux cas, lorsquon essaye de faire un FETCH alors quil ny a pas ou
plus de ligne rcuprer. Or, vous vous souvenez peut-tre dune condition prdfinie pour les
gestionnaires derreur, NOT FOUND, qui reprsente les erreurs dont lidentifiant SQL commence
par '02', ce qui est le cas ici.

On va donc utiliser cette condition pour arrter la boucle : on dfinit un gestionnaire pour la condi-
tion NOT FOUND, qui change la valeur dune variable locale. Cette variable locale vaut 0 au dpart,
et passe 1 quand le gestionnaire est dclench (donc quand il ny a plus de ligne). Il suffit alors
dajouter une structure IF qui vrifie la valeur de la variable locale une fois le FETCH excut. Si
elle vaut 1, on quitte la boucle.

Exemple : la procdure test_condition2() ci-dessous fait la mme chose que test_condition(), mais
inclut le gestionnaire derreur et le IF ncessaires pour stopper la boucle ds que toutes les lignes
slectionnes ont t parcourues :

DELIMITER |
CREATE PROCEDURE test_condition2(IN p_ville VARCHAR(100))
BEGIN
DECLARE v_nom, v_prenom VARCHAR(100);
DECLARE fin TINYINT DEFAULT 0 ; -- Variable locale utilise po

DECLARE curs_clients CURSOR


FOR SELECT nom, prenom
FROM Client
WHERE ville = p_ville;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET fin = 1 ; -- Gestionnaire d'erreur pour

OPEN curs_clients;

loop_curseur: LOOP
FETCH curs_clients INTO v_nom, v_prenom;

IF fin = 1 THEN -- Structure IF pour quitter la


LEAVE loop_curseur;
END IF ;

SELECT CONCAT(v_prenom, ' ', v_nom) AS 'Client' ;


END LOOP ;

CLOSE curs_clients;
END|
DELIMITER ;

CALL test_condition2('Houtsiplou');
CALL test_condition2('Bruxelles');

307
6 Scuriser et automatiser ses actions

Je vous laisse le soin, si vous le dsirez, de rcrire cette procdure en utilisant une boucle WHILE
ou une boucle REPEAT. Cest un excellent exercice ! :)

Notez que ce nest pas la seule solution pour arrter la boucle. On pourrait par exemple compter
le nombre de lignes rcupres, grce la fonction FOUND_ROWS() (dont on a dj parl dans le
chapitre sur les fonctions). Cependant, cette utilisation du gestionnaire derreur est la solution
la plus utilise, et je la trouve personnellement trs lgante.

6.6.2.3.2 Le cas des boolens chez MySQL Un boolen, en informatique, est un type
de donne pouvant prendre deux tats : vrai, ou faux. MySQL ne propose pas ce type de donnes.
Avec MySQL, on reprsente la valeur vrai par 1, et la valeur faux par 0.

Dmonstration :

SELECT 1 = 1, 1 = 2 ; -- 1 = 1 est vrai, bien sr. Contrairement 1 = 2 (si si !)

1=1 1=2

1 0

Par consquent, pour reprsenter un boolen, on utilise en gnral un TINYINT, valant soit 0, soit
1. Cest ce que lon a fait pour la colonne paye de la table Client par exemple.

Une autre consquence, est que la structure IF que lon utilise dans la procdure test_condition2()
peut tre rcrite de la manire suivante :

IF fin THEN
LEAVE loop_curseur;
END IF ;

Et pour amliorer encore la lisibilit pour les donnes de ce genre, MySQL a cr plusieurs syno-
nymes que lon peut utiliser dans ces situations.

BOOL et BOOLEAN sont synonymes de TINYINT(1).


TRUE est synonyme de 1.
FALSE est synonyme de 0.

On peut donc rcrire la procdure test_condition2() en utilisant ces synonymes.

DROP PROCEDURE test_condition2;


DELIMITER |
CREATE PROCEDURE test_condition2(IN p_ville VARCHAR(100))
BEGIN
DECLARE v_nom, v_prenom VARCHAR(100);
DECLARE fin BOOLEAN DEFAULT FALSE ; -- On dclare fin comme un

DECLARE curs_clients CURSOR


FOR SELECT nom, prenom
FROM Client
WHERE ville = p_ville;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET fin = TRUE ; -- On utilise TRUE au lieu

308
6.6 Gestionnaires derreurs, curseurs et utilisation avance

OPEN curs_clients;

loop_curseur: LOOP
FETCH curs_clients INTO v_nom, v_prenom;

IF fin THEN -- Plus besoin de "= 1"


LEAVE loop_curseur;
END IF ;

SELECT CONCAT(v_prenom, ' ', v_nom) AS 'Client' ;


END LOOP ;

CLOSE curs_clients;
END|
DELIMITER ;

*SGBD : Systme de Gestion de Bases de Donnes

6.6.3 Utilisation avance des blocs dinstructions

Vous avez maintenant vu les principales structures quil est possible dutiliser dans un bloc dins-
tructions. Nous allons ici combiner ces structures avec des objets ou notions vues prcdemment.

La puissance du langage SQL (et de tous les langages informatiques) rside dans le fait quon
peut combiner diffrentes notions pour raliser des traitements complexes. Voici quelques
exemples de telles combinaisons. Notez que ces exemples utilisent tous des procdures stockes,
mais la plupart sont adaptables dautres objets, comme les triggers, les fonctions stockes ou
les vnements.

6.6.3.1 Utiliser des variables utilisateur dans un bloc dinstructions

En plus des variables locales, il est tout fait possible dutiliser des variables utilisateur dans
un bloc dinstructions. Mais noubliez pas quune variable utilisateur est dfinie pour toute la
session, pas uniquement le bloc, mme si elle est cre lintrieur de celui-ci.

Exemple : procdure stocke utilisant une variable utilisateur.

DELIMITER |
CREATE PROCEDURE test_vu()
BEGIN
SET @var = 15 ;
END|
DELIMITER ;

SELECT @var; -- @var n'existe pas encore, on ne l'a pas dfinie


CALL test_vu(); -- On excute la procdure
SELECT @var; -- @var vaut maintenant 15, mme en dehors de la procdure, puisqu'elle

309
6 Scuriser et automatiser ses actions

Voyant cela, on pourrait tre tent dutiliser des variables utilisateur la place des paramtres
OUT et INOUT des procdures stockes.

Cependant, il convient dtre extrmement prudent lorsque lon utilise des variables utilisateur
dans un bloc dinstructions. Si lon reste par exemple dans le contexte des procdures stockes, un
des intrts de celles-ci est davoir une interface entre la base de donnes et lutilisateur. Luti-
lisateur nest donc pas ncessairement conscient des variables utilisateur qui sont dfinies ou
modifies dans la procdure. Il pourrait donc dfinir des variables utilisateur, puis excuter une
procdure et constater que certaines de ses variables ont t crases par la procdure.

Ainsi dans le code suivant :

SET @var = 'Bonjour' ;


CALL test_vu();
SELECT @var; -- Donne 15 !

Un utilisateur inconscient du fait que test_vu() modifie @var pourrait bien y perdre des cheveux !

Un raisonnement similaire peut tre tenu pour les autres objets utilisant les blocs dinstructions.
vitez donc les variables utilisateur dans les blocs quand cest possible (et nous allons bientt
voir un cas o ce nest pas possible).

6.6.3.2 Utiliser une procdure dans un bloc

CALL nom_procedure(); est une instruction. On peut donc parfaitement excuter une proc-
dure dans un bloc dinstructions.

Exemple : la procdure surface_cercle() calcule la surface dun cercle partir de son rayon. Elle
excute pour cela la procdure carre(), qui lve un nombre au carr. Pour rappel, on calcule la
surface dun cercle avec la formule suivante : S = r2

DELIMITER |
CREATE PROCEDURE carre(INOUT p_nb FLOAT) SET p_nb = p_nb * p_nb|

CREATE PROCEDURE surface_cercle(IN p_rayon FLOAT, OUT p_surface FLOAT)


BEGIN
CALL carre(p_rayon);

SET p_surface = p_rayon * PI();


END|
DELIMITER ;

CALL surface_cercle(1, @surface); -- Donne environ pi (3,14...)


SELECT @surface;
CALL surface_cercle(2, @surface); -- Donne environ 12,57...
SELECT @surface;

[[information]] | Un FLOAT stockant une valeur approche, il est tout fait possible (voire pro-
bable) que vous obteniez un rsultat diffrent de celui donn par une calculette.

310
6.6 Gestionnaires derreurs, curseurs et utilisation avance

6.6.3.3 Transactions et gestion derreurs

Un usage classique et utile des gestionnaires derreur est lannulation des transactions en cas
derreur.

Exemple : la procdure suivante prend en paramtre lid dun client, et de deux animaux que le
client veut adopter :

DELIMITER |
CREATE PROCEDURE adoption_deux_ou_rien(p_client_id INT, p_animal_id_1 INT, p_animal_id_
BEGIN
DECLARE v_prix DECIMAL(7,2);

DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK ; -- Gestionnaire qui annule la tra

START TRANSACTION ;

SELECT COALESCE(Race.prix, Espece.prix) INTO v_prix


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.id = p_animal_id_1;

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix,


VALUES (p_animal_id_1, p_client_id, CURRENT_DATE(), CURRENT_DATE(), v_prix, TRUE);

SELECT 'Adoption animal 1 russie' AS message;

SELECT COALESCE(Race.prix, Espece.prix) INTO v_prix


FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.id = p_animal_id_2;

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix,


VALUES (p_animal_id_2, p_client_id, CURRENT_DATE(), CURRENT_DATE(), v_prix, TRUE);

SELECT 'Adoption animal 2 russie' AS message;

COMMIT ;
END|
DELIMITER ;

CALL adoption_deux_ou_rien(2, 43, 55); -- L'animal 55 a dj t adopt

La procdure sinterrompt, puisque la seconde insertion choue. On nexcute donc pas le second
SELECT. Ici, grce la transaction et au ROLLBACK du gestionnaire, la premire insertion a t
annule.

[[attention]] | On ne peut pas utiliser de transactions dans un trigger.

311
6 Scuriser et automatiser ses actions

6.6.3.4 Prparer une requte dans un bloc dinstructions

Pour finir, on peut crer et excuter une requte prpare dans un bloc dinstructions. Ceci per-
met de crer des requtes dynamiques, puisquon prpare une requte partir dune chane de
caractres.

Exemple : la procdure suivante ajoute la ou les clauses que lon veut une simple requte
SELECT :

DELIMITER |
CREATE PROCEDURE select_race_dynamique(p_clause VARCHAR(255))
BEGIN
SET @sql = CONCAT('SELECT nom, description FROM Race ', p_clause);

PREPARE requete FROM @sql;


EXECUTE requete;
END|
DELIMITER ;

CALL select_race_dynamique('WHERE espece_id = 2'); -- Affichera les races de chats


CALL select_race_dynamique('ORDER BY nom LIMIT 2'); -- Affichera les deux premires rac

Il va sans dire que ce genre de construction dynamique de requtes peut poser dnormes
problmes de scurit si lon ne prend pas de prcaution. Par ailleurs, il nest pas possible de
construire une requte prpare partir dune variable locale. Il est donc ncessaire dutiliser
une variable utilisateur.

[[attention]] | Lutilisation des requtes prpares nest pas permise dans un trigger.

6.6.3.5 En rsum

Un gestionnaire derreur permet dintercepter un ou plusieurs types derreurs (ou aver-


tissements) SQL et de dclencher une srie dinstructions en cas derreur.
Les erreurs interceptes peuvent tre reprsentes par un numro derreur MySQL, un
identifiant SQLSTATE ou une CONDITION.
Il existe trois conditions prdfinies : SQLEXCEPTION (pour tout type derreur SQL),
SQLWARNING (pour tout avertissement) et NOT FOUND (en cas de FETCH sur un jeu de
rsultats vide ou puis).
Un curseur est une structure qui permet de parcourir un jeu de rsultats
Dans un bloc dinstructions, on dclare dabord les variables locales, puis les conditions,
suivies des curseurs, et pour finir les gestionnaires derreurs. Toutes ces dclarations
doivent tre faites avant les instructions du bloc.

6.7 Triggers
Les triggers (ou dclencheurs) sont des objets de la base de donnes. Attachs une table, ils vont
dclencher lexcution dune instruction, ou dun bloc dinstructions, lorsquune, ou plusieurs
lignes sont insres, supprimes ou modifies dans la table laquelle ils sont attachs.

312
6.7 Triggers

Dans ce chapitre, nous allons voir comment ils fonctionnent exactement, comment on peut les
crer et les supprimer, et surtout, comment on peut sen servir et quelles sont leurs restrictions.

6.7.1 Principe et usage

6.7.1.1 Quest-ce quun trigger ?

Tout comme les procdures stockes, les triggers servent excuter une ou plusieurs instructions.
Mais la diffrence des procdures, il nest pas possible dappeler un trigger : un trigger doit tre
dclench par un vnement.

Un trigger est attach une table, et peut tre dclench par :

une insertion dans la table (requte INSERT) ;


la suppression dune partie des donnes de la table (requte DELETE) ;
la modification dune partie des donnes de la table (requte UPDATE).

Par ailleurs, une fois le trigger dclench, ses instructions peuvent tre excutes soit juste avant
lexcution de lvnement dclencheur, soit juste aprs.

6.7.1.1.1 Que fait un trigger ? Un trigger excute un traitement pour chaque ligne ins-
re, modifie ou supprime par lvnement dclencheur. Donc si lon insre cinq lignes, les
instructions du trigger seront excutes cinq fois, chaque itration permettant de traiter les don-
nes dune des lignes insres.

Les instructions dun trigger suivent les mmes principes que les instructions dune procdure
stocke. Sil y a plus dune instruction, il faut les mettre lintrieur dun bloc dinstructions. Les
structures que nous avons vues dans les deux chapitres prcdents sont bien sr utilisables (struc-
tures conditionnelles, boucles, gestionnaires derreur, etc.), avec toutefois quelques restrictions
que nous verrons en fin de chapitre.

Un trigger peut modifier et/ou insrer des donnes dans nimporte quelle table sauf les tables
utilises dans la requte qui la dclench. En ce qui concerne la table laquelle le trigger est
attach (qui est forcment utilise par lvnement dclencheur), le trigger peut lire et modifier
uniquement la ligne insre, modifie ou supprime quil est en train de traiter.

6.7.1.2 quoi sert un trigger ?

On peut faire de nombreuses choses avec un trigger. Voici quelques exemples dusage frquent de
ces objets. Nous verrons plus loin certains de ces exemples appliqus notre levage danimaux.

6.7.1.2.1 Contraintes et vrifications de donnes Comme cela a dj t mentionn dans


le chapitre sur les types de donnes, MySQL nimplmente pas de contraintes dassertion, qui
sont des contraintes permettant de limiter les valeurs acceptes par une colonne (limiter une
colonne TINYINT TRUE (1) ou FALSE (0) par exemple). Avec des triggers se dclenchant avant
lINSERT et avant lUPDATE, on peut vrifier les valeurs dune colonne lors de linsertion ou de
la modification, et les corriger si elles ne font pas partie des valeurs acceptables, ou bien faire
chouer la requte. On peut ainsi pallier labsence de contraintes dassertion.

313
6 Scuriser et automatiser ses actions

6.7.1.2.2 Intgrit des donnes Les triggers sont parfois utiliss pour remplacer les options
des cls trangres ON UPDATE RESTRICT|CASCADE|SET NULL et ON DELETE RESTRICT|CASCADE|SET
NULL. Notamment pour des tables MyISAM, qui sont non-transactionnelles et ne supportent pas
les cls trangres. Cela peut aussi tre utilis avec des tables transactionnelles, dans les cas o
le traitement appliquer pour garder des donnes cohrentes est plus complexe que ce qui est
permis par les options de cls trangres.

Par exemple, dans certains systmes, on veut pouvoir appliquer deux systmes de suppression :

une vraie suppression pure et dure, avec effacement des donnes, donc une requte
DELETE ;
un archivage, qui masquera les donnes dans lapplication mais les conservera dans la base
de donnes.

Dans ce cas, une solution possible est dajouter aux tables contenant des donnes archivables une
colonne archive, pouvant contenir 0 (la ligne nest pas archive) ou 1 (la ligne est archive). Pour
une vraie suppression, on peut utiliser simplement un ON DELETE RESTRICT|CASCADE|SET
NULL, qui se rpercutera sur les tables rfrenant les donnes supprimes. Par contre, dans le cas
dun archivage, on utilisera plutt un trigger pour traiter les lignes qui rfrencent les donnes
archives, par exemple en les archivant galement.

6.7.1.2.3 Historisation des actions On veut parfois garder une trace des actions effectues
sur la base de donnes, cest--dire par exemple, savoir qui a modifi telle ligne, et quand. Avec
les triggers, rien de plus simple, il suffit de mettre jour des donnes dhistorisation chaque
insertion, modification ou suppression. Soit directement dans la table concerne, soit dans une
table utilise spcialement et exclusivement pour garder un historique des actions.

6.7.1.2.4 Mise jour dinformations qui dpendent dautres donnes Comme pour
les procdures stockes, une partie de la logique business de lapplication peut tre code direc-
tement dans la base de donnes, grce aux triggers, plutt que du ct applicatif (en PHP, Java ou
quel que soit le langage de programmation utilis). nouveau, cela peut permettre dharmoniser
un traitement travers plusieurs applications utilisant la mme base de donnes.

Par ailleurs, lorsque certaines informations dpendent de la valeur de certaines donnes, on peut
en gnral les retrouver en faisant une requte SELECT. Dans ce cas, il nest pas indispensable
de stocker ces informations. Cependant, utiliser les triggers pour stocker ces informations peut
faciliter la vie de lutilisateur, et peut aussi faire gagner en performance. Par exemple, si lon a
trs souvent besoin de cette information, ou si la requte faire pour trouver cette information
est longue excuter. Cest typiquement cet usage qui est fait des triggers dans ce quon appelle
les vues matrialises, auxquelles un chapitre est consacr dans la partie 6.

6.7.2 Cration des triggers

6.7.2.1 Syntaxe

Pour crer un trigger, on utilise la commande suivante :

CREATE TRIGGER nom_trigger moment_trigger evenement_trigger


ON nom_table FOR EACH ROW
corps_trigger

314
6.7 Triggers

CREATE TRIGGER nom_trigger : les triggers ont donc un nom.


moment_trigger evenement_trigger : servent dfinir quand et comment le trigger
est dclench.
ON nom_table : cest l quon dfinit quelle table le trigger est attach.
FOR EACH ROW : signifie littralement pour chaque ligne, sous-entendu pour chaque
ligne insre/supprime/modifie selon ce qui a dclench le trigger.
corps_trigger : cest le contenu du trigger. Comme pour les procdures stockes, il peut
sagir soit dune seule instruction, soit dun bloc dinstructions.

6.7.2.1.1 vnement dclencheur Trois vnements diffrents peuvent dclencher lex-


cution des instructions dun trigger.

Linsertion de lignes (INSERT) dans la table attache au trigger.


La modification de lignes (UPDATE) de cette table.
La suppression de lignes (DELETE) de la table.

Un trigger est soit dclench par INSERT, soit par UPDATE, soit par DELETE. Il ne peut pas tre
dclench par deux vnements diffrents. On peut par contre crer plusieurs triggers par table
pour couvrir chaque vnement.

6.7.2.1.2 Avant ou aprs Lorsquun trigger est dclench, ses instructions peuvent tre
excutes deux moments diffrents. Soit juste avant que lvnement dclencheur nait lieu
(BEFORE), soit juste aprs (AFTER).

Donc, si vous avez un trigger BEFORE UPDATE sur la table A, lexcution dune requte UPDATE
sur cette table va dabord dclencher lexcution des instructions du trigger, ensuite seulement
les lignes de la table seront modifies.

6.7.2.1.3 Exemple Pour crer un trigger sur la table Animal, dclench par une insertion, et
sexcutant aprs ladite insertion, on utilisera la syntaxe suivante :

CREATE TRIGGER after_insert_animal AFTER INSERT


ON Animal FOR EACH ROW
corps_trigger;

6.7.2.2 Rgle et convention

Il ne peut exister quun seul trigger par combinaison moment_trigger/evenement_trigger


par table. Donc un seul trigger BEFORE UPDATE par table, un seul AFTER DELETE, etc. tant
donn quil existe deux possibilits pour le moment dexcution, et trois pour lvnement d-
clencheur, on a donc un maximum de six triggers par table.

Cette rgle tant tablie, il existe une convention quant la manire de nommer ses triggers, que
je vous encourage suivre : nom_trigger = moment_evenement_table. Donc le trigger BEFORE UPDATE
ON Animal aura pour nom : before_update_animal.

6.7.2.3 OLD et NEW

Dans le corps du trigger, MySQL met disposition deux mots-cls : OLD et NEW.

315
6 Scuriser et automatiser ses actions

OLD : reprsente les valeurs des colonnes de la ligne traite avant quelle ne soit modifie
par lvnement dclencheur. Ces valeurs peuvent tre lues, mais pas modifies.
NEW : reprsente les valeurs des colonnes de la ligne traite aprs quelle a t modifie
par lvnement dclencheur. Ces valeurs peuvent tre lues et modifies.

Il ny a que dans le cas dun trigger UPDATE que OLD et NEW coexistent. Lors dune insertion, OLD
nexiste pas, puisque la ligne nexiste pas avant lvnement dclencheur ; dans le cas dune sup-
pression, cest NEW qui nexiste pas, puisque la ligne nexistera plus aprs lvnement dclen-
cheur.

Premier exemple : linsertion dune ligne.

Excutons la commande suivante :

INSERT INTO Adoption (client_id, animal_id, date_reservation, prix, paye)


VALUES (12, 15, NOW(), 200.00, FALSE);

Pendant le traitement de cette ligne par le trigger correspondant,

NEW.client_id vaudra 12 ;
NEW.animal_id vaudra 15 ;
NEW.date_reservation vaudra NOW() ;
NEW.date_adoption vaudra NULL ;
NEW.prix vaudra 200.00 ;
NEW.paye vaudra FALSE (0).

Les valeurs de OLD ne seront pas dfinies. Dans le cas dune suppression, on aura exactement
linverse.

Second exemple : la modification dune ligne. On modifie la ligne que lon vient dinsrer en
excutant la commande suivante :

UPDATE Adoption
SET paye = TRUE
WHERE client_id = 12 AND animal_id = 15 ;

Pendant le traitement de cette ligne par le trigger correspondant,

NEW.paye vaudra TRUE, tandis que OLD.paye vaudra FALSE.


Par contre les valeurs respectives de NEW.animal_id, NEW.client_id, NEW.date_reservation,
NEW.date_adoption et NEW.prix seront les mmes que OLD.animal_id, OLD.client_id,
OLD.date_reservation, OLD.date_adoption et OLD.prix, puisque ces colonnes ne
sont pas modifies par la requte.

[[attention]] | Dans le cas dune insertion ou dune modification, si un trigger peut potentielle-
ment changer la valeur de NEW.colonne, il doit tre excut avant lvnement (BEFORE). Sinon,
la ligne aura dj t insre ou modifie, et la modification de NEW.colonne naura plus aucune
influence sur celle-ci.

6.7.2.4 Erreur dclenche pendant un trigger

Si un trigger BEFORE gnre une erreur (non intercepte par un gestionnaire derreur), la
requte ayant dclench le trigger ne sera pas excute. Si lvnement devait galement
dclencher un trigger AFTER, il ne sera bien sr pas non plus excut.

316
6.7 Triggers

Si un trigger AFTER gnre une erreur, la requte ayant dclench le trigger chouera.
Dans le cas dune table transactionnelle, si une erreur est dclenche, un ROLLBACK sera
fait. Dans le cas dune table non-transactionnelle, tous les changements qui auraient t
faits par le (ou les) trigger(s) avant le dclenchement de lerreur persisteront.

6.7.3 Suppression des triggers


Encore une fois, la commande DROP permet de supprimer un trigger.

DROP TRIGGER nom_trigger;

Tout comme pour les procdures stockes, il nest pas possible de modifier un trigger. Il faut le
supprimer puis le recrer diffremment.

Par ailleurs, si lon supprime une table, on supprime galement tous les triggers qui y sont atta-
chs.

6.7.4 Exemples
6.7.4.1 Contraintes et vrification des donnes

6.7.4.1.1 Vrification du sexe des animaux Dans notre table Animal se trouve la colonne
sexe. Cette colonne accepte tout caractre, ou NULL. Or, seuls les caractres M et F ont du
sens. Nous allons donc crer deux triggers, un pour linsertion, lautre pour la modification, qui
vont empcher quon donne un autre caractre que M ou F pour sexe.

Ces deux triggers devront se dclencher avant linsertion et la modification. On aura donc :

-- Trigger dclench par l'insertion


DELIMITER |
CREATE TRIGGER before_insert_animal BEFORE INSERT
ON Animal FOR EACH ROW
BEGIN
-- Instructions
END |

-- Trigger dclench par la modification


CREATE TRIGGER before_update_animal BEFORE UPDATE
ON Animal FOR EACH ROW
BEGIN
-- Instructions
END |
DELIMITER ;

Il ne reste plus qu crire le code du trigger, qui sera similaire pour les deux triggers. Et comme
ce corps contiendra des instructions, il ne faut pas oublier de changer le dlimiteur.

Le corps consistera en une simple structure conditionnelle, et dfinira un comportement adop-


ter si le sexe donn ne vaut ni M, ni F, ni NULL.

[[question]] | Quel comportement adopter en cas de valeur errone ?

Deux possibilits :

317
6 Scuriser et automatiser ses actions

on modifie la valeur du sexe, en le mettant NULL par exemple ;


on provoque une erreur, ce qui empchera linsertion/la modification.

Commenons par le plus simple : mettre le sexe NULL.

DELIMITER |
CREATE TRIGGER before_update_animal BEFORE UPDATE
ON Animal FOR EACH ROW
BEGIN
IF NEW.sexe IS NOT NULL -- le sexe n'est ni NULL
AND NEW.sexe != 'M' -- ni "M"
AND NEW.sexe != 'F' -- ni "F"
THEN
SET NEW.sexe = NULL ;
END IF ;
END |
DELIMITER ;

Test :

UPDATE Animal
SET sexe = 'A'
WHERE id = 20 ; -- l'animal 20 est Balou, un mle

SELECT id, sexe, date_naissance, nom


FROM Animal
WHERE id = 20 ;

id sexe date_naissance nom

20 NULL 2007-04-24 12 :45 :00 Balou

Le sexe est bien NULL, le trigger a fonctionn.

Pour le second trigger, dclench par linsertion de lignes, on va implmenter le second compor-
tement : on va dclencher une erreur, ce qui empchera linsertion, et affichera lerreur.

[[question]] | Mais comment dclencher une erreur ?

Contrairement certains SGBD, MySQL ne dispose pas dune commande permettant de dclen-
cher une erreur personnalise. La seule solution est donc de faire une requte dont on sait quelle
va gnrer une erreur.

Exemple :

SELECT 1, 2 INTO @a;

ERROR1222(21000):TheusedSELECTstatementshaveadifferentnumberofcolumns

Cependant, il serait quand mme intressant davoir un message derreur qui soit un peu explicite.
Voici une manire dobtenir un tel message : on cre une table Erreur, ayant deux colonnes, id et

318
6.7 Triggers

erreur. La colonne id est cl primaire, et erreur contient un texte court dcrivant lerreur. Un index
UNIQUE est ajout sur cette dernire colonne. On insre ensuite une ligne correspondant lerreur
quon veut utiliser dans le trigger. Ensuite dans le corps du trigger, en cas de valeur errone, on
refait la mme insertion. Cela dclenche une erreur de contrainte dunicit, laquelle affiche le
texte quon a essay dinsrer dans Erreur.

-- Cration de la table Erreur


CREATE TABLE Erreur (
id TINYINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
erreur VARCHAR(255) UNIQUE);

-- Insertion de l'erreur qui nous intresse


INSERT INTO Erreur (erreur) VALUES ('Erreur : sexe doit valoir "M", "F" ou NULL.');

-- Cration du trigger
DELIMITER |
CREATE TRIGGER before_insert_animal BEFORE INSERT
ON Animal FOR EACH ROW
BEGIN
IF NEW.sexe IS NOT NULL -- le sexe n'est ni NULL
AND NEW.sexe != 'M' -- ni "M"
AND NEW.sexe != 'F' -- ni "F"
THEN
INSERT INTO Erreur (erreur) VALUES ('Erreur : sexe doit valoir "M", "F" ou NULL
END IF ;
END |
DELIMITER ;

Test :

INSERT INTO Animal (nom, sexe, date_naissance, espece_id)


VALUES ('Babar', 'A', '2011-08-04 12:34', 3);

ERROR1062(23000):Duplicateentry'Erreur:sexedoitvaloir"M","F"ouNULL.'fork

Et voil, ce nest pas parfait, mais au moins le message derreur permet de cerner do vient le
problme. Et Babar na pas t insr.

6.7.4.1.2 Vrification du boolen dans Adoption Il est important de savoir si un client a


pay ou non pour les animaux quil veut adopter. Il faut donc vrifier la valeur de ce quon insre
dans la colonne paye, et refuser toute insertion/modification donnant une valeur diffrente de
TRUE (1) ou FALSE (0). Les deux triggers crer sont trs similaires ce que lon a fait pour la
colonne sexe dAnimal. Essayez donc de construire les requtes vous-mmes.

INSERT INTO Erreur (erreur) VALUES ('Erreur : paye doit valoir TRUE (1) ou FALSE (0).')

DELIMITER |
CREATE TRIGGER before_insert_adoption BEFORE INSERT
ON Adoption FOR EACH ROW

319
6 Scuriser et automatiser ses actions

BEGIN
IF NEW.paye != TRUE -- ni TRUE
AND NEW.paye != FALSE -- ni FALSE
THEN
INSERT INTO Erreur (erreur) VALUES ('Erreur : paye doit valoir TRUE (1) ou FALS
END IF ;
END |

CREATE TRIGGER before_update_adoption BEFORE UPDATE


ON Adoption FOR EACH ROW
BEGIN
IF NEW.paye != TRUE -- ni TRUE
AND NEW.paye != FALSE -- ni FALSE
THEN
INSERT INTO Erreur (erreur) VALUES ('Erreur : paye doit valoir TRUE (1) ou FALS
END IF ;
END |
DELIMITER ;

Test :

UPDATE Adoption
SET paye = 3
WHERE client_id = 9 ;

ERROR1062(23000):Duplicateentry'Erreur:payedoitvaloirTRUE(1)ouFALSE(0)'f

6.7.4.1.3 Vrification de la date dadoption Il reste une petite chose vrifier, et ce sera
tout pour les vrifications de donnes : la date dadoption ! En effet, celle-ci doit tre postrieure
ou gale la date de rservation. Un client ne peut pas emporter chez lui un animal avant mme
davoir prvenu quil voulait ladopter. nouveau, essayez de faire le trigger vous-mmes. Pour
rappel, il ne peut exister quun seul trigger BEFORE UPDATE et un seul BEFORE INSERT pour
chaque table.

INSERT INTO Erreur (erreur) VALUES ('Erreur : date_adoption doit tre >= date_reserva

DELIMITER |
DROP TRIGGER before_insert_adoption|
CREATE TRIGGER before_insert_adoption BEFORE INSERT
ON Adoption FOR EACH ROW
BEGIN
IF NEW.paye != TRUE -- On remet la vrification
AND NEW.paye != FALSE
THEN
INSERT INTO Erreur (erreur) VALUES ('Erreur : paye doit valoir TRUE (1) ou FALS

ELSEIF NEW.date_adoption < NEW.date_reservation THEN -- Adoption avant rservati


INSERT INTO Erreur (erreur) VALUES ('Erreur : date_adoption doit tre >= date
END IF ;

320
6.7 Triggers

END |

DROP TRIGGER before_update_adoption|


CREATE TRIGGER before_update_adoption BEFORE UPDATE
ON Adoption FOR EACH ROW
BEGIN
IF NEW.paye != TRUE -- On remet la vrification
AND NEW.paye != FALSE
THEN
INSERT INTO Erreur (erreur) VALUES ('Erreur : paye doit valoir TRUE (1) ou FALS

ELSEIF NEW.date_adoption < NEW.date_reservation THEN -- Adoption avant rservati


INSERT INTO Erreur (erreur) VALUES ('Erreur : date_adoption doit tre >= date
END IF ;
END |
DELIMITER ;

On aurait pu faire un second IF au lieu dun ELSEIF, mais de toute faon, le trigger ne pourra
dclencher quune erreur la fois.

Test :

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix, paye


VALUES (10, 10, NOW(), NOW() - INTERVAL 2 DAY, 200.00, 0);

INSERT INTO Adoption (animal_id, client_id, date_reservation, date_adoption, prix, paye


VALUES (10, 10, NOW(), NOW(), 200.00, 4);

ERROR1062(23000):Duplicateentry'Erreur:date_adoptiondoittre>=date_reserva

ERROR1062(23000):Duplicateentry'Erreur:payedoitvaloirTRUE(1)ouFALSE(0).'

Les deux vrifications fonctionnent !

6.7.4.2 Mise jour dinformations dpendant dautres donnes

Pour linstant, lorsque lon a besoin de savoir quels animaux restent disponibles pour ladoption,
il faut faire une requte avec sous-requte.

SELECT id, nom, sexe, date_naissance, commentaires


FROM Animal
WHERE NOT EXISTS (
SELECT *
FROM Adoption
WHERE Animal.id = Adoption.animal_id
);

Mais une telle requte nest pas particulirement performante, et elle est relativement peu facile
lire. Les triggers peuvent nous permettre de stocker automatiquement une donne permettant
de savoir immdiatement si un animal est disponible ou non.

321
6 Scuriser et automatiser ses actions

Pour cela, il suffit dajouter une colonne disponible la table Animal, qui vaudra FALSE ou TRUE, et
qui sera mise jour grce trois triggers sur la table Adoption.

linsertion dune nouvelle adoption, il faut retirer lanimal adopt des animaux dispo-
nibles ;
en cas de suppression, il faut faire le contraire ;
en cas de modification dune adoption, si lanimal adopt change, il faut remettre lancien
parmi les animaux disponibles et retirer le nouveau.

-- Ajout de la colonne disponible


ALTER TABLE Animal ADD COLUMN disponible BOOLEAN DEFAULT TRUE ; -- l'insertion, un a

-- Remplissage de la colonne
UPDATE Animal
SET disponible = FALSE
WHERE EXISTS (
SELECT *
FROM Adoption
WHERE Animal.id = Adoption.animal_id
);

-- Cration des trois triggers


DELIMITER |
CREATE TRIGGER after_insert_adoption AFTER INSERT
ON Adoption FOR EACH ROW
BEGIN
UPDATE Animal
SET disponible = FALSE
WHERE id = NEW.animal_id;
END |

CREATE TRIGGER after_delete_adoption AFTER DELETE


ON Adoption FOR EACH ROW
BEGIN
UPDATE Animal
SET disponible = TRUE
WHERE id = OLD.animal_id;
END |

CREATE TRIGGER after_update_adoption AFTER UPDATE


ON Adoption FOR EACH ROW
BEGIN
IF OLD.animal_id <> NEW.animal_id THEN
UPDATE Animal
SET disponible = TRUE
WHERE id = OLD.animal_id;

UPDATE Animal
SET disponible = FALSE

322
6.7 Triggers

WHERE id = NEW.animal_id;
END IF ;
END |
DELIMITER ;

Test :

SELECT animal_id, nom, sexe, disponible, client_id


FROM Animal
INNER JOIN Adoption ON Adoption.animal_id = Animal.id
WHERE client_id = 9 ;

animal_id nom sexe disponible client_id

33 Caribou M 0 9
54 Bubulle M 0 9
55 Relou M 0 9

DELETE FROM Adoption -- 54 doit redeveni


WHERE animal_id = 54 ;

UPDATE Adoption
SET animal_id = 38, prix = 985.00 -- 38 doit devenir
WHERE animal_id = 33 ; -- et 33 redevenir

INSERT INTO Adoption (client_id, animal_id, date_reservation, prix, paye)


VALUES (9, 59, NOW(), 700.00, FALSE); -- 59 doit

SELECT Animal.id AS animal_id, nom, sexe, disponible, client_id


FROM Animal
LEFT JOIN Adoption ON Animal.id = Adoption.animal_id
WHERE Animal.id IN (33, 54, 55, 38, 59);

animal_id nom sexe disponible client_id

33 Caribou M 1 NULL
38 Boule F 0 9
54 Bubulle M 1 NULL
55 Relou M 0 9
59 Bavard M 0 9

Dsormais, pour savoir quels animaux sont disponibles, il suffira de faire la requte suivante :

SELECT *
FROM Animal
WHERE disponible = TRUE ;

-- Ou mme

SELECT *

323
6 Scuriser et automatiser ses actions

FROM Animal
WHERE disponible;

6.7.4.3 Historisation

Voici deux exemples de systmes dhistorisation :

lun trs basique, gardant simplement trace de linsertion (date et utilisateur) et de la der-
nire modification (date et utilisateur), et se faisant directement dans la table concerne ;
lautre plus complet, qui garde une copie de chaque version antrieure des lignes dans une
table ddie, ainsi quune copie de la dernire version en cas de suppression.

6.7.4.3.1 Historisation basique On va utiliser cette historisation pour la table Race. Libre
vous dadapter ou de crer les triggers dautres tables pour les historiser galement de cette
manire.

On ajoute donc quatre colonnes la table. Ces colonnes seront toujours remplies automatique-
ment par les triggers.

-- On modifie la table Race


ALTER TABLE Race ADD COLUMN date_insertion DATETIME, -- date d'insertio
ADD COLUMN utilisateur_insertion VARCHAR(20), -- utilisateur aya
ADD COLUMN date_modification DATETIME, -- date de dernir
ADD COLUMN utilisateur_modification VARCHAR(20); -- utilisateur aya

-- On remplit les colonnes


UPDATE Race
SET date_insertion = NOW() - INTERVAL 1 DAY,
utilisateur_insertion = 'Test',
date_modification = NOW()- INTERVAL 1 DAY,
utilisateur_modification = 'Test' ;

Jai mis artificiellement les dates dinsertion et de dernire modification la veille daujourdhui,
et les utilisateurs pour linsertion et la modification Test, afin davoir des donnes intres-
santes lors des tests. Idalement, ce type dhistorisation doit bien sr tre mis en place ds la
cration de la table.

Occupons-nous maintenant des triggers. Il en faut sur linsertion et sur la modification.

DELIMITER |
CREATE TRIGGER before_insert_race BEFORE INSERT
ON Race FOR EACH ROW
BEGIN
SET NEW.date_insertion = NOW();
SET NEW.utilisateur_insertion = CURRENT_USER();
SET NEW.date_modification = NOW();
SET NEW.utilisateur_modification = CURRENT_USER();
END |

CREATE TRIGGER before_update_race BEFORE UPDATE


ON Race FOR EACH ROW

324
6.7 Triggers

BEGIN
SET NEW.date_modification = NOW();
SET NEW.utilisateur_modification = CURRENT_USER();
END |
DELIMITER ;

Les triggers sont trs simples : ils mettent simplement jour les colonnes dhistorisation nces-
saires ; ils doivent donc ncessairement tre BEFORE.

Test :

INSERT INTO Race (nom, description, espece_id, prix)


VALUES ('Yorkshire terrier', 'Chien de petite taille au pelage long et soyeux de couleu

UPDATE Race
SET prix = 630.00
WHERE nom = 'Rottweiller' AND espece_id = 1 ;

SELECT nom, DATE(date_insertion) AS date_ins, utilisateur_insertion AS utilisateur_ins,


FROM Race
WHERE espece_id = 1 ;

nom date_ins utilisateur_ins date_mod utilisateur_mod

Berger allemand 2012-05-02 Test 2012-05-02 Test


Berger blanc suisse 2012-05-02 Test 2012-05-02 Test
Rottweiller 2012-05-02 Test 2012-05-03 sdz@localhost
Yorkshire terrier 2012-05-03 sdz@localhost 2012-05-03 sdz@localhost

6.7.4.3.2 Historisation complte Nous allons mettre en place un systme dhistorisation


complet pour la table Animal. Celle-ci ne change pas et contiendra la dernire version des donnes.
Par contre, on va ajouter une table Animal_histo, qui contiendra les versions antrieures (quand il
y en a) des donnes dAnimal.

CREATE TABLE Animal_histo (


id SMALLINT(6) UNSIGNED NOT NULL, -- Colonnes historises
sexe CHAR(1),
date_naissance DATETIME NOT NULL,
nom VARCHAR(30),
commentaires TEXT,
espece_id SMALLINT(6) UNSIGNED NOT NULL,
race_id SMALLINT(6) UNSIGNED DEFAULT NULL,
mere_id SMALLINT(6) UNSIGNED DEFAULT NULL,
pere_id SMALLINT(6) UNSIGNED DEFAULT NULL,
disponible BOOLEAN DEFAULT TRUE,

date_histo DATETIME NOT NULL, -- Colonnes techniques


utilisateur_histo VARCHAR(20) NOT NULL,
evenement_histo CHAR(6) NOT NULL,
PRIMARY KEY (id, date_histo)

325
6 Scuriser et automatiser ses actions

) ENGINE=InnoDB;

Les colonnes date_histo et utilisateur_histo contiendront bien sr la date laquelle la ligne a t


historise, et lutilisateur qui a provoqu cette historisation. Quant la colonne evenement_histo,
elle contiendra lvnement qui a dclench le trigger (soit DELETE, soit UPDATE). La cl
primaire de cette table est le couple (id, date_histo).

Voici les triggers ncessaires. Cette fois, ils pourraient tre soit BEFORE, soit AFTER. Cependant,
aucun traitement ne concerne les nouvelles valeurs de la ligne modifie (ni, a fortiori, de la ligne
supprime). Par consquent, autant utiliser AFTER, cela vitera dexcuter les instructions du
trigger en cas derreur lors de la requte dclenchant celui-ci.

DELIMITER |
CREATE TRIGGER after_update_animal AFTER UPDATE
ON Animal FOR EACH ROW
BEGIN
INSERT INTO Animal_histo (
id,
sexe,
date_naissance,
nom,
commentaires,
espece_id,
race_id,
mere_id,
pere_id,
disponible,

date_histo,
utilisateur_histo,
evenement_histo)
VALUES (
OLD.id,
OLD.sexe,
OLD.date_naissance,
OLD.nom,
OLD.commentaires,
OLD.espece_id,
OLD.race_id,
OLD.mere_id,
OLD.pere_id,
OLD.disponible,

NOW(),
CURRENT_USER(),
'UPDATE');
END |

CREATE TRIGGER after_delete_animal AFTER DELETE


ON Animal FOR EACH ROW

326
6.7 Triggers

BEGIN
INSERT INTO Animal_histo (
id,
sexe,
date_naissance,
nom,
commentaires,
espece_id,
race_id,
mere_id,
pere_id,
disponible,

date_histo,
utilisateur_histo,
evenement_histo)
VALUES (
OLD.id,
OLD.sexe,
OLD.date_naissance,
OLD.nom,
OLD.commentaires,
OLD.espece_id,
OLD.race_id,
OLD.mere_id,
OLD.pere_id,
OLD.disponible,

NOW(),
CURRENT_USER(),
'DELETE');
END |
DELIMITER ;

Cette fois, ce sont les valeurs avant modification/suppression qui nous intressent, do lutili-
sation de OLD.

Test :

UPDATE Animal
SET commentaires = 'Petit pour son ge'
WHERE id = 10 ;

DELETE FROM Animal


WHERE id = 47 ;

SELECT id, sexe, date_naissance, nom, commentaires, espece_id


FROM Animal
WHERE id IN (10, 47);

327
6 Scuriser et automatiser ses actions

SELECT id, nom, date_histo, utilisateur_histo, evenement_histo


FROM Animal_histo;

id sexe date_naissance nom commentaires espece_id

10 M 2010-07-21 15 :41 :00 Bobo Petit pour son ge 1

id nom date_histo utilisateur_histo evenement_histo

10 Bobo 2012-05-03 21 :51 :12 sdz@localhost UPDATE


47 Scroupy 2012-05-03 21 :51 :12 sdz@localhost DELETE

6.7.4.3.3 Quelques remarques sur lhistorisation Les deux systmes dhistorisation mon-
trs dans ce cours ne sont que deux possibilits parmi des dizaines. Si vous pensez avoir besoin
dun systme de ce type, prenez le temps de rflchir, et de vous renseigner sur les diverses pos-
sibilits qui soffrent vous. Dans certains systmes, on combine les deux historisations que jai
prsentes. Parfois, on ne conserve pas les lignes supprimes dans la table dhistorisation, mais
on utilise plutt un systme darchive, spar de lhistorisation. Au-del du modle dhistorisa-
tion que vous choisirez, les dtails sont galement modifiables. Voulez-vous garder toutes les
versions des donnes, ou les garder seulement pour une certaine priode de temps ? Voulez-vous
enregistrer lutilisateur SQL ou plutt des utilisateurs crs pour votre application, dcoupls des
utilisateurs SQL ? Ne restez pas bloqus sur les exemples montrs dans ce cours (que ce soit pour
lhistorisation ou le reste), le monde est vaste !

*SGBD : Systme de Gestion de Base de Donnes

6.7.5 Restrictions
Les restrictions sur les triggers sont malheureusement trop importantes pour quon puisse se
permettre de ne pas les mentionner. On peut esprer quune partie de ces restrictions soit leve
dans une prochaine version de MySQL, mais en attendant, il est ncessaire davoir celles-ci en
tte. Voici donc les principales.

6.7.5.0.1 Commandes interdites Il est impossible de travailler avec des transactions


lintrieur dun trigger. Cette restriction est ncessaire, puisque la requte ayant provoqu lex-
cution du trigger pourrait trs bien se trouver elle-mme lintrieur dune transaction. Auquel
cas, toute commande START TRANSACTION, COMMIT ou ROLLBACK interagirait avec cette tran-
saction, de manire intempestive.

Les requtes prpares ne peuvent pas non plus tre utilises.

Enfin, on ne peut pas appeler nimporte quelle procdure partir dun trigger.

Les procdures appeles par un trigger ne peuvent pas envoyer dinformations au client
MySQL. Par exemple, elles ne peuvent pas excuter un simple SELECT, qui produit un af-
fichage dans le client (un SELECT...INTO par contre est permis). Elles peuvent toutefois
renvoyer des informations au trigger grce des paramtres OUT ou INOUT.
Les procdures appeles ne peuvent utiliser ni les transactions (START TRANSACTION,
COMMIT ou ROLLBACK) ni les requtes prpares. Cest--dire quelles doivent respecter

328
6.7 Triggers

les restrictions des triggers.

6.7.5.0.2 Tables utilises par la requte Comme mentionn auparavant, il est impossible
de modifier les donnes dune table utilise par la requte ayant dclench le trigger lint-
rieur de celui-ci.

Cette restriction est importante, et peut remettre en question lutilisation de certains triggers.

Exemple : le trigger AFTER INSERT ON Adoption modifie les donnes de la table Animal. Si lon
excute la requte suivante, cela posera problme.

INSERT INTO Adoption (animal_id, client_id, date_reservation, prix, paye)


SELECT Animal.id, 4, NOW(), COALESCE(Race.prix, Espece.prix), FALSE
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.nom = 'Boucan' AND Animal.espece_id = 2 ;

ERROR1442(HY000):Can'tupdatetable'animal'instoredfunction/triggerbecauseiti

Le trigger choue puisque la table Animal est utilise par la requte INSERT qui le dclenche. Lin-
sertion elle-mme est donc finalement annule.

6.7.5.0.3 Cls trangres Une suppression ou modification de donnes dclenche par


une cl trangre ne provoquera pas lexcution du trigger correspondant. Par exemple, la co-
lonne Animal.race_id possde une cl trangre, qui rfrence la colonne Race.id. Cette cl trangre
a t dfinie avec loption ON DELETE SET NULL. Donc en cas de suppression dune race, tous
les animaux de cette race seront modifis, et leur race_id change en NULL. Il sagit donc dune
modification de donnes. Mais comme cette modification a t dclenche par une contrainte
de cl trangre, les ventuels triggers BEFORE UPDATE et AFTER UPDATE de la table Animal ne
seront pas dclenchs.

En cas dutilisation de triggers sur des tables prsentant des cls trangres avec ces options, il
vaut donc mieux supprimer celles-ci et dplacer ce comportement dans des triggers. Une autre
solution est de ne pas utiliser les triggers sur les tables concernes. Vous pouvez alors remplacer
les triggers par lutilisation de procdures stockes et/ou de transactions.

[[question]] | Quavons-nous comme cls trangres dans nos tables ?

Race : CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES


Espece (id) ON DELETE CASCADE;
Animal : CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race
(id) ON DELETE SET NULL;
Animal : CONSTRAINT fk_espece_id FOREIGN KEY (espece_id) REFERENCES
Espece (id);
Animal : CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal
(id) ON DELETE SET NULL;
Animal : CONSTRAINT fk_pere_id FOREIGN KEY (pere_id) REFERENCES Animal
(id) ON DELETE SET NULL;

329
6 Scuriser et automatiser ses actions

Quatre dentre elles pourraient donc poser problme. Quatre, sur cinq ! Ce nest donc pas anodin
comme restriction !

On va donc modifier nos cls trangres pour quelles reprennent leur comportement par dfaut.
Il faudra ensuite crer (ou recrer) quelques triggers pour reproduire le comportement que lon
avait dfini. ceci prs que la restriction sur la modification des donnes dune table utilise par
lvnement dclencheur fait quon ne pourra pas reproduire certains comportements. On ne
pourra pas mettre NULL les colonnes pere_id et mere_id de la table Animal en cas de suppression
de lanimal de rfrence.

Voici les commandes :

-- On supprime les cls


ALTER TABLE Race DROP FOREIGN KEY fk_race_espece_id;
ALTER TABLE Animal DROP FOREIGN KEY fk_race_id,
DROP FOREIGN KEY fk_mere_id,
DROP FOREIGN KEY fk_pere_id;

-- On les recre sans option


ALTER TABLE Race ADD CONSTRAINT fk_race_espece_id FOREIGN KEY (espece_id) REFERENCES Es
ALTER TABLE Animal ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race (id)
ADD CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES Animal (i
ADD CONSTRAINT fk_pere_id FOREIGN KEY (pere_id) REFERENCES Animal (i

-- Trigger sur Race


DELIMITER |
CREATE TRIGGER before_delete_race BEFORE DELETE
ON Race FOR EACH ROW
BEGIN
UPDATE Animal
SET race_id = NULL
WHERE race_id = OLD.id;
END|

-- Trigger sur Espece


CREATE TRIGGER before_delete_espece BEFORE DELETE
ON Espece FOR EACH ROW
BEGIN
DELETE FROM Race
WHERE espece_id = OLD.id;
END |
DELIMITER ;

6.7.5.1 En rsum

Un trigger est un objet stock dans la base de donnes, la manire dune table ou dune
procdure stocke. La seule diffrence est quun trigger est li une table, donc en cas de
suppression dune table, les triggers lis celle-ci sont supprims galement

330
6.7 Triggers

Un trigger dfinit une ou plusieurs instructions, dont lexcution est dclenche par une
insertion, une modification ou une suppression de donnes dans la table laquelle le
trigger est li.
Les instructions du trigger peuvent tre excutes avant la requte ayant dclench celui-
ci, ou aprs. Ce comportement est dfinir la cration du trigger.
Une table ne peut possder quun seul trigger par combinaison vnement/moment
(BEFORE UPDATE, AFTER DELETE,)
Les triggers sous MySQL sont soumis dimportantes (et potentiellement trs gnantes)
restrictions.

Scuriser une base de donnes et automatiser les traitements ne se limite bien sr pas ce que
nous venons de voir. Les deux prochaines parties vous donneront de nouveaux outils pour avoir
une base de donnes bien construite, sre et efficace. Cependant, tout ne pourra pas tre abord
dans ce cours, donc nhsitez pas poursuivre votre apprentissage.

331
7 Au-del des tables classiques : vues,
tables temporaires et vues
matrialises
Jusqu prsent, toutes les manipulations de donnes seffectuaient sur des tables. Dans cette
partie vous allez dcouvrir dautres objets et concepts permettant de stocker et/ou manipuler des
donnes.

7.1 Vues
Les vues sont des objets de la base de donnes, constitus dun nom, et dune requte de slec-
tion.

Une fois quune vue est dfinie, on peut lutiliser comme on le ferait avec une table ; table qui
serait constitue des donnes slectionnes par la requte dfinissant la vue.

Nous verrons dans ce chapitre :

comment crer, modifier, supprimer une vue ;


quoi peut servir une vue ;
deux algorithmes diffrents pouvant tre utilises par les vues ;
comment modifier les donnes partir dune vue.

7.1.0.1 Etat actuel de la base de donnes

Note : les tables de test et les procdures stockes ne sont pas reprises.

[[secret]] |
sql | SET NAMES utf8; | | DROP TABLE IF EXISTS Erreur; | DROP
TABLE IF EXISTS Animal_histo; | | DROP TABLE IF EXISTS Adoption; | DROP
TABLE IF EXISTS Animal; | DROP TABLE IF EXISTS Race; | DROP TABLE IF EXISTS
Espece; | DROP TABLE IF EXISTS Client; | | | CREATE TABLE Client ( |
id smallint(5) unsigned NOT NULL AUTO_INCREMENT, | nom varchar(100) NOT
NULL, | prenom varchar(60) NOT NULL, | adresse varchar(200) DEFAULT
NULL, | code_postal varchar(6) DEFAULT NULL, | ville varchar(60) DEFAULT
NULL, | pays varchar(60) DEFAULT NULL, | email varbinary(100) DEFAULT
NULL, | PRIMARY KEY (id), | UNIQUE KEY ind_uni_email (email) | ) ENGINE=InnoDB
AUTO_INCREMENT=17; | | LOCK TABLES Client WRITE; | INSERT INTO Client
VALUES (1,'Dupont','Jean','Rue du Centre, 5','45810','Houtsiplou','France','jean.dupont
de la Gare, 2','35840','Troudumonde','France','marie.boudur@email.com'),(3,'Trachon','F
haute, 54b','3250','Belville','Belgique','fleurtrachon@email.com'), | (4,'Van
Piperseel','Julien',NULL,NULL,NULL,NULL,'jeanvp@email.com'),(5,'Nouvel','Johan',NULL,NU

333
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

| (7,'Antoine','Maximilien','Rue Moineau, 123','4580','Trocoul','Belgique','max.antoine


Paolo','Hector',NULL,NULL,NULL,'Suisse','hectordipao@email.com'),(9,'Corduro','Anaelle'
| (10,'Faluche','Eline','Avenue circulaire, 7','45870','Garduche','France','elinefaluch
Haussman, 85','1514','Plasse','Suisse','cpenni@email.com'),(12,'Broussaille','Virginie'
du Fleuve, 18','45810','Houtsiplou','France','vibrousaille@email.com'), |
(13,'Durant','Hannah','Rue des Pendus, 66','1514','Plasse','Suisse','hhdurant@email.com
de Flore, 1','3250','Belville','Belgique','e.delfour@email.com'),(15,'Kestau','Joel',NU
| UNLOCK TABLES; | | | CREATE TABLE Espece ( | id smallint(6) unsigned
NOT NULL AUTO_INCREMENT, | nom_courant varchar(40) NOT NULL, | nom_latin
varchar(40) NOT NULL, | description text, | prix decimal(7,2) unsigned
DEFAULT NULL, | PRIMARY KEY (id), | UNIQUE KEY nom_latin (nom_latin) |
) ENGINE=InnoDB AUTO_INCREMENT=6; | | LOCK TABLES Espece WRITE; | INSERT
INTO Espece VALUES (1,'Chien','Canis canis','Bestiole quatre pattes qui
aime les caresses et tire souvent la langue',200.00),(2,'Chat','Felis silvestris','Best
quatre pattes qui saute trs haut et grimpe aux arbres',150.00),(3,'Tortue
d''Hermann','Testudo hermanni','Bestiole avec une carapace trs dure',140.00),
| (4,'Perroquet amazone','Alipiopsitta xanthops','Joli oiseau parleur vert
et jaune',700.00),(5,'Rat brun','Rattus norvegicus','Petite bestiole avec
de longues moustaches et une longue queue sans poils',10.00); | UNLOCK
TABLES; | | | CREATE TABLE Race ( | id smallint(6) unsigned NOT NULL
AUTO_INCREMENT, | nom varchar(40) NOT NULL, | espece_id smallint(6)
unsigned NOT NULL, | description text, | prix decimal(7,2) unsigned
DEFAULT NULL, | date_insertion datetime DEFAULT NULL, | utilisateur_insertion
varchar(20) DEFAULT NULL, | date_modification datetime DEFAULT NULL,
| utilisateur_modification varchar(20) DEFAULT NULL, | PRIMARY KEY
(id) | ) ENGINE=InnoDB AUTO_INCREMENT=11; | | LOCK TABLES Race WRITE; |
INSERT INTO Race VALUES (1,'Berger allemand',1,'Chien sportif et lgant
au pelage dense, noir-marron-fauve, noir ou gris.',485.00,'2012-05-21 00:53:36','Test',
00:53:36','Test'),(2,'Berger blanc suisse',1,'Petit chien au corps compact,
avec des pattes courtes mais bien proportionnes et au pelage tricolore ou
bicolore.',935.00,'2012-05-21 00:53:36','Test','2012-05-21 00:53:36','Test'),(3,'Singap
de petite taille aux grands yeux en amandes.',985.00,'2012-05-21 00:53:36','Test','2012
00:53:36','Test'), | (4,'Bleu russe',2,'Chat aux yeux verts et la robe
paisse et argente.',835.00,'2012-05-21 00:53:36','Test','2012-05-21 00:53:36','Test')
coon',2,'Chat de grande taille, poils mi-longs.',735.00,'2012-05-21 00:53:36','Test',
00:53:36','Test'),(7,'Sphynx',2,'Chat sans poils.',1235.00,'2012-05-21
00:53:36','Test','2012-05-21 00:53:36','Test'), | (8,'Nebelung',2,'Chat
bleu russe, mais avec des poils longs...',985.00,'2012-05-21 00:53:36','Test','2012-05-
00:53:36','Test'),(9,'Rottweiller',1,'Chien d''apparence solide, bien muscl,
la robe noire avec des taches feu bien dlimites.',630.00,'2012-05-21
00:53:36','Test','2012-05-22 00:54:13','sdz@localhost'),(10,'Yorkshire
terrier',1,'Chien de petite taille au pelage long et soyeux de couleur
bleu et feu.',700.00,'2012-05-22 00:58:25','sdz@localhost','2012-05-22
00:58:25','sdz@localhost'); | UNLOCK TABLES; | | | CREATE TABLE Animal
( | id smallint(6) unsigned NOT NULL AUTO_INCREMENT, | sexe char(1)
DEFAULT NULL, | date_naissance datetime NOT NULL, | nom varchar(30)
DEFAULT NULL, | commentaires text, | espece_id smallint(6) unsigned
NOT NULL, | race_id smallint(6) unsigned DEFAULT NULL, | mere_id smallint(6)

334
7.1 Vues

unsigned DEFAULT NULL, | pere_id smallint(6) unsigned DEFAULT NULL, |


disponible tinyint(1) DEFAULT 1, | PRIMARY KEY (id), | UNIQUE KEY ind_uni_nom_espec
(nom,espece_id) | ) ENGINE=InnoDB AUTO_INCREMENT=76; | | LOCK TABLES Animal
WRITE; | INSERT INTO Animal VALUES (1,'M','2010-04-05 13:43:00','Rox','Mordille
beaucoup',1,1,18,22,1),(2,NULL,'2010-03-24 02:23:00','Roucky',NULL,2,NULL,40,30,1),(3,'
15:02:00','Schtroumpfette',NULL,2,4,41,31,0), | (4,'F','2009-08-03 05:12:00',NULL,'Best
avec une carapace trs dure',3,NULL,NULL,NULL,1),(5,NULL,'2010-10-03 16:44:00','Choupi'
sans oreille gauche',2,NULL,NULL,NULL,0),(6,'F','2009-06-13 08:17:00','Bobosse','Carapa
bizarre',3,NULL,NULL,NULL,1), | (7,'F','2008-12-06 05:18:00','Caroline',NULL,1,2,NULL,N
15:38:00','Bagherra',NULL,2,5,NULL,NULL,0),(9,NULL,'2010-08-23 05:18:00',NULL,'Bestiole
avec une carapace trs dure',3,NULL,NULL,NULL,1), | (10,'M','2010-07-21
15:41:00','Bobo','Petit pour son ge',1,NULL,7,21,1),(11,'F','2008-02-20
15:45:00','Canaille',NULL,1,NULL,NULL,NULL,1),(12,'F','2009-05-26 08:54:00','Cali',NULL
| (13,'F','2007-04-24 12:54:00','Rouquine',NULL,1,1,NULL,NULL,1),(14,'F','2009-05-26
08:56:00','Fila',NULL,1,2,NULL,NULL,1),(15,'F','2008-02-20 15:47:00','Anya',NULL,1,NULL
| (16,'F','2009-05-26 08:50:00','Louya',NULL,1,NULL,NULL,NULL,0),(17,'F','2008-03-10
13:45:00','Welva',NULL,1,NULL,NULL,NULL,1),(18,'F','2007-04-24 12:59:00','Zira',NULL,1,
| (19,'F','2009-05-26 09:02:00','Java',NULL,1,2,NULL,NULL,1),(20,NULL,'2007-04-24
12:45:00','Balou',NULL,1,1,NULL,NULL,1),(21,'F','2008-03-10 13:43:00','Pataude',NULL,1,
| (22,'M','2007-04-24 12:42:00','Bouli',NULL,1,1,NULL,NULL,1),(24,'M','2007-04-12
05:23:00','Cartouche',NULL,1,NULL,NULL,NULL,1),(25,'M','2006-05-14 15:50:00','Zambo',NU
| (26,'M','2006-05-14 15:48:00','Samba',NULL,1,1,NULL,NULL,0),(27,'M','2008-03-10
13:40:00','Moka',NULL,1,NULL,NULL,NULL,0),(28,'M','2006-05-14 15:40:00','Pilou',NULL,1,
| (29,'M','2009-05-14 06:30:00','Fiero',NULL,2,3,NULL,NULL,1),(30,'M','2007-03-12
12:05:00','Zonko',NULL,2,5,NULL,NULL,0),(31,'M','2008-02-20 15:45:00','Filou',NULL,2,4,
| (32,'M','2009-07-26 11:52:00','Spoutnik',NULL,3,NULL,52,NULL,0),(33,'M','2006-05-19
16:17:00','Caribou',NULL,2,4,NULL,NULL,1),(34,'M','2008-04-20 03:22:00','Capou',NULL,2,
| (35,'M','2006-05-19 16:56:00','Raccou','Pas de queue depuis la naissance',2,4,NULL,NU
06:42:00','Boucan',NULL,2,3,NULL,NULL,1),(37,'F','2006-05-19 16:06:00','Callune',NULL,2
| (38,'F','2009-05-14 06:45:00','Boule',NULL,2,3,NULL,NULL,0),(39,'F','2008-04-20
03:26:00','Zara',NULL,2,5,NULL,NULL,0),(40,'F','2007-03-12 12:00:00','Milla',NULL,2,5,N
| (41,'F','2006-05-19 15:59:00','Feta',NULL,2,4,NULL,NULL,0),(42,'F','2008-04-20
03:20:00','Bilba','Sourde de l''oreille droite 80%',2,5,NULL,NULL,0),(43,'F','2007-03
11:54:00','Cracotte',NULL,2,5,NULL,NULL,1), | (44,'F','2006-05-19 16:16:00','Cawette',N
18:17:00','Nikki','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL,1),(46,'F','2
08:23:00','Tortilla','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL,1),
| (48,'F','2006-03-15 14:56:00','Lulla','Bestiole avec une carapace trs
dure',3,NULL,NULL,NULL,1),(49,'F','2008-03-15 12:02:00','Dana','Bestiole
avec une carapace trs dure',3,NULL,NULL,NULL,0),(50,'F','2009-05-25 19:57:00','Cheli',
avec une carapace trs dure',3,NULL,NULL,NULL,1), | (51,'F','2007-04-01
03:54:00','Chicaca','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL,1),(52,'F',
14:26:00','Redbul','Insomniaque',3,NULL,NULL,NULL,1),(54,'M','2008-03-16
08:20:00','Bubulle','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL,1),
| (55,'M','2008-03-15 18:45:00','Relou','Surpoids',3,NULL,NULL,NULL,0),(56,'M','2009-05
18:54:00','Bulbizard','Bestiole avec une carapace trs dure',3,NULL,NULL,NULL,1),(57,'M
19:36:00','Safran','Coco veut un gteau !',4,NULL,NULL,NULL,0), | (58,'M','2008-02-20
02:50:00','Gingko','Coco veut un gteau !',4,NULL,NULL,NULL,0),(59,'M','2009-03-26
08:28:00','Bavard','Coco veut un gteau !',4,NULL,NULL,NULL,0),(60,'F','2009-03-26

335
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

07:55:00','Parlotte','Coco veut un gteau !',4,NULL,NULL,NULL,1), | (61,'M','2010-11-09


00:00:00','Yoda',NULL,2,5,NULL,NULL,1),(62,'M','2010-11-05 00:00:00','Pipo',NULL,1,9,NU
15:45:00','Baba',NULL,5,NULL,NULL,NULL,0), | (70,'M','2012-02-13 15:48:00','Bibo','Agre
02:25:00','Momy',NULL,5,NULL,NULL,NULL,1),(73,'M','2007-03-11 12:45:00','Popi',NULL,5,N
| (75,'F','2007-03-12 22:03:00','Mimi',NULL,5,NULL,NULL,NULL,0); | UNLOCK
TABLES; | | | CREATE TABLE Adoption ( | client_id smallint(5) unsigned
NOT NULL, | animal_id smallint(5) unsigned NOT NULL, | date_reservation
date NOT NULL, | date_adoption date DEFAULT NULL, | prix decimal(7,2)
unsigned NOT NULL, | paye tinyint(1) NOT NULL DEFAULT '0', | PRIMARY
KEY (client_id,animal_id), | UNIQUE KEY ind_uni_animal_id (animal_id) |
) ENGINE=InnoDB; | | | LOCK TABLES Adoption WRITE; | INSERT INTO Adoption
VALUES (1,8,'2012-05-21',NULL,735.00,1),(1,39,'2008-08-17','2008-08-17',735.00,1),(1,40
| (2,3,'2011-03-12','2011-03-12',835.00,1),(2,18,'2008-06-04','2008-06-04',485.00,1),(3
| (4,26,'2007-02-21','2007-02-21',485.00,1),(4,41,'2007-02-21','2007-02-21',835.00,1),(
| (6,16,'2010-01-27','2010-01-27',200.00,1),(7,5,'2011-04-05','2011-04-05',150.00,1),(8
| (9,38,'2007-02-11','2007-02-11',985.00,1),(9,55,'2011-02-13','2011-02-13',140.00,1),(
| (10,49,'2010-08-17','2010-08-17',140.00,0),(11,32,'2008-08-17','2010-03-09',140.00,1)
| (12,15,'2012-05-22',NULL,200.00,1),(12,69,'2007-09-20','2007-09-20',10.00,1),(12,75,'
| (13,57,'2012-01-10','2012-01-10',700.00,1),(14,58,'2012-02-25','2012-02-25',700.00,1)
| UNLOCK TABLES; | | ALTER TABLE Race ADD CONSTRAINT fk_race_espece_id
FOREIGN KEY (espece_id) REFERENCES Espece (id); | | ALTER TABLE Animal
ADD CONSTRAINT fk_race_id FOREIGN KEY (race_id) REFERENCES Race (id); |
ALTER TABLE Animal ADD CONSTRAINT fk_mere_id FOREIGN KEY (mere_id) REFERENCES
Animal (id); | ALTER TABLE Animal ADD CONSTRAINT fk_pere_id FOREIGN KEY
(pere_id) REFERENCES Animal (id); | ALTER TABLE Animal ADD CONSTRAINT fk_espece_id
FOREIGN KEY (espece_id) REFERENCES Espece (id); | | ALTER TABLE Adoption
ADD CONSTRAINT fk_client_id FOREIGN KEY (client_id) REFERENCES Client (id);
| ALTER TABLE Adoption ADD CONSTRAINT fk_adoption_animal_id FOREIGN KEY
(animal_id) REFERENCES Animal (id); | | | CREATE TABLE Animal_histo (
| id smallint(6) unsigned NOT NULL, | sexe char(1) DEFAULT NULL, |
date_naissance datetime NOT NULL, | nom varchar(30) DEFAULT NULL, | commentaires
text, | espece_id smallint(6) unsigned NOT NULL, | race_id smallint(6)
unsigned DEFAULT NULL, | mere_id smallint(6) unsigned DEFAULT NULL, |
pere_id smallint(6) unsigned DEFAULT NULL, | disponible tinyint(1) DEFAULT
1, | date_histo datetime NOT NULL, | utilisateur_histo varchar(20) NOT
NULL, | evenement_histo char(6) NOT NULL, | PRIMARY KEY (id,date_histo)
| ) ENGINE=InnoDB; | | LOCK TABLES Animal_histo WRITE; | INSERT INTO Animal_histo
VALUES (10,'M','2010-07-21 15:41:00','Bobo',NULL,1,NULL,7,21,1,'2012-05-22
01:00:34','sdz@localhost','UPDATE'),(47,'F','2009-03-26 01:24:00','Scroupy','Bestiole
avec une carapace trs dure',3,NULL,NULL,NULL,1,'2012-05-22 01:00:34','sdz@localhost','
| UNLOCK TABLES; | | | CREATE TABLE Erreur ( | id tinyint(3) unsigned
NOT NULL AUTO_INCREMENT, | erreur varchar(255) DEFAULT NULL, | PRIMARY
KEY (id), | UNIQUE KEY erreur (erreur) | ) ENGINE=InnoDB AUTO_INCREMENT=8;
| | LOCK TABLES Erreur WRITE; | INSERT INTO Erreur VALUES (5,'Erreur :
date_adoption doit tre >= date_reservation.'),(3,'Erreur : paye doit
valoir TRUE (1) ou FALSE (0).'),(1,'Erreur : sexe doit valoir \"M\", \"F\"
ou NULL.'); | UNLOCK TABLES; | | -- -------- -- | -- TRIGGERS -- | --
-------- -- | DELIMITER | | CREATE TRIGGER before_insert_adoption BEFORE

336
7.1 Vues

INSERT | ON Adoption FOR EACH ROW | BEGIN | IF NEW.paye != TRUE


AND NEW.paye != FALSE | THEN | INSERT INTO Erreur (erreur)
VALUES ('Erreur : paye doit valoir TRUE (1) ou FALSE (0).'); | | ELSEIF
NEW.date_adoption < NEW.date_reservation THEN | INSERT INTO
Erreur (erreur) VALUES ('Erreur : date_adoption doit tre >= date_reservation.');
| END IF; | END | | | CREATE TRIGGER after_insert_adoption AFTER INSERT
| ON Adoption FOR EACH ROW | BEGIN | UPDATE Animal | SET disponible
= FALSE | WHERE id = NEW.animal_id; | END | | | CREATE TRIGGER before_update_adopt
BEFORE UPDATE | ON Adoption FOR EACH ROW | BEGIN | IF NEW.paye != TRUE
| AND NEW.paye != FALSE | THEN | INSERT INTO Erreur
(erreur) VALUES ('Erreur : paye doit valoir TRUE (1) ou FALSE (0).'); | |
ELSEIF NEW.date_adoption < NEW.date_reservation THEN | INSERT
INTO Erreur (erreur) VALUES ('Erreur : date_adoption doit tre >= date_reservation.')
| END IF; | END | | | CREATE TRIGGER after_update_adoption AFTER UPDATE
| ON Adoption FOR EACH ROW | BEGIN | IF OLD.animal_id <> NEW.animal_id
THEN | UPDATE Animal | SET disponible = TRUE | WHERE
id = OLD.animal_id; | | UPDATE Animal | SET disponible
= FALSE | WHERE id = NEW.animal_id; | END IF; | END | | |
CREATE TRIGGER after_delete_adoption AFTER DELETE | ON Adoption FOR EACH
ROW | BEGIN | UPDATE Animal | SET disponible = TRUE | WHERE id
= OLD.animal_id; | END | | | CREATE TRIGGER before_insert_animal BEFORE
INSERT | ON Animal FOR EACH ROW | BEGIN | IF NEW.sexe IS NOT NULL |
AND NEW.sexe != 'M' | AND NEW.sexe != 'F' | THEN |
INSERT INTO Erreur (erreur) VALUES ('Erreur : sexe doit valoir "M", "F"
ou NULL.'); | END IF; | END | | | CREATE TRIGGER before_update_animal
BEFORE UPDATE | ON Animal FOR EACH ROW | BEGIN | IF NEW.sexe IS NOT
NULL | AND NEW.sexe != 'M' | AND NEW.sexe != 'F' |
THEN | SET NEW.sexe = NULL; | END IF; | END | | | CREATE TRIGGER
after_update_animal AFTER UPDATE | ON Animal FOR EACH ROW | BEGIN | INSERT
INTO Animal_histo ( | id, sexe, date_naissance, nom, commentaires,
espece_id, race_id, | mere_id, pere_id, disponible, date_histo,
utilisateur_histo, evenement_histo) | VALUES ( | OLD.id, OLD.sexe,
OLD.date_naissance, OLD.nom, OLD.commentaires, OLD.espece_id, OLD.race_id,
| OLD.mere_id, OLD.pere_id, OLD.disponible, NOW(), CURRENT_USER(),
'UPDATE'); | END | | | CREATE TRIGGER after_delete_animal AFTER DELETE |
ON Animal FOR EACH ROW | BEGIN | INSERT INTO Animal_histo ( | id,
sexe, date_naissance, nom, commentaires, espece_id, race_id, | mere_id,
pere_id, disponible, date_histo, utilisateur_histo, evenement_histo) |
VALUES ( | OLD.id, OLD.sexe, OLD.date_naissance, OLD.nom, OLD.commentaires,
OLD.espece_id, OLD.race_id, | OLD.mere_id, OLD.pere_id, OLD.disponible,
NOW(), CURRENT_USER(), 'DELETE'); | END | | | CREATE TRIGGER before_delete_espece
BEFORE DELETE | ON Espece FOR EACH ROW | BEGIN | DELETE FROM Race |
WHERE espece_id = OLD.id; | END | | | CREATE TRIGGER before_insert_race
BEFORE INSERT | ON Race FOR EACH ROW | BEGIN | SET NEW.date_insertion
= NOW(); | SET NEW.utilisateur_insertion = CURRENT_USER(); | SET
NEW.date_modification = NOW(); | SET NEW.utilisateur_modification =
CURRENT_USER(); | END | | | CREATE TRIGGER before_update_race BEFORE UPDATE
| ON Race FOR EACH ROW | BEGIN | SET NEW.date_modification = NOW(); |

337
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

SET NEW.utilisateur_modification = CURRENT_USER(); | END | | | CREATE


TRIGGER before_delete_race BEFORE DELETE | ON Race FOR EACH ROW | BEGIN |
UPDATE Animal | SET race_id = NULL | WHERE race_id = OLD.id; |
END | | DELIMITER ; |

7.1.1 Cration dune vue

7.1.1.1 Le principe

Pour notre levage, la requte suivante est trs utile.

SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.commentaires,


Animal.espece_id, Animal.race_id, Animal.mere_id, Animal.pere_id, Animal.disponi
Espece.nom_courant AS espece_nom, Race.nom AS race_nom
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
LEFT JOIN Race ON Animal.race_id = Race.id;

Avec ou sans clause WHERE, il arrive rgulirement quon veuille trouver des renseignements sur
nos animaux, y compris leur race et leur espce (et, seul, lid contenu dans Animal nest pas une
information trs explicite). Il serait donc bien pratique de pouvoir stocker cette requte plutt
que de devoir la retaper en entier chaque fois.

Cest trs exactement le principe dune vue : on stocke une requte SELECT en lui donnant un
nom, et on peut ensuite appeler directement la vue par son nom.

Quelques remarques importantes :

Il sagit bien dobjets de la base de donnes, stocks de manire durable, comme le sont
les tables ou les procdures stockes.
Cest donc bien diffrent des requtes prpares, qui ne sont dfinies que le temps dune
session, et qui ont un tout autre but.
Ce qui est stock est la requte, et non pas les rsultats de celle-ci. On ne gagne absolu-
ment rien en terme de performance en utilisant une vue plutt quen faisant une requte
directement sur les tables.

7.1.1.2 Cration

Pour crer une vue, on utilise tout simplement la commande CREATE VIEW, dont voici la syntaxe :

CREATE [OR REPLACE] VIEW nom_vue


AS requete_select;

La clause OR REPLACE est facultative. Si elle est fournie, la vue nom_vue sera soit cre si elle
nexiste pas, soit remplace si elle existait dj. Si OR REPLACE est omise et quune vue portant
le mme nom a t prcdemment dfinie, cela dclenchera une erreur.

Donc, si lon reprend la requte prcdente, voici comment crer une vue pour stocker celle-ci :

CREATE VIEW V_Animal_details


AS SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.commentaire
Animal.espece_id, Animal.race_id, Animal.mere_id, Animal.pere_id, Animal.disponi

338
7.1 Vues

Espece.nom_courant AS espece_nom, Race.nom AS race_nom


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
LEFT JOIN Race ON Animal.race_id = Race.id;

Dornavant, plus besoin de retaper cette requte, il suffit de travailler partir de la vue, comme
sil sagissait dune table :

SELECT * FROM V_Animal_details;

[[attention]] | Jai prfix le nom de la vue par V_. Il sagit dune convention que je vous
conseille fortement de respecter. Cela permet de savoir au premier coup dil si lon travaille
avec une vraie table, ou avec une vue.

Dailleurs, si lon demande la liste des tables de la base de donnes, on peut voir que la vue est
reprise (bien quil ne sagit pas dune table, mais dune requte SELECT stocke, jinsiste).

SHOW TABLES ;

Tables_in_elevage

Adoption
Animal
Animal_histo
Client
Erreur
Espece
Race
V_Animal_details

7.1.1.3 Les colonnes de la vue

Comme pour les tables, on peut utiliser la commande DESCRIBE pour voir les diffrentes colonnes
de notre vue.

DESCRIBE V_Animal_details;

Field Type Null Key Default Extra

id smallint(6) unsigned NO 0
sexe char(1) YES NULL
date_naissance datetime NO NULL
nom varchar(30) YES NULL
commentaires text YES NULL
espece_id smallint(6) unsigned NO NULL
race_id smallint(6) unsigned YES NULL
mere_id smallint(6) unsigned YES NULL
pere_id smallint(6) unsigned YES NULL
disponible tinyint(1) YES 1
espece_nom varchar(40) NO NULL
race_nom varchar(40) YES NULL

339
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

Comme on pouvait sy attendre, les noms de colonnes ont t dtermins par la clause SELECT de
la requte dfinissant la vue. Sil ny avait pas dalias pour la colonne, cest simplement le mme
nom que dans la table dorigine (date_naissance par exemple), et si un alias a t donn, il est utilis
(espece_nom par exemple).

7.1.1.3.1 Lister les colonnes dans CREATE VIEW Il existe une autre possibilit que les
alias dans la clause SELECT pour nommer les colonnes dune vue. Il suffit de lister les colonnes
juste aprs le nom de la vue, lors de la cration de celle-ci. La commande suivante est lquivalent
de la prcdente pour la cration de V_Animal_details, mais cette fois sans alias dans la requte
SELECT.

CREATE VIEW V_Animal_details (id, sexe, date_naissance, nom, commentaires, espece_id,


AS SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.commentaire
Animal.espece_id, Animal.race_id, Animal.mere_id, Animal.pere_id, Animal.disponi
Espece.nom_courant, Race.nom
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
LEFT JOIN Race ON Animal.race_id = Race.id;

[[attention]] | Il faut toujours vrifier que les colonnes slectionnes sont dans le bon ordre par
rapport la liste des noms, car la correspondance se fait uniquement sur la base de la position.
MySQL ne lvera pas le petit doigt si lon nomme espece une colonne contenant le sexe.

7.1.1.3.2 Doublons interdits Comme pour une table, il est impossible que deux colonnes
ayant le mme nom cohabitent dans une mme vue.

CREATE VIEW V_test


AS SELECT Animal.id, Espece.id
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id;

ERROR1060(42S21):Duplicatecolumnname'id'

Pour pouvoir crer cette vue, il est ncessaire de renommer une des deux colonnes dans la vue
(soit avec un alias, soit en listant les colonnes juste aprs CREATE VIEW nom_vue).

7.1.1.4 Requte SELECT stocke dans la vue

[[question]] | Que peut-on mettre dans la requte SELECT qui sert dfinir la vue ?

La requte dfinissant une vue peut tre nimporte quelle requte SELECT, quelques exceptions
prs.

Il nest pas possible dutiliser une requte SELECT dont la clause FROM contient une sous-
requte SELECT.
La requte ne peut pas faire rfrence des variables utilisateur, des variables systme, ni
mme des variables locales (dans le cas dune vue dfinie par une procdure stocke).
Toutes les tables (ou vues) mentionnes dans la requte doivent exister (au moment de la
cration du moins).

340
7.1 Vues

En dehors de a, carte blanche ! La requte peut contenir une clause WHERE, une clause GROUP
BY, des fonctions (scalaires ou dagrgation), des oprations mathmatiques, une autre vue, des
jointures, etc.

Exemple 1 : une vue pour les chiens.

Lemploy de llevage prpos aux chiens peut crer une vue sur ceux-ci, afin de ne pas devoir
ajouter une clause WHERE espece_id = 1 chacune de ses requtes.

CREATE VIEW V_Chien


AS SELECT id, sexe, date_naissance, nom, commentaires, espece_id, race_id, mere_id, per
FROM Animal
WHERE espece_id = 1 ;

Exemple 2 : combien de chats possdons-nous ?

Il est tout fait possible de crer une vue dfinie par une requte qui compte le nombre danimaux
de chaque espce que lon possde.

CREATE OR REPLACE VIEW V_Nombre_espece


AS SELECT Espece.id, COUNT(Animal.id) AS nb
FROM Espece
LEFT JOIN Animal ON Animal.espece_id = Espece.id
GROUP BY Espece.id;

Exemple 3 : vue sur une vue.

La vue V_Chien slectionne les chiens. Crons la vue V_Chien_race qui slectionne les chiens dont
on connat la race. On peut bien sr crer cette vue partir de la table Animal, mais on peut aussi
repartir simplement de la vue V_Chien.

CREATE OR REPLACE VIEW V_Chien_race


AS SELECT id, sexe, date_naissance, nom, commentaires, espece_id, race_id, mere_id, per
FROM V_Chien
WHERE race_id IS NOT NULL ;

Exemple 4 : expression dans une vue.

Voici un exemple de vue dfinie par une requte contenant une expression, qui slectionne les
espces, avec leur prix en dollars.

CREATE VIEW V_Espece_dollars


AS SELECT id, nom_courant, nom_latin, description, ROUND(prix*1.31564, 2) AS prix_dolla
FROM Espece;

7.1.1.4.1 La requte SELECT est fige La requte SELECT dfinissant une vue est fige :
les changements de structure faits par la suite sur la ou les tables sous-jacentes ninfluent pas
sur la vue.

Par exemple, si lon cre une vue V_Client toute simple.

CREATE VIEW V_Client


AS SELECT *
FROM Client;

341
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

Cette vue slectionne les huit colonnes de Client (id, nom, prenom, adresse, code_postal, ville, pays, email).
Une fois cette vue cre, elle est fige, et lajout dune colonne dans la table Client ne changera
pas les rsultats dune requte sur V_Client. Cette vue ne slectionnera jamais que id, nom, prenom,
adresse, code_postal, ville, pays et email, malgr le caractre * dans la clause SELECT. Pour que V_Client
slectionne la nouvelle colonne de Client, il faudrait recrer la vue pour linclure.

Exemple : ajout dune colonne date_naissance la table Client.

ALTER TABLE Client ADD COLUMN date_naissance DATE ;

DESCRIBE V_Client;

Field Type Null Key Default Extra

id smallint(5) unsigned NO 0
nom varchar(100) NO NULL
prenom varchar(60) NO NULL
adresse varchar(200) YES NULL
code_postal varchar(6) YES NULL
ville varchar(60) YES NULL
pays varchar(60) YES NULL
email varbinary(100) YES NULL

7.1.1.4.2 Tri des donnes directement dans la vue Si lon met un ORDER BY dans la
requte dfinissant une vue, celui-ci prendra effet, sauf si lon fait une requte sur la vue avec un
ORDER BY. Dans ce cas, cest ce dernier qui prime, et lORDER BY originel est ignor.

Exemple

CREATE OR REPLACE VIEW V_Race


AS SELECT Race.id, nom, Espece.nom_courant AS espece
FROM Race
INNER JOIN Espece ON Espece.id = Race.espece_id
ORDER BY nom;

SELECT *
FROM V_Race; -- Slection sans ORDER BY, on prend l'ORDER BY de la dfinition

SELECT *
FROM V_Race
ORDER BY espece; -- Slection avec ORDER BY, c'est celui-l qui sera pris en compte

La premire requte donnera bien les races par ordre alphabtique, les races de chiens et les races
de chats sont mlanges. Par contre, dans la deuxime, on a dabord toutes les races de chats, puis
toutes les races de chiens.

7.1.1.4.3 Comportement dautres clauses de SELECT En ce qui concerne notamment


les clauses LIMIT et DISTINCT (et son oppos, ALL), le comportement est indfini. Dans le cas
o lon fait une requte avec un LIMIT sur une vue dont la requte possde aussi un LIMIT, on

342
7.1 Vues

ne sait pas quelle clause, de la dfinition ou de la slection, sera applique. Par consquent, il est
dconseill dinclure ces clauses dans la dfinition dune vue.

7.1.2 Slection des donnes dune vue


Une fois la vue cre, on peut bien entendu faire plus quun simple SELECT * FROM la_vue; :
on peut tout simplement traiter cette vue comme une table, et donc ajouter des clauses WHERE,
GROUP BY, des fonctions, des jointures et tout ce que lon veut !

Exemple 1 : on slectionne les rats bruns partir de la vue V_Animal_details.

SELECT id, nom, espece_nom, date_naissance, commentaires, disponible


FROM V_Animal_details
WHERE espece_nom = 'Rat brun' ;

espece_nom commentaires
id nom date_naissance disponible

69 Baba Rat brun 2012-02-13 15 :45 :00 NULL 0

70 Bibo Rat brun 2012-02-13 15 :48 :00 Agressif 1

72 Momy Rat brun 2008-02-01 NULL 1


02 :25 :00
73 Popi Rat brun 2007-03-11 12 :45 :00 NULL 1
75 Mimi Rat brun 2007-03-12 22 :03 :00 NULL 0

Exemple 2 : on slectionne le nombre danimaux par espce avec la vue V_Nombre_espece, en ajou-
tant une jointure sur la table Espece pour avoir le nom des espces en plus de leur id.

SELECT V_Nombre_espece.id, Espece.nom_courant, V_Nombre_espece.nb


FROM V_Nombre_espece
INNER JOIN Espece ON Espece.id = V_Nombre_espece.id;

id nom_courant nb

1 Chien 21
2 Chat 20
3 Tortue dHermann 14
4 Perroquet amazone 4
5 Rat brun 5

Exemple 3 : on slectionne le nombre de chiens quon possde pour chaque race, en utilisant un
regroupement sur la vue V_Chien_race, avec jointure sur Race pour avoir le nom de la race.

SELECT Race.nom, COUNT(V_Chien_race.id)


FROM Race
INNER JOIN V_Chien_race ON Race.id = V_Chien_race.race_id

343
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

GROUP BY Race.nom;

nom COUNT(V_Chien_race.id)

Berger allemand 8
Berger blanc suisse 4
Rottweiller 1

7.1.3 Modification et suppression dune vue


7.1.3.1 Modification

7.1.3.1.1 CREATE OR REPLACE Comme dj mentionn, pour modifier une vue, on peut
tout simplement ajouter la clause OR REPLACE la requte de cration de la vue, et lui donner le
nom de la vue modifier. Lancienne vue sera alors remplace.

[[attention]] | Si le nom de votre vue est incorrect et ne correspond aucune vue existante, aucune
erreur ne sera renvoye, et vous crerez tout simplement une nouvelle vue.

7.1.3.1.2 ALTER Il existe aussi la commande ALTER VIEW, qui aura le mme effet que
CREATE OR REPLACE VIEW si la vue existe bel et bien. Dans le cas contraire, ALTER VIEW
gnrera une erreur.

Syntaxe :

ALTER VIEW nom_vue [(liste_colonnes)]


AS requete_select

Exemples : les deux requtes suivantes ont exactement le mme effet : on modifie la vue
V_espece_dollars pour mettre jour le taux de change du dollar.

CREATE OR REPLACE VIEW V_Espece_dollars


AS SELECT id, nom_courant, nom_latin, description, ROUND(prix*1.30813, 2) AS prix_dolla
FROM Espece;

ALTER VIEW V_Espece_dollars


AS SELECT id, nom_courant, nom_latin, description, ROUND(prix*1.30813, 2) AS prix_dolla
FROM Espece;

7.1.3.2 Suppression

Pour supprimer une vue, on utilise simplement DROP VIEW [IF EXISTS] nom_vue;.

Exemple : suppression de V_Race.

DROP VIEW V_Race;

7.1.4 Utilit des vues


Au-del de la lgendaire paresse des informaticiens, bien contents de ne pas devoir retaper la
mme requte encore et encore, les vues sont utilises pour diffrentes raisons, dont voici les
principales.

344
7.1 Vues

7.1.4.1 Clarification et facilitation des requtes

Lorsquune requte implique un grand nombre de tables, ou ncessite des fonctions, des regrou-
pements, etc., mme une bonne indentation ne suffit pas toujours rendre la requte claire. Qui
plus est, plus la requte est longue et complexe, plus lajout de la moindre condition peut se r-
vler pnible. Avoir une vue pour des requtes complexes permet de simplifier et de clarifier les
requtes.

Exemple : on veut savoir quelles espces rapportent le plus, anne aprs anne. Comme cest
une question importante pour le dveloppement de llevage, et quelle reviendra souvent, on
cre une vue, que lon pourra interroger facilement.

CREATE OR REPLACE VIEW V_Revenus_annee_espece


AS SELECT YEAR(date_reservation) AS annee, Espece.id AS espece_id, SUM(Adoption.prix) A
FROM Adoption
INNER JOIN Animal ON Animal.id = Adoption.animal_id
INNER JOIN Espece ON Animal.espece_id = Espece.id
GROUP BY annee, Espece.id;

Avec des requtes toutes simples, on peut maintenant obtenir des informations rsultant de re-
qutes complexes, par exemple :

1. Les revenus obtenus par anne

SELECT annee, SUM(somme) AS total


FROM V_Revenus_annee_espece
GROUP BY annee;

annee total

2007 2315.00
2008 3565.00
2009 400.00
2010 340.00
2011 1755.00
2012 3045.00

2. Les revenus obtenus pour chaque espce, toutes annes confondues

SELECT Espece.nom_courant AS espece, SUM(somme) AS total


FROM V_Revenus_annee_espece
INNER JOIN Espece ON V_Revenus_annee_espece.espece_id = Espece.id
GROUP BY espece;

espece total

Chat 6480.00
Chien 2400.00
Perroquet amazone 2100.00
Rat brun 20.00
Tortue dHermann 420.00

345
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

3. Les revenus moyens gnrs par la vente dun individu de lespce

SELECT Espece.nom_courant AS espece, SUM(somme)/SUM(nb) AS moyenne


FROM V_Revenus_annee_espece
INNER JOIN Espece ON V_Revenus_annee_espece.espece_id = Espece.id
GROUP BY espece;

espece moyenne

Chat 720.000000
Chien 342.857143
Perroquet amazone 700.000000
Rat brun 10.000000
Tortue dHermann 140.000000

7.1.4.2 Cration dune interface entre lapplication et la base de donnes

Lorsque lon a une base de donnes exploite par une application (crite dans un langage de pro-
grammation comme Java, ou PHP par exemple), cest souvent dans cette application que sont
construites les requtes qui vont insrer, modifier, et slectionner les donnes de la base. Si pour
une raison ou une autre (mauvaise conception de la base de donnes au dpart, modle de don-
nes qui stend fortement,) la structure des tables de la base change, il faut rcrire galement
lapplication pour prendre en compte les modifications ncessaires pour les requtes.

Cependant, si lon a utilis des vues, on peut viter de rcrire toutes ces requtes, ou du moins
limiter le nombre de requtes rcrire. Si les requtes sont faites sur des vues, il suffit en effet
de modifier la dfinition de ces vues pour quelles fonctionnent avec la nouvelle structure.

Exemple

On a cr une vue V_Client, qui permet de voir le contenu de notre table Client (sauf la date de
naissance, ajoute aprs la dfinition de la vue).

Si un beau jour on dcidait de stocker les adresses postales dans une table part, il faudrait mo-
difier la structure de notre base. Au lieu dune seule table Client, on en aurait deux : Client(id, nom,
prenom, date_naissance, email, adresse_id) et Adresse(id, rue_numero, code_postal, ville, pays).

Pour que lapplication puisse continuer slectionner les personnes et leur adresse sans quon
doive la modifier, il suffirait de changer la requte dfinissant la vue :

CREATE OR REPLACE VIEW V_Client -- le OR REPLACE indispensable (ou on utilise ALTER


AS SELECT Client.id, nom, prenom, rue_numero AS adresse, code_postal, ville, pays, emai
FROM Client
LEFT JOIN Adresse ON Client.adresse_id = Adresse.id -- LEFT JOIN au cas o certains cli

Le changement de structure de la base de donnes serait ainsi transparent pour lapplication (du
moins en ce qui concerne la slection des clients) !

7.1.4.3 Restriction des donnes visibles par les utilisateurs

La gestion des utilisateurs et de leurs droits fera lobjet dun prochain chapitre. Sachez simple-
ment que pour chaque utilisateur, il est possible de dfinir des droits particuliers pour chaque

346
7.1 Vues

table et chaque vue (entre autres choses). On peut par exemple autoriser lutilisateur A faire
des requtes dinsertion, modification, suppression et slection (le fameux CRUD) sur la table T1,
mais nautoriser lutilisateur B qu faire des slections sur la table T1. Et imaginons quil existe
aussi un utilisateur C, auquel on veut donner lautorisation de faire des requtes de slection sur la
table T1, mais auquel il faudrait cacher certaines colonnes qui contiennent des donnes sensibles,
ou certaines lignes auxquelles il ne devrait pas accder. Il suffit de crer la vue V_T1, nayant accs
quaux colonnes/lignes publiques de la table T1, et de donner C les droits sur la vue V_T1, mais
pas sur la table T1.

Exemple

Le stagiaire travaillant dans notre levage soccupe exclusivement des chats, et ne doit pas
avoir accs aux commentaires. On ne lui donne donc pas accs la table Animal, mais une vue
V_Animal_stagiaire cre de la manire suivante :

CREATE VIEW V_Animal_stagiaire


AS SELECT id, nom, sexe, date_naissance, espece_id, race_id, mere_id, pere_id, disponib
FROM Animal
WHERE espece_id = 2 ;

*[CRUD] : Create Read Update Delete

7.1.5 Algorithmes
Lors de la cration dune vue, on peut dfinir quel algorithme sera utilis par MySQL lors dune
slection sur celle-ci.

CREATE [OR REPLACE]


[ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW nom_vue
AS requete_select

Il sagit dune clause non standard, donc valable uniquement pour MySQL. Deux algorithmes
diffrents peuvent tre utiliss : MERGE et TEMPTABLE.

Les algorithmes interviennent au moment o lon slectionne des donnes de la vue, et pas
directement la cration de celle-ci.

7.1.5.1 MERGE

Si lalgorithme MERGE (fusion en anglais) a t choisi, lorsque lon slectionne des donnes de
la vue, MySQL va fusionner la requte SELECT qui dfinit la vue avec les clauses de slections.
Faire une slection sur une vue qui utilise cet algorithme revient donc faire une requte directe-
ment sur les tables sous-jacentes.

Comme ce nest pas ncessairement trs clair, voici deux exemples.

Exemple 1

On a cr la vue V_Animal_details partir de la requte suivante :

SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.commentaires,


Animal.espece_id, Animal.race_id, Animal.mere_id, Animal.pere_id, Animal.disponi

347
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

Espece.nom_courant AS espece_nom, Race.nom AS race_nom


FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
LEFT JOIN Race ON Animal.race_id = Race.id;

Ensuite, on fait une slection sur cette vue :

SELECT *
FROM V_Animal_details
WHERE MONTH(date_naissance) = 6 ;

Si cest lalgorithme MERGE qui est utilis, MySQL va fusionner :

la requte dfinissant la vue :

SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.commentaires,


Animal.espece_id, Animal.race_id, Animal.mere_id, Animal.pere_id, Animal.disponi
Espece.nom_courant AS espece_nom, Race.nom AS race_nom
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
LEFT JOIN Race ON Animal.race_id = Race.id;

les clauses de la requte de slection :

WHERE MONTH(date_naissance) = 6 ;

Au final, faire cette requte sur la vue provoquera lexcution de la requte suivante :

SELECT Animal.id, Animal.sexe, Animal.date_naissance, Animal.nom, Animal.commentaires,


Animal.espece_id, Animal.race_id, Animal.mere_id, Animal.pere_id, Animal.disponi
Espece.nom_courant AS espece_nom, Race.nom AS race_nom
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
LEFT JOIN Race ON Animal.race_id = Race.id
WHERE MONTH(date_naissance) = 6 ;

Exemple 2

Si lon excute la requte suivante sur la vue V_Chien, et que lalgorithme MERGE est utilis :

SELECT nom, date_naissance


FROM V_Chien
WHERE pere_id IS NOT NULL ;

MySQL va fusionner les deux requtes, et excuter ceci :

SELECT nom, date_naissance


FROM Animal
WHERE espece_id = 1
AND pere_id IS NOT NULL ;

348
7.1 Vues

7.1.5.2 TEMPTABLE

Lalgorithme TEMPTABLE, par contre, cre une table temporaire contenant les rsultats de la
requte dfinissant la vue puis, par la suite, excute la requte de slection sur cette table tempo-
raire.

Donc, si lon excute la requte suivante sur la vue V_Chien :

SELECT nom, date_naissance


FROM V_Chien
WHERE pere_id IS NOT NULL ;

Avec lalgorithme TEMPTABLE, la requte dfinissant la vue va tre excute et ses rsultats sto-
cks dans une table temporaire.

SELECT id, sexe, date_naissance, nom, commentaires, espece_id, race_id, mere_id, pere_i
FROM Animal
WHERE espece_id = 1 ;

Ensuite, sur cette table temporaire, va tre excute la requte finale :

SELECT nom, date_naissance


FROM table_temporaire
WHERE pere_id IS NOT NULL ;

7.1.5.3 Algorithme par dfaut et conditions

Il existe une troisime option possible pour la clause ALGORITHM dans la requte de cration des
vues : UNDEFINED.

Par dfaut, si on ne prcise pas dalgorithme pour la vue, cest loption UNDEFINED qui est utilise.
Cette option laisse MySQL dcider lui-mme de lalgorithme quil appliquera. Si cest possible,
MERGE sera utilis, car cet algorithme est plus performant que TEMPTABLE. Cependant, toutes les
vues ne peuvent pas utiliser lalgorithme MERGE. En effet, une vue utilisant un ou plusieurs des
lments suivants ne pourra pas utiliser MERGE :

DISTINCT ;
LIMIT ;
une fonction dagrgation (SUM(), COUNT(), MAX(), etc.) ;
GROUP BY ;
HAVING ;
UNION ;
une sous-requte dans la clause SELECT.

7.1.6 Modification des donnes dune vue


On a tendance penser que les vues ne servent que pour la slection de donnes. En ralit, il
est possible de modifier, insrer et supprimer des donnes par lintermdiaire dune vue. Les
requtes sont les mmes que pour insrer, modifier et supprimer des donnes partir dune table
(si ce nest quon met le nom de la vue au lieu du nom de la table bien sr).

Cependant, pour quune vue ne soit pas en lecture seule, elle doit rpondre une srie de condi-
tions.

349
7 Au-del des tables classiques : vues, tables temporaires et vues matrialises

7.1.6.1 Conditions pour quune vue permette de modifier des donnes (requtes
UPDATE)

7.1.6.1.1 Jointures Il est possible de modifier des donnes partir dune vue dfinie avec une
jointure, condition de ne modifier quune seule table.

Exemple

-- Modifie Animal
UPDATE V_Animal_details
SET commentaires = 'Rhume chronique'
WHERE id = 21 ;

-- Modifie Race
UPDATE V_Animal_details
SET race_nom = 'Maine Coon'
WHERE race_nom = 'Maine coon' ;

-- Erreur
UPDATE V_Animal_details
SET commentaires = 'Vilain oiseau', espece_nom = 'Perroquet pas beau' -- commentaires v
WHERE espece_id = 4 ;

Les deux premires modifications ne posent aucun problme, mais la troisime chouera, car elle
modifie des colonnes appartenant deux tables diffrentes.

ERROR1393(HY000):Cannotmodifymorethanonebasetablethroughajoinview'elevag

7.1.6.1.2 Algorithme La vue doit utiliser lalgorithme MERGE (que lalgorithme ait t spcifi
la cration, ou choisi par MySQL si lalgorithme na pas t dfini). Par consquent, les mmes
conditions que pour utiliser lalgorithme MERGE sappliquent. Les lments suivants sont ainsi
interdits dans une vue si lon veut pouvoir modifier les donnes partir de celle-ci :

DISTINCT ;
LIMIT ;
une fonction dagrgation (SUM(), COUNT(), MAX(), etc.) ;
GROUP BY ;
HAVING ;
UNION ;
une sous-requte dans la clause SELECT.

Exemple : la vue V_Nombre_espece, qui utilise une fonction dagrgation, ne permet pas de modifier
les donnes.

UPDATE V_Nombre_espece
SET nb = 6
WHERE id = 4 ;

1288(HY000):ThetargettableV_Nombre_especeoftheUPDATEisnotupdatable

350
7.1 Vues

7.1.6.1.3 Autres conditions


On ne peut pas modifier les donnes partir dune vue qui est elle-mme dfinie partir
dune vue qui ne permet pas la modification des donnes.
Ce nest pas non plus possible partir dune vue dont la clause WHERE contient une sous-
requte faisant rfrence une des tables de la clause FROM.

7.1.6.2 Conditions pour quune vue permette dinsrer des donnes (requtes
INSERT)

On peut insrer des donnes dans une vue si celle-ci respecte les mmes conditions que pour la
modification de donnes, ainsi que les conditions supplmentaires suivantes.

7.1.6.2.1 Valeurs par dfaut Toutes les colonnes nayant pas de valeur par dfaut (et ne
pouvant pas tre NULL) de la table dans laquelle on veut faire linsertion doivent tre rfrences
par la vue.

Exemple

INSERT INTO V_Animal_stagiaire (nom, sexe, date_naissance, espece_id, race_id)


VALUES ('Rocco', 'M', '2012-03-12', 1, 9);

Ceci fonctionne. La colonne commentaires nest pas rfrence par V_Animal_stagiaire mais peut tre
NULL (qui est donc sa valeur par dfaut).

CREATE VIEW V_Animal_mini


AS SELECT id, nom, sexe, espece_id
FROM Animal;

INSERT INTO V_Animal_mini(nom, sexe, espece_id)


VALUES ('Toxi', 'F', 1);

Par contre, linsertion dans V_Animal_mini choue puisque date_naissance nest pas rfrence, ne
peut pas tre NULL, et na pas de valeur par dfaut.

ERROR1423(HY000):Fieldofview'elevage.v_animal_mini'underlyingtabledoesn'thave

7.1.6.2.2 Jointures Les vues avec jointure peuvent supporter linsertion si :

il ny a que des jointures internes ;


linsertion se fait sur une seule table (comme pour la modification).

Exemple

INSERT INTO V_Animal_details (espece_nom, espece_nom_latin)


VALUES ('Perruche terrestre', 'Pezoporus wallicus');

Il y a une jointure externe dans V_Animal_details, donc linsertion ne fonctionnera pas.

ERROR1471(HY000):ThetargettableV_Animal_detailsoftheINSERTisnotinsertable-i

Par contre, si lon cre une table V_Animal_espece, avec uniquement une jointure interne, il ny a
aucun problme.