WHERE filters rows before grouping; HAVING filters groups after aggregation. Same idea, different stage.
WHERE | HAVING | |
|---|---|---|
| Filters | Individual rows | Groups (aggregated rows) |
| Runs | Before GROUP BY | After GROUP BY |
| Can use aggregates | No | Yes |
| Can use indexes | Yes | Rarely — groups are computed first |
Needs GROUP BY | No | Usually (without it, the whole table is one group) |
-- departments whose average salary exceeds 90000, counting only people hired before 2023
SELECT dept_id,
COUNT(*) AS staff,
ROUND(AVG(salary)) AS avg_salary
FROM employees
WHERE hire_date < '2023-01-01' -- rows
GROUP BY dept_id
HAVING AVG(salary) > 90000 -- groups
ORDER BY avg_salary DESC;dept_id | staff | avg_salary
--------+-------+-----------
1 | 4 | 115000.0Customers with more than one order
SELECT customer_id, COUNT(*) AS orders
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 1
ORDER BY customer_id;customer_id | orders
------------+-------
1 | 3
2 | 2
3 | 2Put row filters in WHERE
-- HAVING without GROUP BY: the whole table is one group
SELECT COUNT(*) AS products FROM products HAVING MIN(price) < 100;
SELECT COUNT(*) AS products FROM products HAVING MIN(price) < 10;products
--------
7
products
--------What is the difference between WHERE and HAVING?
Find duplicate emails in a users table.