Skip to content

6. Approfondissement du langage SQL

6.1. Introduction

Dans ce chapitre nous présentons

  • d'autres syntaxes de la commande SELECT qui en font une commande de consultation très puissante notamment pour consulter plusieurs tables à la fois.
  • des syntaxes élargies de commandes déjà étudiées

Pour illustrer les diverses commandes, nous travaillerons avec les tables suivantes utilisées pour la gestion des commandes dans une PME de diffusion de livres :

6.1.1. la table CLIENTS

Elle mémorise des informations sur les clients de la PME :

 

Image

ID
n° identifiant le client de façon unique - clé primaire
NOM
nom du client
STATUT
I=Individu, E=Entreprise, A=Administration
PRENOM
prénom dans le cas d'un individu
CONTACT
Nom de la personne à contacter chez le client (dans le cas d'une entreprise ou d'une administration)
RUE
Adresse du client - rue
VILLE
ville
CPOSTAL
code postal
TELEPH
Téléphone
DEPUIS
Client depuis quelle date ?
DEBITEUR
O (Oui) si le client doit de l'argent à l'entreprise et N (Non) sinon.

6.1.2. la table ARTICLES

Elle mémorise des informations sur les produits vendus, ici des livres. Sa structure est la suivante :

Image

ISBN
n° identifiant un livre de façon unique (ISBN= International Standard Book Number) - clé primaire
TITRE
Titre du livre
CODEDITEUR
Code identifiant un éditeur de façon unique
AUTEUR
Nom de l'auteur
RESUME
Résumé du livre
QTEANCOUR
Quantité vendue dans l'année
QTEANPREC
Quantité vendue l'année précédente
DERNVENTE
date de la dernière vente
QTERECUE
Quantité de la dernière livraison
DERNLIV
Date de la dernière livraison
PRIXVENTE
Prix de vente
COUT
Coût d'achat
MINCDE
Quantité minimale à commander
MINSTOCK
Seuil minimal du stock
QTESTOCK
Quantité en stock

Son contenu pourrait être le suivant :

Image

6.1.3. la table COMMANDES

Elle enregistre les informations sur les commandes faites par les clients. Sa structure est la suivante :

Image

NOCMD
N° identifiant une commande de façon unique - clé primaire
IDCLI
N° du client faisant cette commande - clé étangère - référence CLIENTS(ID)
DATE_CMD
Date de saisie de cette commande
ANNULE
O (Oui) si la commande a été annulée et N (Non) sinon.

Image

6.1.4. la table DETAILS

Elle contient les détails d'une commande, c'est à dire les références et quantités des livres commandés. Sa structure est la suivante :

Image

NOCMD
N° de la commande - clé étrangère référençant la colonne NOCMD de la table COMMANDES
ISBN
N° du livre commandé - clé étrangère référençant la colonne ISBN de la table LIVRES
QTE
Quantité commandée

Son contenu pourrait être le suivant :

Image

Ci-dessus, on voit que la commande n° 3 (NOCMD) concerne trois livres. Cela veut dire que le client a commandé trois livres en même temps. Les références de ce client peuvent être trouvées dans la table [COMMANDES] où l'on voit que la commande n° 3 a été effectuée par le client n° 5. La table [CLIENTS] nous apprend que le client n° 5 est la société NetLogos de Segré.

6.2. La commande SELECT

Nous nous proposons ici d'approfondir notre connaissance de la commande SELECT en présentant de nouvelles syntaxes de celle-ci.

6.2.1. Syntaxe d'une requête multi-tables

syntaxe
SELECT colonne1, colonne2, ...
FROM table1, table2, ..., tablep
WHERE condition
ORDER BY ...
action
La nouveauté ici vient du fait que les colonnes colonne1, colonne2, ... proviennent de plusieurs tables table1, table2, ... Si deux tables ont des colonnes de même nom, on lève l'ambigüité par la notation tablei.colonnej. La condition peut porter sur les colonnes des différentes tables.

Fonctionnement

1
La table produit cartésien de table1, table2, ..., tablep est réalisée. Si ni est le nombre de lignes de tablei, la table construite a donc n1*n2*...*np lignes comportant l'ensemble des colonnes des différentes tables.
2
La condition du WHERE est appliquée à cette table. Une nouvelle table est ainsi produite
3
Celle-ci est ordonnée selon le mode indiqué dans ORDER.
4
Les colonnes demandées derrière SELECT sont affichées.

Exemples

On utilise les tables présentées précédemment. On veut connaître le détail des commandes passées après le 25 septembre :

SQL>select details.nocmd,isbn,qte from commandes,details
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd

Image

On remarquera que derrière FROM, on met le nom de toutes les tables dont on référence les colonnes. Dans l'exemple précédent, les colonnes sélectionnées appartiennent toutes à la table DETAILS. Cependant la condition fait référence à la table COMMANDES. D'où la nécessité de nommer cette dernière derrière le FROM. L'opération qui teste l'égalité de colonnes de deux tables différentes est souvent appelée une équi-jointure.

La requête SELECT aurait pu être également écrite de la façon suivante :

SQL> select details.nocmd,isbn,qte from commandes
    inner join details on details.nocmd=commandes.nocmd
    where commandes.datecmd>'25-sep-91'

Continuons nos exemples. On désire le même résultat que précédemment mais avec le titre du livre commandé, plutôt que son n° ISBN :

SQL>select commandes.nocmd, articles.titre, details.qte
  from commandes,articles,details
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd
  and details.isbn=articles.isbn

Image

Le même résultat est obtenu avec la requête SQL suivante, moins lisible :

SQL> select details.nocmd,articles.titre,details.qte from details
    inner join commandes on details.nocmd=commandes.nocmd
    inner join articles on  details.isbn=articles.isbn
    where commandes.datecmd>'25-sep-91'

Ci-dessus, deux jointures internes sont faites avec la table [DETAILS] :

  • l'une avec la table [COMMANDES] pour avoir accès à la date de commande d'un livre
  • l'une avec la table [ARTICLES] pour avoir accès au titre du livre commandé

On veut de plus le nom du client qui fait la commande :

SQL>select commandes.nocmd, articles.titre, qte ,clients.nom
  from commandes,details,articles,clients
  where commandes.datecmd>'25-sep-91'
  and details.nocmd=commandes.nocmd
  and details.isbn=articles.isbn
  and commandes.idcli=clients.id

Image

On veut de plus les dates de commande et un affichage par ordre décroissant de ces dates :

SQL>select commandes.nocmd, commandes.datecmd, articles.titre, qte ,clients.nom
    from commandes,details,articles,clients
    where commandes.datecmd>'25-sep-91'
    and details.nocmd=commandes.nocmd
    and details.isbn=articles.isbn
    and commandes.idcli=clients.id
    order by commandes.datecmd descending

Image

Voici quelques règles à observer dans les jointures :

  1. Derrière SELECT, on met les colonnes que l'on désire obtenir à l'affichage. Si la colonne existe dans diverses tables, on la fait précéder du nom de la table.
  2. Derrière FROM, on met toutes les tables qui seront explorées par le SELECT, c'est à dire les tables propriétaires des colonnes qui se trouvent derrière SELECT et WHERE.

6.2.2. L'auto-jointure

On veut connaître les livres qui ont un prix de vente supérieur à celui du livre 'Using SQL' :

SQL>select a.titre from articles a, articles b
  where b.titre='Using SQL'
  and a.prixvente>b.prixvente

Image

Les deux tables de la jointure sont ici identiques : la table articles. Pour les différencier, on leur donne un alias : from articles a, articles b. L'alias de la première table s'appelle a et celui de la seconde, b. Cette syntaxe peut s'utiliser même si les tables sont différentes. Lors de l'utilisation d'un alias, celui-ci doit être utilisé partout dans la commande SELECT en lieu et place de la table qu'il désigne.

6.2.3. Jointure externe

On veut connaître les clients qui ont acheté quelque chose en septembre avec la date de la commande. Les autres clients sont affichés sans cette date :

SQL>select clients.nom,commandes.datecmd from clients
    left outer join commandes  on clients.id=commandes.idcli
    where datecmd between '01-sep-91' and '30-sep-91'

Image

On est étonné ici de ne pas avoir le bon résultat. On devrait avoir tous les clients présents dans la table [CLIENTS], ce qui n'est pas le cas. Lorsqu'on réfléchit au fonctionnement de la jointure externe, on réalise que les clients n'ayant pas acheté ont été associées à une ligne vide de la table COMMANDES et donc avec une date vide (valeur NULL dans la terminologie SQL). Cette date ne vérifie pas alors la condition fixée sur la date et le client correspondant n'est pas affiché. Essayons autre chose :

SQL>select clients.nom,commandes.datecmd from clients
    left outer join commandes  on clients.id=commandes.idcli
    where (commandes.datecmd between '01-sep-91' and '30-sep-91')
        or (commandes.datecmd is null)

Image

On obtient cette fois-ci la réponse correcte à notre question.

6.2.4. Requêtes imbriquées

syntaxe
SELECT colonne[s] FROM table[s]
WHERE expression opérateur requête
ORDER BY ...
fonctionnement
requête est une commande SELECT qui délivre un groupe de 0, 1 ou plusieurs valeurs. On a alors une condition WHERE du type
expression opérateur (val1, val2, ..., vali)
expression et vali doivent être de même type. Si la requête délivre une seule valeur, on est ramené à une condition du type
expression opérateur valeur
que nous connaissons bien. Si la requête délivre une liste de valeurs, on pourra employer les opérateurs suivants :
IN
expression IN (val1, val2, ..., vali) : vraie si expression a pour valeur l'un des éléments de la liste vali.
NOT IN
inverse de IN
ANY
doit être précédé de =,!=,>,>=,<,<=
expression >= ANY (val1, val2, .., valn) : vraie si expression est >= à l'une des valeurs vali de la liste
ALL
doit être précédé de =,!=,>,>=,<,<=
expression >= ALL (val1, val2, .., valn) : vraie si expression est >= à toutes les valeurs vali de la liste
EXISTS 
requête : vraie si la requête rend au moins une ligne.

Exemples

On reprend la question déjà résolue par une équi-jointure : afficher les titres ayant un prix de vente supérieur à celui du livre 'Using SQL'.

SQL>select titre from ARTICLES
    where prixvente > (select prixvente from ARTICLES where titre='Using SQL')

Image

Cette solution semble plus intuitive que celle de l'équi-jointure. On fait un premier filtrage avec un SELECT, puis un second sur le résultat obtenu. On peut opérer ainsi plusieurs filtrages en série.

On veut connaître les titres ayant un prix de vente supérieur au prix moyen de vente :

SQL> select titre from ARTICLES
    where prixvente > (select avg(prixvente) from ARTICLES)

Image

Quels sont les clients ayant commandé les titres résultat de la requête précédente ?

SQL>select distinct idcli from COMMANDES,DETAILS
    where DETAILS.isbn in
    (select isbn from ARTICLES where prixvente
        > (select avg(prixvente) from ARTICLES))
    and COMMANDES.nocmd=DETAILS.nocmd

Image

Explications

  1. on sélectionne dans la table DETAILS les codes ISBN se trouvant parmi les livres ayant un prix supérieur au prix moyen des livres.
  2. dans les lignes sélectionnées de l'étape précédente, il n'y a pas le code client IDCLI. Il se trouve dans la table COMMANDES. Le lien entre les deux tables se fait par le n° de commande NOCMD, d'où l'équi-jointure COMMANDES.nocmd=DETAILS.nocmd.
  3. Un même client peut avoir acheté plusieurs fois l'un des livres concernés, auquel cas on aura son code IDCLI plusieurs fois. Pour éviter cela, on met le mot clé DISTINCT derrière SELECT. DISTINCT de façon générale élimine les doublons dans les lignes résultat d'un SELECT.
  4. pour avoir le nom du client, il nous faudrait faire une équi-jointure supplémentaire entre les tables COMMANDES et CLIENTS comme le montre la requête qui suit.
SQL> select distinct CLIENTS.nom from COMMANDES,DETAILS,CLIENTS
    where DETAILS.isbn in
    (select isbn from ARTICLES where prixvente
        > (select avg(prixvente) from ARTICLES))
    and COMMANDES.nocmd=DETAILS.nocmd
    and COMMANDES.IDCLI=CLIENTS.ID

Image

Trouver les clients qui n'ont pas fait de commande depuis le 24 septembre :

SQL>select nom from CLIENTS
    where clients.id not in
    (select distinct commandes.idcli from commandes where datecmd>='24-sep-91')

Image

Nous avons vu qu'on pouvait filtrer des lignes autrement qu'avec la clause WHERE : en utilisant la clause HAVING en conjonction avec la clause GROUP BY. La clause HAVING filtre des groupes de lignes.

De la même façon que pour la clause WHERE, la syntaxe


     HAVING expression opérateur requête 

est possible, avec la contrainte déjà présentée que expression doit être l'une des expressions expri de la clause


     GROUP BY expr1, expr2, ...

Exemples

Quelles sont les quantités vendues pour les livres de plus de 200F ?

Affichons tout d'abord les quantités vendues par titre :

SQL>select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    group by titre

Image

Maintenant, filtrons les titres :

SQL> select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    group by titre
    having titre in (select titre from ARTICLES where prixvente>200)

Image

De façon peut-être plus évidente on aurait pu écrire :

SQL>select ARTICLES.titre,sum(qte) QTE from ARTICLES, DETAILS
    where DETAILS.isbn=ARTICLES.isbn
    and ARTICLES.prixvente>200
    group by titre

Image

6.2.5. Requêtes corrélées

Dans le cas des requêtes imbriquées, on a une requête parent (la requête la plus externe) et une requête fille (la requête la plus interne). La requête mère n'est évaluée que lorsque la requête fille l'a été complètement.

Les requêtes corrélées ont la même syntaxe au détail près suivant : la requête fille fait une jointure sur la table de la requête mère. Dans ce cas, l'ensemble requête mère-requête fille est évalué de façon répétée pour chaque ligne de la table mère.

Exemple

Nous reprenons l'exemple où nous désirons les noms des clients n'ayant pas fait de commande depuis le 24 septembre :

SQL> 
select nom from clients
    where not exists
        (select idcli from commandes
            where datecmd>='24-sep-91'
                and commandes.idcli=clients.id)

Image

La requête mère s'exerce sur la table clients. La requête fille fait une jointure entre les tables clients et commandes. On a donc une requête corrélée. Pour chaque ligne de la table clients, la requête fille s'exécute : elle cherche le code id du client dans les commandes faites après le 24 septembre. Si elle n'en trouve pas (not exists), le nom du client est affiché. Ensuite, on passe à la ligne suivante de la table clients.

6.2.6. Critères de choix pour l'écriture du SELECT

Nous avons vu, à plusieurs reprises, qu'il était possible d'obtenir un même résultat par différentes écritures du SELECT. Prenons un exemple : Afficher les clients ayant commandé quelque chose :

Jointure

SQL>
select distinct nom from clients,commandes
    where clients.id=commandes.idcli

Image

Requêtes imbriquées

SQL> 
select nom from clients
    where id in (select idcli from commandes)

donne le même résultat.

Requêtes corrélées

SQL>
select nom from clients
    where exists (select * from commandes where commandes.idcli=clients.id)

donne le même résultat.

Les auteurs Christian MAREE et Guy LEDANT, dans leur livre ' SQL, Initiation, Programmation et Maîtrise' proposent quelques critères de choix :

Performances

L'utilisateur ne sait pas comment le SGBD "se débrouille" pour trouver les résultats qu'il demande. Ce n'est donc que par expérience, qu'il découvrira que telle écriture est plus performante qu'une autre. MAREE et LEDANT affirment par expérience que les requêtes corrélées semblent généralement plus lentes que les requêtes imbriquées ou les jointures.

Formulation

La formulation par requêtes imbriquées est souvent plus lisible et plus intuitive que la jointure. Elle n'est cependant pas toujours utilisable. Deux points sont notamment à observer :

  • Les tables propriétaires des colonnes arguments du SELECT ( SELECT col1, col2, ...) doivent être nommées derrière le mot clé FROM. Le produit cartésien de ces tables est alors effectué, ce qu'on appelle une jointure.
  • Lorsque la requête affiche des résultats provenant d'une seule table, et que le filtrage des lignes de cette dernière impose la consultation d'une autre table, les requêtes imbriquées peuvent être utilisées.

6.3. Extensions de syntaxe

Pour des questions de commodité, nous avons le plus souvent présenté des syntaxes réduites des différentes commandes. Dans cette section, nous en présentons des syntaxes élargies. Elles se comprennent d'elles-mêmes car elles sont analogues à celles de la commande SELECT largement étudiée.

INSERT

syntaxe1
INSERT INTO table (col1, col2, ..) VALUES (val1, val2, ...)
syntaxe2
INSERT INTO table (col1, col2, ..) (requête)
explication
Ces deux syntaxes ont été présentées

DELETE

syntaxe1
DELETE FROM table WHERE condition
explication
Cette syntaxe est connue. Ajoutons que la condition peut contenir une requête avec la syntaxe WHERE expression opérateur (requête)

UPDATE

syntaxe1
UPDATE table
SET col1=expr1, col2=expr2, ...
WHERE condition
explication
Cette syntaxe a déjà été présentée. Ajoutons que la condition peut contenir une requête avec la syntaxe WHERE expression opérateur (requête)
syntaxe2
UPDATE table
SET (col1, col2, ..)= requête1, (cola, colb, ..)= requête2, ...
WHERE condition
explication
Les valeurs affectées aux différentes colonnes peuvent provenir d'une requête.