Retour au cours

data / sql

Sous-requêtes

Leçon 61 exercice

Explication

Ce que vous allez apprendre

  • Écrire une sous-requête scalaire dans un WHERE (ex : comparer à une moyenne)
  • Utiliser IN, EXISTS et NOT EXISTS pour tester une appartenance ou une présence
  • Comprendre pourquoi NOT IN est dangereux en présence de valeurs NULL
  • Construire une sous-requête corrélée qui dépend de chaque ligne externe
  • Utiliser une sous-requête comme table dérivée dans un FROM

Dans quel contexte ?

Une équipe data doit identifier, dans la table produits, le produit le plus cher de chaque catégorie pour un tableau de bord commercial. Le calcul dépend d'une comparaison ligne par ligne avec un maximum calculé "à l'intérieur" de chaque catégorie : exactement le rôle d'une sous-requête corrélée, expliquée en détail plus bas dans cette leçon.

D'abord, l'idée centrale en une phrase

Une sous-requête est simplement une requête SELECT utilisée comme brique à l'intérieur d'une autre requête. C'est comme utiliser le résultat d'un calcul intermédiaire dans une formule plus large.

Un exemple pour bien comprendre

Imagine la question "quels produits coûtent plus cher que le prix moyen ?". Il faut d'abord calculer ce prix moyen, PUIS comparer chaque produit à ce chiffre. La sous-requête calcule justement ce prix moyen, à l'intérieur même du WHERE.

Une fois ça acquis, où peut-on en mettre d'autres ?

Une sous-requête peut vivre à trois endroits. Dans le WHERE, comme on vient de le voir. Dans le FROM, pour construire une table temporaire qu'on interroge ensuite (on l'appelle alors "table dérivée"). Dans le SELECT, pour calculer une valeur supplémentaire par ligne.

Un raffinement : la sous-requête corrélée

Une sous-requête "indépendante" est calculée une seule fois, à part. Mais parfois, elle doit dépendre de chaque ligne de la requête externe — par exemple pour trouver "le produit le plus cher DE CHAQUE catégorie". On dit alors qu'elle est corrélée : elle est recalculée pour chaque ligne traitée, ce qui coûte plus cher en performance.

Un piège classique qu'il faut absolument connaître

Avec NOT IN, si la liste retournée par la sous-requête contient ne serait-ce qu'un seul NULL, la requête entière ne retourne plus AUCUNE ligne, sans le moindre message d'erreur. C'est la même logique du "NULL = inconnu" déjà vue en leçon 2.

Piège fréquent

WHERE id NOT IN (SELECT client_id FROM commandes) retourne zéro ligne dès qu'une seule commande a un client_id NULL (commande anonyme par exemple), alors que l'intention était de lister les clients sans commande. Ce bug est redoutable car il ne produit aucune erreur visible.

La parade fiable

La solution est d'utiliser NOT EXISTS à la place : il n'a pas ce défaut et s'arrête dès qu'il trouve une correspondance, ce qui le rend souvent plus rapide en plus d'être plus sûr.

OutilSensible aux NULL ?PerformanceUsage typique
IN (sous-requête)NonCorrectetester une appartenance simple
NOT IN (sous-requête)Oui, dangereuxCorrecteà éviter si NULL possible
EXISTSNonBonne, s'arrête au 1er matchtester une présence liée
NOT EXISTSNonBonne, sûreremplacer NOT IN en sécurité

Bonne pratique

Prends l'habitude d'écrire systématiquement NOT EXISTS plutôt que NOT IN dès que la colonne de la sous-requête peut contenir un NULL, ce qui est le cas la plupart du temps en production.

Et la suite

Les sous-requêtes préparent le terrain pour les CTE, vues en leçon 11, qui offrent une façon bien plus lisible d'organiser ces mêmes idées étape par étape.

Commandes & code

Sous-requêtes

sql
-- Sous-requête scalaire (retourne une seule valeur) dans WHERE
SELECT nom, prix
FROM produits
WHERE prix > (SELECT AVG(prix) FROM produits);

-- Sous-requête avec IN
SELECT nom
FROM clients
WHERE id IN (SELECT client_id FROM commandes WHERE montant > 500);

-- Sous-requête avec NOT IN -- attention aux NULL !
-- Si la sous-requête retourne ne serait-ce qu'un NULL, NOT IN ne retourne AUCUNE ligne
SELECT nom FROM clients
WHERE id NOT IN (SELECT client_id FROM commandes WHERE client_id IS NOT NULL);

-- Préférer NOT EXISTS à NOT IN (sûr vis-à-vis des NULL, souvent plus rapide)
SELECT c.nom
FROM clients c
WHERE NOT EXISTS (
    SELECT 1 FROM commandes co WHERE co.client_id = c.id
);

-- EXISTS : tester la présence de lignes liées
SELECT c.nom
FROM clients c
WHERE EXISTS (
    SELECT 1 FROM commandes co WHERE co.client_id = c.id AND co.montant > 1000
);

-- Sous-requête corrélée : référence une colonne de la requête externe
SELECT p.nom, p.prix, p.categorie
FROM produits p
WHERE p.prix = (
    SELECT MAX(p2.prix)
    FROM produits p2
    WHERE p2.categorie = p.categorie   -- corrélation : dépend de chaque ligne de p
);
-- Le produit le plus cher DE CHAQUE catégorie

-- Sous-requête dans le FROM (table dérivée) : doit avoir un alias
SELECT categorie, prix_moyen
FROM (
    SELECT categorie, AVG(prix) AS prix_moyen
    FROM produits
    GROUP BY categorie
) AS stats_categorie
WHERE prix_moyen > 100;

-- Sous-requête dans le SELECT (retourne une valeur par ligne)
SELECT
    c.nom,
    (SELECT COUNT(*) FROM commandes co WHERE co.client_id = c.id) AS nb_commandes
FROM clients c;

-- ANY / ALL
SELECT nom, prix FROM produits
WHERE prix > ANY (SELECT prix FROM produits WHERE categorie = 'jeux');  -- > au moins un
SELECT nom, prix FROM produits
WHERE prix > ALL (SELECT prix FROM produits WHERE categorie = 'jeux');  -- > tous

Résumé

  • NOT IN est dangereux avec des NULL : préférer NOT EXISTS.
  • Une sous-requête corrélée est réévaluée pour chaque ligne externe (coût potentiellement élevé).
  • Une table dérivée (FROM (SELECT ...)) doit obligatoirement porter un alias.
  • EXISTS/NOT EXISTS s'arrêtent au premier match trouvé (souvent plus rapides que IN).

Exercices pratiques

1 disponible
1

Mission : corriger un rapport de clients inactifs qui ment

Objectif : Remplacer un NOT IN dangereux par NOT EXISTS, et écrire une sous-requête corrélée pour un classement par catégorie.

Contexte

Un rapport censé lister les clients n'ayant jamais commandé, basé sur WHERE id NOT IN (SELECT client_id FROM commandes), revient vide depuis une semaine, alors que le service client confirme qu'il existe bien des clients sans commande. Tu dois diagnostiquer la cause et fournir une version fiable, puis produire un classement du produit le plus cher de chaque catégorie.

Résoudre l’exercice →