%% 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]]