Les Sous-Requêtes SQL

Imbriquer des SELECT — dans WHERE, FROM, SELECT, avec IN, EXISTS, ANY et ALL

9
Sections
20+
Exemples
SQL
Standard

SECTION 01

C’est quoi une sous-requête ?

🪆 Un SELECT dans un SELECT

Une sous-requête (ou subquery) est une requête SELECT imbriquée dans une autre requête. Elle est entourée de parenthèses et peut apparaître dans le WHERE, le FROM ou le SELECT.

— Requête simple : quel est le montant moyen ?
SELECT AVG(montant) FROM commandes; — 246

— Sous-requête : les commandes au-dessus de la moyenne
SELECT * FROM commandes
WHERE montant > (SELECT AVG(montant) FROM commandes);
— ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
— sous-requête scalaire

La sous-requête est exécutée en premier, puis son résultat est utilisé par la requête principale. C’est comme une variable temporaire calculée à la volée.

SECTION 02

Sous-requête dans le WHERE

🎯 Sous-requête scalaire (retourne UNE valeur)
— Commandes supérieures à la moyenne
SELECT produit, montant
FROM commandes
WHERE montant > (SELECT AVG(montant) FROM commandes);

— Le client qui a la commande la plus chère
SELECT * FROM clients
WHERE id = (
SELECT client_id FROM commandes
ORDER BY montant DESC LIMIT 1
);

— Le dernier produit commandé
SELECT * FROM commandes
WHERE date_commande = (SELECT MAX(date_commande) FROM commandes);

Une sous-requête scalaire doit retourner exactement une ligne et une colonne. Si elle retourne plusieurs lignes avec =, tu auras une erreur. Utilise IN pour plusieurs lignes.

SECTION 03

Sous-requête avec IN / NOT IN

📋 Retourne une liste de valeurs
— Clients qui ont passé au moins une commande
SELECT * FROM clients
WHERE id IN (SELECT client_id FROM commandes);

— Clients qui n’ont JAMAIS commandé
SELECT * FROM clients
WHERE id NOT IN (SELECT client_id FROM commandes);

— Produits achetés par Alice
SELECT * FROM produits
WHERE id IN (
SELECT produit_id FROM commandes
WHERE client_id = (SELECT id FROM clients WHERE nom = ‘Alice’)
);

⚠️ Piège de NOT IN avec NULL : si la sous-requête retourne un NULL parmi les valeurs, NOT IN ne retourne aucun résultat. Exemple : 5 NOT IN (1, 2, NULL) = UNKNOWN, pas TRUE. Utilise NOT EXISTS pour éviter ce piège.

SECTION 04

Sous-requête dans le FROM (table dérivée)

📦 Créer une « table temporaire »

Tu peux utiliser un SELECT comme source de données dans le FROM, comme si c’était une table. On appelle ça une table dérivée (ou inline view).

— Le CA par client, puis filtrer les top clients
SELECT sub.nom, sub.ca
FROM (
SELECT c.nom, SUM(co.montant) AS ca
FROM clients c
INNER JOIN commandes co ON c.id = co.client_id
GROUP BY c.nom
) AS sub
WHERE sub.ca > 500;

— Comparer chaque vendeur à la moyenne générale
SELECT v.vendeur, v.ca, moy.ca_moyen,
v.ca moy.ca_moyen AS ecart
FROM (
SELECT vendeur, SUM(montant) AS ca
FROM ventes GROUP BY vendeur
) AS v,
(
SELECT AVG(total) AS ca_moyen
FROM (SELECT SUM(montant) AS total FROM ventes GROUP BY vendeur) t
) AS moy;

La table dérivée doit avoir un alias (AS sub). Sans alias, MySQL, PostgreSQL et SQL Server renvoient une erreur. L’alternative moderne : utiliser un CTE (WITH).

SECTION 05

Sous-requête dans le SELECT

📊 Ajouter une colonne calculée
— Chaque commande + la moyenne globale
SELECT produit, montant,
(SELECT AVG(montant) FROM commandes) AS moyenne,
montant (SELECT AVG(montant) FROM commandes) AS ecart
FROM commandes;

— Nombre de commandes par client (sous-requête corrélée)
SELECT c.nom,
(SELECT COUNT(*) FROM commandes co
WHERE co.client_id = c.id) AS nb_commandes
FROM clients c;

La sous-requête dans le SELECT est corrélée quand elle référence la requête principale (co.client_id = c.id). Elle est exécutée pour chaque ligne — potentiellement lente sur de grandes tables. Préfère un LEFT JOIN + GROUP BY pour la performance.

SECTION 06

EXISTS / NOT EXISTS

✅ Tester l’existence de lignes

EXISTS retourne TRUE si la sous-requête retourne au moins une ligne. C’est une sous-requête corrélée — elle dépend de la requête principale.

— Clients qui ont au moins une commande
SELECT * FROM clients c
WHERE EXISTS (
SELECT 1 FROM commandes co
WHERE co.client_id = c.id
);

— Clients qui n’ont JAMAIS commandé (mieux que NOT IN)
SELECT * FROM clients c
WHERE NOT EXISTS (
SELECT 1 FROM commandes co
WHERE co.client_id = c.id
);

— Catégories avec au moins un produit en stock
SELECT * FROM categories cat
WHERE EXISTS (
SELECT 1 FROM produits p
WHERE p.categorie_id = cat.id AND p.stock > 0
);

NOT EXISTS est plus sûr que NOT IN car il gère correctement les NULL. C’est la méthode recommandée pour trouver les lignes « sans correspondance ». SELECT 1 est une convention — le contenu du SELECT dans EXISTS n’a pas d’importance, seule l’existence de lignes compte.

SECTION 07

Sous-requête vs JOIN

Critère Sous-requête JOIN
Lisibilité Logique étape par étape Plus compact pour les cas simples
Performance ⚠️ Corrélées = lentes (N+1) ✅ Généralement plus rapide
Colonnes multiples Une seule colonne retournée Accès à toutes les colonnes des deux tables
Filtrage d’existence EXISTS / NOT EXISTS LEFT JOIN … IS NULL
Calculs intermédiaires ✅ Idéal (table dérivée, CTE) Moins naturel
— MÊME RÉSULTAT — clients avec commandes

— Version sous-requête
SELECT * FROM clients
WHERE id IN (SELECT client_id FROM commandes);

— Version JOIN
SELECT DISTINCT c.*
FROM clients c
INNER JOIN commandes co ON c.id = co.client_id;

— Version EXISTS (recommandée pour les gros volumes)
SELECT * FROM clients c
WHERE EXISTS (SELECT 1 FROM commandes co WHERE co.client_id = c.id);

En pratique : utilise JOIN pour combiner les données de plusieurs tables. Utilise les sous-requêtes pour les calculs intermédiaires (moyennes, max, agrégats dans WHERE). Utilise EXISTS pour tester l’existence. Les optimiseurs modernes (PostgreSQL, MySQL 8+) réécrivent souvent les sous-requêtes en JOIN automatiquement.

SECTION 08

Erreurs fréquentes

Erreur Problème Solution
Sous-requête retourne plusieurs lignes avec = Erreur : subquery returns more than 1 row Utiliser IN au lieu de =
NOT IN avec des NULL Retourne 0 résultats Utiliser NOT EXISTS
Oublier l’alias dans le FROM Erreur de syntaxe Ajouter AS nom après la sous-requête
Sous-requête corrélée lente Exécutée pour chaque ligne (N+1) Réécrire en JOIN + GROUP BY
Sous-requête trop imbriquée Code illisible et difficile à débugger Utiliser des CTE (WITH)

SECTION 09

Questions fréquentes

C’est quoi une sous-requête corrélée ?
C’est une sous-requête qui référence la requête principale (ex : WHERE co.client_id = c.id). Elle est exécutée pour chaque ligne de la requête externe, ce qui la rend plus lente qu’une sous-requête indépendante. EXISTS et les sous-requêtes dans le SELECT sont souvent corrélées.
C’est quoi un CTE (WITH) ?
Un Common Table Expression : WITH nom AS (SELECT …) SELECT … FROM nom. C’est une alternative lisible aux sous-requêtes dans le FROM. Le CTE est nommé, réutilisable dans la requête, et plus facile à lire que des sous-requêtes imbriquées. Supporté par MySQL 8+, PostgreSQL, SQL Server et Oracle.
Peut-on imbriquer des sous-requêtes à l’infini ?
Techniquement oui (la limite dépend du SGBD — 255 niveaux en SQL Server par exemple). En pratique, au-delà de 2 niveaux, le code devient illisible. Utilise des CTE pour structurer les requêtes complexes.
IN vs EXISTS — lequel est plus rapide ?
Ça dépend du volume de données. EXISTS s’arrête dès qu’il trouve une correspondance (court-circuit). IN évalue toute la liste. Sur une petite sous-requête, IN est souvent aussi rapide. Sur de gros volumes ou avec des index, EXISTS est généralement meilleur. Les optimiseurs modernes les rendent souvent équivalents.

Les sous-requêtes SQL — SELECT imbriqués

Référence : sql.sh Sous-requêtes