Filtrer avec HAVING
GROUP BY produit une ligne par groupe — mais comment ne garder que les groupes qui dépassent un seuil ? WHERE ne peut pas le faire : il s'applique avant le regroupement. HAVING est le filtre des groupes.
Filtrer des groupes
La leçon précédente s'est terminée sur un résultat GROUP BY genre : quatre genres, chacun avec la note moyenne de son unique film. Voilà ce tableau :
GROUP BY genre — résultat avant tout filtre (4 lignes)
| genre | nb_films | note_moyenne |
|---|---|---|
| Comédie | 1 | 8.3 |
| Crime | 1 | 8.9 |
| Drame | 1 | 9.2 |
| SF | 1 | 8.8 |
La commission cinéma veut concentrer l'analyse sur les genres les mieux notés — ceux dont la moyenne dépasse 8.5. L'instinct serait d'écrire WHERE note_moyenne > 8.5, mais ça échoue : quand WHERE s'exécute, les groupes n'existent pas encore. WHERE travaille sur les lignes brutes, avant tout regroupement.
Il faut un filtre qui intervient après le calcul des groupes. C'est le rôle de HAVING.
HAVING — filtrer après le regroupement
HAVING se place immédiatement après GROUP BY. Sa syntaxe ressemble à WHERE, mais sa condition porte sur le résultat d'un agrégat — une valeur calculée par groupe. PostgreSQL constitue tous les groupes, applique l'agrégat à chacun, puis ne conserve que ceux dont la condition est vraie.
SELECT genre, COUNT(*) AS nb_films, ROUND(AVG(note), 1) AS note_moyenne
FROM films
GROUP BY genre
HAVING AVG(note) > 8.5
ORDER BY genre| genre | nb_films | note_moyenne |
|---|---|---|
| Crime | 1 | 8.9 |
| Drame | 1 | 9.2 |
| SF | 1 | 8.8 |
Comédie (Amélie Poulain, 8.3) est éliminée — sa note moyenne ne dépasse pas 8.5. Les trois autres genres passent. La condition écrit AVG(note) — l'expression agrégée complète. Écrire l'alias note_moyenne à la place produirait une erreur ; la section §ordre le démontre.
HAVING vs WHERE — deux étages distincts
L'erreur la plus fréquente avec HAVING est d'utiliser WHERE à sa place — tenter WHERE AVG(note) > 8.5. PostgreSQL répond : aggregate functions are not allowed in WHERE. La raison est logique : WHERE s'exécute ligne par ligne, avant tout regroupement. À ce stade, AVG(note) n'a pas encore été calculé.
WHERE et HAVING ne font pas la même chose — et ils peuvent coexister dans la même requête. WHERE filtre les lignes brutes en amont ; GROUP BY regroupe ce qui reste ; HAVING filtre les groupes résultants en aval :
SELECT genre, COUNT(*) AS nb_films, ROUND(AVG(note), 1) AS note_moyenne
FROM films
WHERE annee > 1990
GROUP BY genre
HAVING AVG(note) > 8.5
ORDER BY genre| genre | nb_films | note_moyenne |
|---|---|---|
| Crime | 1 | 8.9 |
| SF | 1 | 8.8 |
WHERE annee > 1990 retire Le Parrain (1972) avant le regroupement — la catégorie Drame n'apparaît même plus dans les groupes : elle a perdu son seul film en amont. Il reste trois films à regrouper (Inception, Pulp Fiction, Amélie Poulain). Puis HAVING AVG(note) > 8.5 élimine Comédie (8.3). Les deux clauses travaillent à des étages distincts de l'exécution.
Ordre des clauses
L'ordre d'écriture est fixe et correspond à l'ordre d'exécution : FROM → WHERE → GROUP BY → HAVING → ORDER BY. HAVING vient toujours après GROUP BY — le placer avant provoque une erreur de syntaxe. Un exemple en changeant la dimension de groupement :
SELECT oscar, ROUND(AVG(note), 1) AS note_moyenne
FROM films
GROUP BY oscar
HAVING AVG(note) > 8.5
ORDER BY note_moyenne DESC| oscar | note_moyenne |
|---|---|
| true | 9.0 |
| false | 8.6 |
Les deux groupes passent le seuil (9.0 et 8.6 sont tous deux > 8.5). Asymétrie à retenir : ORDER BY note_moyenne DESC utilise l'alias — c'est valide. Mais HAVING note_moyenne > 8.5 produirait ERROR: column "note_moyenne" does not exist. PostgreSQL évalue HAVING avant de projeter les alias du SELECT ; il ne les voit donc pas encore. Toujours écrire l'expression agrégée complète dans HAVING.
Récapitulatif
Ce que tu retiens de cette leçon :
SELECT genre, ROUND(AVG(note), 1) AS note_moyenneFROM filmsWHERE annee > 1990GROUP BY genreHAVING AVG(note) > 8.5ORDER BY note_moyenne DESC💡 Bon à savoir : dans l'exemple ci-dessus, ORDER BY note_moyenne DESC utilise l'alias — c'est valide. Mais HAVING note_moyenne > 8.5 produirait une erreur. La leçon suivante introduit les sous-requêtes : une requête imbriquée dans une autre, pour aller encore plus loin dans les filtrages.
Passe à la pratique
Cette leçon t'a montré la théorie. Pour maîtriser SQL, rien ne vaut la pratique : accède aux exercices et au bac à sable PostgreSQL réel.