%% 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/04_La jointure complète - FULL JOIN\|← La jointure complète : FULL JOIN]] | [[Code et Développement/Développement d'Applications/SQL IV/06_Sous-requête renvoyant une valeur\|Sous-requête renvoyant une valeur →]] | > |:---|---:| %% nav-fin %% [[Code et Développement/Développement d'Applications/SQL IV/04_La jointure complète - FULL JOIN|La jointure complète - FULL JOIN]] # Les sous-requêtes : vue d'ensemble ## Introduction Dans le chapitre [[Code et Développement/Développement d'Applications/SQL I/05_Comment répondre à une demande|Comment répondre à une demande]] de SQL I, nous avons vu qu'une demande peut nécessiter **plusieurs requêtes** : le résultat d'une requête sert d'entrée à une autre. Dans SQL III, vous avez placé une requête dans le `FROM` d'une autre requête. Une requête placée à l'intérieur d'une autre s'appelle une **sous-requête**. Dans SQL IV, nous allons voir qu'elle peut se placer **ailleurs que dans le `FROM`**. ### Objectifs du chapitre À la fin de ce chapitre, vous serez capable de : - Reconnaître une sous-requête et savoir dans quel ordre elle est exécutée. - Distinguer les trois formes de la table renvoyée par une sous-requête : une table classique, une table d'une seule valeur, une table d'un seul champ. - Savoir où chaque forme peut être placée. --- ## 1. Un besoin : comparer à un résultat calculé On nous demande : ***quelles sont les commandes dont le montant est supérieur au montant moyen des commandes ?*** Nous utilisons la table *Commandes* présentée au chapitre [[Code et Développement/Développement d'Applications/SQL IV/01_Supprimer les doublons - DISTINCT|Supprimer les doublons : DISTINCT]]. Une idée naturelle serait d'écrire : ```sql SELECT id_commande, montant_total FROM Commandes WHERE montant_total > AVG(montant_total); -- ERREUR ``` Cette requête provoque une erreur. Pourquoi ? Le `WHERE` examine les enregistrements **un par un**, alors qu'une moyenne se calcule sur **un ensemble** d'enregistrements. Une fonction d'agrégat ne peut donc pas être utilisée dans un `WHERE` (voir le chapitre [[Code et Développement/Développement d'Applications/SQL I/03_ Opérations entre les enregistrements|Opérations entre les enregistrements]]). Appliquons la méthode de SQL I : le montant moyen n'existe pas dans la base, **il faut une requête pour le calculer**. ```sql SELECT AVG(montant_total) FROM Commandes; ``` | AVG(montant_total) | | ------------------ | | 267.142857 | > [!example] Ce que vous savez déjà faire : la sous-requête dans le `FROM` > Avec la méthode de SQL I (appliquée en SQL III), on place cette requête dans le `FROM`, comme une table à part : > > ```sql > SELECT Co.id_commande, Co.montant_total > FROM Commandes Co, > (SELECT AVG(montant_total) AS montant_moyen FROM Commandes) AS m > WHERE Co.montant_total > m.montant_moyen; > ``` > > | id_commande | montant_total | > | ----------- | ------------- | > | 103 | 560.00 | > | 105 | 310.00 | > | 107 | 400.00 | > > La sous-requête `m` ne contient **qu'une seule ligne** (la moyenne). La virgule réalise un produit cartésien entre les commandes et cette ligne : cela revient à ajouter le champ `montant_moyen` à chaque commande. Le `WHERE` peut alors comparer les deux champs. > > Cette écriture est juste, et vous pouvez continuer à l'utiliser. Dans SQL IV, nous découvrons une écriture plus directe : placer la requête, **entre parenthèses**, là où on a besoin de sa valeur : ```sql SELECT id_commande, montant_total FROM Commandes WHERE montant_total > (SELECT AVG(montant_total) FROM Commandes); ``` | id_commande | montant_total | | ----------- | ------------- | | 103 | 560.00 | | 105 | 310.00 | | 107 | 400.00 | > [!definition] Définition : sous-requête > Une **sous-requête** est une requête `SELECT` écrite **entre parenthèses** à l'intérieur d'une autre requête, appelée **requête principale**. > [!note] Les deux écritures font le même travail > - Elles donnent le même résultat. > - Dans les deux cas, la moyenne n'est calculée **qu'une seule fois** : la sous-requête n'utilise aucun champ de la requête principale, son résultat est donc le même pour toutes les commandes. Le logiciel le calcule une fois et le réutilise pour chaque commande. > - La version dans le `WHERE` se lit comme la demande : « les commandes dont le montant est supérieur à (la moyenne) ». --- ## 2. Dans quel ordre est-ce exécuté ? La sous-requête est exécutée **en premier**. Son résultat remplace la sous-requête dans la requête principale, qui est exécutée ensuite. ```mermaid graph TD A@{ shape: text, label: "Commandes" }-->S["Sous-requête<br/>SELECT AVG(montant_total) FROM Commandes"] S-->V@{ shape: text, label: "267.14" } B@{ shape: text, label: "Commandes" }-->P["Requête principale"] V-->P P-->R@{ shape: text, label: "Commandes de plus de 267.14 €" } ``` Pour la requête principale, tout se passe comme si on avait écrit : ```sql SELECT id_commande, montant_total FROM Commandes WHERE montant_total > 267.142857; ``` > [!tip] Lire une requête avec sous-requête > On lit **de l'intérieur vers l'extérieur** : > 1. Je cherche la sous-requête (entre parenthèses) et je comprends ce qu'elle renvoie. > 2. Je remplace mentalement la sous-requête par son résultat. > 3. Je lis la requête principale comme une requête habituelle. > [!note] Une exception > Il existe des sous-requêtes qui utilisent un champ de la requête principale : elles sont exécutées **une fois pour chaque enregistrement** de la requête principale. Nous les verrons au chapitre [[Code et Développement/Développement d'Applications/SQL IV/08_Tester l'existence - EXISTS|Tester l'existence : EXISTS]]. --- ## 3. Trois formes de table > [!important] Rappel : le résultat d'une requête est toujours une table > Comme nous l'avons vu dans le chapitre [[Code et Développement/Développement d'Applications/SQL I/01_ Détails sur les 6 mots clés d'une requête sélection|Détails sur les 6 mots clés d'une requête sélection]], le résultat d'une requête `SELECT` est toujours une **table éphémère**. C'est vrai aussi pour une sous-requête. Ce qui change d'une sous-requête à l'autre, c'est **la forme** de cette table. Pour savoir **où** placer une sous-requête, il faut connaître cette forme. Il y a trois cas : | Forme de la table | Champs | Enregistrements | Exemple | Raccourci utilisé dans le cours | | --- | --- | --- | --- | --- | | **Table classique** | un ou plusieurs | un ou plusieurs | le chiffre d'affaires de chaque client | « une table » | | **Table d'une seule valeur** | un seul | un seul | le montant moyen des commandes | « une valeur » | | **Table d'un seul champ** | un seul | plusieurs | les identifiants des clients qui ont commandé | « une liste » | Exemples : **Table classique** : `SELECT id_client, SUM(montant_total) AS ca FROM Commandes GROUP BY id_client` | id_client | ca | | --------- | ------ | | 1 | 370.00 | | 2 | 950.00 | | 3 | 150.00 | | 4 | 400.00 | **Table d'une seule valeur** : `SELECT AVG(montant_total) FROM Commandes` | AVG(montant_total) | | ------------------ | | 267.142857 | **Table d'un seul champ** : `SELECT id_client FROM Commandes` | id_client | | --------- | | 1 | | 1 | | 2 | | 2 | | 2 | | 3 | | 4 | > [!note] Pourquoi ces raccourcis ? > Une table d'une seule valeur peut être utilisée **comme une valeur** (un nombre, une date…) : on peut la comparer avec `>` ou `=`. Une table d'un seul champ peut être utilisée **comme une liste de valeurs**, celle que l'on écrivait à la main après `IN` en SQL I. Dans la suite du cours, « la sous-requête renvoie une valeur » signifie donc « la sous-requête renvoie une table d'une seule valeur », et « une liste » signifie « une table d'un seul champ ». > [!tip] Une table d'une seule valeur reste une table > Elle peut donc aussi être placée dans le `FROM` : c'est ce que vous faites depuis SQL I (chapitre [[Code et Développement/Développement d'Applications/SQL I/05_Comment répondre à une demande|Comment répondre à une demande]]) et SQL III, et c'est l'encadré « Ce que vous savez déjà faire » du point 1. Dans SQL IV, vous découvrez qu'elle peut **aussi** se placer dans le `WHERE`, le `HAVING` ou le `SELECT`. Les deux possibilités sont justes. --- ## 4. Où placer une sous-requête ? | La sous-requête renvoie une table… | Je peux la placer… | Avec… | Chapitre | | --- | --- | --- | --- | | **classique** | dans le `FROM` | un alias obligatoire : `(SELECT …) AS t` | déjà vu en SQL III | | **d'une seule valeur** | dans le `FROM`, séparée des autres tables par une virgule | un alias obligatoire : `FROM Commandes Co, (SELECT AVG(…) …) AS m` ; la valeur est ajoutée à chaque enregistrement (produit cartésien avec une table d'une seule ligne) | déjà vu en SQL I et SQL III, et dans le point 1 ci-dessus | | **d'une seule valeur** | dans le `WHERE` ou le `HAVING` | `=`, `<>`, `<`, `>`, `<=`, `>=` | [[Code et Développement/Développement d'Applications/SQL IV/06_Sous-requête renvoyant une valeur\|Sous-requête renvoyant une valeur]] | | **d'une seule valeur** | dans le `SELECT` | un alias pour nommer la colonne | [[Code et Développement/Développement d'Applications/SQL IV/06_Sous-requête renvoyant une valeur\|Sous-requête renvoyant une valeur]] | | **d'un seul champ** | dans le `WHERE` ou le `HAVING` | `IN` ou `NOT IN` | [[Code et Développement/Développement d'Applications/SQL IV/07_Sous-requête renvoyant une liste - IN\|Sous-requête renvoyant une liste : IN]] | | **de n'importe quelle forme** (on teste seulement si elle contient au moins un enregistrement) | dans le `WHERE` | `EXISTS` ou `NOT EXISTS` | [[Code et Développement/Développement d'Applications/SQL IV/08_Tester l'existence - EXISTS\|Tester l'existence : EXISTS]] | > [!warning] La forme doit correspondre à l'endroit > - Avec `>` ou `=`, la sous-requête doit renvoyer une table d'**une seule valeur** (un seul champ, un seul enregistrement). On ne peut pas comparer un montant à une liste de montants. > - Avec `IN`, la sous-requête doit renvoyer une table d'**un seul champ**. > - Dans le `FROM`, la sous-requête doit avoir **un alias**. --- ## 5. Les bonnes habitudes > [!tip] Écrire une sous-requête en 3 temps > 1. **Écrivez et exécutez la sous-requête seule.** Vérifiez son résultat : est-ce une table classique, une table d'une seule valeur, une table d'un seul champ ? Les valeurs sont-elles correctes ? > 2. **Écrivez la requête principale** en remplaçant la sous-requête par une valeur « en dur » (par exemple `267.14`) et vérifiez qu'elle fonctionne. > 3. **Remplacez la valeur en dur par la sous-requête** entre parenthèses. > [!tip] Rendre la requête lisible > - Décalez la sous-requête vers la droite (indentation). > - Ajoutez un commentaire avec `--` pour dire ce qu'elle calcule. > > ```sql > SELECT id_commande, montant_total > FROM Commandes > WHERE montant_total > ( > -- montant moyen de toutes les commandes > SELECT AVG(montant_total) > FROM Commandes > ); > ``` > [!note] Le lien avec la méthode de SQL I > 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 du diagramme est une requête. Avec les sous-requêtes, chaque requête « intermédiaire » devient une sous-requête placée **là où son résultat est utilisé**. La fiche de préparation reste la même : on la remplit pour chaque requête, en commençant par les plus « profondes ». --- > [!important] Ce qu'il faut retenir > - Une sous-requête est une requête `SELECT` entre parenthèses, à l'intérieur d'une autre requête. > - Elle est exécutée en premier ; on lit donc de l'intérieur vers l'extérieur. > - Elle renvoie toujours une table : une **table classique**, une table d'**une seule valeur** (« une valeur ») ou une table d'**un seul champ** (« une liste »). C'est cette forme qui détermine où on peut la placer. > - On écrit et on teste toujours la sous-requête seule avant de l'insérer. Prochaine étape, les sous-requêtes qui renvoient une seule valeur : [[Code et Développement/Développement d'Applications/SQL IV/06_Sous-requête renvoyant une valeur|Sous-requête renvoyant une valeur]] %% 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/04_La jointure complète - FULL JOIN\|← La jointure complète : FULL JOIN]] | [[Code et Développement/Développement d'Applications/SQL IV/06_Sous-requête renvoyant une valeur\|Sous-requête renvoyant une valeur →]] | > |:---|---:| %% nav-fin %% --- 🗓️ Historique Dernière mise à jour : `30 octobre 2026` Rédigé par : [[Vincent TELLIER]]