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)

genrenb_filmsnote_moyenne
Comédie18.3
Crime18.9
Drame19.2
SF18.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.

La requête
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
Résultat — 3 lignes (sur 4 sans le filtre) · 3 lignes
genrenb_filmsnote_moyenne
Crime18.9
Drame19.2
SF18.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 :

La requête
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
Résultat — 2 lignes · 2 lignes
genrenb_filmsnote_moyenne
Crime18.9
SF18.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 :

La requête
SELECT oscar, ROUND(AVG(note), 1) AS note_moyenne FROM films GROUP BY oscar HAVING AVG(note) > 8.5 ORDER BY note_moyenne DESC
Résultat — 2 lignes (les deux groupes dépassent 8.5) · 2 lignes
oscarnote_moyenne
true9.0
false8.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 :

📌 HAVING — l'essentiel
HAVING condition→ filtre les groupes après GROUP BY
WHERE vs HAVING→ WHERE filtre les lignes (avant), HAVING les groupes (après)
condition dans HAVING→ toujours l'expression agrégée, jamais l'alias
ORDER BY vs HAVING→ ORDER BY accepte les alias, HAVING les refuse
ordre des clauses→ FROM · WHERE · GROUP BY · HAVING · ORDER BY
Exemple complet
SELECT genre, ROUND(AVG(note), 1) AS note_moyenne
FROM films
WHERE annee > 1990
GROUP BY genre
HAVING AVG(note) > 8.5
ORDER 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.

← Tous les cours SQL