Introduction aux requêtes SQL

publicité
Michael J. Hernandez
Introduction
aux requêtes SQL
ISBN 2-212-09272-5
Avant-propos
« Le langage est par sa nature même un élément collectif :
il n’exprime jamais un concept précis
mais un compromis qui nous lie vous, moi et tous les autres. »
Thomas Ernest Hulme, Spéculations
La récupération d'informations sur une base de données est en général un exercice complexe.
Elle peut néanmoins se révéler relativement aisée à partir du moment où l'on comprend la
question posée à la base de données. Une fois comprise, la question peut être traduite vers le
langage utilisé par n'importe quel système de base de données. Dans la plupart des cas, il s'agit
de SQL (Structured Query Language). Votre requête doit prendre la forme d'une instruction
SQL pour que le système de base de données puisse identifier les informations que vous
souhaitez récupérer. SQL vous permet ainsi de communiquer avec votre système de base de
données.
Notre longue expérience de consultants sur les bases de données nous a permis de constater
que les individus souhaitant simplement récupérer des informations sur une base de données
sont nettement plus nombreux que ceux chargés de développer des programmes et des applications pour ces bases. Malheureusement, il n'existe aucun ouvrage consacré exclusivement à
la récupération d'informations, en particulier du point de vue du « simple mortel ». Il existe
naturellement un grand nombre d'ouvrages intéressants sur SQL mais la plupart traitent de la
programmation et du développement des bases de données.
C'est pourquoi nous avons pensé qu'il était temps de publier un ouvrage vous apprenant à
réaliser une requête correctement et efficacement. Vous avez entre les mains le fruit de notre
réflexion. Parmi les ouvrages traitant de SQL, celui-ci est unique en son genre dans le sens où
il ne concerne que les requêtes. Une fois que vous l'aurez lu, vous disposerez de toutes les
compétences nécessaires à la récupération des informations dont vous avez besoin.
La rédaction d'un tel ouvrage ne peut être l'œuvre d'une seule personne. Des rédacteurs, des
collègues, des amis et des parents nous ont témoigné tout leur soutien et nous ont prodigué de
2
Introduction aux requêtes SQL
précieux conseils au moment le plus opportun. Ces personnes nous ont encouragés et nous ont
aidés à rester motivés pour mener ce projet à bien.
Tout d'abord, nous souhaiterions remercier notre éditrice Mary O’Brien pour nous avoir
permis d'écrire ce livre. Elle a su deviner tout le potentiel de l'idée de départ et s'est entièrement consacrée à ce projet. Nous remercions Mary et son assistante Mariann Kourafas pour
leur infinie patience et leur soutien constant tout au long de la rédaction de cet ouvrage. Merci
également à Marilyn Rash et à l'équipe de production, et bravo à tous !
Nous souhaiterions également remercier nos rédacteurs techniques : Malcom C. Rubel,
Michael Blaha, Alexander Tarasul et Keith W. Hare. Malcom, comme toujours, travailler avec
toi fut un grand plaisir ! Michael et Alexander, merci pour tous vos commentaires et suggestions fort utiles. Nous remercions tout particulièrement Keith pour avoir corrigé quelques
petites erreurs dans l'historique de SQL et pour toutes les informations présentées dans la
section « Ce que le futur nous réserve » du Chapitre 3. Merci encore à vous tous pour le temps
et les efforts que vous avez consacrés au projet et qui nous ont permis de rédiger un traité
exhaustif sur les requêtes SQL.
Enfin, nous remercions tout particulièrement Joe Celko pour son avant-propos. Joe est non
seulement expert en SQL mais c'est également un collègue et un bon ami. Nous respectons
son savoir et son expertise sur le sujet et sommes fiers qu'il ait accepté de nous faire part de
son opinion et de ses commentaires en début d'ouvrage.
J'adresse mes plus sincères remerciements à mon ami et collègue John L. Viescas pour m'avoir
donné l'opportunité de rédiger cet ouvrage avec lui. C'est lui qui est à l'origine du projet et qui
m'a convaincu d’y prendre part. John n'en est pas à son coup d'essai dans le domaine et il est
un auteur respecté et reconnu. C'est donc un honneur pour moi d'avoir collaboré avec lui sur
ce projet.
Enfin, je souhaiterais remercier Kendra, mon épouse. Elle s'est une fois de plus montrée très
patiente alors que je travaillais d'arrache-pied sur ce projet. Son aide a été inestimable et je lui
en suis infiniment reconnaissant.
Michael J. Hernandez,
Bellevue, Washington
Eh bien, Mike ! Je suis flatté de tous ces compliments, mais n’exagérons rien… Je t'ai peutêtre convaincu de collaborer à la rédaction de cet ouvrage, mais c'est bien toi qui as donné
corps à un projet adapté à un public de « simples mortels ». Expliquer le monde complexe de
SQL (c'est d'ailleurs l'un de mes thèmes de prédilection) à un si large public s'est révélé être
une tâche à la fois amusante et intéressante. Merci de m'avoir permis de le faire.
Contrairement à la tienne, mon épouse Suzanne ne déteste pas les marques publiques d'affection. Nous lui devons tous les deux un grand merci, non seulement pour m'avoir supporté
pendant que je me consacrais entièrement à cet ouvrage, mais également pour ses excellents
commentaires critiques et ses corrections. Elle s'y connaît en ordinateurs, mais elle est bel et
bien une « simple mortelle » lorsqu'il s'agit de bases de données. Elle représentait donc la
cible type lorsqu'il s'agissait de tester les premiers jets des chapitres. J'ai seulement dû lui
promettre une chose : cet été, pour nos vacances à Hawaï, je laisserai mon ordinateur portable
à la maison !
John L.Viescas
Austin, Texas
4
Création d’une requête simple
« Pensez comme un sage, mais communiquez dans la langue des gens ordinaires. »
William Butler Yeats
Dans ce chapitre :
• La commande SELECT
• Opposition entre données et informations
• Traduction de la requête en SQL
• Suppression des doublons
• Tri des informations
• Sauvegarde
• Exemples de commandes
• Problèmes à résoudre
Introduction
Parmi toutes les commandes de SQL, SELECT a un statut vraiment particulier car c’est
l’instruction la plus puissante et la plus complexe de ce langage : elle permet de retrouver les
informations stockées dans les tables d’une base de données. On utilise SELECT conjointement avec des mots-clés et des clauses afin de trouver et de visualiser quasiment toutes les
informations possibles. SELECT permet de répondre à toutes les questions imaginables. Pour
peu que vous ayez conçu correctement votre base de données et que vous y ayez introduit des
informations fiables, vous obtiendrez toutes les réponses nécessaires pour prendre les bonnes
décisions et mener à bien votre projet.
SELECT peut être décomposé en trois opérations que nous appellerons commande SELECT,
expression SELECT et requête SELECT. (Cette décomposition a un but pédagogique : mieux
64
Les bases de SQL
PARTIE 2
vaut immédiatement appréhender la complexité de SELECT.) Chacune de ces opérations a
son propre ensemble de mots-clés et de clauses permettant de créer une commande SQL
finale. Comme vous l’apprendrez au fil de cet ouvrage, vous pouvez même combiner ces
opérations de différentes manières pour répondre à des questions très complexes.
Dans ce chapitre, nous commencerons par la commande SELECT et nous aborderons brièvement la requête SELECT. Nous approfondirons la commande SELECT dans les deux chapitres suivants.
INFO
Dans les livres ou des magazines traitant des bases de données relationnelles, on rencontre relativement
souvent les termes « enregistrement » et « champ » utilisés au lieu de ligne et de colonne. Dans ce livre,
nous employons exclusivement ligne et colonne afin de rester cohérents avec la norme SQL.
La commande SELECT
La commande SELECT est l’élément-clé de toute question posée à une base de données.
Quand on crée et exécute une commande SELECT, on interroge la base de données à l’aide
d’une requête. En fait, de nombreux SGBDR permettent de sauvegarder les commandes
SELECT sous la forme d’une requête, d’une vue ou d’une procédure stockée. En fonction du
SGBDR utilisé, les commandes SELECT peuvent être exécutées directement à partir d’une
ligne de commande, d’une grille QBE (Query By Example ou Requête par l’exemple) ou bien
à partir d’un bloc de code. Quelle que soit la manière dont elle est définie ou exécutée, la
syntaxe de la commande SELECT est toujours identique.
Principales clauses d’une commande SELECT
Une commande SELECT est composée de plusieurs mots-clés appelés clauses. On définit une
commande SELECT en utilisant diverses combinaisons de ces clauses pour retrouver l’information recherchée. Certaines clauses sont obligatoires alors que d’autres sont optionnelles.
De plus, chaque clause comporte un ou plusieurs mots-clés qui représentent des valeurs obligatoires ou optionnelles. La Figure 4.1 montre un diagramme de la commande SELECT et de
ses clauses.
INFO
Le diagramme de la Figure 4.1 montre une commande SELECT dont la syntaxe est rudimentaire. Nous
affinerons ce diagramme au fil de l’étude de SELECT. Les lecteurs connaissant déjà les notions élémentaires de SQL devront se montrer patients.
Voici un petit résumé des clauses d’une commande SELECT.
• SELECT – Il s’agit de la clause principale de la commande SELECT et elle est absolument obligatoire. On l’utilise pour spécifier les colonnes de la requête qui sont extraites de
la table ou de la vue spécifiée dans la clause FROM. (On peut aussi les extraire simultanément de plusieurs tables mais nous aborderons cet aspect dans la troisième partie de cet
ouvrage.) On peut aussi utiliser dans cette clause des fonctions de totalisation comme
Sum(HeuresTravaillées) ou des expressions mathématiques, comme Quantité ¥ Prix.
• FROM – C’est la deuxième clause la plus importante de la commande SELECT et elle est
également obligatoire. On utilise la clause FROM pour spécifier les tables dont on extrait
les colonnes listées dans la clause SELECT. On peut employer cette clause de manière plus
complexe mais nous verrons cet aspect ultérieurement.
Création d’une requête simple
CHAPITRE 4
Figure 4-1
Diagramme de la commande SELECT.
• WHERE – C’est une clause optionnelle qu’on utilise pour filtrer les lignes retournées par
la clause FROM. Le mot-clé WHERE est suivi d'une expression (dont l’appellation technique est prédicat) à laquelle on attribue la valeur Vrai, Faux ou Inconnu. On peut tester
l’expression en utilisant les opérateurs de comparaison standard, des opérateurs booléens
ou des opérateurs spéciaux. Nous aborderons les éléments de la clause WHERE au
Chapitre 6.
• GROUP BY – Quand on utilise des fonctions de totalisation dans la clause SELECT pour
produire une synthèse des informations, on emploie la clause GROUP BY pour diviser les
informations en groupes distincts. Votre base de données utilise n’importe quelle colonne
ou groupe de colonnes suivant la clause GROUP BY comme colonne de regroupement. La
clause GROUP BY est optionnelle et nous l’étudierons au Chapitre 13.
• HAVING – La clause HAVING est associée à la clause GROUP BY ; on l’utilise pour
filtrer les informations regroupées. Elle est similaire à la clause WHERE dans le sens où le
mot-clé HAVING est suivi d'une expression évaluée à Vrai, Faux ou Inconnu. On peut
tester l’expression en utilisant les opérateurs de comparaison standard, des opérateurs
booléens ou des opérateurs spéciaux. HAVING est également une clause optionnelle et
nous l’approfondirons au Chapitre 14.
Nous allons commencer par travailler avec une commande SELECT très basique et nous nous
concentrerons donc sur les clauses SELECT et FROM. Nous étudierons progressivement les
autres clauses au fil des chapitres afin de parvenir à une commande SELECT plus complexe.
65
66
Les bases de SQL
PARTIE 2
Opposition entre données et informations
Avant de commencer à interroger la base de données, il est nécessaire d’apporter une
précision : il existe une grande différence entre données et informations. Par définition, les
données sont les éléments stockés dans une base de données alors que les informations sont
les éléments recherchés dans une base de données. Rappelez-vous qu’une base de données est
conçue pour fournir des informations pertinentes. Toutefois, ces informations ne peuvent être
fournies que si des données appropriées ont été entrées dans la base et si la base elle-même a
été structurée correctement. Examinons cela de plus près.
Les valeurs stockées dans une base de données sont des données. Les données sont statiques
dans le sens où elles demeurent dans le même état tant qu’elles ne sont pas modifiées manuellement ou par un processus automatisé. La Figure 4.2 montre un exemple de données.
Catherine Martin 69005 Actif 89931
Figure 4-2
Exemple de données.
En apparence, ces données n’ont aucun sens. Il n’est pas facile, par exemple, de déterminer ce
que représente 89931. S’agit-il d’un code postal ? Est-ce un numéro d’article ? Même si vous
savez qu’il s’agit d’un numéro client, est-il associé à Catherine Martin ? Il n’y a aucun moyen
de le savoir tant que les données ne sont pas traitées. Les données ne deviennent des informations qu’une fois qu’elles ont été traitées et qu’elles sont devenues pertinentes et utiles. Les
informations sont dynamiques dans le sens où elles varient constamment par rapport aux
données stockées dans la base de données et qu’elles peuvent être traitées et présentées de
diverses manières. On peut montrer des informations résultant d’une commande SELECT, les
afficher dans un formulaire sur un écran d’ordinateur ou bien les imprimer sur du papier dans
un rapport. Aspect essentiel à retenir : vous devez traiter les données pour les transformer en
informations.
La Figure 4.3 montre les données de l’exemple précédent transformées en informations dans
une fiche client. Cet exemple illustre la manière dont les données peuvent être manipulées
pour sembler pertinentes à la personne qui les visualise.
Figure 4-3
Exemple
de données
transformées
en informations.
Quand on travaille avec une commande SELECT, on utilise ses clauses pour manipuler des
données mais la commande proprement dite retourne des informations.
Création d’une requête simple
CHAPITRE 4
Toutefois, il subsiste un problème. Quand on exécute une commande SELECT, elle retourne
une ou plusieurs lignes d’informations ; le nombre exact de lignes dépend de la procédure
utilisée pour construire la commande. On appelle ces lignes ensemble de résultats et c’est la
terminologie que nous emploierons tout au long de cet ouvrage. Ce terme est bien choisi parce
qu’on travaille toujours avec des ensembles de données quand on utilise une base de données
relationnelle (rappelez-vous que le modèle relationnel repose sur la théorie des ensembles).
On peut facilement visualiser les informations d’un ensemble de résultats et, dans de
nombreux cas, en modifier les données.
Mais revenons à notre propos et commençons à utiliser la commande SELECT.
Traduction de la requête en SQL
Quand on interroge la base de données, c’est en général sous la forme d’une question ou bien
d’une commande qui implique une question. Par exemple, on peut formuler les commandes
de la manière suivante :
« Dans quelles villes habitent nos clients ? »
« Montrez-moi la liste de nos employés avec leurs numéros de téléphone. »
« Quels types de cours proposons-nous ? »
« Donnez-moi le nom de nos employés avec leur date d’embauche. »
Une fois que vous savez ce que vous voulez demander, vous pouvez traduire votre requête en
une instruction plus formelle. Cette traduction revêt la forme suivante :
Sélectionner <élément> dans la <source>
Commencez par remplacer les mots et les phrases comme « Montrez-moi », « Listez »,
« Quels », « Qui » par le mot Sélectionner. Identifiez ensuite tous les noms de votre requête et
déterminez si un nom donné représente un élément que vous voulez voir ou si le nom est celui
d’une table dans laquelle un élément peut être stocké. S’il s’agit d’un élément, utilisez-le à la
place de <élément> dans l’instruction de traduction ; s’il s’agit d’un nom de table, utilisez-le
à la place de <source>. Si vous traduisez la première question de nos exemples précédents,
vous obtenez ce résultat :
Sélectionner ville dans la table clients
Une fois que vous avez défini votre instruction de traduction, vous devez la transformer en une
commande SELECT proprement dite en utilisant la syntaxe SQL illustrée à la Figure 4.4.
Toutefois, la première étape consiste à nettoyer l’instruction de traduction. Pour ce faire, vous
devez barrer tous les mots qui ne représentent pas le nom d’une colonne ou d’une table ou qui
ne sont pas des mots spécifiquement employés par la syntaxe SQL. Voici le résultat obtenu
après la phase de nettoyage :
Sélectionner ville dans la table clients
Il apparaîtra clairement au lecteur anglophone que la phase de nettoyage fonctionne beaucoup
mieux en anglais dans la mesure où elle permet de générer la commande SQL exacte. Les
différentes traductions des termes anglais (« sélectionner » par SELECT, « dans » par
FROM…) imposent au lecteur français une ultime transformation pour passer à la requête
67
68
Les bases de SQL
PARTIE 2
SQL réelle. Par conséquent, nous avons préféré introduire les commandes et mots-clés correspondants entre crochets dans la phase de nettoyage pour en faciliter la compréhension :
Sélectionner [SELECT] ville dans [FROM] la table clients
Supprimez alors les mots que vous avez barrés et remplacez les termes français
« sélectionner » et « dans » par les mots-clés SQL figurant entre crochets. Vous obtenez ainsi
la commande SQL complète :
SELECT Ville FROM Client
Cette technique comptant trois étapes s’applique à toute question posée à la base de données.
En fait, nous emploierons cette technique dans la majeure partie du livre et nous vous encourageons à l’utiliser au début de votre apprentissage de SQL. Quand vous serez plus au fait des
commandes SELECT, vous les construirez en une seule étape.
Figure 4-4
Exemple d’une commande SELECT simple.
Gardez à l'esprit que vous travaillerez généralement avec des colonnes et des tables au début
de votre apprentissage de SQL. Le diagramme de la syntaxe de la Figure 4.4 reflète cet état de
fait en utilisant nom de colonne dans la clause SELECT et nom de table dans la clause FROM.
Au chapitre suivant, vous apprendrez à utiliser d’autres termes dans ces clauses pour créer des
commandes SELECT plus complexes.
Vous avez probablement remarqué que la requête employée dans l’exemple précédent est relativement simple. En effet, la redéfinition sous forme de traduction ainsi que l’identification
des noms de colonnes présentes dans la commande ne posaient pas de problème. Mais que se
passe-t-il si la requête est plus complexe ou que les colonnes sont moins faciles à identifier ?
Il faut affiner votre requête et la rendre plus spécifique. Par exemple, vous pouvez affiner la
requête suivante :
« Montrez-moi les informations concernant nos clients. »
en la reformulant plus clairement :
« Listez le nom, la ville et le numéro de téléphone de chacun de nos clients. »
Si le problème n’est pas résolu, vous avez toujours deux autres solutions. Il faut commencer par
déterminer si la table spécifiée dans la clause FROM de la commande SELECT contient des
noms de colonnes qui peuvent vous aider à clarifier la requête et donc faciliter sa traduction.
Deuxième solution : examiner la requête de plus près et déterminer si un mot ou une phrase
implique des noms de colonne. L’emploi de ces deux méthodes dépend de la requête propre-
Création d’une requête simple
CHAPITRE 4
ment dite. Prenons un exemple pour mieux comprendre cet aspect. Pour illustrer la première
technique, supposons que l’on tente de traduire la requête suivante :
« J’ai besoin des noms et adresses de tous nos employés. »
Cette requête paraît simple. Mais en y regardant de plus près, vous constaterez un léger
problème : alors qu’on peut déterminer la table nécessaire (Employés) pour traduire la
commande, rien dans la requête n'indique précisément les colonnes à spécifier pour la clause
SELECT. Bien que les mots « noms » et « adresses » apparaissent dans la requête, ce sont des
termes trop généraux. On peut résoudre ce problème en examinant la table identifiée dans la
requête et en déterminant si elle contient des colonnes que l’on peut substituer à ces termes.
Dans ce cas, utilisez les noms des colonnes dans la phase de traduction. Vous pouvez choisir
d’utiliser les versions génériques des noms de colonnes dans la phase de traduction si cela vous
aide à visualiser plus clairement la commande ; cependant, vous devrez utiliser les noms réels
de colonnes dans la syntaxe SQL. Dans ce cas, cherchez les noms des colonnes de la table
Employés qui peuvent être utilisés au lieu des mots « noms » et « adresses ». Utilisez la table
Employés montrée à la Figure 4.5 et demandez-vous si vous pouvez utiliser ses colonnes.
Figure 4-5
Structure de la table Employés.
Vous utiliserez en fait cinq colonnes de cette table. EmpNom et EmpPrénom remplaceront
« noms » dans la requête alors que EmpAdresse, EmpVille, EmpCodePostal remplaceront
« adresses ». Appliquons à présent l'ensemble du processus de traduction à la requête. Nous
utilisons la forme générique des noms de colonnes (prénom, nom…) dans la phase de traduction et les noms réels de colonne (EmpPrénom, EmpNom…) dans la syntaxe SQL. Enfin, de
manière à rester plus proche de l’anglais FROM, nous emploierons désormais l’expression « à
partir de la table… » dans la phase de traduction, plutôt que « dans la table… ».
Énoncé
Traduction
Nettoyage
« J’ai besoin des noms et des adresses de tous nos employés. »
Sélectionner le prénom, le nom, l’adresse, la ville et le code postal à partir de
la table des employés
Sélectionner [SELECT] le prénom, le nom, l’adresse, la ville, et le code
postal à partir de [FROM] la table des employés
SQL
SELECT EmpPrénom, EmpNom, EmpAdresse, EmpVille, EmpCodePostal
FROM Employés
INFO
Cet exemple illustre clairement l'utilisation de plusieurs colonnes dans une clause SELECT. Nous reviendrons plus en détail sur cette technique dans cette section.
69
70
Les bases de SQL
PARTIE 2
L’exemple suivant illustre la seconde technique qui nécessite la recherche des colonnes impliquées dans la requête. Supposons que vous vouliez traduire la requête suivante :
« Quels types de cours proposons-nous actuellement ? »
À première vue, il peut sembler difficile de traduire cette requête. Il n’y a aucun nom de
colonne et il est impossible de créer une requête s’il n’y a même pas un élément à sélectionner.
Que faire à présent ? Il faut examiner plus précisément chaque mot de la requête et déterminer
s’il y en a un qui implique un nom de colonne dans la table Cours. Avant d’aller plus loin,
prenons un instant pour réétudier la requête. Pouvez-vous trouver un tel mot ?
Dans notre exemple, le mot « types » peut impliquer un nom de colonne dans la table Cours.
Pourquoi ? Parce qu'un type de cours peut aussi prendre le sens de catégorie de cours. S’il y a
une colonne catégorie dans la table Cours, vous devrez alors compléter la traduction et donc
la commande SELECT. Supposons qu’il y ait une colonne catégorie dans la table Cours ;
recommencez le processus de traduction en trois étapes :
Énoncé
Traduction
Nettoyage
« Quels types de cours proposons-nous actuellement ? »
Sélectionner la catégorie à partir de la table des cours
Sélectionner [SELECT] la catégorie à partir de [FROM] la table des cours
SQL
SELECT Catégorie
FROM Cours
Comme le montre cet exemple, cette technique implique l’utilisation de synonymes pour
remplacer certains mots ou expressions dans la requête. Si vous identifiez un mot ou une
expression qui peut impliquer un nom de colonne, essayez de le remplacer par un synonyme
qui peut être une colonne de la base de données. Mais si le premier synonyme qui vous vient
à l’esprit ne fonctionne pas, essayez-en un autre. Continuez ainsi jusqu’à ce que vous trouviez
un synonyme qui correspond à un nom de colonne.
INFO
Sauf spécification contraire, les noms des colonnes et des tables utilisés dans ces exemples sont extraits
des exemples de base de données que vous trouverez sur le CD.
Élargissement du champ de vision
La commande SELECT permet également d’extraire plusieurs colonnes et l’opération sera
aussi simple que pour une colonne unique. Listez les noms des colonnes que vous voulez
utiliser dans la clause SELECT et séparez chaque nom de la liste par une virgule. Dans le
diagramme présenté sur la Figure 4.6, l’option permettant d’utiliser plus d’une colonne est
indiquée par une ligne allant de droite à gauche sous nom de colonne. La virgule se trouvant
au milieu de la ligne indique que vous devez insérer une virgule avant l’autre nom de colonne
à utiliser dans la clause SELECT.
Cette option, qui permet d’utiliser plusieurs colonnes dans une commande SELECT, vous
donne la possibilité de répondre à des questions similaires à celles-ci :
Énoncé
Traduction
« Montrez-moi la liste actuelle de nos employés et leurs numéros de
téléphone. »
Sélectionner le nom, le prénom et le numéro de téléphone de tous nos
employés à partir de la table des employés
Création d’une requête simple
CHAPITRE 4
Figure 4-6
Utilisation de plusieurs colonnes
dans une clause SELECT.
Nettoyage
Sélectionner [SELECT] le nom, le prénom et le numéro de téléphone de
tous nos employés à partir de [FROM] la table des employés
SQL
SELECT EmpNom, EmpPrénom, EmpTéléphone
FROM Employés
Énoncé
« Quels sont les noms et les prix des produits que nous vendons et dans quelle
catégorie chaque produit est-il listé ? »
Sélectionner le nom, le prix et la catégorie de chaque produit à partir de la
table des produits
Sélectionner [SELECT] le nom, le prix et la catégorie de chaque produit à
partir de [FROM] la table des produits
Traduction
Nettoyage
SQL
SELECT NomProduit, PrixPublic, Catégorie
FROM Produits
L’ordre des colonnes dans la clause SELECT n’a pas d’importance et vous pouvez lister les
colonnes dans l’ordre que vous souhaitez. Cela vous donne la possibilité de voir les mêmes
informations sous des angles différents.
Supposons par exemple que vous travaillez avec la table présentée à la Figure 4.7, et qu’on
vous demande de poser la question suivante à la base de données :
« Montrez-moi la liste des sujets, la catégorie à laquelle ils appartiennent et le code que
nous utilisons dans notre catalogue. Mais j’aimerais tout d’abord voir le nom, suivi de la
catégorie et enfin le code. »
Figure 4-7
Structure de la table Sujets.
Vous pouvez toujours transformer cette requête pour que les colonnes apparaissent dans
l’ordre souhaité. Il suffit de lister les noms des colonnes dans l’ordre spécifié quand on définit
la commande. Voici l’aspect pris par le processus quand vous transformez cette requête en une
commande SELECT.
71
72
Les bases de SQL
PARTIE 2
Traduction
Sélectionner le nom du sujet, le numéro de la catégorie et le code du sujet à
partir de la table des sujets
Nettoyage
Sélectionner [SELECT] le nom du sujet, le numéro de la catégorie et le
code du sujet à partir de [FROM] la table des sujets
Il n’y a pas de limite quant au nombre de colonnes spécifiées dans la clause SELECT et vous
pouvez en fait lister toutes les colonnes de la table source. L’exemple suivant montre la
commande SELECT qui spécifie toutes les colonnes de la table Sujets de la Figure 4.7.
SQL
SELECT NumSujet, NumCatégorie, CodeSujet, NomSujet, DescriptionSujet
FROM Sujets
Quand vous spécifiez toutes les colonnes de la table source, vous devez saisir de nombreux
caractères si la table contient beaucoup de colonnes ! Heureusement, la norme SQL propose
un raccourci, l’astérisque, qui allège considérablement la commande. Le diagramme de la
Figure 4.8 montre que l’on peut utiliser l’astérisque au lieu de lister toutes les colonnes dans
la clause SELECT.
Figure 4-8
Utilisation d’un astérisque pour spécifier
toutes les colonnes de la table source.
Placez l’astérisque immédiatement après la clause SELECT quand vous voulez spécifier
toutes les colonnes de la table source dans la clause FROM. Par exemple, la commande précédente prend cet aspect quand on utilise le raccourci :
SQL
SELECT *
FROM Sujets
Avouez que le nombre de caractères à taper est nettement réduit ! Toutefois, un problème se
pose quand vous créez des commandes SELECT de cette manière : l’astérisque représente
toutes les colonnes qui existent actuellement dans la table source ; or, l’ajout ou la suppression de colonnes affecte ce que vous voyez dans l’ensemble de résultats de la commande
SELECT. (Étrangement, la norme SQL stipule que l’ajout ou la suppression de colonnes ne
doit pas affecter l’ensemble des résultats.) Ce problème n’est important que si vous devez
voir systématiquement les mêmes colonnes dans l’ensemble de résultats. Votre base de
données ne vous avertira pas si des colonnes ont été supprimées lorsque vous utilisez l’astérisque dans la clause SELECT mais elle émettra un message d’erreur quand elle ne trouvera
pas une colonne que vous avez spécifiée explicitement. Bien que cela ne constitue pas un réel
problème pour notre propos, cette difficulté prend des proportions beaucoup plus importantes dans le cadre de la programmation en SQL. Nous vous conseillons de réserver l’astérisque aux requêtes devant être créées rapidement pour voir toutes les informations d’une
Création d’une requête simple
CHAPITRE 4
table donnée. Dans le cas contraire, spécifiez toutes les colonnes dont vous avez besoin dans
la requête. Finalement, la requête retournera exactement les informations nécessaires et en
outre, elle sera auto-documentée.
Suppression des doublons
Quand on travaille avec des commandes SELECT, on rencontre inévitablement des ensembles
de résultats qui comportent des doublons. Il n’y a aucune raison de s’inquiéter dans ce cas.
Utilisez le mot-clé DISTINCT dans votre commande SELECT et l’ensemble de résultats ne
comportera aucune ligne en double. La Figure 4.9 montre la syntaxe du mot-clé DISTINCT.
Figure 4-9
Syntaxe du mot-clé DISTINCT.
Comme le montre le diagramme, DISTINCT est un mot-clé optionnel qui précède la liste des
colonnes spécifiées dans la clause SELECT. Le mot-clé DISTINCT demande à votre base de
données d’évaluer la valeur de toutes les colonnes comme des unités simples dans une comparaison ligne à ligne et d’éliminer toutes les redondances qu’elle rencontre. Les lignes uniques
restantes sont ensuite retournées dans l’ensemble de résultats. L’exemple suivant montre la
différence engendrée par l’emploi du mot-clé DISTINCT.
Imaginons la question suivante posée à notre base de données:
Énoncé
« Quelles sont les villes représentées par les membres de notre ligue de
bowling ? »
La question paraît assez simple et nous allons la traduire :
Traduction
Sélectionner la ville à partir de la table des joueurs de bowling
Nettoyage
Sélectionner [SELECT] la ville à partir de [FROM] la table des joueurs de
bowling
SQL
SELECT Ville
FROM Joueurs
Le problème est que l’ensemble de résultats de cette commande SELECT montre toutes les
occurrences de chaque nom de ville trouvé dans la table Joueurs. Par exemple, s’il y a
20 joueurs de Paris, 7 joueurs de Marseille et 14 joueurs de Lyon, l’ensemble de résultats
affiche 20 occurrences de Paris, 7 occurrences de Marseille et 14 occurrences de Lyon. Il est
évident que cette redondance ne nous intéresse pas ; seule une occurrence de chaque nom de
73
74
Les bases de SQL
PARTIE 2
ville trouvé dans le table Joueurs est nécessaire. On résout ce problème en utilisant le mot-clé
DISTINCT dans la commande SELECT pour éliminer les informations redondantes.
SQL
SELECT DISTINCT Ville
FROM Joueurs
L’ensemble de résultats de cette commande SELECT affiche exactement ce que vous
souhaitez : une seule occurrence de chaque ville distincte (ou unique) trouvée dans la table
Joueurs.
On peut également utiliser le mot-clé DISTINCT sur plusieurs colonnes. Modifions l’exemple
précédent en demandant à la fois la ville et le département dans la table Joueurs. Notre
nouvelle commande SELECT se présente sous cette forme :
SQL
SELECT DISTINCT Département, Ville
FROM Joueurs
Cette commande SELECT retourne un ensemble de résultats qui contient des enregistrements
uniques ; si des villes de départements différents portent le même nom, elles ne sont pas considérées comme des doublons et sont toutes affichées.
Le mot-clé DISTINCT est un outil très puissant et son utilisation est limitée au cas où vous
voulez réellement voir des lignes uniques dans votre ensemble de résultats.
Tri des informations
Au début de ce chapitre, nous avons dit que l’opération SELECT pouvait être divisée en trois
opérations : la commande SELECT, l’expression SELECT et la requête SELECT. Nous avons
également dit qu’il était possible de combiner ces opérations de différentes manières afin de
répondre à des requêtes complexes. Toutefois, il faut combiner ces opérations pour trier les
lignes d’un ensemble de résultats.
Par définition, les lignes d’un ensemble de résultats qui sont retournées par une commande
SELECT ne sont pas triées ; leur ordre d’apparition dépend de leur position physique dans la
table. Le seul moyen de trier l’ensemble de résultats est d’incorporer la commande SELECT
au sein d’une requête SELECT, comme le montre la Figure 4.10. On définit une requête
SELECT comme une commande SELECT accompagnée d’une clause ORDER BY. C’est la
clause ORDER BY de la requête SELECT qui permet de spécifier l’ordre des lignes de
l’ensemble de résultats final. Comme vous l’apprendrez par la suite, on peut incorporer une
commande SELECT au sein d’une autre commande SELECT ou d’une expression SELECT
pour répondre à des questions très complexes. En revanche, la requête SELECT ne peut pas
être incorporée à n’importe quel niveau.
INFO
Dans ce livre, nous utilisons la terminologie de la norme SQL ANSI ou encore celle en usage dans les
bases de données les plus courantes. Cependant, la norme SQL ANSI ne définit la clause ORDER BY
que comme élément d’un curseur, à savoir un objet créé à l’intérieur d’un programme d’application. Une
étude approfondie des curseurs dépasse le cadre de cet ouvrage. Dans la mesure où de nombreuses
mises en œuvre de SQL autorisent l’inclusion d’une clause ORDER BY à la fin d’une commande
SELECT, nous avons inventé le terme requête SELECT pour décrire ce type de commande.
Création d’une requête simple
CHAPITRE 4
Figure 4-10
Syntaxe de la requête SELECT.
La clause ORDER BY permet de trier l’ensemble de résultats sur une ou plusieurs colonnes
en précisant pour chaque colonne si l’ordre est croissant ou décroissant. Les seules colonnes
pouvant être utilisées dans la clause ORDER BY sont celles qui sont actuellement présentées
dans la clause SELECT. (Bien que cette exigence soit spécifiée dans la norme SQL, certains
logiciels l’ignorent totalement. Dans les exemples de ce livre, nous suivons la norme SQL).
Quand vous utilisez plusieurs colonnes dans une clause ORDER BY, séparez chaque colonne
par une virgule. La requête SELECT retourne un ensemble de résultats définitif une fois que
le tri est terminé.
INFO
La clause ORDER BY n’affecte pas l’ordre physique des lignes de la table. Si vous avez besoin de modifier l’ordre physique des lignes, reportez-vous à la documentation de vos logiciels.
Ordre de tri
Avant d’étudier des exemples de requêtes SELECT, il faut examiner le concept de séquence
de classement (ou ordre de tri).
La manière dont la clause ORDER BY trie les informations dépend de l’ordre de tri utilisé par
votre logiciel de base de données. La séquence de classement détermine l’ordre de préséance
de chaque caractère listé dans le jeu de caractères de la langue utilisé par votre système
d’exploitation. Par exemple, elle décide si les lettres minuscules sont triées avant les lettres
majuscules ou bien si la différence entre les minuscules et les majuscules est appliquée.
Vérifiez la documentation de votre base de données et consultez éventuellement votre administrateur de base de données pour déterminer l’ordre de tri par défaut.
Ordonner
La clause ORDER BY permet de présenter de manière plus pertinente les informations
extraites de la base de données. Ce principe s’applique aussi bien aux requêtes simples qu’aux
requêtes complexes. Vous pouvez à présent reformuler les requêtes pour qu’elles soient triées.
Par exemple, une question telle que :
« Quels sont les types de cours que nous proposons actuellement ? »
peut être reformulée ainsi :
« Listez les types de cours que nous proposons et montrez-les dans l’ordre alphabétique. »
75
76
Les bases de SQL
PARTIE 2
Avant d’étudier les requêtes SELECT, il faut rectifier la manière dont nous définissons une
commande de traduction. Cela implique l’ajout d’une nouvelle section à la fin de la
commande de traduction pour prendre en compte les nouvelles exigences de tri spécifiées
dans la requête. Utilisez cette nouvelle forme pour définir la commande de traduction.
Sélectionner <élément> dans la <source> et triez par <colonne(s)>
Maintenant que votre requête va inclure des expressions telles que « Triez les résultats par
ville », « Montrez-les dans l’ordre chronologique » ou bien « Listez-les par nom et prénom »,
il nous faut étudier soigneusement la requête pour déterminer les colonnes devant être utilisées à des fins de tri. Vous y parviendrez facilement car la plupart des gens utilisent ce genre
d’expressions et les colonnes nécessaires pour le tri sont habituellement évidentes. Une fois
que vous avez identifié la ou les colonnes appropriées, utilisez-les pour remplacer
<colonne(s)> dans la commande de traduction. Prenons un exemple pour mieux comprendre
le fonctionnement :
Énoncé
« Listez les types de cours que nous proposons et montrez-les dans l’ordre
alphabétique. »
Traduction
Sélectionner la catégorie à partir de la table des cours et trier par catégorie
Nettoyage
Sélectionner [SELECT] la catégorie à partir de [FROM] la table des cours
et trier par [ORDER BY] catégorie
SQL
SELECT Catégorie
FROM Cours
ORDER BY Catégorie
Dans cet exemple, on suppose que Catégorie sera utilisé pour le tri car c’est la seule colonne
indiquée dans la requête. On peut également supposer que le tri doit être réalisé dans l’ordre
croissant car rien n’indique le contraire dans la requête. Cette supposition est tout à fait
fondée. Selon la norme SQL, le tri croissant est utilisé par défaut si aucun ordre de tri n’est
spécifié. Toutefois, si vos voulez être parfaitement explicite, il suffit d’insérer ASC après Catégorie dans la clause ORDER BY.
Dans la requête suivante, la colonne de tri est définie plus clairement :
Énoncé
« Montrez-moi une liste des fournisseurs selon l’ordre de leur code postal »
Traduction
Sélectionner le nom du fournisseur et son code postal à partir de la table des
fournisseurs et trier par code postal
Nettoyage
Sélectionner [SELECT] le nom du fournisseur et son code postal à partir de
[FROM] la table des fournisseurs et trier par [ORDER BY] code postal
SQL
SELECT NomFournisseur, CodePostalFournisseur
FROM Fournissseurs
ORDER BY CodePostalFournisseur
En règle générale, les personnes concernées vous diront si elles veulent voir les informations
dans l’ordre décroissant. Dans ce cas, il faut afficher l’ensemble de résultats dans l’ordre
inverse : insérez le mot-clé DESC après la colonne appropriée dans la clause ORDER BY. Par
exemple, voici comment modifier la commande SELECT de l’exemple précédent pour voir
les informations triées par code postal dans l’ordre décroissant :
Création d’une requête simple
CHAPITRE 4
SQL
SELECT NomFournisseur, CodePostalFournisseur
FROM Fournisseurs
ORDER BY CodePostalFournisseur DESC
L’exemple suivant illustre une requête plus complexe qui nécessite un tri sur plusieurs
colonnes. La seule différence entre cet exemple et les deux précédents est qu’il utilise deux
colonnes dans la clause ORDER BY. Notez que les colonnes sont séparées par des virgules,
ce qui est conforme à la syntaxe exposée dans le diagramme de la Figure 4.10.
Énoncé
« Affichez les noms de nos employés avec leur numéro de téléphone et leur
matricule et listez les par ordre alphabétique de nom et de prénom »
Traduction
Sélectionner le nom, le prénom, le téléphone et le numéro d’employé à partir
de la table des employés et trier par le nom et le prénom
Nettoyage
Sélectionner [SELECT] le nom, le prénom, le téléphone et le numéro
d’employé à partir de [FROM] la table des employés et trier par [ORDER
BY] le nom et le prénom
SQL
SELECT Nom, Prénom, Téléphone, NumEmployé
FROM Employés
ORDER BY Nom, Prénom
Une des opérations les plus intéressantes pouvant être réalisée avec les colonnes dans une
clause ORDER BY est la spécification d’un ordre de tri différent pour chaque colonne. Dans
l’exemple précédent, vous pouvez spécifier un ordre décroissant pour le nom et un ordre croissant pour le prénom. La commande SELECT se présente alors sous cette forme :
SQL
SELECT Nom, Prénom, Téléphone, NumEmployé
FROM Employés
ORDER BY Nom DESC, Prénom ASC
Bien qu’il ne soit pas nécessaire d’utiliser explicitement le mot-clé ASC, la commande est
plus lisible en sa présence.
L’exemple précédent permet de se poser une question intéressante : l’ordre des colonnes dans
la clause ORDER BY a-t-il de l’importance ? La réponse est Oui ! L’ordre est important car
votre base de données évalue les colonnes de la clause ORDER BY de la gauche vers la droite.
Sauvegarde
Vous devez sauvegarder vos commandes SELECT et tous les bons logiciels de base de
données en donnent la possibilité. La sauvegarde de vos commandes permet de ne pas les
recréer chaque fois que vous posez la même question à la base de données. Quand vous sauvegardez une commande SELECT, donnez-lui un nom significatif qui vous aidera à vous
souvenir du type d’information délivré par la commande. Si votre logiciel de base de données
le permet, écrivez un bref commentaire décrivant l’objet de la requête. Cette description vous
rafraîchira la mémoire quand vous serez de nouveau confronté à une commande SELECT
écrite plusieurs mois auparavant.
77
78
Les bases de SQL
PARTIE 2
Une commande SELECT sauvegardée est parfois appelée requête dans certains logiciels et
vue dans d’autres. Quelle que soit la dénomination, chaque logiciel de base de données permet
d’exécuter la commande sauvegardée et de travailler avec son ensemble de résultats.
INFO
Dans cet ouvrage, nous employons le terme requête pour représenter une commande SELECT sauvegardée.
Deux méthodes peuvent être utilisées pour exécuter une requête. La première est interactive
et implique une barre d’outils ou une grille de saisie. La seconde utilise un éditeur de code.
Vous utiliserez essentiellement la première méthode ; l’autre méthode ne sera envisagée que
quand vous commencerez à étudier le langage de programmation de votre base de données.
Nous nous limiterons ici à vous apprendre comment créer et utiliser des commandes SQL.
Exemples de commandes
Après avoir étudié les caractéristiques de base de la commande SELECT, nous allons
examiner quelques exemples en utilisant les bases de données proposées sur le CD-Rom.
Après chaque commande, nous avons inclus un exemple de l’ensemble de résultats retourné
par la commande. Le nom qui apparaît immédiatement au-dessus de l’ensemble de résultats a
deux objectifs : il est utilisé pour identifier l’ensemble de résultats lui-même, et c’est le nom
assigné à la commande SQL de l’exemple.
Au cas où vous vous demanderiez pourquoi nous avons assigné un nom à chaque commande
SQL, c’est tout simplement parce que nous les avons sauvegardés ! En fait, nous avons sauvegardé toutes les commandes SQL qui apparaissent dans les exemples de ce livre. Chaque
commande est stockée dans la base de données appropriée; et vous pouvez recopier sur votre
disque dur les bases de données qui se trouvent sur le CD-Rom. Cela vous permet de travailler
de manière interactive avec ces commandes avant d’essayer de les écrire vous-même.
Base de données Ventes
Énoncé
Traduction
Nettoyage
« Montrez-moi les noms de tous nos fournisseurs »
Sélectionner le nom du fournisseur à partir de la table des fournisseurs
Sélectionner [SELECT] le nom du fournisseur à partir de [FROM] la table
des fournisseurs
SQL
SELECT NomFournisseur
FROM Fournisseurs
Énoncé
Traduction
Nettoyage
« Quels sont les noms et les prix de tous les produits que nous vendons ? »
Sélectionner le nom du produit, le prix public à partir de la table des produits
Sélectionner [SELECT] le nom du produit, le prix public à partir de
[FROM] la table des produits
SQL
SELECT NomProduit, PrixPublic
FROM Produits
Création d’une requête simple
CHAPITRE 4
Énoncé
« Dans quelles villes habitent nos clients ? »
Traduction
Sélectionner sans doublon la ville à partir de la table des clients
Nettoyage
Sélectionner [SELECT] sans doublon [DISTINCT] la ville à partir de
[FROM] la table des clients
SQL
SELECT DISTINCT VilleClient
FROM Clients
Nom des fournisseurs (10 lignes)
NomFournisseur
Shinoman
Taupe Bike
Le guidon dans les nuages
ProFormance
Pédale douce
Vélo 2000
Le paradis du VTT
Vélo en gros
VTT Star
Poulie d'or
Produits vendus (40 lignes)
NomProduit
PrixPublic
Villes des clients (17 lignes)
VilleClient
1 200,00 €
Auxerre
VTT Eagle FS-3
1 800,00 €
Bordeaux
Compteur Dog Ear
75,00 €
Chalon-sur-Saône
Pneus tout temps Victoria Pro
54,95 €
Dijon
Rétroviseur Dog Ear
7,45 €
Etretat
VTT Viscount
635,00 €
Lille
Compteur sans fil Viscount C-500
49,00 €
Lyon
Antivol Kryptonite Advanced 2000
50,00 €
Marseille
Antivol Nikoma Lok-Tight
33,00 €
Metz
Casque Viscount Microshell
36,00 €
Montpellier
VTT Trek 9000
Lignes supplémentaires
Paris
Périgueux
Saint-Etienne
Strasbourg
Valence
Villeurbanne
79
80
Les bases de SQL
PARTIE 2
Base de données AgenceSpectables
Énoncé
« Listez tous les groupes et la ville où ils sont basés en triant par ville et par
nom de scène dans l’ordre croissant »
Sélectionner la ville et le nom de scène à partir de la table des groupes et trier
par ordre alphabétique de ville et nom de scène
Sélectionner [SELECT] la ville et le nom de scène à partir de [FROM] la
table des groupes et trier par [ORDER BY] ordre alphabétique [ASC] de
ville et nom de scène
Traduction
Nettoyage
SQL
SELECT VilleGroupe, NomScène
FROM Groupes
ORDER BY VilleGroupe ASC, NomScène ASC
Énoncé
« Donnez-moi une des dates d’engagement dédoublonnée ; je ne souhaite pas
savoir combien il y a d’engagements par date »
Sélectionner sans doublon la date de début à partir de la table des engagements
Sélectionner [SELECT] sans doublon [DISTINCT] la date de début à partir
de [FROM] la table des engagements
Traduction
Nettoyage
SQL
SELECT DISTINCT DateDébut
FROM Engagements
Villes des groupes (13 lignes)
VilleGroupe
NomScène
Dates d’engagement (66 lignes)
DateDébut
Besançon
Albert Tillon
01-07-1999
Lille
Danses du ballet
10-07-1999
Lyon
Hélène De La Mole
11-07-1999
Lyon
Les chaussettes rouges
15-07-1999
Lyon
Quartet Devos
17-07-1999
Marseille
Les haricots noirs
18-07-1999
Marseille
Suzie Baker
24-07-1999
Marseille
Topaze
29-07-1999
Montpellier
Trio urbain
30-07-1999
Paris
Blues de vache
31-07-1999
Paris
Country Porter
Lignes supplémentaires
Paris
Julie Martin
Paris
Le lièvre du samedi soir
Création d’une requête simple
CHAPITRE 4
Base de données EmploiDuTemps
Énoncé
Traduction
Nettoyage
« Pouvons-nous visualiser toutes les informations sur le planning des cours ? »
Sélectionner toutes les colonnes à partir de la table du planning
Sélectionner [SELECT] toutes les colonnes [*] à partir de [FROM] la table
du planning
SQL
SELECT *
FROM Planning
Informations planning (76 lignes)
NumCours
UV
Salle
Coefficient
HeureDébut
1000
11
1231
5
10:00
1002
12
1619
4
15:30
1004
13
1627
4
08:00
1006
13
1627
4
09:00
1012
14
1627
4
13:00
1020
15
3404
4
13:00
1030
16
1231
5
11:00
1031
16
1231
5
14:00
1156
37
3443
5
08:00
1162
37
3443
5
09:00
1168
37
3445
5
11:00
1180
38
3446
5
11:30
Autres colonnes
Lignes supplémentaires
Énoncé
Traduction
Nettoyage
SQL
« Donnez-moi la liste des bâtiments de l’université et le nombre d’étages de
chaque bâtiment. Classez-moi la liste par bâtiment dans l’ordre croissant. »
Sélectionner le nom et le nombre d’étages à partir de la table des bâtiments,
trier par nom de bâtiment dans l’ordre croissant
Sélectionner [SELECT] le nom et le nombre d’étages à partir de [FROM]
la table des bâtiments, trier par [ORDER BY] nom de bâtiment dans l’ordre
croissant [ASC]
SELECT NomBâtiment, NombreEtages
FROM Bâtiments
ORDER BY NomBâtiment ASC
81
82
Les bases de SQL
PARTIE 2
Liste des bâtiments (6 lignes)
NomBâtiment
NombreEtages
Arts et sciences
3
Bâtiment central
3
Bibliothèque
2
Salle des sports
1
Sciences humaines
3
Technologie
2
Base de données Bowling
Énoncé
Traduction
Nettoyage
« Où ont lieu nos tournois ? »
Sélectionner sans doublon les lieux des tournois à partir de la table des tournois
Sélectionner [SELECT] sans doublon [DISTINCT] les lieux des tournois à
partir de [FROM] la table des tournois
SQL
SELECT DISTINCT LieuTournoi
FROM Tournois
Énoncé
« Donnez-moi la liste de toutes les dates de tournois et leur emplacement.
J’ai besoin des dates dans l’ordre décroissant et des lieux dans l’ordre alphabétique. »
Sélectionner la date du tournoi et l’emplacement à partir de la table des tournois et trier par date de tournoi dans l’ordre décroissant et par lieu de tournoi
dans l’ordre croissant
Sélectionner [SELECT] la date du tournoi et l’emplacement à partir de
[FROM] la table des tournois et trier par [ORDER BY] date de tournoi dans
l’ordre décroissant [DESC] et par lieu de tournoi dans l’ordre croissant
[ASC]
Traduction
Nettoyage
SQL
SELECT DateTournoi, LieuTournoi
FROM Tournois
ORDER BY DateTournoi DESC, LieuTournoi ASC
Création d’une requête simple
CHAPITRE 4
Lieux des tournois (11 lignes)
LieuTournoi
Dates des tournois (14 lignes)
DateTournoi
LieuTournoi
Au Paradis du Bowling
04-09-1999
Bowling Acapulco
Bowling Acapulco
28-08-1999
Bowling République
Bowling des Acacias
21-08-1999
Salle Omnisports
Bowling des Rois
14-08-1999
Bowling Impérial
Bowling du Marais
07-08-1999
Salle Lebowsky
Bowling Impérial
31-07-1999
Bowling République
Bowling République
24-07-1999
Les couloirs de la mort
Les couloirs de la mort
17-07-1999
Bowling Acapulco
Salle Lebowsky
10-07-1999
Bowling des Rois
Salle Omnisports
03-07-1999
Au Paradis du Bowling
Salle Polyvalente
26-06-1999
Salle Polyvalente
19-06-1999
Bowling des Acacias
12-06-1999
Salle Omnisports
05-06-1999
Bowling du Marais
Base de données Recettes
Énoncé
Traduction
Nettoyage
« Quels sont les types de recettes et quels sont les noms des recettes pour
chaque type ? Peut-on trier ces informations par type et par nom de
recette ? »
Sélectionner le numéro de type de recette et le nom de la recette à partir de la
table des recettes et trier par numéro de type de recette et par nom de recette
Sélectionner [SELECT] le numéro de type de recette et le nom de la recette
à partir de [FROM] la table des recettes et trier par [ORDER BY] numéro
de type de recette et par nom de recette
SQL
SELECTNumTypeRecette, NomRecette
FROM Recettes
ORDER BY NumTypeRecette, NomRecette
Énoncé
Traduction
« Montrez-moi une liste sans doublon des numéros des types de recettes. »
Sélectionner sans doublon les numéros des types de recettes à partir de la
table des recettes
Sélectionner [SELECT] sans doublon [DISTINCT] les numéros des types
de recettes à partir de [FROM] la table des recettes
Nettoyage
SQL
SELECT DISTINCT NumTypeRecette
FROM Recettes
83
84
Les bases de SQL
PARTIE 2
Recettes par type de recette (15 lignes)
NumTypeRecette
NomRecette
Type de recettes (6 lignes)
NumTypeRecette
1
Fettuccini Alfredo
1
1
Filets de saumon en papillotes
2
1
Pollo Picoso
3
1
Ragoût irlandais
4
1
Roast Beef
5
1
Tourtière
6
1
Vivaneau
2
Asperges
2
Haricots verts à l'ail
3
Yorkshire Pudding
4
Salade d'été
5
Machos Nachos
5
Salsa Buena
6
Coupe Colonel
6
Trifle
Problèmes à résoudre
Vous trouverez ci-dessous quelques problèmes pour vous entraîner à écrire des requêtes SQL.
La solution de ces problèmes est fournie sur le CD-Rom d’accompagnement aux formats
Access 2000 et Access 97. Après avoir dézippé le fichier requetessql.zip fourni dans le répertoire RequetesSQL, vous trouverez les solutions dans le répertoire \Solutions\Access2000
(respectivement \Solutions\Access97). Par exemple, la solution du premier des problèmes
portant sur la base de données Ventes est fournie dans le fichier Ventes.mdb (requête intitulée
Chapitre 4-1). Ne vous inquiétez pas si votre propre solution ne correspond pas exactement à
celle que nous proposons – il y a souvent plusieurs syntaxes possibles pour une même requête
SQL. L’essentiel est que l’ensemble de résultat soit le même.
Base de données Ventes
1. « Montrez-moi toutes les informations sur nos employés. »
2. « Montrez-moi une liste des villes par ordre alphabétique où sont situés nos fournisseurs et
incluez le nom des fournisseurs avec lesquels nous travaillons dans chaque ville. »
Base de données AgenceSpectacles
1. « Donnez-moi le nom, le prénom et le numéro de téléphone de nos agents, et listez-les dans
l’ordre nom/prénom. »
2. « Donnez-moi des informations sur tous nos engagements. »
3. « Listez tous les engagements et leur date de début associée. Triez les lignes par date en
ordre décroissant et par engagement dans l’ordre croissant. »
Création d’une requête simple
CHAPITRE 4
Base de données EmploiDuTemps
1. « Montrez-moi une liste complète de toutes les UV que nous offrons. »
2. « Quels sont les grades des enseignants ? »
3. « Listez le nom, le prénom et le numéro de téléphone de tout notre personnel, et triez-les
par nom et prénom. »
Base de données Bowling
1. « Listez toutes équipes en ordre alphabétique. »
2. « Montrez-moi tous les scores de chacun de nos membres. »
3. « Montrez-moi une liste des joueurs avec leur moyenne et leur handicap, et triez-la par
ordre alphabétique. »
Base de données Recettes
1. « Montrez-moi une liste de tous les ingrédients dont nous disposons. »
2. « Montrez-moi toutes les informations sur nos recettes et triez cette liste par nom de recette
dans l’ordre alphabétique. »
85
11
Sous-requêtes
« Il est impossible de résoudre des problèmes
en utilisant le mécanisme de pensée qui nous a permis de les créer. »
Albert Einstein
Dans ce chapitre :
• Qu'est-ce qu'une sous-requête ?
• Sous-requêtes et expressions
• Sous-requêtes et filtrage
• Utilisation des sous-requêtes
• Exemples de commandes.
Dans les trois chapitres précédents, nous vous avons présenté plusieurs méthodes pour traiter
des données issues de plusieurs tables. Toutes ces techniques visent à mettre en relation des
sous-ensembles d'informations (on utilisera une ou plusieurs colonnes et une ou plusieurs
lignes d'une table entière, une requête intégrée à l’aide d'une clause FROM ou de l'opérateur
UNION). Dans ce chapitre, nous allons vous montrer comment récupérer une colonne unique
à partir d'une table ou d'une requête et comment l'utiliser comme expression de valeur dans
une clause SELECT ou WHERE.
Vous devrez retenir deux choses de ce chapitre :
1. En SQL, un problème peut toujours être résolu de plusieurs manières. Nous vous montrerons donc comment résoudre des problèmes déjà exposés dans les chapitres précédents en
utilisant des méthodes différentes de celles choisies auparavant.
2. Dans une clause FROM, il est possible de créer des filtres complexes ne reposant pas sur les
tables. Ce concept est fondamental étant donné que l'utilisation d'une sous-requête dans
une clause WHERE est le seul moyen d'obtenir un nombre de lignes exact lorsqu'il s'agit de
286
Les tables multiples
PARTIE 3
récupérer les lignes d'une table à partir du contenu filtré d'autres tables liées. Tout ceci vous
sera expliqué en détail par la suite dans ce chapitre.
INFO
Ce chapitre porte sur des concepts complexes et sa bonne compréhension implique que vous ayez lu et
compris les Chapitres 7, 8 et 9, respectivement intitulés « Penser en termes d'ensembles », « Jointures
internes » et « Jointures externes ».
Qu'est-ce qu'une sous-requête ?
En des termes simples, une sous-requête est une expression SELECT intégrée à l'une des
clauses d'une commande SELECT afin de formuler une requête finale. Dans ce chapitre, nous
définirons une sous-requête de manière plus formelle et vous montrerons comment l'utiliser
ailleurs que dans une clause FROM.
La norme SQL définit trois sortes de sous-requêtes :
1. SOUS-REQUÊTE DE LIGNE – il s'agit d'une expression SELECT intégrée qui retourne
plus d'une colonne, mais pas plus d'une ligne.
2. SOUS-REQUÊTE DE TABLE – cette expression SELECT intégrée retourne une ou
plusieurs colonnes et zéro ou plusieurs lignes.
3. SOUS-REQUÊTE SCALAIRE – il s'agit d'une expression SELECT qui ne retourne
qu'une colonne et pas plus d'une ligne.
Sous-requêtes de lignes
Au Chapitre 6, vous avez appris à filtrer les données retournées par une requête, c'est-à-dire à
intégrer un prédicat de comparaison à une clause WHERE qui compare la valeur d'une colonne
à un littéral, une expression ou une autre colonne. Vous vous souvenez sans doute qu'une
requête simple utilisant un prédicat de comparaison unique se présenterait sous cette forme :
SQL
SELECT Clients.NomClient
FROM Clients
WHERE Clients.CA > 500
La norme SQL définit un constructeur de valeur de ligne à inclure dans les prédicats de conditions de recherche des clauses WHERE, HAVING ou ON. Malheureusement, les produits
disponibles à la vente et prenant en charge cette syntaxe sont peu nombreux. Voici un exemple
de clause WHERE comprenant un tel constructeur :
SQL
SELECT Clients.NomClient
FROM Clients
WHERE
(Clients.CA, Clients.CodePostalClient)
> (500, '75000')
Cette clause WHERE retourne les lignes dans lesquelles la combinaison ClientsCA et CodePostalClient est supérieure à la combinaison 500 et 75000. Elle équivaut à la requête suivante :
Sous-requêtes
CHAPITRE 11
SQL
287
SELECT Clients.NomClient
FROM Clients
WHERE (Clients.CA > 500)
OR ((Clients.CA = 500)
AND (Clients.CodePostalClient > '75000'))
Dans ce cas, on pourrait remplacer la deuxième partie de la comparaison par une commande
SELECT qui retournerait une ligne unique de deux colonnes (autrement dit par une sousrequête de ligne). La plupart des bases de données disponibles dans le commerce ne prennent
en charge ni les constructeurs de valeurs de lignes, ni les sous-requêtes de lignes. Nous n'en
dirons donc pas plus à ce sujet dans ce chapitre.
Sous-requêtes de tables
Même si nous ne l'avons pas dit explicitement, nous vous avons déjà montré, dans les trois
chapitres précédents, comment intégrer une expression SELECT permettant de retourner
plusieurs lignes et colonnes à une clause FROM. Nous avons en effet utilisé des sous-requêtes
de tables pour définir un résultat complexe qui est ensuite intégré à la clause FROM d'une autre
requête. Dans ce chapitre, nous allons vous montrer à utiliser une sous-requête de table en tant
que source de la liste des valeurs de comparaison d'un prédicat IN, dont nous vous avons déjà
parlé au Chapitre 6. Nous vous apprendrons également quelques mots-clés nouveaux des
prédicats de comparaison qui s'utilisent uniquement dans les sous-requêtes de tables.
Sous-requêtes scalaires
Dans ce chapitre, nous vous montrerons également comment utiliser une sous-requête scalaire
chaque fois qu'elle peut remplacer une expression de valeur. Ce type de sous-requête permet
de récupérer une seule colonne ou une seule expression calculée dans une table qui n'est pas
nécessairement incluse dans la clause FROM de la requête principale. La valeur unique
retournée par une sous-requête scalaire peut être utilisée dans la liste des colonnes demandées
au sein d'une clause SELECT ou en tant que valeur de comparaison dans une clause WHERE.
Sous-requêtes et expressions
Au Chapitre 5, nous avons vu en détail comment utiliser des expressions pour générer des
colonnes calculées devant être retournées par une requête. Nous avons toutefois omis de vous
signaler qu'un type particulier de commande SELECT peut être utilisé pour récupérer les
données d'une autre table, même lorsqu’elle ne figure pas dans votre clause FROM : il s'agit
des sous-requêtes.
Syntaxe
Revenons aux connaissances de base que vous avez déjà assimilées et examinons la commande
SELECT simple présentée sur la Figure 11.1.
Les choses sont loin d'être aussi simples qu'elles ne le paraissent. En fait, l'expression d'une
valeur peut se révéler complexe. La Figure 11.2 montre toutes les options pouvant constituer
l'expression de valeur.
288
Les tables multiples
PARTIE 3
Figure 11-1
Commande SELECT simple.
Figure 11-2
Diagramme d’une expression de valeur.
Au Chapitre 5, nous vous avons montré à créer des expressions de valeurs simples en utilisant
des valeurs littérales, des références de colonnes et des fonctions. Notez que l'expression
SELECT vient s'ajouter à cette liste. Par conséquent, il est possible d'intégrer une sousrequête scalaire à la liste des expressions qui suivent immédiatement le mot-clé SELECT.
Comme nous l'avons mentionné auparavant, les requêtes scalaires sont des expressions
SELECT qui retournent une seule colonne et pas plus d'une ligne. Tout ceci est logique dans
la mesure où cette sous-requête peut remplacer les noms de colonnes uniques ou les expressions retournant une colonne unique.
Vous vous demandez peut-être quelle est l'utilité de cette possibilité. Lorsqu'elle est utilisée
ainsi, une sous-requête permet de recueillir une valeur unique provenant d'une autre table ou
requête ; cette valeur sera incluse dans les résultats retournés par votre requête. La table ou
requête source de cette valeur est nommée dans la clause FROM de la sous-requête et il n'est
Sous-requêtes
CHAPITRE 11
289
pas nécessaire de la référencer dans la clause FROM de la requête externe. En général, il faut
ajouter des critères à la clause WHERE de la sous-requête pour qu’elle ne retourne pas plus
d'une ligne. Ces critères peuvent même référencer une valeur retournée par la requête externe
afin d'obtenir des données relatives à la ligne considérée.
Examinons quelques exemples simples tirés de la base de données Ventes et utilisant les tables
Clients et Commandes. La Figure 11.3 montre la relation établie entre ces deux tables.
Figure 11-3
Tables Clients et Commandes.
Nous allons maintenant construire une requête permettant de lister les commandes passées à
une date précise et qui récupère le nom de client associé dans la table Clients en utilisant une
sous-requête.
INFO
Dans ce chapitre, nous utiliserons à nouveau la technique « Requête/Traduction/Nettoyage/SQL »
présentée au Chapitre 4. En outre, nous mettrons les sous-requêtes entre parenthèses dans l'étape
« Nettoyage » et présenterons les sous-requêtes en retrait dès que possible pour vous aider à comprendre leur utilisation.
Énoncé
Traduction
Nettoyage
SQL
« Montrez-moi toutes les commandes envoyées le 24 décembre 1999 et le nom
de client qui leur est associé. »
Sélectionner le numéro de commande, la date de commande, la date d'expédition et sélectionner également le nom de client associé à partir de la table
des clients, à partir de la table des commandes où la date d'envoi est le 24
décembre 1999
Sélectionner [SELECT] le numéro de commande, la date de commande, la
date d'expédition et (sélectionner [SELECT] également le nom de client
associé à partir de [FROM] la table des clients), à partir de [FROM] la table
des commandes où [WHERE] la date d'envoi est le = 24 décembre 1999
SELECT Commandes.NumCommande, Commandes.DateCommande,
Commandes.DateExpédition,
(SELECT Clients.NomClient
FROM Clients
WHERE Clients.NumClient =
Commandes.NumClient)
290
Les tables multiples
PARTIE 3
FROM Commandes
WHERE Commandes.DateExpédition = '24-12-1999'
Notez que nous avons dû limiter la valeur du numéro de client figurant dans la sous-requête à
celle du numéro de client dans chaque ligne récupérée dans la table Commandes, sans quoi la
requête retournerait toutes les lignes de la table Clients. Souvenez-vous qu'il doit s'agir d'une
sous-requête scalaire ; le résultat retourné ne devra donc pas contenir plus d’une ligne.
Ceux d'entre vous que le concept de jointure interne présenté au Chapitre 8 a interpellé se
demandent probablement pourquoi on souhaite résoudre ce problème en procédant ainsi au
lieu de faire une jointure entre Commandes et Clients dans la clause FROM de la requête
externe. En fait, il s'agit simplement de donner un exemple simple et ce problème pourrait
également être résolu à l'aide de la requête suivante, qui comprend une jointure interne.
SQL
SELECT Commandes.NumCommande, Commandes.DateCommande,
Commandes.DateExpédition, Clients.NomClient
FROM Clients INNER JOIN Commandes
ON Clients.NumClient = Commandes.NumCommande
WHERE Commandes.DateExpédition = '24-12-1999'
Introduction aux fonctions d'agrégation : les opérateurs COUNT et MAX
Maintenant que vous savez pourquoi on utilise une sous-requête pour générer une colonne de
résultats, nous allons voir dans quelle mesure cette fonctionnalité peut être utile. Commençons par en apprendre davantage sur deux fonctions d’agrégation (nous verrons l'intégralité de
ces fonctions dans le chapitre suivant).
La norme SQL définit plusieurs fonctions qui permettent de calculer des valeurs dans une
requête. Parmi ces fonctions, les fonctions d’agrégation permettent de calculer une valeur
unique pour un groupe de lignes d'un ensemble de résultats. Par exemple, une fonction d’agrégation peut être utilisée pour compter des lignes, trouver la valeur la plus élevée ou la plus
faible dans un ensemble de lignes ou encore faire la moyenne ou le total de valeurs ou expressions de l'ensemble de résultats.
La Figure 11.4 montre le diagramme des fonctions COUNT et MAX qui permettent de
générer une colonne de résultats dans une clause SELECT.
Figure 11-4
Utilisation des fonctions d’agrégation
COUNT et MAX.
COUNT peut être utilisé pour déterminer le nombre de lignes ou de valeurs autres que Null
dans un ensemble de résultats. Utilisez COUNT(*) pour savoir combien de lignes constituent
Sous-requêtes
CHAPITRE 11
291
l'ensemble. Si vous précisez une colonne particulière dans l'ensemble de résultats en utilisant
COUNT(nom_colonne), le système de base de données compte le nombre de lignes dont la
valeur n'est pas Null dans cette colonne. COUNT permet également de ne compter que les
valeurs uniques lorsqu'on l'associe au mot-clé DISTINCT.
De même, la valeur la plus élevée d'une colonne peut être identifiée à l'aide de MAX. Si l'expression d'une valeur est numérique, vous obtiendrez la valeur numérique la plus élevée de la
colonne ou de l'expression spécifiée. Si une expression de valeur retourne une donnée alphabétique, la valeur la plus élevée dépend de la séquence de classement du système de base de
données.
Nous allons maintenant intégrer ces fonctions à une sous-requête afin de résoudre deux
problèmes intéressants.
Énoncé
Traduction
Nettoyage
SQL
« Listez tous les noms de clients et le nombre de commandes qu'ils ont
passées. »
Sélectionner le prénom du client et le nom du client, sélectionner également
le nombre de commandes dans la table des commandes pour ce client dans la
table des clients
Sélectionner [SELECT] le prénom du client et le nom du client, (sélectionner [SELECT] également le nombre de commandes [COUNT(*)] à
partir de [FROM] la table des commandes pour ce où [WHERE] numéro
client = numéro client) à partir de [FROM] la table des clients
SELECT Clients.PrénomClient,
Clients.NomClient,
(SELECT COUNT(*)
FROM Commandes
WHERE Commandes.NumClient =
Clients.NumClient)
AS NombreDeCommandes
FROM Clients
Dans les trois chapitres suivants, vous en apprendrez plus sur les multiples utilisations des
fonctions d'agrégat. Cependant, si vous souhaitez tout simplement compter des lignes mises
en relation, les sous-requêtes représentent une solution optimale. Examinons maintenant un
autre problème qui peut être résolu à l'aide de la fonction d’agrégation MAX :
Énoncé
Traduction
Nettoyage
« Montrez-moi la liste des clients et leurs dates de commande les plus
récentes. »
Sélectionner le prénom du client, le nom du client, sélectionner également la
date de commande la plus élevée à partir de la table des commandes pour ce
client dans la table des clients
Sélectionner [SELECT] le prénom du client, le nom du client, (sélectionner
[SELECT MAX] également la (date de commande) la plus élevée à partir de
[FROM] la table des commandes où [WHERE] numéro de client = numéro
de client) pour ce client à partir de [FROM] la table des clients
292
Les tables multiples
PARTIE 3
SQL
SELECT Clients.PrénomClient,
Clients.NomClient,
(SELECT MAX(DateCommande)
FROM Commandes
WHERE Commandes.NumClient =
Clients.NumClient)
AS DernièreDateCommande
FROM Clients
Comme vous pouvez l'imaginer, cette utilisation de MAX fonctionne bien lorsqu'il s'agit de
trouver la valeur la plus élevée ou la « dernière » valeur d'une table. Nous vous montrerons les
autres utilisations possibles de ces fonctions par la suite dans ce chapitre, dans la section intitulée « Exemples de commandes ».
Sous-requêtes et filtrage
Au Chapitre 6, vous avez appris à filtrer les informations retournées en ajoutant une clause
WHERE à vos requêtes. Vous avez également vu comment utiliser les comparaisons simples et
complexes pour que seules les lignes désirées figurent dans l'ensemble de résultats. Nous allons
maintenant élargir vos connaissances et vous montrer comment utiliser une sous-requête en
tant qu'argument de comparaison afin d'améliorer le filtrage.
Syntaxe
Revenons à la commande SELECT précédente et examinons la syntaxe de construction d'une
requête comprenant un prédicat de comparaison simple intégré à une clause WHERE. La
Figure 11.5 représente un diagramme simplifié de cette requête.
Comme nous l'avons vu sur la Figure 11.2, une expression de valeur peut se présenter sous la
forme d'une sous-requête. L'exemple de la Figure 11.5 permet de comparer une expression de
valeur à une colonne unique. Cette figure montre que l'expression de valeur doit être une
valeur unique, autrement dit une sous-requête scalaire qui retourne exactement une colonne et
pas plus d'une ligne. Résolvons un problème simple nécessitant une comparaison à une valeur
retournée par une sous-requête. Dans cet exemple, nous allons rechercher tous les détails des
commandes des clients, mais nous ne souhaiterons voir que la dernière commande de chacun
de ces clients. La Figure 11.6 montre les tables nécessaires à la résolution de cette requête.
Énoncé
Traduction
Nettoyage
« Listez les clients et tous les détails de leur dernière commande. »
Sélectionner le prénom du client, le nom du client, le numéro de commande,
la date de commande, le numéro de produit, le nom du produit et la quantité
commandée à partir de la table des clients en faisant une jointure avec la table
des commandes sur le numéro de client, puis en faisant une jointure avec la
table du détail des commandes sur le numéro de commande, et enfin en
faisant une jointure avec la table des produits sur le numéro de produit où la
date de commande est égale à la date de commande maximale à partir de la
table des commandes pour ce client
Sélectionner [SELECT] le prénom du client, le nom du client, le numéro de
commande, la date de commande, le numéro de produit, le nom du produit
Sous-requêtes
CHAPITRE 11
Figure 11-5
Filtrage d’un résultat en utilisant
un simple prédicat de comparaison.
Figure 11-6
Tables nécessaires pour lister toutes
les informations d’une commande.
293
294
Les tables multiples
PARTIE 3
et la quantité commandée à partir de [FROM] la table des clients en faisant
une jointure [INNER JOIN] avec la table des commandes sur [ON] le
numéro de client, puis en faisant une jointure [INNER JOIN] avec la table
du détail des commandes sur [ON] le numéro de commande, et enfin en
faisant une jointure [INNER JOIN] avec la table des produits sur [ON] le
numéro de produit où [WHERE] la date de commande est égale à la =
(Sélectionner [SELECT MAX] (date de commande) maximale à partir de
[FROM] la table des commandes pour ce client où [WHERE]
commandes.numéro de commande = clients.numéro de client)
SQL
SELECT Clients.PrénomClient,
Clients.NomClient, Commandes.NumCommande,
Commandes.DateCommande,
DétailCommandes.NumProduit,
Produits.NomProduit,
DétailCommandes.QuantitéCommandée
FROM ((Clients
INNER JOIN Commandes
ON Clients.NumClient = Commandes.NumClient)
INNER JOIN DétailCommandes
ON Commandes.OrderID = DétailCommandes.OrderID)
INNER JOIN Produits
ON Produits.NumProduit =
DétailCommandes.NumProduit
WHERE Commandes.DateCommande =
(SELECT MAX(DateCommande)
FROM Commandes AS O2
WHERE O2.NumClient = Clients.NumClient)
Avez-vous remarqué que nous avons défini un alias pour la seconde référence à la table
Commandes (autrement dit pour la table Commandes de la sous-requête) ? Même si l'on omet
cet alias, la plupart des systèmes de bases de données sauront que vous parlez de la copie de
la table Commandes citée dans la sous-requête. En fait, dans la norme SQL, toute référence
non définie doit tout d'abord être traitée à partir de la requête la plus interne. Nous avons
malgré tout inclus l'alias afin d'indiquer clairement que la copie de la table Commandes référencée dans la clause WHERE de la sous-requête est celle qui se trouve dans la clause FROM
de la sous-requête. Si vous procédez ainsi, vos requêtes seront beaucoup plus claires et faciles
à comprendre.
Sous-requêtes
CHAPITRE 11
295
Mots-clés de prédicats spécifiques aux sous-requêtes
La norme SQL définit un certain nombre de mots-clés de prédicats utilisables dans une clause
WHERE en même temps qu'une sous-requête.
Appartenance définie : l'opérateur IN
Au Chapitre 6, vous avez appris à utiliser le mot-clé IN dans une clause WHERE afin de
comparer une colonne ou une expression à une liste de valeurs. Vous savez maintenant que
chaque expression d'une valeur de la liste IN peut être une sous-requête scalaire. Comme le
montre la Figure 11.7, il est également possible d'utiliser une sous-requête pour générer la
liste dans son intégralité.
Figure 11-7
Utilisation d’une sous-requête
avec un prédicat IN.
Dans ce cas, on peut utiliser une sous-requête de table qui retourne une colonne et toutes les
lignes nécessaires pour construire la liste. Utilisons maintenant la base de données Recettes à
titre d'exemple. La Figure 11.8 montre les tables qui nous intéressent.
Supposons que vous ayez invité à dîner un ami qui adore les poissons. Vous savez que vous
disposez d'un certain nombre de recettes contenant des poissons mais vous ne connaissez pas
tous les noms d'ingrédients de votre base de données. Vous savez que les poissons correspondent à une DescriptionTypeIngrédient, vous pouvez donc faire une jointure de toutes les tables
et filtrer les données obtenues sur DescriptionTypeIngrédient ; vous pouvez également utiliser
les sous-requêtes et le prédicat IN :
296
Les tables multiples
PARTIE 3
Figure 11-8
Tables pour lister les recettes et leurs ingrédients.
Énoncé
« Listez toutes mes recettes contenant des poissons. »
Traduction
Sélectionner le nom de recette à partir de la table des recettes où le numéro de
recette est dans la sélection de numéros de recettes de la table des ingrédients
de recettes où le numéro d'ingrédient est dans la sélection des numéros
d'ingrédients de la table des ingrédients en faisant une jointure avec la table
des types d'ingrédients sur le numéro de type d'ingrédient où la description du
type d'ingrédient est « Poisson»
Nettoyage
Sélectionner [SELECT] le nom de recette à partir de [FROM] la table des
recettes où [WHERE] le numéro de recette est dans [IN] la (sélection
[SELECT] de numéros de recettes à partir de [FROM] la table des ingrédients de recettes où [WHERE] le numéro d'ingrédient est dans [IN] la
(sélection [SELECT] des numéros d'ingrédients à partir de [FROM] la
table des ingrédients en faisant une jointure [INNER JOIN] avec la table des
types d'ingrédients sur [ON] le numéro de type d'ingrédient où [WHERE]
la description du type d'ingrédient est = 'Poisson'))
SQL
SELECT NomRecette
FROM Recettes
WHERE Recettes.NumRecette IN
(SELECT NumRecette
FROM IngrédientsRecette
WHERE IngrédientsRecette.NumIngrédient IN
(SELECT NumIngrédient
FROM Ingrédients
INNER JOIN TypesIngrédients
ON Ingrédients.NumTypeIngrédient =
TypesIngrédients.NumTypeIngrédient
Sous-requêtes
CHAPITRE 11
297
WHERE
TypesIngrédients.DescriptionTypeIngrédient
= 'Poisson'))
Avez-vous remarqué qu'il était possible d'inclure une sous-requête à l'intérieur d'une autre
sous-requête ? Nous aurions même pu aller plus loin en éliminant la jointure interne figurant
dans la deuxième sous-requête et en utilisant la syntaxe suivante :
SQL
(SELECT NumIngrédient
FROM Ingrédients
WHERE Ingrédients.NumTypeIngrédient IN
(SELECT NumTypeIngrédient
FROM TypesIngrédients
WHERE
TypesIngrédients.DescriptionTypeIngrédient
= 'Poisson'))
Utiliser cette requête reviendrait cependant à faire preuve d'un zèle excessif étant donné que
l'intégration de clauses IN dans d'autres clauses IN la rend plus difficile à lire. Mieux vaut
donc utiliser un seul prédicat IN et une jointure plus complexe dans la sous-requête. La
requête ci-dessus pourrait également être formulée ainsi :
SQL
SELECT NomRecette
FROM Recettes
WHERE Recettes.NumRecette IN
(SELECT NumRecette
FROM (IngrédientsRecette
INNER JOIN Ingrédients
ON IngrédientsRecette.NumIngrédient =
Ingrédients.NumIngrédient)
INNER JOIN TypesIngrédients
ON Ingrédients.NumTypeIngrédient =
TypesIngrédients.NumTypeIngrédient
WHERE
TypesIngrédients.DescriptionTypeIngrédient
= 'Poisson')
Vous vous demandez peut-être pourquoi nous nous donnons tant de mal. Ne pourrait-on pas
en effet se contenter de faire la jointure dans la requête externe ? La réponse est non étant
donné que la requête retournerait effectivement toutes les lignes de la table Recettes pour les
recettes de poissons, mais que l'on obtiendrait également des doublons. Pour savoir pourquoi
des doublons sont retournés, essayons maintenant de résoudre ce problème sans inclure de
sous-requête.
SQL
SELECT NomRecette
FROM ((Recettes
298
Les tables multiples
PARTIE 3
INNER JOIN IngrédientsRecette
ON Recettes.NumRecette =
IngrédientsRecette.NumRecette)
INNER JOIN Ingrédients
ON IngrédientsRecette.NumIngrédient =
Ingrédients.NumIngrédient)
INNER JOIN TypesIngrédients
ON Ingrédients.NumTypeIngrédient =
TypesIngrédients.NumTypeIngrédient
WHERE
TypesIngrédients.DescriptionTypeIngrédient
= 'Poisson')
Revenons à la Figure 11.8 : comme vous pouvez le constater, dans la table IngrédientsRecette,
plusieurs lignes peuvent correspondre à une ligne de la table Recettes. L'ensemble de résultats
défini par la clause FROM contiendra au moins autant de lignes que la table IngrédientsRecette et la valeur de la colonne NomRecette apparaîtra plusieurs fois. Même si l'on ajoute
le filtre pour limiter le résultat aux ingrédients du type Poisson, on obtiendra toujours plus
d'une ligne par recette pour toutes les recettes comprenant plusieurs sortes de poissons.
L'utilisation des sous-requêtes est également à préconiser lorsqu'il s'agit de lister des informations ne se limitant pas au titre de la recette. Supposons par exemple que vous souhaitiez lister
tous les ingrédients des recettes contenant des poissons. Si l'on utilise une jointure complexe
dans la requête externe et si l'on filtre le type d'ingrédients de la manière indiquée ci-dessus,
nous n'obtiendrons que les ingrédients qui sont des poissons et tous les autres ingrédients
seront exclus. Intéressons-nous maintenant à une requête légèrement plus complexe :
Énoncé
« Listez toutes mes recettes et tous les ingrédients des recettes contenant des
poissons. »
Traduction
Sélectionner le nom de recette et le nom d'ingrédient à partir de la table des
recettes en faisant une jointure avec la table IngrédientsRecette sur le numéro
de recette, puis en faisant une jointure avec la table des ingrédients sur le
numéro d'ingrédient où le numéro de recette est dans la sélection des numéros
de recettes dans la table IngrédientsRecette en faisant une jointure avec la
table des ingrédients sur le numéro d'ingrédient, puis en faisant une jointure
avec la table TypesIngrédients sur le numéro de type d'ingrédient où la
description du type d'ingrédient est « Poisson»
Nettoyage
Sélectionner [SELECT] le nom de recette et le nom d'ingrédient à partir de
[FROM] la table des recettes en faisant une jointure [INNER JOIN] avec la
table IngrédientsRecette sur [ON] le numéro de recette, puis en faisant une
jointure [INNER JOIN] avec la table des ingrédients sur [ON] le numéro
d'ingrédient où [WHERE] le numéro de recette est dans [IN] la (sélection
[SELECT] des numéros de recettes à partir de [FROM] la table IngrédientsRecette en faisant une jointure [INNER JOIN] avec la table des ingrédients sur [ON] le numéro d'ingrédient, puis en faisant une jointure [INNER
JOIN] avec la table TypesIngrédients sur [ON] le numéro de type d'ingrédient où [WHERE] la description du type d'ingrédient est = 'Poisson')
Sous-requêtes
CHAPITRE 11
SQL
299
SELECT Recettes.NomRecette,
Ingrédients.NomIngrédient
FROM (Recettes
INNER JOIN IngrédientsRecette
ON Recettes.NumRecette =
IngrédientsRecette.NumRecette)
INNER JOIN Ingrédients
ON Ingrédients.NumIngrédient =
IngrédientsRecette.NumIngrédient
WHERE Recettes.NumRecette IN
(SELECT NumRecette
FROM (IngrédientsRecette
INNER JOIN Ingrédients
ON IngrédientsRecette.NumIngrédient =
Ingrédients.NumIngrédient)
INNER JOIN TypesIngrédients
ON Ingrédients.NumTypeIngrédient =
TypesIngrédients.NumTypeIngrédient
WHERE
TypesIngrédients.DescriptionTypeIngrédient
= 'Poisson')
En l’occurrence, la jointure externe complexe qui se trouve dans la partie principale de la
requête retournera tous les ingrédients des recettes sélectionnées ; la sous-requête complexe
retournera la liste des numéros de recettes pour les recettes contenant des poissons. En fait,
nous avons tout simplement réalisé deux jointures complexes.
Prédicats quantifiés : ALL/SOME/ANY
Comme nous venons de le voir, le prédicat IN permet de comparer une colonne ou une expression à une liste afin de savoir si cette colonne ou expression se trouve dans cette liste. En
d'autres termes, la colonne ou expression est un membre de la liste. Pour savoir si cette
colonne ou expression est supérieure ou inférieure à un ou plusieurs des éléments de la liste,
on peut utiliser un prédicat quantifié. La Figure 11.9 montre la syntaxe à utiliser.
En l’occurrence, l'expression SELECT doit être une sous-requête de table qui retournera une
colonne unique et zéro ligne ou plus. Les valeurs des lignes forment une liste lorsque la sousrequête retourne plus d'une ligne. Comme vous pouvez le voir, ce prédicat combine un opérateur de comparaison à un mot-clé indiquant au système de base de données comment appliquer l'opérateur aux membres de la liste. Lorsqu'on utilise le mot-clé ALL, la comparaison
doit avoir la valeur Vrai pour toutes les valeurs retournées par la sous-requête ; lorsqu'on
utilise les mots-clés SOME ou ANY, il suffit que la comparaison ait la valeur Vrai pour une
des valeurs de la liste.
En examinant la situation de plus près, on constate que lorsqu'une sous-requête retourne
plusieurs lignes, = ALL aura toujours la valeur Faux, sauf si toutes les valeurs retournées par
la sous-requête sont identiques et si l'expression d'une valeur se trouvant à gauche de la
comparaison est égale à toutes ces valeurs. Si l'on suit la même logique, on pourrait penser que
300
Les tables multiples
PARTIE 3
Commande SELECT
Expression de valeur
SELECT
alias
,
FROM
WHERE
AS
Référence de table
,
Expression de valeur
=
<>
<
>
<=
>=
ALL
SOME
ANY
(Expression SELECT)
Figure 11-9
Utilisation d’un prédicat quantifié
dans une commande SELECT.
< > ANY aura toujours la valeur Faux si l'expression d'une valeur se trouvant à gauche de la
comparaison est égale à l'une des valeurs de la liste. En fait, la norme SQL considère SOME
et ANY de la même manière. Par conséquent, si l'on utilise < > SOME ou < > ANY, le prédicat
aura la valeur Vrai si l'expression de valeur figurant à gauche de la comparaison n'est égale à
aucune des valeurs de la liste. En outre, si la sous-requête ne retourne aucune ligne, tous les
prédicats de comparaison comportant le mot-clé ALL auront la valeur Vrai et tous les prédicats de comparaison comportant le mot-clé SOME ou ANY auront la valeur Faux.
INFO
Comme nous l'avons mentionné à propos des sous-requêtes de lignes, la norme SQL définit le concept
de constructeur de valeur de ligne. Si votre base de données prend en charge ces constructeurs, l'expression SELECT incluse dans un prédicat quantifié pourra retourner plus d'une colonne. Dans ce cas,
l'élément se trouvant à gauche de la comparaison devra être une liste d'expressions de valeurs séparées
par des virgules et mises entre parenthèses. En outre, l'expression SELECT doit retourner un nombre de
colonnes égal au nombre d'expressions de valeurs situées à gauche. On peut par exemple construire une
commande SQL se présentant sous cette forme :
SELECT *
FROM MaTable
WHERE (MaTable.Colonne1, MaTable.Colonne2) > ALL
(SELECT ColonneA, ColonneB FROM OtherTable)
Pour savoir si votre base de données prend en charge cette syntaxe, reportez-vous à la documentation
fournie avec le système.
Sous-requêtes
CHAPITRE 11
301
Nous allons maintenant mettre en œuvre les prédicats quantifiés en construisant deux requêtes.
Nous utiliserons tout d'abord la base de données Recettes. Reportez-vous à la Figure 11.8 pour
savoir quelles tables vont être utilisées.
Énoncé
Traduction
Nettoyage
SQL
« Montrez-moi les recettes contenant du bœuf ou de l'ail. »
Sélectionner le nom de recette à partir de la table des recettes où le numéro de
recette est dans la sélection des numéros de recettes à partir de la table des
ingrédients de recettes où le numéro d'ingrédient est égal à l'un de ceux de la
sélection de numéros d'ingrédients à partir de la table des ingrédients où le
nom d'ingrédient est « bœuf » ou « ail »
Sélectionner [SELECT] le nom de recette à partir de [FROM] la table des
recettes où [WHERE] le numéro de recette est dans [IN] la (sélection
[SELECT] des numéros de recettes à partir de [FROM] la table des ingrédients de recettes où [WHERE] le numéro d'ingrédient est égal à = l'un
[ANY] de ceux de la (sélection [SELECT] de numéros d'ingrédients à partir
de [FROM] la table des ingrédients où [WHERE] le nom d'ingrédient est
dans [IN] 'bœuf' ou ' ail'))
SELECT Recettes.NomRecette
FROM Recettes
WHERE Recettes.NumRecette IN
(SELECT IngrédientsRecette.NumRecette
FROM IngrédientsRecette
WHERE IngrédientsRecette.NumIngrédient = ANY
(SELECT Ingrédients.NumIngrédient
FROM Ingrédients
WHERE Ingrédients.NomIngrédient
IN ('Bœuf', 'Ail')))
Si vous pensez qu'il aurait été possible d'utiliser IN au lieu de = ANY, sachez que vous avez
raison. Nous aurions également pu faire une jointure entre IngrédientsRecette et Ingrédients
dans la première sous-requête afin de retourner la liste de numéros de recettes requise.
Comme nous l'avons souligné en début de chapitre, il existe presque toujours plusieurs
manières de résoudre un problème en SQL. Parfois, l'utilisation d'un prédicat quantifié vous
permettra d'améliorer la clarté de vos requêtes.
Résolvons maintenant un problème un peu plus complexe pour que vous puissiez réaliser
combien les prédicats quantifiés peuvent être utiles. Cet exemple utilise la base de données
Ventes et les tables concernées sont répertoriées dans la Figure 11.10.
Énoncé
Traduction
« Trouvez tous les accessoires dont le prix est supérieur à celui de n'importe
quel vêtement. »
Sélectionner le nom de produit et le prix public à partir de la table des
produits en faisant une jointure avec la table des catégories sur le numéro de
catégorie où la description de la catégorie est « accessoires » et le prix public
est supérieur à toute la sélection des prix publics à partir de la table des
produits en faisant une jointure avec la table des catégories sur le numéro de
catégorie où le nom de catégorie est « vêtements »
302
Les tables multiples
PARTIE 3
Figure 11-10
Catégories et les produits qui s’y rapportent.
Nettoyage
Sélectionner [SELECT] le nom de produit et le prix public à partir de
[FROM] la table des produits en faisant une jointure [INNER JOIN] avec la
table des catégories sur [ON] le numéro de catégorie où [WHERE] la
description de la catégorie est = 'Accessoires' et [AND] le prix public est
supérieur à > toute [ALL] la (sélection [SELECT] des prix publics à partir
de [FROM] la table des produits en faisant une jointure [INNER JOIN]
avec la table des catégories sur [ON] le numéro de catégorie où le nom de
catégorie est = 'Vêtements')
SQL
SELECT Produits.NomProduit,
Produits.PrixPublic
FROM Produits
INNER JOIN Catégories
ON Produits.NumCatégorie = Catégories.NumCatégorie
WHERE Catégories.DescriptionCatégorie =
'Accessoires'
AND Produits.PrixPublic > ALL
(SELECT Produits.PrixPublic
FROM Produits
INNER JOIN Catégories
ON Produits.NumCatégorie =
Catégories.NumCatégorie
WHERE Catégories.DescriptionCatégorie =
'Vêtements')
Que se passe-t-il ici ? La sous-requête récupère tous les prix des vêtements. La requête
externe liste alors tous les accessoires dont le prix est supérieur à tous les prix retournés par la
sous-requête.
Prédicat d'existence : EXISTS
Les prédicats d'appartenance définie (IN) et quantifiée (SOME/ANY/ALL) effectuent une
comparaison avec l'expression de valeur (il s’agit généralement d'une colonne de la source
définie dans la clause FROM de la requête externe). Le prédicat EXISTS permet de savoir si
une ligne correspondante existe (EXISTS) dans l'ensemble de résultats retourné par une sous-
Sous-requêtes
CHAPITRE 11
303
requête. Au Chapitre 8, nous vous avons montré comment résoudre les problèmes avec l’opérateur AND en utilisant des jointures internes complexes. Ces problèmes peuvent également être
résolus à l'aide de EXISTS. Nous allons réexaminer l'un des problèmes déjà vus au Chapitre 8.
Énoncé
« Trouvez tous les clients qui ont commandé un VTT et également un
casque. »
Traduction
Sélectionner le numéro de client, le prénom du client et le nom du client à
partir de la table des clients où il existe une ligne dans la table des commandes
en faisant une jointure avec la table du détail des commandes sur le numéro de
commande, puis en faisant une jointure avec la table des produits sur le
numéro de produit où le nom de produit contient « VTT » et le numéro de
client la table des commandes est égal au numéro de client dans la table des
clients ; il existe également une ligne à partir de la table des commandes en
jointure avec la table des détails des commandes sur le numéro de commande,
puis en jointure avec la table des produits sur le numéro de produit où le nom
de produit contient « casque » et le numéro de client dans la table des
commandes est égal au numéro de commande dans la table des clients
Nettoyage
Sélectionner [SELECT] le numéro de client, le prénom du client et le nom
du client à partir de [FROM] la table des clients où [WHERE] il existe
[EXISTS] une ligne (Sélectionner [SELECT] * à partir de [FROM] la table
des commandes en faisant une jointure [INNER JOIN] avec la table du
détail des commandes sur [ON] le numéro de commande, puis en faisant
une jointure [INNER JOIN] avec la table des produits sur [ON] le numéro
de produit où [WHERE] le nom de produit contient [LIKE] 'VTT' et
[AND] le numéro de client dans la table des commandes est égal au =
numéro de client dans la table des clients); il existe [EXISTS] également une
ligne (Sélectionner [SELECT] * à partir de [FROM] la table des
commandes en jointure [INNER JOIN] avec la table des détails des
commandes sur [ON] le numéro de commande, puis en jointure [INNER
JOIN] avec la table des produits sur [ON] le numéro de produit où
[WHERE] le nom de produit contient [LIKE] 'casque' et [AND] le
numéro de client dans la table des commandes est égal au = numéro de
commande dans la table des clients)
SQL
SELECT Clients.NumClient,
Clients.PrénomClient,
Clients.NomClient
FROM Clients
WHERE EXISTS
(SELECT *
FROM (Commandes
INNER JOIN DétailCommandes
ON Commandes.NumCommande =
DétailCommandes.NumCommande)
INNER JOIN Produits
ON Produits.NumProduit =
304
Les tables multiples
PARTIE 3
DétailCommandes.NumProduit
WHERE Produits.NomProduit Like '%VTT%'
AND Commandes.NumClient =
Clients.NumClient)
AND EXISTS
(SELECT *
FROM (Commandes
INNER JOIN DétailCommandes
ON Commandes.NumCommande =
DétailCommandes.NumCommande)
INNER JOIN Produits
ON Produits.NumProduit =
DétailCommandes.NumProduit
WHERE Produits.NomProduit Like '%Casque%'
AND Commandes.NumClient =
Clients.NumClient)
Notez qu'il est possible d'utiliser n'importe quelle colonne de n'importe quelle table dans la
clause FROM en tant que colonne à retourner par la clause SELECT de la sous-requête. Nous
avons utilisé « * » pour symboliser toutes les colonnes. En d'autres termes, cette requête pourrait être formulée ainsi : « Donnez-moi les clients pour lesquels il existe une ligne
« bicyclette » dans le détail des commandes et pour lesquels il existe également une ligne
« casque » dans le détail des commandes. » Étant donné que nous n'avons pas établi de correspondance sur le numéro de commande, peu importe que la bicyclette et le casque aient fait
l'objet de deux commandes différentes.
INFO
Étant donné l'intérêt de la requête ci-dessus, nous avons enregistré cette solution sous le nom « EXISTS
commandes de VTT et de casques » dans la base de données fournie à titre d'exemple. La solution utilisant une jointure interne est enregistrée sous le nom « Union commandes de VTT et de casques. »
Utilisation des sous-requêtes
En principe, vous en savez maintenant beaucoup sur l'utilisation des sous-requêtes pour
générer une colonne de résultats ou pour effectuer une comparaison complexe au sein d'une
clause WHERE. Pour vous donner une idée de toutes les utilisations que l'on peut faire des
sous-requêtes, nous allons vous soumettre quelques problèmes pouvant être résolus à l'aide de
celles-ci.
Expressions d'une colonne
Comme nous l'avons déjà mentionné dans ce chapitre, l'utilisation d'une sous-requête pour
récupérer une valeur unique dans une table est des plus efficaces lorsqu'on inclut une jointure.
Cependant, en ce qui concerne les fonctions d'agrégat, les sous-requêtes permettent également
d'obtenir le résultat d'un calcul de fonction. Nous nous intéresserons à cet aspect des fonctions
d’agrégation dans le prochain chapitre. En attendant, voici quelques problèmes pouvant être
résolus grâce aux sous-requêtes :
Sous-requêtes
CHAPITRE 11
305
«Listez nos fournisseurs et le nombre de produits qu'ils nous ont vendus. »
« Affichez les produits et leurs dernières dates de commande. »
« Montrez-moi les groupes et le nombre d'engagements de chaque groupe. »
« Affichez chaque client et la date de la dernière réservation effectuée. »
« Listez tous les membres du personnel et le nombre de cours dispensés par chacun. »
« Affichez toutes les matières et le nombre de cours pour chaque matière ayant lieu le lundi. »
« Montrez-moi tous les joueurs et le nombre de jeux joués par chacun. »
« Affichez les joueurs et le meilleur score qu'ils aient jamais eu. »
« Listez toutes les viandes et le nombre de recettes dans lesquelles chacune d'entre elles
apparaît. »
« Montrez-moi les types de recettes et le nombre de recettes pour chacun de ces types. »
Filtres
Votre connaissance des sous-requêtes va vous permettre de résoudre plus efficacement des
requêtes complexes. Nous avons vu dans ce chapitre plusieurs manières d'utiliser les sousrequêtes en tant que filtres dans des clauses WHERE. Au Chapitre 14, nous vous montrerons
comment utiliser des sous-requêtes pour filtrer des groupes d'informations dans les clauses
HAVING.
Voici certains problèmes qui peuvent être résolus à l'aide de sous-requêtes utilisées pour filtrer
des lignes dans des clauses WHERE. Notez que nous avons déjà résolu plusieurs de ces
problèmes dans les chapitres précédents en utilisant d'autres méthodes.
INFO
Pour vous guider, nous avons ajouté le(s) mot(s)-clé(s) pouvant être utilisés pour résoudre le problème
figurant entre parenthèses après chaque énoncé.
« Listez les clients ayant commandé une bicyclette. » (IN)
« Affichez les clients ayant commandé des vêtements ou des accessoires. » (= SOME)
« Trouvez tous les clients ayant commandé un casque. » (IN)
« Trouvez tous les clients ayant commandé une bicyclette, mais pas de casque. » (NOT
EXISTS)
« Quels sont les produits qui n'ont jamais été commandés ? » (NOT IN)
« Listez les clients qui ont réservé des groupes jouant de la country ou du country rock. »
(IN)
« Trouvez les groupes qui ont eu des engagements chez les clients Dupont ou Lenoir. »
(= SOME)
« Affichez les agents qui n'ont fait engager aucun groupe. » (NOT IN)
« Listez les groupes ayant eu des engagements chez les clients Dupont et Lenoir. »
(EXISTS)
« Affichez les étudiants inscrits à des cours se déroulant le mardi. » (IN)
« Montrez-moi les étudiants dont la moyenne est égale ou supérieure à 17 en arts plastiques
et en informatique. » (EXISTS)
306
Les tables multiples
PARTIE 3
« Affichez les étudiants qui n'ont jamais abandonné aucun cours. » (NOT IN)
« Listez les matières enseignées le mercredi. » (IN)
« Affichez tous les capitaines d'équipes dont la moyenne est supérieure à celle de tous les
autres membres de leurs équipes. » (> ALL)
« Montrez-moi les tournois qui n'ont pas encore été joués. » (NOT IN)
« Trouvez les joueurs dont le score est de 170 ou plus au Bowling République et au Bowling
Acapulco. » (EXISTS)
« Listez tous les joueurs dont la moyenne est inférieure à celle de tous les autres membres
de leurs équipes. » (< ALL)
« Montrez-moi les recettes qui contiennent du bœuf et de l'ail. » (EXISTS)
« Affichez tous les ingrédients des recettes contenant des carottes. » (IN)
« Listez les ingrédients utilisés dans les recettes où la mesure utilisée n'est pas la mesure
pas défaut. » (<> SOME)
« Listez les ingrédients qui n'ont encore été utilisés dans aucune recette. » (NOT IN)
Exemples de commandes
Vous savez maintenant construire des requêtes à l'aide de sous-requêtes et savez également
avec quel type de requêtes elles doivent être utilisées. Examinons maintenant quelques exemples qui impliquent l'utilisation d'une ou plusieurs sous-requêtes.
À la suite de la ligne de syntaxe SQL, nous avons également inclus des exemples d'ensembles
de résultats retournés par ces opérations. Le nom qui apparaît juste au dessus de chaque
ensemble de résultat est celui que nous avons donné à chaque requête sur le CD qui accompagne cet ouvrage.
INFO
Souvenez-vous que tous les noms de colonnes et de tables utilisés dans ces exemples sont tirés des
structures des exemples de bases de données présentées dans l'Annexe B. Étant donné que bon nombre
de ces exemples mettent en œuvre des jointures complexes, il se peut que votre système de base de
données choisisse une autre manière de résoudre la requête en question. C'est pourquoi les premières
lignes retournées ne correspondront peut-être pas complètement au résultat que vous obtiendrez, cependant le nombre total de lignes sera le même. Par souci de simplification, nous avons combiné les étapes
« Traduction » et « Nettoyage » dans tous les exemples suivants.
Sous-requêtes dans des expressions
Base de données Ventes
Énoncé
« Listez les fournisseurs et le nombre des produits qu’ils nous revendent. »
Traduction et
Nettoyage
Sélectionner [SELECT] le nom du fournisseur et aussi (Sélectionner
[SELECT] le total [COUNT] (*) des produits à partir de [FROM] la table
du catalogue des fournisseurs pour ce fournisseur où [WHERE] le numéro
de fournisseur = numéro du fournisseur du produit) à partir de [FROM] la
table des fournisseurs
Sous-requêtes
CHAPITRE 11
SQL
307
SELECT NomFournisseur,
(SELECT COUNT(*)
FROM CatalogueFournisseurs
WHERE CatalogueFournisseurs.NumFournisseur =
Fournisseurs.NumFournisseur)
AS ProduitsVendus
FROM Fournisseurs
Fournisseurs et total produits vendus (10 lignes)
NomFournisseur
ProduitsVendus
Shinoman
3
Taupe Bike
6
Le guidon dans les nuages
5
ProFormance
3
Pédale douce
1
Vélo 2000
22
Le paradis du VTT
9
Vélo en gros
5
VTT Star
30
Poulie d'or
6
Base de données AgenceSpectacles
Énoncé
« Afficher chaque client et la date de la dernière réservation qu’ils ont
effectuée. »
Traduction et
Nettoyage
Sélectionner [SELECT] le prénom du client, le nom du client, et aussi
(Sélectionner [SELECT] la plus haute [MAX] (date de début) à partir de
[FROM] la table des engagements pour ce client où [WHERE] le numéro
client dans la table des engagements = numéro client) à partir de [FROM]
la table des clients.
SQL
INFO
SELECT Clients.PrénomClient,
Clients.NomClient,
(SELECT MAX(DateDébut)
FROM Engagements
WHERE Engagements.NumClient =
Clients.NumClient)
AS DernièreRéservation
FROM Clients
La colonne DernièreRéservation de certains clients est vide car ces clients n’ont fait aucune réservation.
308
Les tables multiples
PARTIE 3
Dernière réservation (15 lignes)
PrénomClient
NomClient
DernièreRéservation
Céline
Calisson
23/12/1999
Anne
Fourastié
17/12/1999
Jérémy
Lingois
26/12/1999
Karl
Picard
24/12/1999
Eline
Mercier
19/12/1999
Thomas
Fuchs
23/12/1999
Amélie
Bouchard
19/12/1999
Samuel
Poulain
19/12/1999
Sarah
Thorez
24/12/1999
Richard
Rouland
24/12/1999
Base de données EmploiDuTemps
Énoncé
« Affichez toutes les UV et comptez les cours pour chaque UV le lundi. »
Traduction et
Nettoyage
Sélectionner [SELECT] le nom de l’UV et aussi(Sélectionner [SELECT] le
total [COUNT] (*) des cours à partir de [FROM] la table des cours où
[WHERE] Lundi est = vrai [-1] pour cette UV et [AND] le numéro de cours
de l’UV = numéro de l’UV) à partir de [FROM] la table des UV
SQL
INFO
SELECT UV.NomUV,
(SELECT Count(*)
FROM Planning
WHERE Lundi = -1
AND Planning.NumUV = UV.NumUV)
AS TotalLundi
FROM UV
Assurez-vous d’utiliser le bon test pour la valeur vrai. Certains SGBD nécessitent la comparaison au mot
clé TRUE alors que d’autres demandent la comparaison au chiffre 1.
Au lieu de retourner une valeur nulle quand il n’y a pas de lignes, la fonction d’agrégation COUNT
retourne un zéro.
Base de données Bowling
Énoncé
« Affichez les joueurs et leur score le plus élevé. »
Traduction et
Nettoyage
Sélectionner [SELECT] le prénom du joueur, le nom du joueur, et aussi
(Sélectionner [SELECT] le plus élevé [MAX](score) à partir de [FROM] la
table des scores des joueurs pour ce joueur où [WHERE] le numéro du
joueur dans la table des scores des joueurs = numéro du joueur) à partir de
[FROM] la table des joueurs
Sous-requêtes
CHAPITRE 11
Total UV le lundi (56 lignes)
SQL
NomUV
TotalLundi
Introduction à la comptabilité 1
2
Introduction à la comptabilité 2
1
Introduction à la gestion financière
1
Comptabilité
1
Fiscalité
1
Introduction au management
0
Développement d'un business plan
0
Introduction à la gestion d'entreprise
1
Technologies de l'information 1
1
Technologies de l'information 2
0
Introduction à l'art
1
SELECT Joueurs.PrénomJoueur, Joueurs.NomJoueur,
(SELECT MAX(Score)
FROM ScoresJoueurs
WHERE ScoresJoueurs.NumJoueur =
Joueurs.NumJoueur)
AS MeilleurScore
FROM Joueurs
Meilleur score (32 lignes)
PrénomJoueur
NomJoueur
MeilleurScore
Barbara
Fournier
164
David
Fournier
178
Jean
Gamblin
191
Sarah
Gamblin
149
Anne
Patisson
165
Yves
Patisson
179
Carole
Bichat
195
David
Bichat
150
Aline
Blanchard
164
David
Blanchard
180
Suzanne
Martin
194
309
310
Les tables multiples
PARTIE 3
Base de données Recettes
Énoncé
« Listez toutes les viandes et le nombre de fois où elles apparaissent dans une
recette. »
Traduction et
Nettoyage
Sélectionner [SELECT] la description du type d’ingrédient, le nom de
l’ingrédient, et aussi (Sélectionner [SELECT] le total [COUNT] (*) des
lignes à partir de [FROM] la tables des ingrédients des recettes pour cet
ingrédient où [WHERE] le numéro d’ingrédient de la recette = le numéro
d’ingrédient) à partir de [FROM] la table des types d’ingrédient reliée à
[INNER JOIN] la table des ingrédients en se basant sur [ON] le numéro du
type d’ingrédient où [WHERE] la description du type d’ingrédient est =
'Viande'
SQL
SELECT TypesIngrédients.DescriptionTypeIngrédient,
Ingrédients.NomIngrédient,
(SELECT COUNT(*)
FROM IngrédientsRecette
WHERE IngrédientsRecette.NumIngrédient =
Ingrédients.NumIngrédient)
AS NombreRecette
FROM TypesIngrédients
INNER JOIN Ingrédients
ON TypesIngrédients.NumTypeIngrédient =
Ingrédients.NumTypeIngrédient
WHERE
TypesIngrédients.DescriptionTypeIngrédient
='Viande'
Viande contenue dans des recettes (11 lignes)
DescriptionTypeIngrédient
NomIngrédient
NombreRecette
Viande
Bœuf
2
Viande
Poulet
0
Viande
Bacon
0
Viande
Poulet découpé
0
Viande
T-bone
0
Viande
Blanc de poulet
0
Viande
Avant cuisse de poulet
1
Viande
Aile de poulet
0
Viande
Cuisse de poulet
1
Viande
Steak
0
Viande
Farce
1
Sous-requêtes
CHAPITRE 11
311
Sous-requêtes dans des filtres
Base de données Ventes
Énoncé
« Affichez les clients qui ont commandé des vêtements ou des accessoires. »
Traduction et
Nettoyage
Sélectionner [SELECT] le numéro client, le prénom du client, le nom du
client à partir de [FROM] la table des clients où [WHERE] le numéro client
est = tout [ANY] numéro client (Sélectionner [SELECT] le numéro client à
partir de [FROM] la table des commandes reliée à [INNER JOIN] la table
des détails de commandes en se basant sur [ON] le numéro de commande,
reliée [INNER JOIN] ensuite à la table des produits en se basant sur [ON] le
numéro du produit, et reliée à [INNER JOIN] la table des catégories en se
basant sur [ON] le numéro de catégorie où [WHERE] la description de la
catégorie est = 'Vêtements' ou [OR] description de la catégorie est =
'Accessoires'
SQL
SELECT Clients.NumClient,
Clients.PrénomClient,
Clients.NomClient
FROM Clients
WHERE Clients.NumClient = ANY
(SELECT Commandes.NumClient
FROM ((Commandes
INNER JOIN DétailCommandes
ON Commandes.NumCommande =
DétailCommandes.NumCommande)
INNER JOIN Produits
ON Produits.NumProduit =
DétailCommandes.NumProduit)
INNER JOIN Catégories
ON Catégories.NumCatégorie =
Produits.NumCatégorie
WHERE Catégories.DescriptionCatégorie
='Vêtements'
OR Catégories.DescriptionCatégorie
= 'Accessoires')
Base de données AgenceSpectacles
Énoncé
INFO
« Listez les groupes qui ont joué pour les clients Mercier et Rouland. »
Nous avons résolu ce problème au Chapitre 8 avec une jointure de deux sous-requêtes de table complexes. À présent, nous utilisons EXISTS.
312
Les tables multiples
PARTIE 3
Clients ayant commandé des vêtements ou des accessoires (27 lignes)
NumClient
PrénomClient
NomClient
1001
Suzanne
Vericel
1002
Gérard
Thomson
1003
Gustave
Haller
1004
Michel
Douillet
1005
Karine
Perrin
1006
Jean
Vistule
1007
Laura
Callahan
1008
Nelly
Pattisson
1009
Georges
Dahan
1010
Marcel
Biron
Traduction et
Nettoyage
Sélectionner [SELECT] le numéro de groupe, et le nom de scène du groupe
à partir de [FROM] la table des groupes où [WHERE] il existe [EXISTS]
(Sélectionner [SELECT] * une ligne à partir de [FROM] la table des clients
reliée à [INNER JOIN] la table des engagements en se basant sur [ON] le
numéro client où [WHERE] le nom du client est = 'Rouland' et [AND] le
numéro du groupe dans la table des groupes est égal = le numéro du groupe
dans la table des engagements), et [AND] il existe [EXISTS] aussi (Sélectionner [SELECT] * une ligne à partir de [FROM] la table des clients reliée
à [INNER JOIN] la table des engagements en se basant sur [ON] le numéro
client où [WHERE] le nom du client est = 'Mercier' et [AND] le numéro
du groupe dans la table des groupes est égal = le numéro du groupe dans la
table des engagements)
SQL
SELECT Groupes.NumGroupe,
Groupes.NomScène
FROM Groupes
WHERE EXISTS
(SELECT *
FROM Clients
INNER JOIN Engagements
ON Clients.NumClient =
Engagements.NumClient
WHERE Clients.NomClient='Rouland'
AND Engagements.NumGroupe =
Groupes.NumGroupe)
AND EXISTS
(SELECT *
FROM Clients
Sous-requêtes
CHAPITRE 11
313
INNER JOIN Engagements
ON Clients.NumClient =
Engagements.NumClient
WHERE Clients.NomClient='Mercier'
AND Engagements.NumGroupe =
Groupes.NumGroupe)
EXISTS Groupes pour Mercier et Rouland (3 lignes)
NumGroupe
NomScène
1001
Trio urbain
1003
Les chaussettes rouges
1006
Danses du ballet
Base de données EmploiDuTemps
Énoncé
« Affichez les étudiants qui n’ont jamais abandonné un cours. »
Traduction et
Nettoyage
Sélectionner [SELECT] le numéro d’étudiant, prénom étudiant, et nom
étudiant à partir de [FROM] la tables des étudiants où [WHERE] le
numéro d’étudiant n’est pas dans [NOT IN] la (Sélectionner [SELECT] le
numéro d’étudiant à partir de [FROM] la table du planning des étudiants
reliée [INNER JOIN] à la table du statut des cours des étudiants en se
basant sur [ON] le statut du cours où [WHERE] la description du statut du
cours est = 'Abandon'
SQL
INFO
SELECT Etudiants.NumEtudiant,
Etudiants.PrénomEtudiant,
Etudiants.NomEtudiant
FROM Etudiants
WHERE Etudiants.NumEtudiant NOT IN
(SELECT PlanningEtudiants.NumEtudiant
FROM PlanningEtudiants
INNER JOIN StatutEtudiant
ON PlanningEtudiants.NumStatut =
StatutEtudiant.NumStatut
WHERE
StatutEtudiant.DescriptionStatut
= 'Abandon')
Cette requête plutôt simple trouve tous les étudiants qui ont échoué à un cours dans une sous-requête et
demande ensuite tous les étudiants qui ne sont pas dans cette liste. Songez à la manière dont on pourrait
résoudre cela avec une jointure externe.
314
Les tables multiples
PARTIE 3
Etudiants jamais abandonné (15 lignes)
NumEtudiant
PrénomEtudiant
NomEtudiant
1002
André
Fumagali
1003
Sarah
Lemaire
1004
Carole
Pinson
1005
Céline
Carouge
1006
Stéphane
Bouchery
1007
Elisabeth
Marchand
1008
Claire
Coste
1010
Marie-Laurence
Portal
1011
Philippe
Thoiron
1012
Sandrine
Testut
Base de données Bowling
Énoncé
« Affichez les capitaines d’équipe qui ont une moyenne plus élevée que les
autres membres de leur équipe. »
Traduction et
Nettoyage
Sélectionner [INNER JOIN] le nom de l’équipe, le numéro du joueur, le
prénom du joueur, le nom du joueur, et la moyenne à partir de [FROM] la
table des joueurs reliée à [INNER JOIN] la table des équipes en se basant
sur [ON] numéro du joueur du match = numéro du capitaine où [WHERE]
la moyenne est supérieure à > toutes [ALL] de la (sélection de la moyenne à
partir de [FROM] la table des joueurs où [WHERE] le numéro du joueur
n’est pas égal <> au numéro du joueur actuel et [AND] le numéro d’équipe
est égal = au numéro d’équipe actuel
SQL
INFO
SELECT Equipes.NomEquipe, Joueurs.NumJoueur,
Joueurs.PrénomJoueur,
Joueurs.NomJoueur,
Joueurs.Moyenne
FROM Joueurs
INNER JOIN Equipes
ON Joueurs.NumJoueur = Equipes.NumCapitaine
WHERE Joueurs.Moyenne > All
(SELECT B2.Moyenne
FROM Joueurs AS B2
WHERE B2.NumJoueur <> Joueurs.NumJoueur
AND B2.NumEquipe = Joueurs.NumEquipe)
Nous avons donné un alias à la seconde copie de la table Joueurs dans la sous-requête afin de rendre les
choses plus claires. Nous ne voulons pas comparer la comparer à la moyenne du capitaine (cela provoquerait l’échec du prédicat > ALL). Nous ne voulons la comparer qu’aux autres joueurs de la même
équipe.
Sous-requêtes
CHAPITRE 11
315
Meilleure moyenne des capitaines (2 lignes)
NomEquipe
NumJoueur
PrénomJoueur
NomJoueur
Moyenne
Requins
5
Anne
Patisson
170,00
Renards
16
Richard
Fayolle
165,00
Base de données Recettes
Énoncé
INFO
« Affichez tous les ingrédients des recettes qui contiennent des carottes. »
Nous vous avions promis au Chapitre 8 de vous montrer comment résoudre ce problème avec une sousrequête. Nous tenons nos promesses !
Traduction et
Nettoyage
Sélectionner [SELECT] le nom de la recette et le nom de l’ingrédient à
partir de [FROM] la table des recettes reliée à [INNER JOIN] la table des
ingrédients des recettes en se basant sur [ON] le numéro, et ensuite reliée à
[INNER JOIN] la table des ingrédients en se basant sur [ON] le numéro
d’ingrédient où [WHERE] le numéro de recette est dans [IN] la
(sélect[SELECT] ion du numéro de recette à partir de [FROM] la table des
ingrédients reliée à [INNER JOIN] la table des ingrédients des recettes en
se basant sur [ON] le numéro d’ingrédient où [WHERE] le nom d’ingrédient est= 'Carotte'
SQL
INFO
SELECT Recettes.NomRecette,
Ingrédients.NomIngrédient
FROM (Recettes
INNER JOIN IngrédientsRecette
ON Recettes.NumRecette =
IngrédientsRecette.NumRecette)
INNER JOIN Ingrédients
ON Ingrédients.NumIngrédient =
IngrédientsRecette.NumIngrédient
WHERE Recettes.NumRecette
IN
(SELECT IngrédientsRecette.NumRecette
FROM Ingrédients
INNER JOIN IngrédientsRecette
ON Ingrédients.NumIngrédient =
IngrédientsRecette.NumIngrédient
WHERE Ingrédients.NomIngrédient = 'Carotte')
Si vous placez le filtre pour les ‘carottes’ dans une requête externe, vous ne verrez que les ingrédients
carottes dans la sortie. Dans ce problème, nous voulons voir tous les ingrédients de toutes les recettes qui
utilisent des carottes, et la sous-requête est donc un bon moyen de résoudre cela.
316
Les tables multiples
PARTIE 3
Recettes contenant des carottes (16 lignes)
NomRecette
NomIngrédient
Ragoût irlandais
Bœuf
Ragoût irlandais
Oignon
Ragoût irlandais
Pomme de terre
Ragoût irlandais
Carotte
Ragoût irlandais
Eau
Ragoût irlandais
Bière Guinness
Filets de saumon en papillotes
Saumon
Filets de saumon en papillotes
Carotte
Filets de saumon en papillotes
Poireau
Filets de saumon en papillotes
Poivre rouge
Filets de saumon en papillotes
Beurre
Problèmes à résoudre
La solution de ces problèmes est fournie sur le CD-Rom d’accompagnement aux formats
Access 2000 et Access 97. Après avoir dézippé le fichier requetessql.zip fourni dans le répertoire RequetesSQL, vous trouverez les solutions dans le répertoire \Solutions\Access2000
(respectivement \Solutions\Access97). Par exemple, la solution du deuxième des problèmes
portant sur la base de données Ventes est fournie dans le fichier Ventes.mdb (requête intitulée
Chapitre 11-2). Ne vous inquiétez pas si votre propre solution ne correspond pas exactement
à celle que nous proposons – il y a souvent plusieurs syntaxes possibles pour une même
requête SQL. L’essentiel est que l’ensemble de résultat soit le même.
Base de données Ventes
1. « Affichez les produits et leur dernière date de commande. » (Indice : utilisez la fonction
MAX.)
2. « Listez les clients qui ont commandé des VTT. » (Indice : créez un filtre en utilisant IN.)
3. « Trouvez tous les clients qui ont commandé un VTT mais qui n’ont pas commandé de
casque. » (Indice : commencez comme pour la précédente requête et ajoutez un filtre en
utilisant NOT EXISTS.)
4. « Quels produits n’ont jamais été commandés ? » (Indice : créez un filtre en utilisant NOT
IN.)
Base de données AgenceSpectacles
1. « Montrez-moi les groupes et le nombre de leurs engagements. »
2. « Listez les clients qui ont réservé pour des spectacles de jazz ou de karaoke. »
3. « Trouvez les groupes qui ont joué pour les clients Mercier ou Rouland. »
4. « Affichez les agents qui n’ont jamais engagé un groupe. »
Sous-requêtes
CHAPITRE 11
317
Base de données EmploiDuTemps
1. « Listez tous les professeurs et le nombre de cours qu’ils dispensent. »
2. « Affichez les étudiants inscrits à un cours le mardi. »
3. « Montrez-moi les étudiants qui ont une note supérieure ou égale à 85 en arts et qui ont
également une note supérieure ou égale à 85 en informatique. » (Indice : créez un filtre en
utilisant EXISTS.)
Base de données Bowling
1. « Montrez-moi tous les joueurs et le nombre de jeux que chacun a disputés. »
2. « Montrez-moi les tournois qui n’ont pas encore été joués. »
3. « Trouvez les joueurs qui ont un score d’au moins 170 à la fois au Bowling Acapulco et à
la salle Omnisports. »
4. « Listez tous les joueurs qui ont une moyenne qui est inférieure à celle de tous les autres
joueurs de la même équipe. »
Base de données Recettes
1. « Montrez-moi les types de recettes et le nombre de recettes de chaque type. »
2. « Montrez-moi les recettes qui contiennent du bœuf et de l’ail. » (Indice : créez un filtre en
utilisant EXISTS.)
3. « Listez les ingrédients qui sont utilisés dans une recette où la quantité utilisée dans la
recette n’est pas la mesure par défaut. » (Indice : créez un filtre en utilisant <> SOME.)
4. « Les ingrédients qui ne sont utilisés dans aucune recette. » (Indice : créez un filtre en utilisant NOT IN.)
Téléchargement