Une requête imbriquée est une instruction SQL intégrée dans le corps de la requête SQL principale. On l’appelle aussi sous-requête, sous-sélection ou subquery en anglais selon les documentations officielles. Elle permet de résoudre des problèmes qu’une requête de sélection simple ne peut pas traiter en une seule passe. La requête interne s’exécute toujours en premier et transmet son résultat à la requête externe. Ce mécanisme rend vos instructions SQL dynamiques, car les valeurs de filtrage proviennent directement des données présentes en base.
Table des matières
Ordre d’exécution d’une sous-requête
Contrairement à ce que la lecture du code pourrait laisser penser, la subquery s’exécute avant la requête qui la contient. Son résultat est transmis à la requête externe, qui l’utilise comme entrée pour poursuivre son traitement. Ce point est essentiel : si la sous-requête retourne un résultat inattendu, la requête principale produira des données erronées sans lever d’erreur visible. Tester la requête imbriquée de façon isolée avant intégration est donc une étape incontournable. Le type de données retourné — valeur unique, colonne ou table — conditionne directement les opérateurs utilisables dans la requête externe.
Les trois formes de résultats d’une sous-requête
La valeur scalaire
Une subquery scalaire produit exactement une ligne et une colonne : c’est une valeur unique exploitable avec =, <, > ou tout autre opérateur de comparaison binaire. Son principal intérêt est de remplacer une constante codée en dur par une valeur extraite dynamiquement de la base. Si la sous-requête retourne plusieurs lignes dans ce contexte, le moteur SQL interrompt l’exécution avec une erreur. Il faut donc s’assurer que la sous-sélection est bien bornée à un résultat unique, par exemple via LIMIT 1 ou une fonction d’agrégation. C’est le cas d’usage le plus fréquent pour débuter avec les requêtes imbriquées en SQL.
On souhaite lister les produits dont le tarif dépasse le tarif moyen calculé sur l’ensemble du catalogue.
SELECT nom_produit, prix
FROM produits
WHERE prix > (SELECT AVG(prix) FROM produits);
La sous-requête produit un prix moyen global à partir de toutes les lignes de la table produits. La requête externe compare ensuite chaque tarif individuel à cette valeur de référence calculée à la volée. Si la table produits est vide, AVG retourne NULL et aucune ligne ne sera sélectionnée par la requête externe.
La liste de valeurs avec IN
Quand la subquery retourne plusieurs valeurs sur une seule colonne, l’opérateur IN prend le relais dans la clause WHERE. Cette construction évite d’écrire une longue liste statique de valeurs directement dans l’instruction SQL. Elle est particulièrement adaptée quand les identifiants à filtrer proviennent eux-mêmes d’une autre table de la base. L’opérateur NOT IN fonctionne selon le même principe pour les cas d’exclusion. Attention : si la sous-requête retourne au moins une valeur NULL, NOT IN peut produire un résultat vide de façon non intuitive.
On récupère les clients pour lesquels au moins une ligne existe dans la table commandes.
SELECT nom, prenom
FROM clients
WHERE id_client IN (
SELECT id_client FROM commandes
);
La sous-sélection extrait l’ensemble des identifiants présents dans la table commandes et les transmet à la clause IN. La requête principale ne retourne que les clients dont l’identifiant figure dans cet ensemble dynamique. Tout client absent de la table commandes est automatiquement exclu du résultat.
La table dérivée dans FROM
Placée dans la clause FROM, une requête imbriquée se comporte comme une table virtuelle temporaire. Elle ne persiste pas en base de données : le moteur la construit à chaque exécution de la requête principale. On parle de table dérivée, de vue inline ou encore de derived table dans les documentations officielles MySQL et PostgreSQL. Un alias est syntaxiquement obligatoire pour y faire référence dans la suite de l’instruction. Cette forme de subquery est idéale pour pré-agréger des données avant d’y appliquer un second filtre.
On veut isoler les clients ayant passé strictement plus de trois commandes, en passant par un comptage préalable.
SELECT t.id_client, t.nb_commandes
FROM (
SELECT id_client, COUNT(*) AS nb_commandes
FROM commandes
GROUP BY id_client
) AS t
WHERE t.nb_commandes > 3;
La table dérivée t agrège les commandes par client et calcule leur total dans une première passe. La requête externe filtre ensuite ce résultat intermédiaire pour ne conserver que les profils les plus actifs. Omettre l’alias AS t génère systématiquement une erreur de syntaxe, quelle que soit la version de SQL utilisée.
Où insérer une requête imbriquée dans une instruction SQL ?
Une sous-requête peut s’insérer à quatre emplacements distincts d’une instruction SQL standard. Dans WHERE, elle sert à construire un filtre dont la valeur de référence est calculée depuis la base elle-même. Dans HAVING, elle filtre des groupes agrégés après l’application d’un GROUP BY sur la table. Dans SELECT, elle ajoute une colonne calculée indépendamment pour chaque ligne du résultat. Dans FROM, elle constitue une table dérivée interrogeable comme n’importe quelle table physique.
Sous-requête corrélée dans SELECT
Intégrer une subquery dans la liste de colonnes du SELECT permet d’associer une statistique calculée à chaque ligne retournée. On parle de sous-requête corrélée lorsqu’elle fait référence à une colonne de la requête externe via un alias de table. Cette liaison ligne par ligne implique une exécution de la sous-requête pour chaque enregistrement traité par la requête principale. Sur des tables volumineuses, cette mécanique peut engendrer un nombre d’opérations très élevé et dégrader les performances. Réservez ce type de construction aux tables de taille maîtrisée ou aux contextes où une jointure n’est pas envisageable.
On affiche chaque article du catalogue avec le nombre de lignes de commande qui lui correspondent dans la table lignes_commande.
SELECT nom_produit,
(SELECT COUNT(*) FROM lignes_commande lc
WHERE lc.id_produit = p.id_produit) AS nb_commandes
FROM produits p;
La sous-requête corrélée est exécutée une fois par produit en se basant sur l’identifiant de la ligne courante p.id_produit. Elle retourne le nombre de lignes de commande associées à chaque produit du catalogue. COUNT(*) retourne toujours 0 si aucune correspondance n’existe, contrairement à d’autres fonctions d’agrégation qui produiraient NULL.
Sous-requête dans HAVING
La clause HAVING intervient après le regroupement des données produit par GROUP BY, contrairement à WHERE qui filtre avant agrégation. Elle accepte une requête imbriquée pour comparer dynamiquement une valeur agrégée à un seuil issu de la base. Cela permet d’éviter de figer une valeur de référence dans le code SQL, ce qui fragilise la requête dès que les données évoluent. Cette technique est particulièrement pertinente pour des rapports analytiques dont les seuils doivent rester synchronisés avec les données réelles. Elle complète utilement les usages de WHERE pour les cas où le filtrage porte sur un résultat d’agrégation.
On identifie les catégories de produits dont le prix moyen est supérieur à la moyenne générale du catalogue.
SELECT categorie, AVG(prix) AS prix_moyen
FROM produits
GROUP BY categorie
HAVING AVG(prix) > (SELECT AVG(prix) FROM produits);
La subquery calcule la moyenne globale sur l’ensemble des produits, indépendamment de leur catégorie. La clause HAVING compare ensuite cette référence au prix moyen de chaque groupe formé par GROUP BY. Les catégories en dessous de la moyenne générale sont automatiquement écartées du résultat.
Avantages concrets des requêtes imbriquées
La principale force d’une sous-requête est de centraliser la logique de calcul directement dans l’instruction SQL. Elle élimine le besoin d’une requête préalable côté applicatif pour récupérer une valeur de référence. Tester chaque niveau de la requête imbriquée séparément facilite considérablement le débogage et la mise au point. Une subquery bien construite rend l’instruction SQL auto-suffisante et indépendante du code applicatif environnant. Ce découplage améliore la lisibilité et la maintenabilité des scripts SQL sur le long terme.