La différence entre WHERE et HAVING est l'une des questions les plus fréquentes en entretien SQL — et l'une des plus mal comprises. En une phrase : WHERE filtre les lignes avant le regroupement, HAVING filtre les groupes après. Cette distinction semble simple, mais elle cache des subtilités qui peuvent faire la différence entre une requête correcte et une requête silencieusement fausse.
Dans cet article, vous allez maîtriser ces deux clauses de fond en comble : syntaxe, ordre d'exécution, cas d'usage réels et pièges à éviter. Que vous prépariez un entretien technique ou que vous cherchiez à écrire des requêtes plus propres, ce guide vous donne les bases et les réflexes qu'il faut.
📌 Ce qu'il faut retenir
WHEREs'applique sur les lignes brutes, avantGROUP BYHAVINGs'applique sur les groupes, aprèsGROUP BY- On ne peut pas utiliser d'alias de colonne agrégée dans
WHERE HAVINGsansGROUP BYexiste mais reste rare et déroutant- L'ordre d'exécution SQL est :
FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY
L'ordre d'exécution SQL, fondement de tout
Avant d'opposer WHERE et HAVING, il faut comprendre dans quel ordre le moteur SQL exécute une requête. L'ordre d'écriture et l'ordre d'exécution sont différents, et c'est là que la confusion naît.
Voici l'ordre logique d'exécution d'une requête SQL standard :
FROM/JOIN— identification des tables sourcesWHERE— filtrage des lignesGROUP BY— regroupementHAVING— filtrage des groupesSELECT— projection des colonnesORDER BY— tri final
Ce séquencement explique pourquoi vous ne pouvez pas référencer un alias défini dans SELECT à l'intérieur d'un WHERE : au moment où WHERE est évalué, SELECT n'a pas encore été exécuté. De même, vous ne pouvez pas filtrer sur COUNT(*) dans un WHERE, car le comptage n'existe qu'après le GROUP BY.
Gardez cette séquence en tête — elle répond à 90 % des questions sur WHERE vs HAVING.
WHERE : filtrer avant le regroupement
WHERE est la clause de filtrage la plus fondamentale de SQL. Elle agit sur chaque ligne de la table source, avant toute agrégation.
SELECT
pays,
COUNT(*) AS nb_commandes
FROM commandes
WHERE statut = 'livree'
GROUP BY pays;
Dans cet exemple, seules les commandes avec statut = 'livree' entrent dans le calcul. Les commandes annulées ou en attente sont écartées avant que le moteur ne commence à compter. Le résultat est donc le nombre de commandes livrées par pays.
WHERE accepte n'importe quelle condition sur les colonnes brutes : comparaisons, LIKE, IN, BETWEEN, sous-requêtes corrélées ou non. Pour en savoir plus sur les sous-requêtes dans ce contexte, consultez notre article sur les sous-requêtes SQL corrélées et imbriquées.
⚠️ Attention
Vous ne pouvez pas écrire WHERE COUNT(*) > 5. Cette syntaxe est invalide car COUNT n'est pas encore calculé à ce stade. Utilisez HAVING pour ce type de filtre.
HAVING : filtrer après le regroupement
HAVING a été introduit précisément pour pallier l'impossibilité d'utiliser des fonctions d'agrégat dans WHERE. Il s'applique après que les groupes ont été constitués et les agrégations calculées.
SELECT
pays,
COUNT(*) AS nb_commandes
FROM commandes
WHERE statut = 'livree'
GROUP BY pays
HAVING COUNT(*) > 100;
Ici, on conserve uniquement les pays ayant plus de 100 commandes livrées. Les deux clauses coexistent : WHERE réduit le périmètre des données à analyser, HAVING filtre les résultats agrégés.
HAVING peut utiliser :
- des fonctions d'agrégat :
COUNT(),SUM(),AVG(),MIN(),MAX() - des colonnes du
GROUP BY - des sous-requêtes scalaires
Pour maîtriser les fonctions d'agrégat utilisées dans HAVING, notre guide sur les fonctions d'agrégat SQL détaille chaque fonction avec des exemples pratiques.
Tableau comparatif : WHERE vs HAVING
| Critère | WHERE | HAVING |
|---|---|---|
| Moment d'exécution | Avant GROUP BY | Après GROUP BY |
| Agit sur | Lignes individuelles | Groupes agrégés |
| Fonctions d'agrégat autorisées | Non | Oui |
| Utilisation sans GROUP BY | Oui (filtre classique) | Possible, mais rare |
| Impact sur la performance | Réduit les lignes tôt → plus rapide | Agit sur données déjà agrégées |
| Alias SELECT accepté | Non (dans la plupart des SGBD) | Parfois (MySQL, BigQuery) |
| Exemple typique | WHERE date > '2025-01-01' |
HAVING SUM(montant) > 1000 |
