GROUP BY et HAVING en SQL : le guide complet avec exemples

Maßtrisez le regroupement et le filtrage des agrégats en SQL grùce à GROUP BY et HAVING

MatiĂšre
SQL / Bases de données
Niveau
Intermédiaire
Clauses couvertes
GROUP BY, HAVING, WHERE, fonctions d’agrĂ©gation
Compatibilité
MySQL, PostgreSQL, SQL standard
La clause GROUP BY est l’une des fonctionnalitĂ©s les plus puissantes de SQL. Elle permet de regrouper des lignes ayant les mĂȘmes valeurs et d’appliquer des fonctions d’agrĂ©gation (COUNT, SUM, AVG
) sur chaque groupe. La clause HAVING complĂšte GROUP BY en permettant de filtrer les groupes aprĂšs l’agrĂ©gation — lĂ  oĂč WHERE filtre les lignes individuelles avant. Ensemble, ces deux clauses forment le cƓur de toute analyse de donnĂ©es en SQL : elles permettent de produire des rapports, des statistiques et des tableaux de bord directement depuis une base de donnĂ©es relationnelle. Ce cours explique GROUP BY et HAVING avec des exemples concrets, des piĂšges courants, et un quiz pour vĂ©rifier vos connaissances.

1. Le principe du GROUP BY

GROUP BY regroupe les lignes qui ont la mĂȘme valeur dans une ou plusieurs colonnes, et permet d’appliquer un calcul (somme, moyenne, dĂ©compte
) sur chaque groupe. Sans GROUP BY, les fonctions d’agrĂ©gation s’appliquent Ă  toute la table.

Prenons une table commandes avec des colonnes id, client, produit, montant et date_achat.

Sans GROUP BY — le total global :

SELECT SUM(montant) AS total_global
FROM commandes;

Résultat : une seule ligne avec le total de toutes les commandes.

Avec GROUP BY — le total par client :

SELECT client, SUM(montant) AS total_depenses
FROM commandes
GROUP BY client;

RĂ©sultat : une ligne par client, avec le total de ses commandes. C’est la force du GROUP BY : il transforme N lignes en un rĂ©sumĂ© par groupe. ConcrĂštement, si la table contient 10 000 commandes rĂ©parties sur 300 clients, la requĂȘte retourne exactement 300 lignes — une par client.

Syntaxe générale :

SELECT colonne_groupe, fonction_agregation(colonne)
FROM table
WHERE conditions_sur_les_lignes
GROUP BY colonne_groupe
HAVING conditions_sur_les_groupes
ORDER BY colonne;

Pour approfondir les bases du langage SQL avant d’aborder GROUP BY, consultez notre cours SQL complet pour dĂ©butants. Si vous travaillez spĂ©cifiquement avec MySQL, notre cours MySQL couvre les spĂ©cificitĂ©s de ce SGBD.

2. Les fonctions d’agrĂ©gation

Les fonctions d’agrĂ©gation calculent une valeur unique Ă  partir d’un ensemble de lignes. Elles sont presque toujours utilisĂ©es avec GROUP BY.

Fonction Description Exemple
COUNT(*) Nombre de lignes dans le groupe COUNT(*) — compte toutes les lignes, y compris celles avec NULL
COUNT(colonne) Nombre de valeurs non NULL COUNT(email) — ignore les lignes oĂč email est NULL
COUNT(DISTINCT col) Nombre de valeurs uniques non NULL COUNT(DISTINCT ville)
SUM(colonne) Somme des valeurs SUM(montant)
AVG(colonne) Moyenne des valeurs AVG(note) — ignore les NULL
MIN(colonne) Plus petite valeur MIN(prix)
MAX(colonne) Plus grande valeur MAX(date_inscription)
Attention : AVG(), SUM(), MIN() et MAX() ignorent les valeurs NULL. Si une colonne contient 5 valeurs dont 2 NULL, AVG() fait la moyenne sur les 3 valeurs non NULL, pas sur 5. Ce comportement peut conduire à des résultats inattendus si vous ne gérez pas les NULL en amont.

Combiner plusieurs fonctions d’agrĂ©gation dans une mĂȘme requĂȘte :

-- Statistiques complÚtes par catégorie de produit
SELECT 
    categorie,
    COUNT(*)            AS nb_produits,
    MIN(prix)           AS prix_min,
    MAX(prix)           AS prix_max,
    AVG(prix)           AS prix_moyen,
    SUM(stock)          AS stock_total
FROM produits
GROUP BY categorie
ORDER BY nb_produits DESC;

3. Exemples concrets de GROUP BY

Compter le nombre de commandes par client :

SELECT client, COUNT(*) AS nb_commandes
FROM commandes
GROUP BY client;

Calculer le chiffre d’affaires par produit :

SELECT produit, SUM(montant) AS ca_total
FROM commandes
GROUP BY produit
ORDER BY ca_total DESC;

Trouver la commande la plus chĂšre par client :

SELECT client, MAX(montant) AS commande_max
FROM commandes
GROUP BY client;

Calculer la note moyenne par matiĂšre :

SELECT matiere, AVG(note) AS moyenne, COUNT(*) AS nb_eleves
FROM notes
GROUP BY matiere
ORDER BY moyenne DESC;

Nombre de produits distincts achetés par client :

SELECT client, COUNT(DISTINCT produit) AS nb_produits_differents
FROM commandes
GROUP BY client;

Répartition des utilisateurs par pays :

-- Utile pour des rapports géographiques
SELECT pays, COUNT(*) AS nb_utilisateurs,
       ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 2) AS pourcentage
FROM utilisateurs
GROUP BY pays
ORDER BY nb_utilisateurs DESC;

4. GROUP BY sur plusieurs colonnes

On peut grouper par plusieurs colonnes pour obtenir des sous-groupes plus fins. Le GROUP BY crée alors un groupe pour chaque combinaison unique de valeurs.

-- Chiffre d'affaires par client ET par produit
SELECT client, produit, SUM(montant) AS total
FROM commandes
GROUP BY client, produit
ORDER BY client, total DESC;

Si on a 3 clients et 4 produits, on aura au maximum 12 groupes (3 × 4), un pour chaque combinaison client/produit effectivement prĂ©sente dans la table.

Exemple avec une date :

-- Ventes par mois et par catégorie
SELECT 
    YEAR(date_achat) AS annee,
    MONTH(date_achat) AS mois,
    categorie,
    SUM(montant) AS ca
FROM ventes
GROUP BY YEAR(date_achat), MONTH(date_achat), categorie
ORDER BY annee, mois;

Exemple : répartition des commandes par statut et par année :

SELECT 
    YEAR(date_achat) AS annee,
    statut,
    COUNT(*) AS nb_commandes,
    SUM(montant) AS total
FROM commandes
GROUP BY YEAR(date_achat), statut
ORDER BY annee DESC, nb_commandes DESC;
RĂšgle fondamentale : dans le SELECT, chaque colonne qui n’est pas dans une fonction d’agrĂ©gation doit apparaĂźtre dans le GROUP BY. Sinon, le SGBD ne sait pas quelle valeur afficher. MySQL est permissif par dĂ©faut (mode ONLY_FULL_GROUP_BY dĂ©sactivĂ©), mais PostgreSQL refusera la requĂȘte avec une erreur explicite. En SQL standard, la rĂšgle stricte s’applique toujours.

5. HAVING : filtrer aprĂšs l’agrĂ©gation

HAVING filtre les groupes aprĂšs que GROUP BY a créé les agrĂ©gations. C’est l’Ă©quivalent du WHERE, mais pour les rĂ©sultats agrĂ©gĂ©s.

Clients ayant passé plus de 5 commandes :

SELECT client, COUNT(*) AS nb_commandes
FROM commandes
GROUP BY client
HAVING COUNT(*) > 5;

Produits dont le CA total dĂ©passe 10 000 € :

SELECT produit, SUM(montant) AS ca_total
FROM commandes
GROUP BY produit
HAVING SUM(montant) > 10000
ORDER BY ca_total DESC;

MatiĂšres oĂč la moyenne est infĂ©rieure Ă  10 :

SELECT matiere, AVG(note) AS moyenne
FROM notes
GROUP BY matiere
HAVING AVG(note) < 10;

Départements ayant entre 5 et 20 employés avec un salaire moyen > 3000 :

SELECT departement, 
       COUNT(*) AS nb_employes, 
       AVG(salaire) AS salaire_moyen
FROM employes
GROUP BY departement
HAVING COUNT(*) BETWEEN 5 AND 20
  AND AVG(salaire) > 3000;

Clients dont le panier moyen dĂ©passe 150 € et qui ont commandĂ© au moins 3 fois :

SELECT client,
       COUNT(*) AS nb_commandes,
       AVG(montant) AS panier_moyen,
       SUM(montant) AS total
FROM commandes
GROUP BY client
HAVING COUNT(*) >= 3
  AND AVG(montant) > 150
ORDER BY total DESC;
Peut-on utiliser un alias dans HAVING ? En MySQL oui (HAVING ca_total > 10000), mais pas en PostgreSQL ni en SQL standard. Pour ĂȘtre portable, rĂ©pĂ©tez l'expression : HAVING SUM(montant) > 10000.

6. WHERE vs HAVING : la différence clé

C'est la question d'entretien SQL la plus fréquente. La différence est simple mais fondamentale :

WHERE HAVING
Filtre quoi ? Les lignes individuelles Les groupes (résultats agrégés)
Quand ? Avant le GROUP BY AprĂšs le GROUP BY
Fonctions d'agrĂ©gation ? ❌ Non ✅ Oui
Exemple WHERE montant > 100 HAVING SUM(montant) > 1000
Impact performance RĂ©duit les donnĂ©es avant regroupement ✅ Filtre aprĂšs calcul (moins efficace)

Exemple combinant WHERE et HAVING :

-- Clients ayant dĂ©pensĂ© plus de 500€ en 2024
-- (en excluant les commandes annulées)
SELECT client, SUM(montant) AS total_2024
FROM commandes
WHERE statut != 'annulée'          -- filtre les lignes AVANT le regroupement
  AND YEAR(date_achat) = 2024
GROUP BY client
HAVING SUM(montant) > 500          -- filtre les groupes APRÈS le regroupement
ORDER BY total_2024 DESC;

Le WHERE a exclu les commandes annulĂ©es et celles hors 2024 avant le regroupement. Puis le HAVING n'a gardĂ© que les clients dont le total (des commandes restantes) dĂ©passe 500 €.

Autre exemple concret — rapport sur les vendeurs actifs :

-- Vendeurs ayant réalisé plus de 20 ventes de produits "premium"
-- avec un CA mensuel supĂ©rieur Ă  10 000€
SELECT vendeur_id,
       MONTH(date_vente) AS mois,
       COUNT(*) AS nb_ventes,
       SUM(montant) AS ca_mensuel
FROM ventes
WHERE categorie = 'premium'        -- WHERE filtre la catégorie AVANT le GROUP BY
  AND YEAR(date_vente) = 2024
GROUP BY vendeur_id, MONTH(date_vente)
HAVING COUNT(*) > 20               -- HAVING filtre les groupes résultants
  AND SUM(montant) > 10000
ORDER BY ca_mensuel DESC;
PiĂšge classique : Ă©crire WHERE COUNT(*) > 5 provoque une erreur. Les fonctions d'agrĂ©gation ne sont jamais autorisĂ©es dans WHERE — il faut utiliser HAVING.

7. Ordre d'exĂ©cution d'une requĂȘte SQL

L'ordre dans lequel vous écrivez les clauses SQL n'est pas l'ordre dans lequel le moteur les exécute. Comprendre cet ordre est essentiel pour maßtriser GROUP BY et HAVING.

Étape Clause Rîle
1 FROM / JOIN Déterminer les tables sources
2 WHERE Filtrer les lignes individuelles
3 GROUP BY Regrouper les lignes
4 HAVING Filtrer les groupes
5 SELECT Choisir les colonnes et calculer les expressions
6 DISTINCT Éliminer les doublons
7 ORDER BY Trier les résultats
8 LIMIT / OFFSET Limiter le nombre de lignes
Conséquences importantes :

  1. On ne peut pas utiliser un alias défini dans SELECT dans le WHERE (car WHERE est exécuté avant SELECT).
  2. On ne peut pas utiliser de fonction d'agrégation dans WHERE (car WHERE est exécuté avant GROUP BY).
  3. HAVING peut utiliser des fonctions d'agrégation car il est exécuté aprÚs GROUP BY.
  4. ORDER BY est exĂ©cutĂ© en dernier — il peut utiliser les alias du SELECT.

Cet ordre d'exécution est directement lié à la façon dont les jointures SQL interagissent avec les agrégations : les jointures (FROM/JOIN) sont résolues en premier, avant tout filtrage.

8. GROUP BY avec des jointures

GROUP BY est souvent combiné avec des jointures pour agréger des données provenant de plusieurs tables.

Nombre de commandes par client (avec le nom du client) :

SELECT c.nom, c.email, COUNT(o.id) AS nb_commandes
FROM clients c
LEFT JOIN commandes o ON c.id = o.client_id
GROUP BY c.id, c.nom, c.email
ORDER BY nb_commandes DESC;

On utilise LEFT JOIN pour inclure les clients qui n'ont passé aucune commande (ils auront nb_commandes = 0).

Chiffre d'affaires par catégorie de produit :

SELECT cat.nom AS categorie,
       COUNT(DISTINCT o.id) AS nb_commandes,
       SUM(li.quantite * li.prix_unitaire) AS ca_total
FROM categories cat
JOIN produits p ON cat.id = p.categorie_id
JOIN lignes_commande li ON p.id = li.produit_id
JOIN commandes o ON li.commande_id = o.id
WHERE o.statut = 'livree'
GROUP BY cat.id, cat.nom
HAVING SUM(li.quantite * li.prix_unitaire) > 5000
ORDER BY ca_total DESC;

Moyenne des notes par élÚve avec le nom de l'élÚve :

SELECT e.nom, e.prenom,
       COUNT(n.id) AS nb_notes,
       AVG(n.valeur) AS moyenne_generale,
       MIN(n.valeur) AS note_min,
       MAX(n.valeur) AS note_max
FROM eleves e
LEFT JOIN notes n ON e.id = n.eleve_id
GROUP BY e.id, e.nom, e.prenom
HAVING COUNT(n.id) > 0
ORDER BY moyenne_generale DESC;
Attention au GROUP BY avec JOIN : si un client a 3 commandes et qu'on fait un JOIN avec une table adresses (2 adresses par client), on obtiendrait 6 lignes, et COUNT(*) vaudrait 6 au lieu de 3. Utilisez COUNT(DISTINCT commande.id) pour éviter ce piÚge.

9. GROUP BY ROLLUP et CUBE

Au-delà du GROUP BY classique, SQL propose des extensions pour générer des sous-totaux et totaux automatiques : ROLLUP et CUBE. Ces extensions sont disponibles en MySQL, PostgreSQL et la plupart des SGBD modernes.

ROLLUP : sous-totaux hiérarchiques

ROLLUP génÚre des sous-totaux pour chaque niveau de la hiérarchie, plus un total général.

-- CA par pays, puis par ville, avec sous-totaux par pays et total général
SELECT 
    pays,
    ville,
    SUM(montant) AS ca
FROM ventes
GROUP BY ROLLUP(pays, ville)
ORDER BY pays, ville;

Résultat : on obtient les CA par ville, puis une ligne de sous-total par pays (ville = NULL), puis une ligne de total général (pays = NULL et ville = NULL).

CUBE : toutes les combinaisons de sous-totaux

-- CUBE génÚre des sous-totaux pour TOUTES les combinaisons de colonnes
SELECT 
    annee,
    trimestre,
    region,
    SUM(ca) AS total_ca
FROM ventes
GROUP BY CUBE(annee, trimestre, region);

CUBE est utile pour les rapports multidimensionnels (tableaux croisés dynamiques en SQL). Avec 3 colonnes, il génÚre 2³ = 8 niveaux d'agrégation différents.

Identifier les lignes de sous-totaux : utilisez la fonction GROUPING(colonne) qui retourne 1 si la colonne est agrégée (ligne de sous-total) et 0 sinon. Cela permet de distinguer un NULL réel d'un NULL de sous-total introduit par ROLLUP.
SELECT 
    CASE WHEN GROUPING(pays) = 1 THEN 'TOTAL GÉNÉRAL' ELSE pays END AS pays,
    CASE WHEN GROUPING(ville) = 1 THEN 'Sous-total' ELSE ville END AS ville,
    SUM(montant) AS ca
FROM ventes
GROUP BY ROLLUP(pays, ville);

10. PiĂšges courants et bonnes pratiques

❌ Piùge 1 : colonnes manquantes dans le GROUP BY

-- ❌ ERREUR en PostgreSQL (correct en MySQL mode permissif)
SELECT client, email, COUNT(*)
FROM commandes
GROUP BY client;
-- email n'est pas dans GROUP BY ni dans une fonction d'agrégation

-- ✅ Correct
SELECT client, email, COUNT(*)
FROM commandes
GROUP BY client, email;

❌ Piùge 2 : COUNT(*) vs COUNT(colonne)

-- COUNT(*) compte TOUTES les lignes (y compris celles avec NULL)
-- COUNT(email) compte uniquement les lignes oĂč email n'est PAS NULL

SELECT 
    COUNT(*) AS total_lignes,
    COUNT(email) AS avec_email,
    COUNT(*) - COUNT(email) AS sans_email
FROM utilisateurs;

❌ PiĂšge 3 : WHERE avec une fonction d'agrĂ©gation

-- ❌ ERREUR : les fonctions d'agrĂ©gation ne sont pas autorisĂ©es dans WHERE
SELECT client, SUM(montant)
FROM commandes
WHERE SUM(montant) > 1000
GROUP BY client;

-- ✅ Correct : utiliser HAVING
SELECT client, SUM(montant) AS total
FROM commandes
GROUP BY client
HAVING SUM(montant) > 1000;

❌ Piùge 4 : GROUP BY avec JOIN multiplie les lignes

-- ❌ COUNT(*) est faussĂ© si le JOIN crĂ©e des doublons
SELECT u.nom, COUNT(*) AS nb_commandes
FROM utilisateurs u
JOIN adresses a ON u.id = a.user_id    -- 2 adresses → double les lignes
JOIN commandes c ON u.id = c.user_id
GROUP BY u.id, u.nom;

-- ✅ Utiliser COUNT(DISTINCT)
SELECT u.nom, COUNT(DISTINCT c.id) AS nb_commandes
FROM utilisateurs u
JOIN adresses a ON u.id = a.user_id
JOIN commandes c ON u.id = c.user_id
GROUP BY u.id, u.nom;

❌ Piùge 5 : AVG sur une colonne avec des NULL

-- Si la colonne "bonus" contient NULL pour 30% des employés,
-- AVG(bonus) calcule la moyenne sur 70% des employés seulement.
-- Pour inclure les NULL comme des 0 :
SELECT departement,
       AVG(COALESCE(bonus, 0)) AS bonus_moyen_reel
FROM employes
GROUP BY departement;
✅ Bonne pratique : GROUP BY sur l'id plutît que sur le nom
Groupez toujours par la clĂ© primaire (id) quand c'est possible. Deux clients pourraient avoir le mĂȘme nom — grouper par nom les fusionnerait par erreur. PostgreSQL permet d'Ă©crire GROUP BY id puis de mettre les autres colonnes dans le SELECT sans les lister dans le GROUP BY (car elles sont fonctionnellement dĂ©pendantes de l'id).
✅ Bonne pratique : WHERE avant HAVING pour la performance
Filtrez au maximum avec WHERE avant le regroupement. Un WHERE date > '2024-01-01' réduit les lignes à traiter par le GROUP BY, ce qui est plus rapide que de tout regrouper puis filtrer avec HAVING. En d'autres termes : WHERE travaille sur les données brutes (souvent indexées), HAVING travaille sur les données calculées.
✅ Bonne pratique : utiliser des index sur les colonnes de GROUP BY
Si vous faites souvent des GROUP BY client_id, assurez-vous que la colonne client_id est indexée. Le moteur SQL peut alors utiliser l'index pour regrouper les données plus efficacement, sans parcourir toute la table.

11. Questions fréquentes (FAQ)

Peut-on utiliser GROUP BY sans fonction d'agrégation ?
Techniquement oui, mais c'est équivalent à un SELECT DISTINCT. GROUP BY colonne sans agrégation retourne une ligne par valeur unique de la colonne. En pratique, préférez DISTINCT dans ce cas, qui exprime clairement l'intention.

-- Ces deux requĂȘtes donnent le mĂȘme rĂ©sultat :
SELECT DISTINCT pays FROM clients;
SELECT pays FROM clients GROUP BY pays;
Quelle est la différence entre GROUP BY et PARTITION BY ?
GROUP BY rĂ©duit le nombre de lignes (une ligne par groupe). PARTITION BY s'utilise dans les fonctions de fenĂȘtrage (OVER) et conserve toutes les lignes tout en calculant des agrĂ©gations par groupe. Exemple : afficher le salaire de chaque employĂ© ET la moyenne du dĂ©partement sur la mĂȘme ligne nĂ©cessite PARTITION BY, pas GROUP BY.

-- GROUP BY : une ligne par département
SELECT departement, AVG(salaire) FROM employes GROUP BY departement;

-- PARTITION BY : une ligne par employé avec la moyenne du département
SELECT nom, salaire, departement,
       AVG(salaire) OVER (PARTITION BY departement) AS moy_dept
FROM employes;
Comment paginer les résultats d'un GROUP BY ?
Utilisez LIMIT et OFFSET aprÚs le ORDER BY. Par exemple, pour la 2e page de 10 résultats :

SELECT client, SUM(montant) AS total
FROM commandes
GROUP BY client
ORDER BY total DESC
LIMIT 10 OFFSET 10;  -- page 2
Peut-on utiliser GROUP BY dans une sous-requĂȘte ?
Oui, c'est trĂšs courant. On peut utiliser une sous-requĂȘte agrĂ©gĂ©e dans le FROM (table dĂ©rivĂ©e) ou dans une CTE (WITH).

-- Top 3 clients par CA en utilisant une sous-requĂȘte
SELECT *
FROM (
    SELECT client, SUM(montant) AS total
    FROM commandes
    GROUP BY client
) AS ca_clients
WHERE total > 1000
ORDER BY total DESC
LIMIT 3;
GROUP BY préserve-t-il l'ordre des données ?
Non. GROUP BY ne garantit aucun ordre de tri. Pour trier les résultats, utilisez toujours ORDER BY explicitement. L'ordre apparent sans ORDER BY dépend du moteur et peut changer selon les versions ou les optimisations.
Comment gérer les valeurs NULL dans GROUP BY ?
Les valeurs NULL sont regroupĂ©es ensemble par GROUP BY : toutes les lignes oĂč la colonne est NULL forment un seul groupe. Ce comportement est dĂ©fini par le standard SQL. Si vous voulez remplacer NULL par une valeur par dĂ©faut, utilisez COALESCE dans le GROUP BY :

SELECT COALESCE(region, 'Non définie') AS region,
       COUNT(*) AS nb_clients
FROM clients
GROUP BY COALESCE(region, 'Non définie');

12. Quiz

Quelle est la différence entre WHERE et HAVING ?
WHERE filtre les lignes avant le GROUP BY. HAVING filtre les groupes aprÚs l'agrégation. On ne peut pas mettre de fonction d'agrégation dans WHERE.
Pourquoi cette requĂȘte est-elle fausse ? SELECT client, nom, COUNT(*) FROM commandes GROUP BY client;
La colonne nom n'est ni dans le GROUP BY ni dans une fonction d'agrégation. En PostgreSQL, c'est une erreur. Il faut ajouter nom au GROUP BY, ou l'enlever du SELECT, ou utiliser MAX(nom).
Quelle différence entre COUNT(*) et COUNT(email) ?
COUNT(*) compte toutes les lignes. COUNT(email) ne compte que les lignes oĂč email n'est pas NULL.
Comment trouver les départements ayant plus de 10 employés avec un salaire moyen supérieur à 4000 ?
SELECT departement, COUNT(*) AS nb, AVG(salaire) AS moy
FROM employes
GROUP BY departement
HAVING COUNT(*) > 10 AND AVG(salaire) > 4000;
Peut-on mettre WHERE et HAVING dans la mĂȘme requĂȘte ?
Oui, et c'est fréquent ! WHERE filtre d'abord les lignes, GROUP BY regroupe, puis HAVING filtre les groupes. Filtrer au maximum avec WHERE améliore les performances car le moteur traite moins de données lors de l'agrégation.
Quel est l'ordre d'exĂ©cution d'une requĂȘte SQL ?
FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT. C'est pour ça qu'on ne peut pas utiliser un alias SELECT dans WHERE (exĂ©cutĂ© avant), mais qu'on peut dans ORDER BY (exĂ©cutĂ© aprĂšs).
GROUP BY avec un LEFT JOIN : comment éviter de fausser le COUNT ?
Utilisez COUNT(colonne_de_la_table_jointe) ou COUNT(DISTINCT id) au lieu de COUNT(*). Avec un LEFT JOIN, COUNT(*) comptera 1 mĂȘme pour les lignes sans correspondance (la ligne existe avec NULL).
Quelle requĂȘte affiche les produits vendus par au moins 3 clients diffĂ©rents ?
SELECT produit, COUNT(DISTINCT client_id) AS nb_clients
FROM commandes
GROUP BY produit
HAVING COUNT(DISTINCT client_id) >= 3
ORDER BY nb_clients DESC;

COUNT(DISTINCT client_id) est essentiel ici : un mĂȘme client peut avoir commandĂ© le mĂȘme produit plusieurs fois, et on ne veut compter chaque client qu'une seule fois par produit.

En rĂ©sumĂ© : GROUP BY et HAVING sont indispensables pour produire des statistiques et des rapports en SQL. Retenez les points clĂ©s : GROUP BY regroupe les lignes par valeur unique, les fonctions d'agrĂ©gation (COUNT, SUM, AVG, MIN, MAX) calculent une valeur par groupe, WHERE filtre avant l'agrĂ©gation (pour la performance), et HAVING filtre aprĂšs. MaĂźtriser ces clauses vous permettra d'Ă©crire des requĂȘtes analytiques puissantes pour n'importe quelle base de donnĂ©es relationnelle. Pour aller plus loin, explorez les sous-requĂȘtes et les fonctions de fenĂȘtrage (OVER / PARTITION BY) qui complĂštent GROUP BY dans les analyses avancĂ©es.