HAVING
WHERE filters rows; HAVING filters groups. Knowing the difference is a favourite interview question — and essential for reports.
On this page · 5 min · Beginner
On this page
- In simple words
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Beginner
- Updated
- Oct 27, 2026
Related tutorials
In simple words#
WHERE filters students before making piles; HAVING filters the piles afterwards: “only classes with more than 40 students.”
- HAVING
- Filter groups after GROUP BY.
- WHERE vs HAVING
- Rows before grouping vs groups after.
- Typical use
- HAVING COUNT(*) > 40.
Learning objectives#
- Filter groups with HAVING
- Explain WHERE vs HAVING
- Know the logical processing order of a query
- Combine joins, grouping and HAVING
Explanation#
HAVING is applied after GROUP BY, so it can use aggregate functions such as SUM and COUNT.
The logical order is: FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. That is why WHERE cannot refer to aggregates or SELECT aliases.
Filter rows early in WHERE when you can — fewer rows to group means less work.
Syntax#
SELECT col, SUM(x) FROM t
WHERE rowCondition
GROUP BY col
HAVING SUM(x) > 100;
Code example: Customers who spent more than ₹10,000#
SELECT c.Name, SUM(oi.Qty * oi.UnitPrice) AS TotalSpent
FROM Customers AS c
JOIN Orders AS o ON o.CustomerId = c.CustomerId
JOIN OrderItems AS oi ON oi.OrderId = o.OrderId
WHERE o.Status <> 'Cancelled'
GROUP BY c.Name
HAVING SUM(oi.Qty * oi.UnitPrice) > 10000
ORDER BY TotalSpent DESC;
Name TotalSpent Asha 56900.00 Ravi 12000.00
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- The joins produce one row per order item with its customer.
- WHERE removes Meera’s cancelled order before grouping.
- GROUP BY totals each customer: Asha 56,400 + 500 = 56,900; Ravi 12,000.
- HAVING keeps only totals above 10,000.
- ORDER BY can use the alias TotalSpent because it runs after SELECT.
Real-world use case#
Loyalty programmes find “customers who spent over X this year” with exactly this pattern.
Common mistakes#
HAVING Status <> 'Cancelled' works only if Status is grouped and is slower. Put row conditions in WHERE.
HAVING TotalSpent > 10000 fails in SQL Server because HAVING runs before SELECT. Repeat the expression.
Best practices#
- Row filters in WHERE, aggregate filters in HAVING
- Repeat the aggregate expression in HAVING
- Remember the logical processing order when debugging
- Alias aggregate columns for readable output
Remember this#
WHERE filters rows; HAVING filters groups.
HAVING comes after GROUP BY.
HAVING COUNT(*) > 5 = groups with more than 5 rows.
Practice exercise#
Your task: List categories whose total product value is under 20,000.
Show solution
SELECT Category, SUM(Price) AS Total FROM Products GROUP BY Category HAVING SUM(Price) < 20000; -- Furniture 16500.00, Stationery 80.00
Mini quiz#
Which clause can filter using COUNT(*)?
Want more? Practise all SQL Server MCQs · SQL Server interview questions
My notes#
Saved in this browser.