HAVING filters groups after aggregation, the way WHERE filters rows before it. The classic use: find keys that appear more than once.
-- Find duplicate emails — identical in all six dialects
SELECT email, COUNT(*) AS n
FROM users
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY n DESC;
HAVING can compare an aggregate to anything, including another aggregate query — e.g. “products that sold above the average product’s total”:
SELECT product_id, SUM(amount) AS total
FROM sales
GROUP BY product_id
HAVING SUM(amount) > (
SELECT AVG(product_total)
FROM (SELECT SUM(amount) AS product_total FROM sales GROUP BY product_id) x
)
ORDER BY total DESC;
WHEREruns before grouping,HAVINGruns after.WHEREcan’t referenceCOUNT(*)or any other aggregate — the rows haven’t been grouped yet when it runs.HAVINGcan, because it evaluates afterGROUP BY. For performance, filter what you can inWHERE(fewer rows to aggregate); reserveHAVINGfor conditions on the aggregate itself. Some dialects (MySQL, SQLite) letHAVINGreference aSELECT-list alias as a convenience (HAVING n > 1); PostgreSQL doesn’t. Repeating the aggregate expression —HAVING COUNT(*) > 1, as above — works identically in all six.