HAVING
WHERE filters rows, HAVING filters groups
WHERE | HAVING | |
|---|---|---|
Runs | Before grouping | After grouping |
Filters | Individual rows | Entire groups |
Can reference aggregates? | No | Yes |
Can reference raw columns? | Yes | Yes (if grouped or aggregated) |
The error WHERE gives you
COUNT(*) don’t exist yet at the point WHERE runs — grouping and aggregation haven’t happened. Trying to filter on one there raises an error:Error — aggregate function not allowed in WHERE
-- This fails: "aggregate functions are not allowed in WHERE" SELECT department, COUNT(*) AS employee_count FROM employees WHERE COUNT(*) > 5 GROUP BY department;
The HAVING fix
HAVING runs after GROUP BY has produced its groups and after aggregate functions have been computed, so it can filter on them directly:
Correct — filter groups with HAVING
SELECT department, COUNT(*) AS employee_count FROM employees GROUP BY department HAVING COUNT(*) > 5;
department | employee_count -------------+---------------- Engineering | 12 Sales | 8
Departments with five or fewer employees are excluded entirely from the result, because their group didn’t satisfy the HAVING condition — but the counting itself still happened across all departments first.
Combining WHERE and HAVING
The two work together naturally: WHERE trims rows before grouping (for efficiency and to exclude rows that shouldn’t count at all), and HAVING trims groups after aggregation:
Active employees only, then filter by department size
SELECT department, COUNT(*) AS employee_count, AVG(salary) AS avg_salary FROM employees WHERE is_active = TRUE GROUP BY department HAVING COUNT(*) > 5 AND AVG(salary) > 70000;
WHERE filters individual rows before grouping and cannot reference aggregate functions
HAVING filters entire groups after grouping and aggregation, and can reference aggregate functions freely
Use WHERE to cut down rows early for efficiency; use HAVING to filter based on a group's computed values