SQLMentor // articles

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

WHEREHAVING
Runsbefore GROUP BYafter GROUP BY
Filtersindividual rowsgroups
Can reference aggregate functions?no — COUNT/SUM/AVG don't exist yetyes — that's its purpose
Can reference raw (non-grouped) columns?yesgenerally only grouped columns or aggregates
Required alongside GROUP BY?no — optionalno — 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.

Frequently asked questions

Can I use a column alias in HAVING?
It depends on the dialect. PostgreSQL and MySQL allow HAVING to reference a SELECT-list alias; Oracle and SQL Server generally do not (the alias doesn't exist yet at HAVING's logical evaluation point in the standard) — repeat the full expression to be safe and portable.
Is HAVING slower than WHERE?
When a condition doesn't need an aggregate, yes — using HAVING instead of WHERE for that condition forces the database to group unnecessary rows before filtering them out, wasting work. Use WHERE for row-level conditions and HAVING only for group-level (aggregate) conditions.
Can HAVING reference a column that isn't in GROUP BY?
Only if it's wrapped in an aggregate function. A bare, non-aggregated column not in the GROUP BY list is a syntax error in most dialects — the database can't know which value the group should show for it.
What order do WHERE, GROUP BY, HAVING, and ORDER BY actually run in?
FROM, then WHERE, then GROUP BY, then HAVING, then SELECT, then ORDER BY. Writing order matches this except SELECT, which is written first but evaluated 5th — see the SQL clause order table in the Oracle SQL cheat sheet.