SQL : les sous-requêtes (IN, EXISTS, corrélées)
Une sous-requête est une requête écrite à l'intérieur d'une autre, entre parenthèses. Elle permet de répondre en une seule fois à des questions en deux étapes, comme « quels produits coûtent plus cher que la moyenne ? » : il faut d'abord calculer la moyenne, puis comparer chaque produit à cette valeur.
Syntaxe
SELECT colonnes FROM table
WHERE colonne > (SELECT AVG(colonne) FROM table);
WHERE colonne IN (SELECT colonne FROM autre_table)
WHERE EXISTS (SELECT 1 FROM autre_table WHERE …)Comment ça marche
Les trois formes les plus courantes
- Une valeur : la sous-requête renvoie une seule valeur, que l'on compare avec
=,>,<… - Une liste : la sous-requête renvoie une colonne, utilisée avec
INouNOT IN. - Un test d'existence :
EXISTS (…)est vrai si la sous-requête renvoie au moins une ligne.
Les sous-requêtes corrélées
Une sous-requête corrélée fait référence à la ligne en cours de la requête principale. Elle est donc recalculée pour chaque ligne. Exemple : comparer le salaire de chaque employé à la moyenne de son propre département.
Dans le FROM
Une sous-requête peut aussi servir de table temporaire dans le FROM (on lui donne alors un alias). Pour les requêtes complexes, une CTE (WITH) est souvent plus lisible.
Schéma
| nom | prix |
|---|---|
| Clavier | 49 |
| Plaid | 25 |
| Gourde | 12 |
| Casque | 34 |
| nom | prix |
|---|---|
| Clavier | 49 |
| Casque | 34 |
Schéma simplifié pour illustrer le principe. Les exemples ci-dessous utilisent la vraie base d'exercices de SQL Quest (une boutique en ligne fictive : voir le schéma complet).
Exemples avec leurs résultats
1. Comparer à une valeur calculée
Les produits plus chers que le prix moyen.
SELECT nom, prix
FROM produits
WHERE prix > (SELECT AVG(prix) FROM produits)
ORDER BY prix DESC; Essayer dans le bac à sable| nom | prix |
|---|---|
| Écran 27" | 342 |
| SSD 1To | 317 |
| Souris sans fil | 103 |
| Webcam HD | 101 |
| Casque gaming | 98 |
| Bougie parfumée | 60,92 |
6 lignes au total
2. IN avec une sous-requête
Les clients qui ont au moins une commande annulée.
SELECT prenom, nom, ville
FROM clients
WHERE id IN (SELECT client_id FROM commandes WHERE statut = 'annulée')
LIMIT 8; Essayer dans le bac à sable| prenom | nom | ville |
|---|---|---|
| Samir | Dubois | Lille |
| Samir | Martin | Lille |
| Zoé | Laurent | Paris |
| Adam | Dubois | Casablanca |
| Alice | Faure | Lyon |
| Léa | Blanc | Toulouse |
| Jade | Garcia | Paris |
| Camille | Laurent | Bordeaux |
8 lignes au total
3. Sous-requête corrélée
Les employés qui gagnent plus que la moyenne de leur département.
SELECT e.prenom, e.nom, e.departement, e.salaire
FROM employes e
WHERE e.salaire > (SELECT AVG(x.salaire) FROM employes x WHERE x.departement = e.departement); Essayer dans le bac à sable| prenom | nom | departement | salaire |
|---|---|---|---|
| Jade | Lefebvre | Ventes | 6 200 |
| Nathan | Roux | IT | 6 800 |
| Samir | Simon | RH | 4 800 |
| Alice | Simon | Ventes | 4 800 |
| Lucas | Moreau | Ventes | 4 600 |
| Inès | Mercier | Ventes | 5 500 |
| Chloé | Benali | RH | 4 900 |
| Louis | Chevalier | RH | 5 300 |
| Louis | Garcia | Ventes | 5 500 |
| Lina | Simon | Ventes | 5 200 |
12 lignes au total · 10 affichées
4. NOT EXISTS
Les clients qui n'ont jamais commandé, écrits avec NOT EXISTS.
SELECT c.prenom, c.nom
FROM clients c
WHERE NOT EXISTS (SELECT 1 FROM commandes o WHERE o.client_id = c.id); Essayer dans le bac à sable| prenom | nom |
|---|---|
| Manon | Roux |
| Alice | Bernard |
| Emma | Moreau |
| Nathan | Lefebvre |
| Sarah | Simon |
| Karim | Lefebvre |
| Théo | Chevalier |
| Chloé | Lambert |
| Lucas | Benali |
| Inès | Lambert |
10 lignes au total
Les erreurs fréquentes
Utiliser = avec une sous-requête qui renvoie plusieurs lignes
= attend une seule valeur. SQLite prend silencieusement la première, d'autres bases renvoient une erreur. Utilisez IN.
SELECT * FROM clients WHERE id = (SELECT client_id FROM commandes);SELECT * FROM clients WHERE id IN (SELECT client_id FROM commandes);NOT IN avec des NULL
Si la sous-requête renvoie un seul NULL, NOT IN ne renvoie plus aucune ligne. NOT EXISTS n'a pas ce problème.
SELECT * FROM employes WHERE id NOT IN (SELECT manager_id FROM employes);SELECT * FROM employes e WHERE NOT EXISTS (SELECT 1 FROM employes x WHERE x.manager_id = e.id);Oublier l'alias d'une sous-requête dans FROM
Une sous-requête utilisée comme table doit recevoir un nom pour pouvoir être utilisée ensuite.
SELECT AVG(nb) FROM (SELECT COUNT(*) AS nb FROM commandes GROUP BY client_id);SELECT AVG(nb) FROM (SELECT COUNT(*) AS nb FROM commandes GROUP BY client_id) AS t;Exercices
Essayez d'écrire la requête vous-même avant d'ouvrir la solution.
Défi 1. Affichez le ou les produits les plus chers de chaque catégorie (sous-requête corrélée).
Voir une solution
SELECT categorie, nom, prix FROM produits p WHERE prix = (SELECT MAX(prix) FROM produits WHERE categorie = p.categorie);| categorie | nom | prix |
|---|---|---|
| Informatique | Écran 27" | 342 |
| Maison | Bougie parfumée | 60,92 |
| Sport | Raquette | 46,18 |
| Livres | Manga tome 1 | 28,92 |
| Alimentation | Huile d'olive | 40,06 |
5 lignes au total
Essayer dans le bac à sableDéfi 2. Affichez les employés qui ne managent personne.
Voir une solution
SELECT prenom, nom FROM employes e WHERE NOT EXISTS (SELECT 1 FROM employes x WHERE x.manager_id = e.id);| prenom | nom |
|---|---|
| Alice | Simon |
| Nathan | Martin |
| Lucas | Moreau |
| Emma | Lambert |
| Alice | Lefebvre |
| Inès | Mercier |
| Hugo | Bonnet |
| Chloé | Benali |
21 lignes au total · 8 affichées
Essayer dans le bac à sableLes exercices interactifs sont corrigés automatiquement, avec un prof qui explique vos erreurs. Une leçon résumée reste affichée à côté de l'éditeur.
Questions fréquentes
Sous-requête ou jointure : laquelle choisir ?
Les deux peuvent souvent donner le même résultat. Une sous-requête est plus lisible pour « filtrer selon une autre table », une jointure est nécessaire pour afficher des colonnes des deux tables.
Quelle différence entre IN et EXISTS ?
IN compare une valeur à une liste, EXISTS teste simplement si une ligne existe. NOT EXISTS est plus sûr que NOT IN quand la sous-requête peut contenir des NULL.
Une sous-requête corrélée est-elle lente ?
Elle est conceptuellement exécutée une fois par ligne, ce qui peut coûter cher sur de grosses tables. Les bases modernes l'optimisent souvent, mais une jointure ou une fonction de fenêtre est parfois plus rapide.