HAVING vs WHERE in SQL
WHERE filters individual rows before grouping happens; HAVING filters groups after GROUP BY and aggregation. If your condition uses an aggregate like SUM() or COUNT(), it belongs in HAVING.
When Does WHERE Run vs HAVING?
SQL evaluates clauses in this logical order: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. That means WHERE sees raw rows and cannot reference aggregates, while HAVING sees one row per group and can.
-- WHERE can't do this; the aggregate doesn't exist yet:
SELECT region, SUM(amount)
FROM sales
WHERE SUM(amount) > 1000 -- ERROR: aggregate not allowed here
GROUP BY region;
How Do You Filter on an Aggregate?
Put the aggregate condition in HAVING, which runs after the groups are built.
SELECT region, SUM(amount) AS total_sales
FROM sales
GROUP BY region
HAVING SUM(amount) > 1000;
-- region | total_sales
-- West | 4200
-- East | 1850
Can You Use WHERE and HAVING Together?
Yes, and you usually should: WHERE cuts rows early (cheap), then HAVING filters the resulting groups.
-- Regions with over $1,000 in 2025 sales, excluding refunds
SELECT region, SUM(amount) AS total_sales
FROM sales
WHERE sale_date >= '2025-01-01'
AND amount > 0 -- row-level: runs first
GROUP BY region
HAVING SUM(amount) > 1000; -- group-level: runs after
Moving row-level conditions into WHERE shrinks the data before grouping, which is faster and lets indexes help.
Common Pitfalls
- Row conditions in HAVING:
HAVING amount > 0often works (some databases allow it) but filters after grouping — slower and usually wrong. UseWHERE. - Column aliases: most databases won't let you write
HAVING total_sales > 1000; repeat the aggregate expression instead (MySQL is the notable exception). - HAVING without GROUP BY: legal, but it treats the whole table as one group — rarely what you meant.
- COUNT(*) vs COUNT(col):
HAVING COUNT(col) > 5ignores NULLs in that column, which can change which groups pass.
For more on grouping itself, see GROUP BY and HAVING and the rundown of SQL aggregate functions.
Pro Tip: If a condition doesn't contain an aggregate function, put it in WHERE — even if it "works" in HAVING. The optimizer can push WHERE predicates into index scans; HAVING always runs after the expensive grouping step.