Introduction: The Problem HAVING Solves
In SQL, the WHERE clause is your everyday tool for filtering individual records. For instance, you can easily ask: "Give me all employees whose salary is above ₹60,000" or "Give me all orders shipped to Mumbai".
However, real-world analytical questions rarely stop at individual rows. Business questions almost always demand summaries:
- HR Director: "Show me only departments that have more than 5 employees."
- Finance Lead: "Show me only product categories whose total sales exceed ₹1,00,000."
- Operations Head: "Show me only warehouse hubs where the average delivery time is over 4 days."
If you try writing WHERE COUNT(*) > 5, SQL will immediately reject your query with a syntax error. Why? Because WHERE inspects rows beforegroups are formed. At that early stage, the database hasn't calculated the group totals yet. This is exactly why SQL introduced the HAVING clause.
What Does HAVING Do?
The HAVING clause filters the groups produced by GROUP BY, typically using the result of an aggregate function such as COUNT(), SUM(), AVG(), MIN(), or MAX().
As formally documented in PostgreSQL and SQL standard specifications: "The HAVING clause turns a table expression into a grouped table, and rows in the resulting table are eliminated if they do not satisfy the condition in the HAVING clause."
WHERE filters individual source rows, HAVING filters aggregate summary rows (the group buckets).WHERE vs HAVING: The Fundamental Distinction
Understanding the difference between WHERE and HAVING is the single most crucial milestone in SQL querying. Here is how they operate:
| Feature | WHERE Clause | HAVING Clause |
|---|---|---|
| Operates On | Individual raw rows | Aggregated groups / buckets |
| Execution Timing | BEFORE grouping (before GROUP BY) | AFTER grouping (after GROUP BY) |
| Aggregate Functions? | ❌ No (e.g. WHERE SUM(x) > 10 is invalid) | ✅ Yes (e.g. HAVING SUM(x) > 10 is standard) |
| Purpose | Filters which rows participate in grouping | Filters which summary groups appear in final output |
Let's examine a combined query step-by-step:
- Step 1 (WHERE): SQL looks at all rows and discards anyone not located in 'Mumbai'.
- Step 2 (GROUP BY): The remaining Mumbai employees are grouped by
department. - Step 3 (COUNT): SQL counts the Mumbai employees in each department group.
- Step 4 (HAVING): SQL filters the groups, keeping only departments that have 3 or more Mumbai employees.
HAVING With COUNT
COUNT() is the most common aggregate used with HAVING. It allows you to filter groups based on group size or transaction count.
HAVING COUNT(*) > 5 ➔ ✓ KEEP (8 > 5)HAVING COUNT(*) > 5 ➔ ✗ REMOVE (3 is not > 5)HAVING COUNT(*) > 5 ➔ ✓ KEEP (7 > 5)HAVING With SUM
When analyzing financial transactions, sales, or quantities, you often need to find categories or customers whose cumulative total exceeds a target threshold:
Here, individual rows with small amounts (e.g. ₹500 items) are not removed individually. Instead, all items in a category are summed up, and if the category total exceeds ₹1,00,000, the category is retained in the result.
HAVING With AVG
You can also filter groups based on their arithmetic mean using AVG():
Notice that an employee earning ₹30,000 in the Engineering department will still participate in the calculation. If the overall Engineering department average is ₹75,000, the Engineering group satisfies the condition and is shown.
HAVING With MIN / MAX
MIN() and MAX() can be used in HAVING when you want to filter groups based on extremes, such as finding categories that contain at least one high-ticket item:
This retains any product category whose most expensive item costs more than ₹1,000.
HAVING Without GROUP BY (Advanced Note)
In SQL standards (including PostgreSQL, MySQL, SQL Server, SQLite), a query can technically have a HAVING clause without an explicit GROUP BY clause.
In this case, the entire table is treated as a single group. If the overall table average exceeds ₹50,000, one row is returned. If not, zero rows are returned. While syntactically valid, in everyday practice HAVING is almost always paired with GROUP BY.
Multiple HAVING Conditions
Just like WHERE, you can combine multiple conditions in HAVING using boolean logical operators like AND, OR, and NOT:
A department will only appear in the final output if it satisfies both requirements: more than 5 members AND an average compensation over ₹50,000.
Understanding the Full Query Flow
Let's see how all the clauses work together in a complete SQL query:
Common HAVING Mistakes
| Common Mistake | Incorrect SQL ❌ | Correct SQL ✅ |
|---|---|---|
| Filtering aggregates in WHERE | WHERE COUNT(*) > 5(Syntax Error: aggregate functions not allowed in WHERE) | HAVING COUNT(*) > 5 |
| Filtering row columns in HAVING | HAVING city = 'Mumbai'(Inefficient & invalid in strict SQL if city not in GROUP BY) | WHERE city = 'Mumbai'(Filter raw rows early with WHERE) |
| Forgetting GROUP BY | SELECT dept, COUNT(*) FROM emp HAVING COUNT(*) > 5; | SELECT dept, COUNT(*) FROM emp GROUP BY dept HAVING COUNT(*) > 5; |
| Confusing row condition with group condition | Want departments with any salary > 50k but writing HAVING salary > 50k | HAVING MAX(salary) > 50000 or WHERE salary > 50000 depending on intent |
Practical HAVING Exercises
Test your understanding by writing mental SQL queries for these progressive scenarios:
Find all departments with more than 5 employees.
SELECT department, COUNT(*) FROM employees GROUP BY department HAVING COUNT(*) > 5;Find product categories with cumulative sales exceeding ₹1,00,000.
SELECT category, SUM(amount) FROM sales GROUP BY category HAVING SUM(amount) > 100000;Find departments with an average salary greater than ₹50,000.
SELECT department, AVG(salary) FROM employees GROUP BY department HAVING AVG(salary) > 50000;Find categories whose maximum product price is greater than ₹1,000.
SELECT category, MAX(price) FROM products GROUP BY category HAVING MAX(price) > 1000;Find departments in 'Delhi' that have at least 2 employees.
SELECT department, COUNT(*) FROM employees WHERE city = 'Delhi' GROUP BY department HAVING COUNT(*) >= 2;HAVING Best Practices
COUNT(), SUM(), AVG(), etc.What You Should Know Now
AND / OR.Experiment with WHERE (row filtering), GROUP BY (bucketing), and HAVING (group filtering) in real time.
SELECT department,
COUNT(*) AS employees,
AVG(salary) AS avg_salary,
SUM(sales) AS total_sales
FROM employees
GROUP BY department
HAVING COUNT(*) >= 3;| department | employees | avg_salary | total_sales |
|---|---|---|---|
| Sales | 3 | ₹65,000 | ₹375,000 |
| Engineering | 3 | ₹91,667 | ₹120,000 |
Given the complete employees table (9 rows):
SELECT department, COUNT(*) AS emp_count FROM employees GROUP BY department HAVING COUNT(*) >= 3;
🎯 Knowledge Assessment: SQL HAVING
Test your conceptual understanding and reasoning with 8 practical questions.