Chapitre6 - Le - Langage - SQLserie 4
Chapitre6 - Le - Langage - SQLserie 4
LE LANGAGE SQL 65
Chapitre 6
Le langage SQL
Sommaire
6.1 Requêtes simples SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.1.1 Sélections simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.1.2 La clause WHERE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.1.3 Valeurs nulles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.2 Requêtes sur plusieurs tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.2.1 Jointures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.2.2 Union, intersection et différence . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.3 Requêtes imbriquées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
6.3.1 Conditions portant sur des relations . . . . . . . . . . . . . . . . . . . . . . . . 72
6.3.2 Sous-requêtes correllées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.4 Agrégration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.4.1 Fonctions d’agrégation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.4.2 La clause GROUP BY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
6.4.3 La clause HAVING . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.5 Mises-à-jour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.5.1 Insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.5.2 Destruction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.5.3 Modification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
6.6 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
SELECT nomStation
FROM Station
WHERE region = ’Antilles’
Ce premier exemple montre la structure de base d’une requête SQL, avec les trois clauses SELECT,
FROM et WHERE.
– FROM indique la (ou les) tables dans lesquelles on trouve les attributs utiles à la requête. Un attribut
peut être ’utile’ de deux manières (non exclusives) : (1) on souhaite afficher son contenu, (2) on
souhaite qu’il ait une valeur particulière (une constante ou la valeur d’un autre attribut).
– WHERE indique les conditions que doivent satisfaire les n-uplets de la base pour faire partie du
résultat.
Dans l’exemple précédent, l’interprétation est simple : on parcourt les n-uplets de la relation Station.
Pour chaque n-uplet, si l’attribut region a pour valeur ’Antilles’, on place l’attribut nomStation dans
le résultat 1 . On obtient donc le résultat :
nomStation
Venusa
Santalba
Le résultat d’un ordre SQL est toujours une relation (une table) dont les attributs sont ceux spécifiés
dans la clause SELECT. On peut donc considérer en première approche ce résultat comme un ’découpage’,
horizontal et vertical, de la table indiquée dans le FROM, similaire à une utilisation combinée de la sélection
et de la projection en algèbre relationnelle. En fait on dispose d’un peu plus de liberté que cela. On peut :
Les fonctions applicables aux valeurs des attributs sont par exemple les opérations arithmétiques ( ,
*, ...) pour les attributs numériques ou des manipulations de chaîne de caractères (concaténation, sous-
chaînes, mise en majuscule, ...). Il n’existe pas de norme mais la requête suivante devrait fonctionner sur
tous les systèmes : on convertit le prix des activités en euros et on affiche le cours de l’euro avec chaque
tuple.
Renommage
Les noms des attributs sont par défaut ceux indiqués dans la clause SELECT, même quand il y a des
expressions complexes. Pour renommer les attributs, on utilise le mot-clé AS.
On obtient alors :
libelle prixEnEuros ’Cours de l’euro =’ cours
Kayac 7.69 ’Cours de l’euro =’ 6.56
Doublons
L’introduction de fonctions permet d’aller au-delà de ce qui est possible en algèbre relationnelle. Il
existe une autre différence, plus subtile : SQL permet l’existence de doublons dans les tables (il ne s’agit
donc pas d’ensemble au sens strict du terme). La spécification de clés permet d’éviter les doublons dans
les relations stockées, mais il peuvent apparaître dans le résultat d’une requête. Exemple :
SELECT libelle
FROM Activite
libelle
Voile
Plongee
Plongee
Ski
Piscine
Kayac
Pour éviter d’obtenir deux tuples identiques, on peut utiliser le mot-clé DISTINCT.
Tri du résultat
Il est possible de trier le résultat d’un requête avec la clause ORDER BY suivie de la liste des attributs
servant de critère au tri. Exemple :
SELECT *
FROM Station
ORDER BY tarif, nomStation
trie, en ordre ascendant, les stations par leur tarif, puis, pour un même tarif, présente les stations selon
l’ordre lexicographique. Pour trier en ordre descendant, on ajoute le mot-clé DESC après la liste des attri-
buts.
Voici maintenant la plus simple des requêtes SQL : elle consiste à afficher l’intégralité d’une table. Pour
avoir toutes les lignes on omet la clause WHERE, et pour avoir toutes les colonnes, on peut au choix lister
tous les attributs ou utiliser le caractère ’*’ qui a la même signification.
SELECT *
FROM Station
Chaînes de caractères
Les comparaisons de chaînes de caractères soulèvent quelques problèmes délicats.
1. Il faut être attentif aux différences entre chaînes de longueur fixe et chaînes de longueur variable. Les
premières sont complétées par des blancs (’ ’) et pas les secondes.
2. Si SQL ne distingue pas majuscules et minuscules pour les mot-clés, il n’en va pas de même pour
les valeurs. Donc ’SANTALBA’ est différent de ’Santalba’.
SQL fournit des options pour les recherches par motif (pattern matching) à l’aide de la clause LIKE.
Le caractère ’_’ désigne n’importe quel caractère, et le ’%’ n’importe quelle chaîne de caractères. Par
exemple, voici la requête cherchant toutes les stations dont le nom termine par un ’a’.
SELECT nomStation
FROM Station
WHERE nomStation LIKE ’%a’
Quelles sont les stations dont le nom commence par un ’V’ et comprend exactement 6 caractères ?
SELECT nomStation
FROM Station
WHERE nomStation LIKE ’V_____’
CHAPITRE 6. LE LANGAGE SQL 69
Dates
Une autre différence avec l’algèbre est la possibilité de manipuler des dates. En fait tous les systèmes
proposaient bien avant la normalisation leur propre format de date, et la norme préconisée par SQL2 n’est
de ce fait pas suivie par tous.
Une date est spécifiée en SQL2 par le mot-clé DATE suivi d’une chaîne de caractères au format ’aaaa-
mm-jj’, par exemple DATE ’1998-01-01’. Les zéros sont nécessaires afin que le mois et le quantième
comprennent systématiquement deux chiffres.
On peut effectuer des sélections sur les dates à l’aide des comparateurs usuels. Voici par exemple la
requête ’ID des clients qui ont commencé un séjour en juillet 1998’.
SELECT idClient
FROM Sejour
WHERE debut BETWEEN DATE ’1998-07-01’ AND DATE ’1998-07-31’
Les systèmes proposent de plus des fonctions permettant de calculer des écarts de dates, d’ajouter des
mois ou des années à des dates, etc.
– Toute comparaison avec NULL donne un résultat qui n’est ni vrai, ni faux mais une troisième valeur
booléenne, UNKNOWN.
Les valeurs booléennes TRUE, FALSE et UNKNOWN sont définies de la manière suivante : TRUE vaut
1, FALSE 0 et UNKNOWN 1/2. Les connecteurs logiques donnent alors les résultats suivants :
1. AND =
2. OR =
3. NOT =
Les conditions exprimées dans une clause WHERE sont évaluées pour chaque tuple, et ne sont conservés
dans le résultat que les tuples pour lesquels cette évaluation donne TRUE. La présence d’une valeur nulle
dans une comparaison a donc souvent (mais pas toujours !) le même effet que si cette comparaison échoue
et renvoie FALSE.
Voici une instance de la table SEJOUR avec des informations manquantes.
SEJOUR
idClient station début nbPlaces
10 Passac 1998-07-01 2
20 Santalba 1998-08-03
30 Passac 3
La présence de NULL peut avoir des effets surprenants. Par exemple la requête suivante
SELECT station
FROM Sejour
WHERE nbPlaces <= 10 OR nbPlaces >= 10
devrait en principe ramener toutes les stations de la table. En fait ’Santalba’ ne figurera pas dans le
résultat car nbPlaces est à NULL.
Autre piège : NULL est un mot-clé, pas une constante. Donc une comparaison comme nbPlaces =
NULL est incorrecte. Le prédicat pour tester l’absence de valeur dans une colonne est IS NULL (et
son inverse IS NOT NULL). La requête suivante sélectionne tous les séjours pour lesquels on connaît le
nombre de places.
SELECT *
FROM Sejour
WHERE nbPlaces IS NOT NULL
La présence de NULL est une source de problèmes : dans la mesure du possible il faut l’éviter en
spécifiant la contrainte NOT NULL ou en donnant une valeur par défaut.
6.2.1 Jointures
La jointure est une des opérations les plus utiles (et donc une des plus courantes) puisqu’elle permet
d’exprimer des requêtes portant sur des données réparties dans plusieurs tables. La syntaxe pour exprimer
des jointures avec SQL est une extension directe de celle étudiée précédemment dans le cas des sélections
simples : on donne simplement la liste des tables concernées dans la clause FROM, et on exprime les critères
de rapprochement entre ces tables dans la clause WHERE.
Prenons l’exemple de la requête suivante : donner le nom des clients avec le nom des stations où ils
ont séjourné. Le nom du client est dans la table Client, l’information sur le lien client/station dans la
table Sejour. Deux tuples de ces tables peuvent être joints s’ils concernent le même client, ce qui peut
s’exprimer à l’aide de l’identifiant du client. On obtient la requête :
On peut remarquer qu’il n’y a pas dans ce cas d’ambiguité sur les noms des attributs : nom et id
viennent de la table Client, tandis que station et idClient viennent de la table Sejour. Il peut
arriver (il arrive de fait fréquemment) qu’un même nom d’attribut soit partagé par plusieurs tables impli-
quées dans une jointure. Dans ce cas on résout l’ambiguité en préfixant l’attribut par le nom de la table.
Exemple : afficher le nom d’une station, son tarif hebdomadaire, ses activités et leurs prix.
Comme il peut être fastidieux de répéter intégralement le nom d’une table, on peut lui associer un
synonyme et utiliser ce synonyme en tant que préfixe. La requête précédente devient par exemple : 2
Bien entendu, on peut effectuer des jointures sur un nombre quelconque de tables, et les combiner avec
des sélections. Voici par exemple la requête qui affiche le nom des clients habitant Paris, les stations où ils
ont séjourné avec la date, enfin le tarif hebdomadaire pour chaque station.
Il n’y a pas d’ambiguité sur les noms d’attributs donc il est inutile en l’occurence d’employer des
synonymes. Il existe en revanche une situation où l’utilisation des synonymes est indispensable : celle ou
l’on souhaite effectuer une jointure d’une relation avec elle-même.
Considérons la requête suivante : Donner les couples de stations situées dans la même région. Ici toutes
les informations nécessaires sont dans la seule table Station, mais on construit un tuple dans le résultat
avec deux tuples partageant la même valeur pour l’attribut région.
Tout se passe comme s’il on devait faire la jointure entre deux versions distinctes de la table Station.
Techniquement, on résout le problème en SQL en utilisant deux synonymes distincts.
On peut imaginer que s1 et s2 sont deux ’curseurs’ qui parcourent indépendamment la table Station
et permettent de constituer des couples de tuples auxquels on applique la condition de jointure.
1. Boucles imbriquées. On considère chaque synonyme de table (ou par défaut chaque nom de table)
comme une variable tuple. Maintenant on construit des boucles imbriquées, chaque boucle corres-
pondant à une des tables du FROM et permettant à la variable correspondante d’itérer sur le contenu
de la table.
A l’intérieur de l’ensemble des boucles, on applique la clause WHERE.
2. Produit cartésien. On construit le produit cartésien des tables du FROM, en préfixant chaque attribut
par le nom ou le synonyme de sa table pour éviter les ambiguités.
On est alors ramené à la situation où il y a une seule table (le résultat du produit cartésien) et on
interprête l’ordre SQL comme dans le cas des requêtes simples.
La première interprétation est proche de ce que l’on obtiendrait si on devait programmer une requête
avec un langage comme le C ou Pascal, la deuxième s’inspire de l’algèbre relationnelle.
2. Donnez les régions où l’on trouve à la fois des clients et des stations.
3. Quelles sont les régions où l’on trouve des stations mais pas des clients ?
La norme SQL2 spécifie que les doublons doivent être éliminés du résultat lors des trois opérations
ensemblistes. Le coût de l’élimination de doublons n’étant pas négligeable, il se peut cependant que certains
systèmes fassent un choix différent.
L’union ne peut être exprimée autrement qu’avec UNION. En revanche INTERSECT peut être exprimée
avec une jointure, et la différence s’obtient, souvent de manière plus aisée, à l’aide des requêtes imbriquées.
SELECT station
CHAPITRE 6. LE LANGAGE SQL 73
FROM Sejour
WHERE idClient IN (SELECT id FROM Client
WHERE ville = ’Paris’)
est (partiellement) correct car la recherche dans la sous-requête s’effectue par la clé. En revanche il se peut
qu’aucun tuple ne soit ramené, ce qui génère une erreur.
Voici les conditions que l’on peut exprimer sur une relation construite avec une requête imbriquée.
2. IN R où est un tuple dont le type est celui de . TRUE si appartient à , FALSE sinon.
3.
ANY R, où est un comparateur SQL ( , , , etc.). Renvoie TRUE si la comparaison
avec au moins un des tuples de la relation unaire renvoie TRUE.
4.
ALL R, où
est un comparateur SQL ( , , , etc.). Renvoie TRUE si la comparaison
avec tous les tuples de la relation unaire renvoie TRUE.
De plus, toutes ces expressions peuvent être préfixées par NOT pour obtenir la négation. Voici quelques
exemples.
SELECT nomStation
FROM Station
WHERE tarif >= ALL (SELECT tarif FROM Station)
– Dans quelle station pratique-t-on une activité au même prix qu’à Santalba ?
Ces requêtes peuvent s’exprimer sans imbrication (exercice), parfois de manière moins élégante ou
moins concise. La différence, en particulier, s’exprime facilement avec NOT IN ou NOT EXISTS.
Le id dans la requête imbriquée n’appartient pas à la table Sejour mais à la table Client référencée
dans le FROM de la requête principale.
Remarque : on peut employer un NOT IN à la place du NOT EXISTS (exercice), de même que l’on
peut toujours employer EXISTS à la place de IN. Voici une des requêtes précédentes où l’on a appliqué
cette transformation, en utilisant de plus des synonymes.
Dans quelle station pratique-t-on une activité au même prix qu’à Santalba ?
SELECT nomStation
FROM Activite A1
WHERE EXISTS (SELECT ’x’FROM Activite A2
WHERE nomStation = ’Santalba’
AND A1.libelle = A2.libelle
AND A1.prix = A2.prix)
Cette requête est elle-même équivalente à une jointure sans requête imbriquée.
6.4 Agrégration
Toutes les requêtes vues jusqu’à présent pouvaient être interprétées comme une suite d’opérations ef-
fectuées tuple à tuple. De même le résultat était toujours constitué de valeurs issues de tuples individuels.
Les fonctionnalités d’agrégation de SQL permettent d’exprimer des conditions sur des groupes de tuples,
et de constituer le résultat par agrégation de valeurs au sein de chaque groupe.
La syntaxe SQL fournit donc :
Il existe un groupe par défaut : c’est la relation toute entière. Sans même définir de groupe, on peut
utiliser les fonctions d’agrégation.
2. MAX et MIN.
On voit que la condition porte ici sur une propriété de l’ensemble des tuples du groupe, et pas de chaque
tuple pris individuellement. La clause HAVING est donc toujours exprimée sur le résultat de fonctions
d’agrégation.
6.5 Mises-à-jour
Les commandes de mise-à-jour (insertion, destruction, modification) sont considérablement plus simples
que les requêtes.
6.5.1 Insertion
L’insertion s’effectue avec la commande INSERT dont la syntaxe est la suivante :
INSERT INTO R( A1, A2, ... An) VALUES (v1, v2, ... vn)
R est le nom d’une relation, et les A1, ... An sont les noms des attributs dans lequels on souhaite
placer une valeur. Les autres attributs seront donc à NULL (ou à la valeur par défaut). Tous les attributs
spécifiés NOT NULL (et sans valeur par défaut) doivent donc figurer dans une clause INSERT.
Les v1, ... vn sont les valeurs des attributs. Exemple de l’insertion d’un tuple dans la table
Client.
Donc, à l’issue de cette insertion, les attributs ville et region seront à NULL.
Il est également possible d’insérer dans une table le résultat d’une requête. Dans ce cas la partie
VALUES ... est remplacée par la requête elle-même. Exemple : on a créé une table Sites (lieu,
region) et on souhaite y copier les couples (lieu, region) déjà existant dans la table Station.
Bien entendu le nombre d’attributs et le type de ces derniers doivent être cohérents.
6.5.2 Destruction
La destruction s’effectue avec la clause DELETE dont la syntaxe est :
DELETE FROM R
WHERE condition
CHAPITRE 6. LE LANGAGE SQL 77
R est bien entendu la table, et condition est toute condition valide pour une clause WHERE. En
d’autres termes, si on effectue, avant la destruction, la requête
SELECT * FROM R
WHERE condition
on obtient l’ensemble des lignes qui seront détruites par DELETE. Procéder de cette manière est un des
moyens de s’assurer que l’on va bien détruire ce que l’on souhaite....
Exemple : destruction de tous les clients dont le nom commence par ’M’.
DELETE FROM Client
WHERE nom LIKE ’M%’
6.5.3 Modification
La modification s’effectue avec la clause UPDATE. La syntaxe est proche de celle du DELETE :
R est la relation, les Ai sont les attributs, les vi les nouvelles valeurs et condition est toute condition
valide pour la clause WHERE. Exemple : augmenter le prix des activités de la station Passac de 10%.
UPDATE Activite
SET prix = prix * 1.1
WHERE nomStation = ’Passac’
Une remarque importante : toutes les mises-à-jour ne deviennent définitives qu’à l’issue d’une valida-
tion par commit. Entretemps elles peuvent être annulées par rollback. Voir le cours sur la concurrence
d’accès.
6.6 Exercices
Exercice 11 Reprendre les expressions algébriques du premier exercice du chapitre algèbre, et les expri-
mer en SQL.
Exercice 12 Donnez l’expression SQL des requêtes suivantes, ainsi que le résultat obtenu avec la base du
chapitre “Le langage SQL”.
1. Donnez les résultats des requêtes suivantes (rappel : le ’||’ est la concaténation de chaînes de
caractères.).
2. Les deux dernières requêtes sont-elles équivalentes (i.e. donnent-elles le même résultat quel que soit
le contenu de la table) ?
3. Supposons que l’on ait conservé une logique bivaluée (avec TRUE et FALSE) et adopté la règle
suivante : toute comparaison avec un NULL donne FALSE. Obtient-on des résultats équivalents ?
Cette règle est-elle correcte ?
4. Même question, en supposant que toute comparaison avec NULL donne TRUE.
Exercice 14 On reprend la requête constituant la liste des stations avec leurs activités, légèrement modi-
fiée.