SQL WHERE vs HAVING: The Real Difference, Explained
WHERE and HAVING both filter rows, but at different points in a query's execution — and that timing difference is the entire reason both clauses need to exist. Here's exactly how they differ, with an example that uses both correctly.
The one-sentence answer
WHERE filters individual rows before they're grouped. HAVING filters groups after GROUP BY has run. That single ordering fact explains every other difference between them.
WHERE vs HAVING at a glance
| WHERE | HAVING | |
|---|---|---|
| Runs | before GROUP BY | after GROUP BY |
| Filters | individual rows | groups |
| Can reference aggregate functions? | no — COUNT/SUM/AVG don't exist yet | yes — that's its purpose |
| Can reference raw (non-grouped) columns? | yes | generally only grouped columns or aggregates |
| Required alongside GROUP BY? | no — optional | no — optional, but pointless without an aggregate condition |
Both in one query
Find departments with more than 5 employees, counting only employees hired since 2000.
SELECT department_id, COUNT(*) AS emp_count
FROM employees
WHERE hire_date >= DATE '2000-01-01' -- filters rows, before grouping
GROUP BY department_id
HAVING COUNT(*) > 5 -- filters groups, after grouping
ORDER BY emp_count DESC;
Why WHERE can't see aggregates
At the point WHERE runs, grouping hasn't happened yet — there is no "group" for COUNT(*) or AVG(salary) to summarise. Those values only exist once GROUP BY has collapsed rows into groups, which happens after WHERE in the logical processing order.
This is why WHERE COUNT(*) > 5 is a syntax error in every mainstream SQL dialect, while HAVING COUNT(*) > 5 works — HAVING runs at the point in the pipeline where COUNT(*) is a real, computed value.
HAVING without GROUP BY
Rare but legal: with no GROUP BY, the whole table is treated as a single group.
SELECT COUNT(*) AS total_employees
FROM employees
HAVING COUNT(*) > 100; -- returns one row if true, zero rows if false
The most common mistake
Using HAVING where WHERE would be correct and faster: SELECT department_id, COUNT(*) FROM employees GROUP BY department_id HAVING department_id = 60 works, but it filters the department after counting every department's employees. WHERE department_id = 60 filters first, so the database only has to count one department's rows. If a condition doesn't involve an aggregate, it almost always belongs in WHERE.