%% nav-debut : généré par gen_navigation.py, ne pas modifier à la main %%
> [!nav]
> ↩ [[Code et Développement/Développement d'Applications/SQL IV/SQL IV|Accueil · SQL IV]]
>
> | [[Code et Développement/Développement d'Applications/SQL IV/08_Tester l'existence - EXISTS\|← Tester l'existence : EXISTS]] | [[Code et Développement/Développement d'Applications/SQL IV/10_Calculer sur un groupe - PARTITION BY\|Calculer sur un groupe : PARTITION BY →]] |
> |:---|---:|
%% nav-fin %%
[[Code et Développement/Développement d'Applications/SQL IV/08_Tester l'existence - EXISTS|Tester l'existence - EXISTS]]
# Sous-requêtes imbriquées
## Introduction
Une sous-requête est une requête comme une autre : elle peut donc, elle aussi, contenir une sous-requête. On parle alors de **sous-requêtes imbriquées**. Ce n'est pas une nouvelle notion, mais il faut de la méthode pour les lire et les écrire sans se perdre.
### Objectifs du chapitre
À la fin de ce chapitre, vous serez capable de :
- Lire une requête contenant plusieurs niveaux de sous-requêtes.
- Construire une telle requête niveau par niveau.
- (Pour aller plus loin) Rendre une requête plus lisible avec `WITH`.
Nous utilisons les tables *Clients* et *Commandes* présentées au chapitre [[Code et Développement/Développement d'Applications/SQL IV/01_Supprimer les doublons - DISTINCT|Supprimer les doublons : DISTINCT]].
---
## 1. Un exemple à trois niveaux
On nous demande, une dernière fois : ***les noms des clients dont le chiffre d'affaires total est supérieur à la moyenne du chiffre d'affaires de tous les clients.***
### Ce que vous savez déjà faire (SQL I)
Vous avez déjà écrit des sous-requêtes imbriquées sans le savoir ! La requête finale du chapitre [[Code et Développement/Développement d'Applications/SQL I/05_Comment répondre à une demande|Comment répondre à une demande]] (reprise au point 3 du chapitre [[Code et Développement/Développement d'Applications/SQL IV/06_Sous-requête renvoyant une valeur|Sous-requête renvoyant une valeur]]) contient :
- dans le `FROM` de la requête principale, une sous-requête qui calcule la moyenne des chiffres d'affaires ;
- dans le `FROM` de cette sous-requête, une autre sous-requête qui calcule le chiffre d'affaires de chaque client.
C'est déjà deux niveaux de sous-requêtes, tous placés dans des `FROM`.
### Une autre solution : des sous-requêtes dans le `WHERE` et le `HAVING`
Avec ce que nous avons vu dans SQL IV, on peut placer les sous-requêtes ailleurs que dans le `FROM`. Voici une autre requête qui répond à la demande :
```sql
SELECT nom_client
FROM Clients
WHERE id_client IN (
-- Niveau 2 : clients dont le CA dépasse la moyenne
SELECT id_client
FROM Commandes
GROUP BY id_client
HAVING SUM(montant_total) > (
-- Niveau 3 : CA moyen de tous les clients
SELECT AVG(ca_client)
FROM (
-- Niveau 4 : CA de chaque client (0 pour ceux sans commande)
SELECT IFNULL(SUM(Co.montant_total), 0) AS ca_client
FROM Clients C LEFT JOIN Commandes Co ON C.id_client = Co.id_client
GROUP BY C.id_client
) AS t
)
);
```
Elle paraît impressionnante. Pourtant, chaque niveau est une requête que vous savez lire.
---
## 2. Lire de l'intérieur vers l'extérieur
On commence par la sous-requête **la plus profonde** (la plus décalée vers la droite), puis on remonte.
**Niveau 4 : le chiffre d'affaires de chaque client.** Une requête sur deux tables avec `LEFT JOIN`, `GROUP BY` et `IFNULL`, comme en SQL I. Elle renvoie une **table classique** (d'où sa place dans un `FROM`, avec l'alias `t`) :
| ca_client |
| --------- |
| 370.00 |
| 950.00 |
| 150.00 |
| 400.00 |
| 0.00 |
**Niveau 3 : la moyenne de ces chiffres d'affaires.** Elle renvoie une table d'**une seule valeur** : 1870 / 5 = **374**.
**Niveau 2 : les clients dont le chiffre d'affaires dépasse 374.** On remplace mentalement le niveau 3 par sa valeur :
```sql
SELECT id_client
FROM Commandes
GROUP BY id_client
HAVING SUM(montant_total) > 374
```
Elle renvoie une table d'**un seul champ**, utilisée comme une liste : (2, 4).
**Niveau 1 : la requête principale.** On remplace mentalement le niveau 2 par sa liste :
```sql
SELECT nom_client
FROM Clients
WHERE id_client IN (2, 4);
```
| nom_client |
| ---------- |
| Dupont |
| Petit |
```mermaid
graph TD
A@{ shape: text, label: "Clients + Commandes" }-->N4["Niveau 4<br/>CA de chaque client"]
N4-->T@{ shape: text, label: "table : 370, 950, 150, 400, 0" }
T-->N3["Niveau 3<br/>moyenne des CA"]
N3-->V@{ shape: text, label: "valeur : 374" }
B@{ shape: text, label: "Commandes" }-->N2["Niveau 2<br/>clients dont le CA > 374"]
V-->N2
N2-->L@{ shape: text, label: "liste : 2, 4" }
C@{ shape: text, label: "Clients" }-->N1["Niveau 1<br/>noms de ces clients"]
L-->N1
N1-->R@{ shape: text, label: "Dupont, Petit" }
```
> [!tip] Pour chaque niveau, notez ce qu'il renvoie
> En lisant, notez à côté de chaque niveau la forme de la table renvoyée : **table classique**, table d'**une seule valeur** ou table d'**un seul champ**, et ce qu'elle représente. Vous vérifiez ainsi qu'elle est placée au bon endroit (voir le tableau du chapitre [[Code et Développement/Développement d'Applications/SQL IV/05_Les sous-requêtes - vue d'ensemble|Les sous-requêtes : vue d'ensemble]]).
---
## 3. Écrire de l'intérieur vers l'extérieur
Pour **écrire** une requête imbriquée, on suit le même chemin : on part de la requête la plus profonde.
1. **Faites le diagramme** des requêtes nécessaires, comme dans le chapitre [[Code et Développement/Développement d'Applications/SQL I/05_Comment répondre à une demande|Comment répondre à une demande]]. Chaque rectangle sera un niveau.
2. **Écrivez la requête la plus profonde et exécutez-la seule.** Vérifiez son résultat.
3. **Écrivez le niveau suivant** en y collant la requête précédente entre parenthèses, au bon endroit. Exécutez et vérifiez.
4. **Recommencez** jusqu'à la requête principale.
> [!warning] Ne pas tout écrire d'un coup
> Une requête imbriquée écrite d'un seul jet a toutes les chances d'être fausse, et l'erreur sera difficile à trouver. Testez **chaque niveau** avant de passer au suivant.
> [!tip] Rester lisible
> - **Indentez** : chaque niveau est décalé vers la droite.
> - **Commentez** chaque sous-requête avec `--` pour dire ce qu'elle renvoie.
> - **Nommez** clairement les alias et les champs calculés (`ca_client` plutôt que `x`).
---
## 4. Plusieurs écritures possibles
Il y a souvent plusieurs requêtes justes pour une même demande. La même demande a déjà été traitée :
- dans SQL I, avec un produit cartésien dans le `FROM` (chapitre [[Code et Développement/Développement d'Applications/SQL I/05_Comment répondre à une demande|Comment répondre à une demande]]) ;
- dans le chapitre [[Code et Développement/Développement d'Applications/SQL IV/06_Sous-requête renvoyant une valeur|Sous-requête renvoyant une valeur]], avec une sous-requête dans le `HAVING` ;
- ici, avec un `IN` et trois niveaux.
Choisissez **celle que vous comprenez et pouvez vérifier**. Moins il y a de niveaux, plus la requête est facile à relire.
---
## 5. Pour aller plus loin : nommer les étapes avec `WITH`
Quand les niveaux s'accumulent, on peut **donner un nom à chaque étape** avant la requête principale, avec `WITH`. Chaque étape nommée s'utilise ensuite comme une table.
```sql
WITH
ca_par_client AS (
-- CA de chaque client (0 pour ceux sans commande)
SELECT C.id_client, C.nom_client,
IFNULL(SUM(Co.montant_total), 0) AS ca_client
FROM Clients C LEFT JOIN Commandes Co ON C.id_client = Co.id_client
GROUP BY C.id_client, C.nom_client
),
moyenne AS (
-- CA moyen de tous les clients
SELECT AVG(ca_client) AS ca_moyen
FROM ca_par_client
)
SELECT ca_par_client.nom_client, ca_par_client.ca_client
FROM ca_par_client, moyenne
WHERE ca_par_client.ca_client > moyenne.ca_moyen;
```
| nom_client | ca_client |
| ---------- | --------- |
| Dupont | 950.00 |
| Petit | 400.00 |
La requête se lit maintenant **de haut en bas**, dans l'ordre du diagramme :
1. `ca_par_client` : le chiffre d'affaires de chaque client ;
2. `moyenne` : la moyenne de ces chiffres d'affaires (une seule ligne) ;
3. la requête principale : les clients dont le chiffre d'affaires dépasse la moyenne. La virgule dans le `FROM` ajoute la moyenne à chaque client (produit cartésien avec une table d'une seule ligne, comme en SQL I).
> [!note] Syntaxe de `WITH`
> - `WITH` s'écrit **une seule fois**, au tout début.
> - Chaque étape s'écrit `nom AS ( requête )` ; les étapes sont séparées par des **virgules**.
> - Une étape peut utiliser les étapes définies **avant** elle.
> - Pas de virgule après la dernière étape : on enchaîne directement avec le `SELECT` principal.
---
> [!important] Ce qu'il faut retenir
> - Une sous-requête peut contenir une sous-requête : chaque niveau renvoie une table classique, une table d'une seule valeur ou une table d'un seul champ.
> - On **lit** et on **écrit** de l'intérieur vers l'extérieur, en testant chaque niveau.
> - Indentation, commentaires et noms clairs rendent la requête lisible.
> - `WITH` permet de nommer les étapes et de lire la requête de haut en bas.
Prochaine étape, calculer sur un groupe sans perdre le détail, avec les partitions :
[[Code et Développement/Développement d'Applications/SQL IV/10_Calculer sur un groupe - PARTITION BY|Calculer sur un groupe : PARTITION BY]]
%% nav-debut : généré par gen_navigation.py, ne pas modifier à la main %%
> [!nav]
> ↩ [[Code et Développement/Développement d'Applications/SQL IV/SQL IV|Accueil · SQL IV]]
>
> | [[Code et Développement/Développement d'Applications/SQL IV/08_Tester l'existence - EXISTS\|← Tester l'existence : EXISTS]] | [[Code et Développement/Développement d'Applications/SQL IV/10_Calculer sur un groupe - PARTITION BY\|Calculer sur un groupe : PARTITION BY →]] |
> |:---|---:|
%% nav-fin %%
---
🗓️ Historique
Dernière mise à jour : `30 octobre 2026`
Rédigé par : [[Vincent TELLIER]]