GROUP BY regroupe les lignes par paquets
Les fonctions d'agrégation comme COUNT, SUM et AVG condensent plusieurs lignes en une seule valeur. Avec GROUP BY, vous faites ce calcul par catégorie : un total par client, par mois, par statut. Chaque valeur unique (ou combinaison de valeurs) devient une ligne dans le résultat.
Trois clients, trois lignes en sortie. Les six lignes d'origine ont disparu : elles ont été regroupées par client, avec COUNT(*) et SUM(amount) calculés à l'intérieur de chaque groupe.
L'idée à retenir : GROUP BY customer revient à dire « considère toutes les lignes ayant le même client comme un seul groupe ». Les fonctions d'agrégation s'appliquent ensuite à chaque groupe indépendamment.
Ce que l'on peut mettre dans la clause SELECT
C'est là que beaucoup se cassent les dents. Avec GROUP BY, chaque colonne du SELECT doit soit figurer dans le GROUP BY, soit être encapsulée dans une fonction d'agrégation. Sinon, la valeur devient ambiguë : laquelle des lignes du groupe SQLite est-il censé renvoyer ?
Si vous écriviez SELECT region, rep, SUM(amount) avec GROUP BY region, SQLite l'exécuterait sans broncher (il est plus permissif que d'autres SGBD qui rejettent ce genre de requête), mais la valeur de rep serait choisie au hasard dans le groupe. Vous obtiendriez un nom de commercial par région, sans aucune garantie sur lequel. Évitez de compter là-dessus : regroupez sur toutes les colonnes non agrégées que vous affichez.
HAVING : filtrer les groupes après agrégation en SQL
WHERE filtre les lignes avant le regroupement. HAVING filtre les groupes une fois le regroupement effectué. Voilà toute la différence entre WHERE et HAVING, et c'est précisément pour ça qu'on ne peut pas mettre COUNT(*) > 1 dans une clause WHERE : au moment où WHERE s'exécute, le COUNT n'existe pas encore.
Cleo n'a passé qu'une seule commande, donc son groupe est écarté. Restent Ada et Boris. La condition s'applique à la valeur agrégée de chaque groupe, et non à chaque ligne prise individuellement.
Bonne nouvelle : dans SQLite, on peut réutiliser directement les alias de colonnes définis dans le SELECT à l'intérieur de la clause HAVING :
C'est souvent bien plus lisible que de répéter SUM(amount) dans la clause HAVING.
WHERE et HAVING : les utiliser ensemble
Ces deux clauses ne s'opposent pas, elles se complètent. Le WHERE filtre les lignes avant le regroupement ; le HAVING filtre les groupes une fois constitués. Dans la pratique, la plupart des requêtes SQL utilisent les deux à la fois.
Lisez-le de haut en bas, dans l'ordre d'exécution :
WHERE status = 'paid'— on évacue les lignes remboursées dès le départ.GROUP BY customer— on regroupe ce qui reste par client.SUM(amount)est calculée pour chaque groupe.HAVING SUM(amount) > 75— on ne garde que les groupes qui passent le filtre.
Boris (80 + 20 = 100) et Cleo (200) passent la barre. Côté Ada, sa seule commande payée était de 50, ce qui reste sous le seuil.
Plusieurs conditions et plusieurs colonnes de regroupement
HAVING accepte les mêmes opérateurs booléens que WHERE — AND, OR, NOT — et vous pouvez regrouper sur plusieurs colonnes pour créer des sous-groupes :
Chaque couple (region, quarter) forme un groupe distinct. La clause HAVING exige à la fois un total supérieur à 100 et au moins deux ventes. Seuls ('Nord', 'T1') et ('Sud', 'T2') passent le filtre.
Cas pratique : repérer les doublons
La requête GROUP BY ... HAVING COUNT(*) > 1 est la méthode classique pour détecter les valeurs en double dans une colonne :
Deux doublons remontent. À partir de là, vous déciderez généralement s'il faut fusionner les comptes, ajouter une contrainte UNIQUE, ou nettoyer les données — mais la requête de détection garde toujours la même forme.
HAVING sans GROUP BY
C'est un cas de figure rare, mais parfaitement valide. En l'absence de GROUP BY, l'ensemble des résultats est considéré comme un seul groupe, et HAVING filtre ce groupe en bloc : soit vous récupérez toutes les valeurs agrégées, soit rien du tout :
La ligne de résultat unique apparaît parce que la somme vaut 160. Si vous changez le seuil en > 200, la requête ne renvoie plus aucune ligne. En pratique, on combine presque toujours HAVING avec GROUP BY — mais c'est bon à savoir : le langage ne l'impose pas.
Récapitulatif express
GROUP BYregroupe les lignes par clé ; les fonctions d'agrégation s'appliquent à chaque groupe.- Toute colonne non agrégée présente dans le
SELECTdoit aussi figurer dans leGROUP BY. WHEREfiltre les lignes avant le regroupement ;HAVINGfiltre les groupes après.- Les agrégats comme
COUNT(*)etSUM(...)vont dansHAVING, jamais dansWHERE. HAVINGaccepte des conditions composées et peut s'appuyer sur les alias définis dans leSELECT.
La suite : les clés étrangères
Agréger les données d'une seule table, c'est déjà pratique, mais la plupart des schémas réels répartissent l'information sur plusieurs tables — les commandes d'un côté, les clients d'un autre, les produits ailleurs encore. Les clés étrangères servent justement à relier ces tables entre elles pour garantir la cohérence des relations. C'est ce qu'on aborde au prochain chapitre.
Questions fréquentes
Quelle est la différence entre WHERE et HAVING en SQLite ?
WHERE filtre les lignes une par une avant le regroupement. HAVING, lui, filtre les groupes après l'agrégation. Concrètement, WHERE amount > 100 ne garde que les lignes dont le montant dépasse 100, alors que HAVING SUM(amount) > 100 ne garde que les groupes dont le total dépasse 100. Les fonctions d'agrégation comme COUNT ou SUM ne sont pas autorisées dans WHERE — c'est précisément le rôle de HAVING.
Peut-on utiliser HAVING sans GROUP BY en SQLite ?
Oui. Sans GROUP BY, SQLite considère l'ensemble du résultat comme un seul groupe, et HAVING filtre ce groupe unique d'un bloc. La requête renvoie alors soit une ligne, soit zéro. C'est rare en pratique : si on a un HAVING, on a en général un GROUP BY qui va avec.
Comment filtrer les groupes par COUNT en SQLite ?
On met l'agrégat dans HAVING, pas dans WHERE. Par exemple, SELECT customer_id, COUNT(*) FROM orders GROUP BY customer_id HAVING COUNT(*) > 1 renvoie les clients ayant passé plus d'une commande. À noter : SQLite accepte aussi qu'on réutilise un alias de colonne du SELECT directement dans le HAVING.