What is the difference between WHERE and HAVING?
WHERE filters individual rows before they are grouped, so it cannot use aggregate functions like SUM or COUNT. HAVING filters groups after GROUP BY has combined the rows, so it is where conditions on aggregates go.
Explain it simply
Suppose a college wants to know which departments have an average mark above 75, counting only final-year students.
First you go through the pile of mark sheets and throw out everyone who is not in final year. That is WHERE: it looks at one sheet at a time.
Then you sort the remaining sheets into piles by department and work out each pile's average. Finally, you throw away the piles whose average is 75 or less. That is HAVING: it looks at whole piles, after grouping.
Example
Table marks:
| student | department | year | score |
|---|---|---|---|
| Asha | CSE | 4 | 82 |
| Vikram | CSE | 4 | 78 |
| Imran | ECE | 4 | 70 |
| Pooja | ECE | 4 | 74 |
| Karan | CSE | 2 | 60 |
SELECT department, AVG(score) AS avg_score, COUNT(*) AS students
FROM marks
WHERE year = 4 -- row filter: before grouping
GROUP BY department
HAVING AVG(score) > 75; -- group filter: after groupingResult:
| department | avg_score | students |
|---|---|---|
| CSE | 80 | 2 |
Karan was removed by WHERE (not final year). ECE was removed by HAVING (average 72).
Trying to use an aggregate in WHERE fails:
SELECT department FROM marks WHERE AVG(score) > 75 GROUP BY department;
-- ERROR: aggregate functions are not allowed in WHEREHow it works
The database processes a query in a logical order that is different from the order you write it:
FROMandJOIN: get the rowsWHERE: filter rowsGROUP BY: form groups- Aggregates are computed (
SUM,AVG,COUNT) HAVING: filter groupsSELECT: pick the output columnsORDER BY, thenLIMIT
Since WHERE runs before any group exists, there is no SUM or AVG for it to check. HAVING runs after aggregates are calculated, so it can use them.
Real-world use
- Customers who placed more than five Swiggy orders last month:
WHERE order_date >= ...thenGROUP BY customer_id HAVING COUNT(*) > 5. - Finding duplicate emails in a users table:
GROUP BY email HAVING COUNT(*) > 1. - Cities whose total UPI transaction value crossed a threshold in a report.
Common mistakes
- Putting a plain row condition in HAVING, such as
HAVING year = 4. Many databases reject it unlessyearis in GROUP BY, and even when allowed it filters late, after doing all the grouping work. Row conditions belong in WHERE. - Using a SELECT alias in HAVING (
HAVING avg_score > 75). MySQL accepts this, but standard SQL, PostgreSQL and SQL Server do not. Repeat the expression instead. - Thinking HAVING needs GROUP BY. Without GROUP BY, the whole table is treated as one group, so
SELECT COUNT(*) FROM marks HAVING COUNT(*) > 3is valid.
What to say in the interview
"WHERE filters rows before grouping, so it works on individual column values and cannot use aggregate functions. HAVING filters after GROUP BY, on the groups, so it is used for conditions like COUNT(*) > 5 or AVG(score) > 75. Logically the order is FROM, WHERE, GROUP BY, aggregates, HAVING, SELECT, ORDER BY. For performance I put every row-level condition in WHERE, so fewer rows reach the grouping step."
Follow-up questions
- Can you use HAVING without GROUP BY?
- Why can't you use a column alias from SELECT in WHERE?
- How would you find duplicate rows in a table?