SQL GROUP BY: Comment utilise-t-on la fonction GROUP BY en SQL ?

Par :

,le

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.

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 BYCOUNT(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 BY
CREATE 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 BY
ORDER 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égats
HAVING 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 LIMIT
SELECT 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 nullables
COUNT(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