L’instruction SQL GROUP BY regroupe les lignes d’une table qui partagent la même valeur dans une ou plusieurs colonnes, afin d’appliquer une fonction d’agrégation sur chaque groupe.
Table des matières
Syntaxe de base
La clause GROUP BY se place après WHERE et avant HAVING et ORDER BY dans une requête. Elle nécessite obligatoirement une fonction d’agrégation dans SELECT pour calculer un résultat par groupe. Toute colonne listée dans SELECT sans fonction d’agrégation doit apparaître dans GROUP BY, sans exception.
SELECT ville, COUNT(*) AS nb_clients
FROM clients
GROUP BY ville;
La fonction COUNT(*) compte toutes les lignes de chaque groupe, y compris celles avec des valeurs NULL. Le résultat retourne une ligne par valeur distincte de ville, avec son nombre total de clients associé.
Compter les lignes avec COUNT
COUNT(*) détermine le nombre de lignes présentes dans chaque groupe créé par GROUP BY. COUNT(colonne) ignore les lignes où cette colonne vaut NULL, contrairement à COUNT(*) qui les inclut. Ces deux formes produisent des résultats différents quand la colonne contient des valeurs manquantes.
La requête suivante compte le nombre de commandes par statut pour obtenir une vue d’ensemble du traitement en cours.
SELECT statut, COUNT(*) AS total_commandes
FROM commandes
GROUP BY statut;
Chaque statut distinct produit une ligne dans le résultat avec son nombre de commandes associé. Vérifiez si les valeurs NULL dans la colonne statut doivent être comptées ou exclues pour choisir entre COUNT(*) et COUNT(statut).
Additionner des valeurs avec SUM
SUM additionne toutes les valeurs numériques d’une colonne au sein de chaque groupe. Elle ignore automatiquement les valeurs NULL dans le calcul de la somme. Cette fonction convient aux totaux de chiffre d’affaires, de stocks ou de montants financiers.
La requête suivante calcule le chiffre d’affaires total par catégorie de produits en multipliant prix et quantité.
SELECT categorie, SUM(prix * quantite) AS chiffre_affaires
FROM ventes
GROUP BY categorie;
L’expression prix * quantite est calculée ligne par ligne avant que SUM additionne les résultats au sein de chaque catégorie. Vérifiez que les colonnes prix et quantite ne contiennent pas de valeurs NULL qui réduiraient le total calculé.
Calculer une moyenne avec AVG
AVG divise la somme des valeurs d’un groupe par le nombre de valeurs non NULL dans ce groupe. Elle retourne un résultat de type décimal, même si les valeurs source sont des entiers. Cette fonction convient aux analyses de prix moyens, d’âges ou de délais.
La requête suivante calcule l’âge moyen des clients regroupés par ville pour analyser le profil démographique.
SELECT ville, AVG(age) AS age_moyen
FROM clients
GROUP BY ville;
Les lignes dont la colonne age vaut NULL sont exclues du calcul de la moyenne. Utilisez ROUND(AVG(age), 1) pour limiter le résultat à une décimale si la précision complète n’est pas nécessaire.
Identifier les valeurs extrêmes avec MIN et MAX
MIN retourne la valeur la plus basse d’un groupe et MAX retourne la valeur la plus haute. Ces deux fonctions s’appliquent aux nombres, aux dates et aux chaînes de caractères. Elles s’utilisent fréquemment ensemble pour encadrer une plage de valeurs dans un même rapport.
La requête suivante identifie les prix minimum et maximum pratiqués par chaque fournisseur dans le catalogue.
SELECT fournisseur, MIN(prix) AS prix_min, MAX(prix) AS prix_max
FROM produits
GROUP BY fournisseur;
Chaque fournisseur produit une ligne avec ses deux prix extrêmes calculés sur l’ensemble de ses produits. Combinez MIN et MAX avec AVG pour obtenir une vue complète de la distribution des prix par groupe.
Filtrer les groupes avec HAVING
HAVING filtre les groupes produits par GROUP BY, contrairement à WHERE qui filtre les lignes individuelles avant le regroupement. Il se place obligatoirement après GROUP BY dans la requête. HAVING accepte les fonctions d’agrégation directement dans sa condition, ce que WHERE ne permet pas.
La requête suivante sélectionne les statuts de commandes dont le montant total dépasse 5 000 € depuis le début 2026.
SELECT statut_commande, SUM(montant) AS total
FROM commandes
WHERE date_commande >= '2026-01-01'
GROUP BY statut_commande
HAVING SUM(montant) > 5000
ORDER BY total DESC;
WHERE réduit d’abord les lignes aux commandes de 2026, puis GROUP BY regroupe par statut, et HAVING exclut les groupes dont le total reste sous 5 000 €. Les groupes restants sont triés par total décroissant grâce à ORDER BY.
Grouper sur des expressions de date
Les fonctions YEAR() et MONTH() extraient les composants d’une date pour créer des groupes mensuels ou annuels. Ces expressions doivent apparaître à la fois dans SELECT et dans GROUP BY pour respecter la règle des colonnes non agrégées. Cette forme est courante dans les rapports d’activité périodiques.
La requête suivante compte le nombre de commandes par mois et par année, triées de la plus récente à la plus ancienne.
SELECT YEAR(date_commande) AS annee,
MONTH(date_commande) AS mois,
COUNT(*) AS nb_commandes
FROM commandes
GROUP BY YEAR(date_commande), MONTH(date_commande)
ORDER BY annee DESC, mois DESC;
Chaque combinaison mois/année produit une ligne distincte dans le résultat. Vérifiez que les expressions dans GROUP BY sont identiques à celles du SELECT pour éviter une erreur de colonne orpheline.
Combiner GROUP BY avec une jointure
GROUP BY s’applique après que les jointures ont assemblé toutes les tables concernées. Les colonnes de regroupement doivent être préfixées par leur alias de table en cas d’ambiguïté. Cette combinaison produit des synthèses croisées entre plusieurs sources de données.
La requête suivante calcule le prix moyen des produits commandés par ville en croisant trois tables.
SELECT c.ville, AVG(p.prix) AS prix_moyen
FROM clients c
JOIN commandes com ON c.id = com.client_id
JOIN produits p ON com.produit_id = p.id
GROUP BY c.ville
ORDER BY prix_moyen DESC;
Les jointures assemblent d’abord les trois tables, puis GROUP BY regroupe le résultat par ville. Vérifiez que la colonne c.ville est bien préfixée pour éviter une ambiguïté si plusieurs tables contiennent une colonne ville.
La règle des colonnes non agrégées
Toute colonne présente dans SELECT sans fonction d’agrégation doit obligatoirement apparaître dans GROUP BY. Cette contrainte garantit que chaque groupe retourne une valeur unique et non ambiguë. PostgreSQL rejette immédiatement les violations de cette règle, tandis que MySQL peut les tolérer selon sa configuration.
-- ❌ Erreur : nom est une colonne orpheline
SELECT ville, nom, COUNT(*)
FROM clients
GROUP BY ville;
-- ✅ Correct : seule ville apparaît dans GROUP BY
SELECT ville, COUNT(*)
FROM clients
GROUP BY ville;
La colonne nom varie à l’intérieur de chaque groupe ville, ce qui rend le résultat ambigu. Supprimez nom du SELECT ou ajoutez-le dans GROUP BY si chaque combinaison ville/nom doit former un groupe distinct.
Bonnes pratiques
Règle 1 — Indexer les colonnes utilisées dans GROUP BYCREATE INDEX idx_ville ON clients(ville) accélère le regroupement car le moteur trie les valeurs indexées sans parcourir toute la table.
Règle 2 — Ajouter ORDER BY après GROUP BYORDER BY nb_commandes DESC classe les groupes par importance décroissante et rend les résultats directement exploitables dans un rapport.
Règle 3 — Utiliser HAVING pour filtrer les agrégatsHAVING SUM(montant) > 5000 filtre les groupes après calcul, contrairement à WHERE qui ne peut pas accéder aux valeurs agrégées.
Règle 4 — Tester sur un échantillon avec LIMITSELECT ville, COUNT(*) FROM clients GROUP BY ville LIMIT 10 valide la logique du regroupement avant d’exécuter la requête complète en production.
Règle 5 — Préférer COUNT(colonne) à COUNT(*) pour les colonnes nullablesCOUNT(email) ignore les lignes sans email, ce qui produit un décompte plus précis que COUNT(*) sur les colonnes qui acceptent les valeurs NULL.
Cas pratique
Un service commercial analyse le chiffre d’affaires mensuel par catégorie de produits pour les six premiers mois de 2026, en excluant les catégories dont le total reste sous 1 000 €.
SELECT categorie,
MONTH(date_commande) AS mois,
SUM(prix * quantite) AS chiffre_affaires
FROM ventes
WHERE date_commande BETWEEN '2026-01-01' AND '2026-06-30'
GROUP BY categorie, MONTH(date_commande)
HAVING SUM(prix * quantite) > 1000
ORDER BY mois ASC, chiffre_affaires DESC;
Erreurs courantes
❌ Erreur : colonne orpheline dans SELECT
SELECT ville, nom, COUNT(*) FROM clients GROUP BY ville;
-- nom varie dans chaque groupe ville, le résultat est ambigu.
✅ Correct :
SELECT ville, COUNT(*) FROM clients GROUP BY ville;
❌ Erreur : HAVING placé avant GROUP BY
SELECT ville, COUNT(*) FROM clients HAVING COUNT(*) > 10 GROUP BY ville;
-- L'ordre des clauses est incorrect et génère une erreur de syntaxe.
✅ Correct :
SELECT ville, COUNT(*) FROM clients GROUP BY ville HAVING COUNT(*) > 10;
❌ Erreur : WHERE utilisé pour filtrer un agrégat
SELECT ville, COUNT(*) FROM clients WHERE COUNT(*) > 10 GROUP BY ville;
-- WHERE s'exécute avant GROUP BY et ne peut pas accéder aux agrégats.
✅ Correct :
SELECT ville, COUNT(*) FROM clients GROUP BY ville HAVING COUNT(*) > 10;
Sources: MySql, Postgresql, SQL Server