thecodex.expert · The Codex Family of Knowledge
SQL

GROUP BY and HAVING

You cannot filter on an aggregate like COUNT(*) with WHERE — the engine hasn't computed it yet at that stage. That is exactly what HAVING is for.

SQL:2016 / ANSI HAVING runs after GROUP BY Last verified:
Canonical Definition

GROUP BY collapses rows that share a value in the specified column(s) into a single group, and aggregate functions (COUNT, SUM, AVG, MIN, MAX) then compute one summary value per group. WHERE filters individual rows before grouping ever happens, so it cannot reference an aggregate result; HAVING filters entire groups after aggregation, which is the only place a condition on an aggregate value (like COUNT(*) > 5) can go.

GROUP BY: one row per group

Every column in SELECT that isn't inside an aggregate function must appear in GROUP BY — the engine needs to know how to collapse the other rows within each group.

SQLgroup_by.sql
SELECT category, COUNT(*) AS product_count, AVG(price) AS avg_price
FROM products
GROUP BY category;
-- one row per DISTINCT category, with aggregates computed within each

HAVING: filtering groups, not rows

This is the one thing WHERE fundamentally cannot do — filter on the RESULT of an aggregate, since WHERE runs before aggregation happens.

SQLhaving.sql
SELECT category, COUNT(*) AS product_count
FROM products
GROUP BY category
HAVING COUNT(*) > 10;
-- WHERE COUNT(*) > 10 here would be a SYNTAX ERROR — COUNT isn't computed yet at WHERE's stage

WHERE and HAVING together

These commonly appear in the same query: WHERE narrows down which rows even enter the grouping step, and HAVING then filters the resulting groups — the two operate at genuinely different stages of query execution.

SQLwhere_and_having.sql
SELECT category, COUNT(*) AS product_count
FROM products
WHERE in_stock = true          -- filters ROWS first
GROUP BY category
HAVING COUNT(*) > 10;          -- filters GROUPS after aggregation

Sources

1
PostgreSQL Global Development Group. PostgreSQL Documentation, "Aggregate Functions" and "GROUP BY and HAVING", postgresql.org/docs.