Requêtes SQL avancées : tris, regroupements et sous-requêtes
Extraire des informations à l’aide de ORDER BY, GROUP BY, HAVING, fonctions SQL et sous-requêtes, en lien avec des besoins de pilotage.
Introduction
Dans la leçon précédente, nous avons vu comment extraire des informations d’une base de données avec les instructions fondamentales de SQL-92 : SELECT, FROM, WHERE et les jointures (INNER JOIN, LEFT JOIN, RIGHT JOIN).
Cette nouvelle étape approfondit la compétence « manipuler des données d’une base de données » dans sa dimension décisionnelle : il ne s’agit plus seulement de lister des lignes, mais de répondre à un besoin d’information plus élaboré.
En pratique, un gestionnaire ne demande pas seulement :
- « affiche les factures » ;
il demande plutôt :
- « classe les clients par chiffre d’affaires » ;
- « calcule le total des ventes par commercial » ;
- « ne garde que les commerciaux ayant dépassé un objectif » ;
- « affiche les produits dont le prix est supérieur au prix moyen » ;
- « trouve les clients qui n’ont passé aucune commande ».
Pour répondre à ces besoins, on mobilise principalement :
ORDER BYpour le tri ;- les fonctions SQL (
COUNT,SUM,AVG,MIN,MAX, etc.) ; GROUP BYpour le regroupement ;HAVINGpour filtrer les groupes ;- les sous-requêtes pour construire une requête à partir du résultat d’une autre.
L’objectif de cette leçon est donc de vous rendre capable d’écrire des requêtes d’extraction de données en réponse à un besoin d’information, avec une logique proche du pilotage de gestion.
Objectifs d’apprentissage
À l’issue de cette leçon, vous devrez être capable de :
- manipuler des données d’une base de données pour produire une information utile ;
- extraire des informations d’une base de données en triant les résultats ;
- utiliser des fonctions SQL pour calculer des indicateurs ;
- regrouper les données avec
GROUP BY; - filtrer des regroupements avec
HAVING; - construire des sous-requêtes simples et pertinentes ;
- comprendre la logique de traitement d’une requête SQL avancée.
1. Pourquoi les requêtes avancées sont indispensables
Dans un système d’information de gestion, la base de données stocke des faits bruts :
- clients,
- commandes,
- lignes de commande,
- produits,
- règlements,
- salariés,
- écritures, etc.
Mais une organisation ne pilote pas son activité à partir de données brutes. Elle a besoin d’information structurée.
1.1. De la donnée à l’information utile
Prenons un exemple simple.
Une table Commande contient :
id_commandedate_commandeid_clientmontant_total
Si l’on affiche simplement toutes les commandes, on obtient une liste.
Mais si l’on veut savoir :
- quel client commande le plus,
- combien de commandes ont été passées par mois,
- quels clients dépassent 10 000 € de chiffre d’affaires,
alors une simple sélection ne suffit plus.
Il faut :
- trier,
- agréger,
- regrouper,
- parfois comparer à une valeur calculée par une autre requête.
1.2. Logique de pilotage
Ces requêtes sont essentielles pour :
- produire des états de synthèse,
- établir des tableaux de bord,
- repérer des anomalies,
- comparer des performances,
- identifier des exceptions.
Autrement dit, elles permettent de transformer la base de données en outil d’aide à la décision.
2. Le tri des résultats avec ORDER BY
2.1. Rôle de ORDER BY
Par défaut, SQL ne garantit pas l’ordre d’affichage des résultats. Si l’on veut un ordre précis, il faut l’indiquer explicitement.
La clause ORDER BY sert à trier les lignes retournées par la requête.
2.2. Syntaxe générale
SELECT colonne1, colonne2
FROM table
ORDER BY colonne1;
Par défaut, le tri est croissant (ASC).
On peut préciser :
ASC: ordre croissant ;DESC: ordre décroissant.
SELECT nom_client, ville
FROM Client
ORDER BY nom_client ASC;
SELECT nom_client, encours
FROM Client
ORDER BY encours DESC;
2.3. Trier sur plusieurs colonnes
On peut combiner plusieurs critères de tri.
SELECT nom_client, ville
FROM Client
ORDER BY ville ASC, nom_client ASC;
Logique :
- d’abord tri par ville ;
- à l’intérieur de chaque ville, tri par nom.
2.4. Exemple de pilotage
Lister les produits du plus cher au moins cher :
SELECT reference_produit, libelle, prix_vente
FROM Produit
ORDER BY prix_vente DESC;
Utilité :
- repérer les produits haut de gamme,
- vérifier la cohérence de la politique tarifaire,
- préparer une analyse commerciale.
2.5. Point d’attention
ORDER BY intervient à la fin de la requête, après la sélection, les jointures, les regroupements éventuels et les filtres.
3. Les fonctions SQL d’agrégation
Pour extraire des informations d’une base de données, on ne veut pas toujours voir chaque ligne. On veut souvent un résultat synthétique.
C’est le rôle des fonctions d’agrégation.
3.1. Les principales fonctions
Les fonctions les plus courantes sont :
COUNT(): compterSUM(): totaliserAVG(): calculer une moyenneMIN(): valeur minimaleMAX(): valeur maximale
3.2. COUNT()
Compter toutes les lignes
SELECT COUNT(*)
FROM Commande;
Cette requête donne le nombre total de commandes.
Compter une colonne
SELECT COUNT(id_client)
FROM Commande;
En pratique, COUNT(colonne) compte les valeurs non nulles de la colonne.
3.3. SUM()
SELECT SUM(montant_total)
FROM Commande;
Cette requête calcule le montant cumulé des commandes.
3.4. AVG()
SELECT AVG(montant_total)
FROM Commande;
Elle calcule le montant moyen d’une commande.
3.5. MIN() et MAX()
SELECT MIN(prix_vente), MAX(prix_vente)
FROM Produit;
On obtient le produit le moins cher et le plus cher en valeur de prix.
3.6. Pourquoi ces fonctions sont utiles
Ces fonctions servent à produire des indicateurs simples mais essentiels :
- nombre de clients,
- montant total facturé,
- panier moyen,
- prix minimum et maximum,
- nombre de commandes par période.
Elles sont donc au cœur de la compétence « écrire des requêtes d’extraction de données en réponse à un besoin d’information ».
4. Regrouper les données avec GROUP BY
Les fonctions d’agrégation deviennent encore plus puissantes lorsqu’on les applique par catégorie.
4.1. Principe
GROUP BY permet de regrouper les lignes ayant une même valeur sur une ou plusieurs colonnes, afin de calculer un résultat agrégé pour chaque groupe.
4.2. Exemple simple
Nombre de clients par ville :
SELECT ville, COUNT(*)
FROM Client
GROUP BY ville;
Lecture :
- on regroupe les clients par ville ;
- on compte le nombre de clients dans chaque ville.
4.3. Exemple commercial
Chiffre d’affaires par client :
SELECT id_client, SUM(montant_total) AS chiffre_affaires
FROM Commande
GROUP BY id_client;
Ici, chaque groupe correspond à un client.
4.4. Avec jointure pour rendre le résultat lisible
SELECT c.nom_client, SUM(co.montant_total) AS chiffre_affaires
FROM Client c
INNER JOIN Commande co ON c.id_client = co.id_client
GROUP BY c.nom_client;
Cette version est plus utile pour un utilisateur, car elle affiche le nom du client plutôt qu’un identifiant.
4.5. Règle fondamentale de GROUP BY
Quand une requête contient GROUP BY, alors dans le SELECT, chaque élément doit être :
- soit une colonne de regroupement,
- soit le résultat d’une fonction d’agrégation.
Par exemple, cette requête est cohérente :
SELECT ville, COUNT(*)
FROM Client
GROUP BY ville;
Mais celle-ci ne l’est pas :
SELECT ville, nom_client, COUNT(*)
FROM Client
GROUP BY ville;
Pourquoi ? Parce que nom_client n’est ni agrégé, ni présent dans GROUP BY.
SQL ne saurait pas quel nom choisir pour représenter tout le groupe.
4.6. Groupes sur plusieurs colonnes
SELECT annee, mois, SUM(montant_total)
FROM Vente
GROUP BY annee, mois;
On obtient ici un total par année et mois.
5. Filtrer les groupes avec HAVING
5.1. Différence entre WHERE et HAVING
C’est un point classique et fondamental.
WHEREfiltre les lignes avant regroupement.HAVINGfiltre les groupes après regroupement.
5.2. Exemple avec WHERE
SELECT id_client, SUM(montant_total)
FROM Commande
WHERE date_commande >= '2025-01-01'
GROUP BY id_client;
Ici, on ne garde que les commandes de 2025 avant de faire les totaux par client.
5.3. Exemple avec HAVING
SELECT id_client, SUM(montant_total) AS chiffre_affaires
FROM Commande
GROUP BY id_client
HAVING SUM(montant_total) > 10000;
Ici, on garde seulement les clients dont le chiffre d’affaires dépasse 10 000 €.
5.4. Pourquoi WHERE ne convient pas ici
On ne peut pas écrire :
WHERE SUM(montant_total) > 10000
car la somme n’existe qu’après le regroupement.
5.5. WHERE + GROUP BY + HAVING
Les trois peuvent être combinés.
SELECT id_client, COUNT(*) AS nb_commandes, SUM(montant_total) AS ca
FROM Commande
WHERE date_commande >= '2025-01-01'
GROUP BY id_client
HAVING COUNT(*) >= 3
ORDER BY ca DESC;
Lecture complète :
- on prend les commandes de 2025 ;
- on regroupe par client ;
- on ne conserve que les clients ayant au moins 3 commandes ;
- on trie du plus gros chiffre d’affaires au plus faible.
Cette requête illustre parfaitement une extraction d’information à finalité de pilotage.
6. Utiliser les alias pour clarifier la requête
Quand une requête devient plus riche, il est utile de nommer les colonnes calculées.
6.1. Alias de colonne
SELECT id_client, SUM(montant_total) AS chiffre_affaires
FROM Commande
GROUP BY id_client;
AS chiffre_affaires donne un nom lisible à la colonne calculée.
6.2. Alias de table
SELECT c.nom_client, SUM(co.montant_total) AS chiffre_affaires
FROM Client c
INNER JOIN Commande co ON c.id_client = co.id_client
GROUP BY c.nom_client;
Les alias :
- raccourcissent l’écriture ;
- améliorent la lisibilité ;
- évitent les ambiguïtés quand plusieurs tables ont des colonnes de même nom.
7. Les sous-requêtes : principe général
7.1. Définition
Une sous-requête est une requête insérée à l’intérieur d’une autre requête.
Elle permet d’utiliser le résultat d’une première extraction comme critère de la requête principale.
7.2. Pourquoi utiliser une sous-requête ?
Parce qu’un besoin d’information peut dépendre d’un calcul intermédiaire.
Exemples :
- afficher les produits plus chers que le prix moyen ;
- trouver les clients ayant passé une commande supérieure à la moyenne ;
- identifier les commandes du client ayant le plus gros chiffre d’affaires.
7.3. Exemple simple
Produits dont le prix est supérieur au prix moyen :
SELECT libelle, prix_vente
FROM Produit
WHERE prix_vente > (
SELECT AVG(prix_vente)
FROM Produit
);
7.4. Lecture logique
- la sous-requête calcule le prix moyen ;
- la requête principale sélectionne les produits dont le prix dépasse cette moyenne.
7.5. Intérêt pédagogique
La sous-requête permet de décomposer un raisonnement :
- d’abord je calcule une valeur de référence ;
- ensuite je compare chaque ligne à cette valeur.
8. Sous-requêtes avec opérateurs de comparaison
8.1. Sous-requête retournant une seule valeur
Si la sous-requête renvoie une seule valeur, on peut utiliser les opérateurs habituels :
=><>=<=<>
Exemple
Afficher les commandes dont le montant est supérieur au montant moyen :
SELECT id_commande, montant_total
FROM Commande
WHERE montant_total > (
SELECT AVG(montant_total)
FROM Commande
);
8.2. Cas d’usage
Cette logique est utile pour :
- repérer les ventes atypiques,
- isoler les clients au-dessus de la moyenne,
- identifier des valeurs extrêmes relatives.
9. Sous-requêtes avec IN
9.1. Principe
Quand la sous-requête renvoie plusieurs valeurs, on utilise souvent IN.
9.2. Exemple
Afficher les clients ayant passé au moins une commande :
SELECT nom_client
FROM Client
WHERE id_client IN (
SELECT id_client
FROM Commande
);
9.3. Lecture
- la sous-requête retourne la liste des identifiants de clients présents dans
Commande; - la requête principale conserve les clients dont l’identifiant appartient à cette liste.
9.4. Variante utile
Clients n’ayant passé aucune commande :
SELECT nom_client
FROM Client
WHERE id_client NOT IN (
SELECT id_client
FROM Commande
);
Utilité de gestion :
- relance commerciale,
- segmentation clients,
- détection d’inactivité.
10. Sous-requêtes avec EXISTS
10.1. Principe
EXISTS vérifie si une sous-requête retourne au moins une ligne.
C’est une logique d’existence, souvent très efficace conceptuellement.
10.2. Exemple
Afficher les clients ayant au moins une commande :
SELECT c.nom_client
FROM Client c
WHERE EXISTS (
SELECT *
FROM Commande co
WHERE co.id_client = c.id_client
);
10.3. Pourquoi cette forme est intéressante
Ici, la sous-requête dépend de la ligne courante de la requête principale. On parle souvent de sous-requête corrélée.
Pour chaque client, SQL vérifie s’il existe au moins une commande correspondante.
10.4. Version inverse
Clients sans commande :
SELECT c.nom_client
FROM Client c
WHERE NOT EXISTS (
SELECT *
FROM Commande co
WHERE co.id_client = c.id_client
);
11. Sous-requêtes dans FROM
11.1. Principe
Une sous-requête peut aussi être utilisée comme table temporaire dans la clause FROM.
11.2. Exemple
On veut afficher les clients et leur chiffre d’affaires, puis trier ce résultat.
SELECT resultat.nom_client, resultat.chiffre_affaires
FROM (
SELECT c.nom_client, SUM(co.montant_total) AS chiffre_affaires
FROM Client c
INNER JOIN Commande co ON c.id_client = co.id_client
GROUP BY c.nom_client
) AS resultat
ORDER BY resultat.chiffre_affaires DESC;
11.3. Intérêt
Cette technique permet de :
- structurer une requête complexe ;
- réutiliser un résultat intermédiaire ;
- clarifier le raisonnement.
12. Articuler tri, regroupement et sous-requête
Les requêtes les plus utiles combinent souvent plusieurs mécanismes.
12.1. Exemple complet
Besoin : afficher les clients dont le chiffre d’affaires est supérieur au chiffre d’affaires moyen des clients, classés du plus élevé au plus faible.
SELECT nom_client, chiffre_affaires
FROM (
SELECT c.nom_client, SUM(co.montant_total) AS chiffre_affaires
FROM Client c
INNER JOIN Commande co ON c.id_client = co.id_client
GROUP BY c.nom_client
) AS ca_client
WHERE chiffre_affaires > (
SELECT AVG(chiffre_affaires)
FROM (
SELECT SUM(montant_total) AS chiffre_affaires
FROM Commande
GROUP BY id_client
) AS moyennes
)
ORDER BY chiffre_affaires DESC;
12.2. Ce que fait cette requête
- elle calcule le chiffre d’affaires par client ;
- elle calcule ensuite la moyenne de ces chiffres d’affaires ;
- elle conserve les clients au-dessus de cette moyenne ;
- elle les trie par ordre décroissant.
12.3. Pourquoi c’est utile
Cette logique permet d’identifier les clients les plus performants relativement à la moyenne, et non seulement en valeur absolue.
13. Ordre logique de traitement d’une requête SQL
Pour bien écrire une requête, il faut distinguer :
- l’ordre d’écriture,
- l’ordre logique de traitement.
13.1. Ordre d’écriture
On écrit généralement :
SELECTFROMWHEREGROUP BYHAVINGORDER BY
13.2. Ordre logique de traitement
Mais SQL raisonne plutôt ainsi :
FROM: choix des tablesJOIN: rapprochement des tablesWHERE: filtre sur les lignesGROUP BY: constitution des groupesHAVING: filtre sur les groupesSELECT: choix des colonnes affichéesORDER BY: tri final
13.3. Pourquoi c’est important
Cela permet de comprendre :
- pourquoi
WHEREne peut pas utiliserSUM(); - pourquoi
HAVINGintervient aprèsGROUP BY; - pourquoi le tri ne s’applique qu’au résultat final.
14. Étude de cas progressive
Nous allons travailler sur quatre tables simplifiées :
14.1. Tables utilisées
Client
id_clientnom_clientville
Commande
id_commandedate_commandeid_clientmontant_total
Produit
id_produitlibelleprix_vente
LigneCommande
id_commandeid_produitquantiteprix_unitaire
14.2. Cas 1 — Trier une liste de clients
Besoin : afficher les clients par ordre alphabétique.
SELECT nom_client, ville
FROM Client
ORDER BY nom_client ASC;
Analyse :
- pas de regroupement ;
- pas de calcul ;
- seulement une organisation lisible des résultats.
14.3. Cas 2 — Compter le nombre de clients par ville
SELECT ville, COUNT(*) AS nb_clients
FROM Client
GROUP BY ville
ORDER BY nb_clients DESC;
Analyse :
- regroupement par ville ;
- comptage des lignes de chaque groupe ;
- tri décroissant pour repérer les villes les plus représentées.
14.4. Cas 3 — Chiffre d’affaires par client
SELECT c.nom_client, SUM(co.montant_total) AS chiffre_affaires
FROM Client c
INNER JOIN Commande co ON c.id_client = co.id_client
GROUP BY c.nom_client
ORDER BY chiffre_affaires DESC;
Analyse :
- jointure entre clients et commandes ;
- total par client ;
- tri du plus gros au plus petit.
14.5. Cas 4 — Clients ayant dépassé 5 000 €
SELECT c.nom_client, SUM(co.montant_total) AS chiffre_affaires
FROM Client c
INNER JOIN Commande co ON c.id_client = co.id_client
GROUP BY c.nom_client
HAVING SUM(co.montant_total) > 5000
ORDER BY chiffre_affaires DESC;
Analyse :
- on filtre les groupes, pas les lignes ;
- on conserve seulement les clients au-dessus du seuil.
14.6. Cas 5 — Produits plus chers que le prix moyen
SELECT libelle, prix_vente
FROM Produit
WHERE prix_vente > (
SELECT AVG(prix_vente)
FROM Produit
)
ORDER BY prix_vente DESC;
Analyse :
- sous-requête scalaire ;
- comparaison à une moyenne globale.
14.7. Cas 6 — Clients sans commande
SELECT nom_client
FROM Client
WHERE id_client NOT IN (
SELECT id_client
FROM Commande
)
ORDER BY nom_client;
Analyse :
- extraction des clients inactifs ;
- très utile pour le suivi commercial.
15. Erreurs fréquentes à éviter
15.1. Confondre WHERE et HAVING
Erreur :
SELECT id_client, SUM(montant_total)
FROM Commande
WHERE SUM(montant_total) > 1000
GROUP BY id_client;
Correction :
SELECT id_client, SUM(montant_total)
FROM Commande
GROUP BY id_client
HAVING SUM(montant_total) > 1000;
15.2. Oublier une colonne dans GROUP BY
Si une colonne apparaît dans SELECT sans agrégation, elle doit figurer dans GROUP BY.
15.3. Croire que l’ordre est automatique
Sans ORDER BY, le résultat peut sembler trié… mais ce n’est pas garanti.
15.4. Sous-requête renvoyant plusieurs lignes avec =
Si la sous-requête renvoie plusieurs valeurs, = ne convient pas. Il faut souvent IN.
15.5. Requête juste techniquement mais peu lisible
Une requête correcte mais mal présentée est difficile à contrôler. Il faut :
- utiliser des alias,
- aligner les clauses,
- nommer les colonnes calculées.
16. Méthode de construction d’une requête avancée
Pour répondre à un besoin d’information, adoptez une démarche structurée.
Étape 1 — Identifier précisément le besoin
Exemple :
- « Je veux le total des ventes par client »
- « Je veux seulement ceux qui dépassent 10 000 € »
- « Je veux les classer du plus fort au plus faible »
Étape 2 — Repérer les tables utiles
Ici :
ClientCommande
Étape 3 — Déterminer s’il faut une jointure
Oui, pour relier le nom du client à ses commandes.
Étape 4 — Déterminer s’il faut une agrégation
Oui, car on veut un total.
Étape 5 — Déterminer le critère de regroupement
Par client.
Étape 6 — Déterminer le type de filtre
- sur les lignes ?
WHERE - sur les groupes ?
HAVING
Étape 7 — Déterminer le tri final
ORDER BY chiffre_affaires DESC
Étape 8 — Tester la cohérence
Vérifier :
- les colonnes de regroupement,
- les alias,
- le sens du tri,
- le niveau de filtrage.
17. Exemples supplémentaires utiles en gestion
17.1. Nombre de commandes par client
SELECT id_client, COUNT(*) AS nb_commandes
FROM Commande
GROUP BY id_client;
17.2. Montant moyen des commandes par client
SELECT id_client, AVG(montant_total) AS moyenne_commandes
FROM Commande
GROUP BY id_client;
17.3. Client ayant passé le plus grand nombre de commandes
SELECT id_client, COUNT(*) AS nb_commandes
FROM Commande
GROUP BY id_client
ORDER BY nb_commandes DESC;
17.4. Produits vendus en quantité totale
SELECT id_produit, SUM(quantite) AS quantite_totale
FROM LigneCommande
GROUP BY id_produit
ORDER BY quantite_totale DESC;
17.5. Produits jamais commandés
SELECT p.libelle
FROM Produit p
WHERE NOT EXISTS (
SELECT *
FROM LigneCommande lc
WHERE lc.id_produit = p.id_produit
);
18. Lecture critique d’une requête
Savoir écrire une requête est important. Savoir la relire et la contrôler l’est tout autant.
Quand vous lisez une requête SQL avancée, posez-vous systématiquement ces questions :
- Quelles tables sont utilisées ?
- Quels liens existent entre elles ?
- Le filtre porte-t-il sur les lignes (
WHERE) ou sur les groupes (HAVING) ? - Quel est le regroupement demandé ?
- Quel indicateur est calculé ?
- Le tri final correspond-il au besoin ?
- La requête répond-elle vraiment à la question de gestion posée ?
Cette compétence de contrôle est essentielle dans la gestion des données du système d’information.
19. Mémo de syntaxe
Trier
SELECT colonnes
FROM table
ORDER BY colonne ASC;
Agréger sans regroupement
SELECT COUNT(*), SUM(colonne), AVG(colonne)
FROM table;
Regrouper
SELECT colonne_groupe, SUM(colonne_calculee)
FROM table
GROUP BY colonne_groupe;
Filtrer les groupes
SELECT colonne_groupe, SUM(colonne_calculee)
FROM table
GROUP BY colonne_groupe
HAVING SUM(colonne_calculee) > valeur;
Sous-requête scalaire
SELECT colonnes
FROM table
WHERE colonne > (
SELECT AVG(colonne)
FROM table
);
Sous-requête avec IN
SELECT colonnes
FROM table1
WHERE colonne IN (
SELECT colonne
FROM table2
);
Sous-requête avec EXISTS
SELECT colonnes
FROM table1 t1
WHERE EXISTS (
SELECT *
FROM table2 t2
WHERE t2.cle = t1.cle
);
20. Points à retenir
- Les requêtes avancées permettent de manipuler des données d’une base de données pour produire une information utile à la gestion.
ORDER BYsert à trier le résultat.- Les fonctions
COUNT,SUM,AVG,MIN,MAXservent à calculer des synthèses. GROUP BYsert à regrouper les lignes avant calcul.HAVINGsert à filtrer les groupes, alors queWHEREfiltre les lignes.- Une sous-requête permet d’utiliser le résultat d’une requête dans une autre.
- Pour bien construire une requête, il faut partir du besoin d’information, puis identifier les tables, les jointures, les regroupements, les filtres et le tri.
- La lisibilité de la requête est essentielle : alias, indentation, noms explicites.
Résumé final
Cette leçon s’inscrit pleinement dans la compétence de gestion des données du système d’information. Après avoir appris à sélectionner des lignes et à joindre des tables, vous savez désormais écrire des requêtes d’extraction de données en réponse à des besoins plus élaborés.
Vous pouvez maintenant :
- organiser un résultat avec
ORDER BY, - calculer des indicateurs avec les fonctions SQL,
- produire des synthèses avec
GROUP BY, - cibler les résultats pertinents avec
HAVING, - résoudre des besoins d’analyse avec les sous-requêtes.
Autrement dit, vous progressez d’une logique de simple consultation vers une logique de production d’information décisionnelle, ce qui est précisément l’un des rôles essentiels du système d’information de gestion.