HAVING vs WHERE
The key distinction: WHERE filters rows, HAVING filters groups.
| Clause | Filters | When it runs | Can use aggregates? |
|---|---|---|---|
WHERE |
Individual rows | Before GROUP BY | No |
HAVING |
Groups (after aggregation) | After GROUP BY | Yes |
-- WRONG: cannot use aggregate in WHERE
SELECT department, COUNT(*) AS cnt
FROM employees
WHERE COUNT(*) > 5 -- ERROR: aggregate functions not allowed in WHERE
GROUP BY department;
-- CORRECT: use HAVING for post-aggregation filtering
SELECT department, COUNT(*) AS cnt
FROM employees
GROUP BY department
HAVING COUNT(*) > 5; -- filters groups, not rows
HAVING Syntax
SELECT column1, aggregate_function(column2) AS alias
FROM table_name
WHERE row_condition -- optional: filter rows first
GROUP BY column1
HAVING group_condition -- filter groups after aggregation
ORDER BY column1;
MySQL and PostgreSQL allow referencing a SELECT alias in HAVING: HAVING cnt > 5. Standard SQL requires repeating the expression: HAVING COUNT(*) > 5. For portability, use the full expression in HAVING.
Practical Examples
Find departments with more than 2 employees and an average salary above 70,000:
SELECT
department,
COUNT(*) AS headcount,
AVG(salary) AS avg_salary
FROM employees
WHERE status = 'active' -- filter to active employees first
GROUP BY department
HAVING COUNT(*) > 2
AND AVG(salary) > 70000
ORDER BY avg_salary DESC;
Find product categories where total revenue exceeded 100,000 last year:
SELECT
category,
SUM(price * quantity) AS total_revenue
FROM order_items
JOIN products USING (product_id)
WHERE YEAR(order_date) = 2025
GROUP BY category
HAVING SUM(price * quantity) > 100000
ORDER BY total_revenue DESC;
SQL Execution Order
Understanding the logical order SQL processes a query is essential to writing correct HAVING clauses:
| Step | Clause | What happens |
|---|---|---|
| 1 | FROM / JOIN | Build the full dataset from tables |
| 2 | WHERE | Filter individual rows |
| 3 | GROUP BY | Collapse rows into groups |
| 4 | HAVING | Filter groups based on aggregate conditions |
| 5 | SELECT | Choose and compute output columns |
| 6 | ORDER BY | Sort the result set |
| 7 | LIMIT / OFFSET | Restrict the number of rows returned |
If you can eliminate rows before grouping, do so with WHERE. Fewer rows to group means the database engine does less work. Use HAVING only for conditions that genuinely depend on aggregate results.
Summary
π Summary
- HAVING filters groups produced by GROUP BY β it runs after aggregation.
- WHERE filters individual rows before grouping; it cannot reference aggregate functions.
- SQL execution order:
FROM β WHERE β GROUP BY β HAVING β SELECT β ORDER BY. - HAVING supports AND / OR to combine multiple group conditions.
- Filter with WHERE whenever possible for better performance; use HAVING only for aggregate conditions.
FAQ
Yes, technically. Without GROUP BY the entire table is treated as a single group. HAVING COUNT(*) > 100 with no GROUP BY checks whether the table has more than 100 rows and returns either all rows or no rows. In practice this is rarely useful and is a sign of a misunderstanding β use GROUP BY for meaningful grouping.
You can, but it is redundant. HAVING department = 'Engineering' is valid SQL, but the same filter should be written as WHERE department = 'Engineering' β it runs earlier, eliminates rows before grouping, and is more efficient.
Because HAVING is evaluated at step 4 in the logical order, and SELECT aliases are assigned at step 5. Some databases (MySQL, PostgreSQL) extend standard SQL to allow aliases in HAVING, but SQL Server and Oracle require the full expression. Write HAVING COUNT(*) > 5 rather than HAVING cnt > 5 for maximum portability.