HAVING
Filter groups after aggregation — the clause for conditions WHERE can't express.
WHERE filters rows, HAVING filters groups
WHEREruns beforeGROUP BY— it can't see aggregates.HAVINGruns afterGROUP BY— it filters the grouped rows, and it can use aggregates.
SELECT customer_id, SUM(amount) AS total
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 300;SQL playground
Loading editor…
⌘/Ctrl + Enter
Use both — filter early, then filter groups
WHERE shrinks the input before grouping (cheaper); HAVING filters the
aggregated result.
SQL playground
Loading editor…
⌘/Ctrl + Enter
Putting status = 'paid' in HAVING would work here only because there's no
aggregate on it — but it would scan and group rows you could have dropped first.
Row-level conditions belong in WHERE.
Gotchas
HAVINGwithoutGROUP BYtreats the whole result as one group:SELECT SUM(amount) FROM orders HAVING SUM(amount) > 1000returns one row or none.- Select-list aliases usually aren't visible in
HAVING(same asWHERE) — repeat the expression:HAVING SUM(amount) > 300, notHAVING total > 300. Postgres and SQLite allow the alias; SQL Server and Oracle don't. - Non-aggregated columns in
HAVINGmust be inGROUP BY, just like the select list.
- GROUP BY — sets the groups HAVING filters.
- WHERE — the row-level filter that runs first.
- Pivot without PIVOT — conditional aggregation, often paired with HAVING.