Loading content...
You've mastered the WHERE clause. You can filter rows with surgical precision—extracting exactly the records you need from vast tables. You've learned GROUP BY and aggregate functions, transforming raw data into meaningful summaries: total sales by region, average order value by customer, count of products by category.
But now you face a new challenge:
After grouping and aggregating, how do you filter the results of those aggregations? How do you show only regions with sales exceeding $100,000? Only customers who have placed more than 10 orders? Only categories where the average product price is above a threshold?
The WHERE clause cannot help you here. It operates before grouping—on individual rows, not on aggregated groups. You need a different mechanism: the HAVING clause.
By the end of this page, you will understand why group-level filtering requires a distinct clause, how HAVING operates conceptually within SQL's execution model, and when to reach for HAVING versus WHERE. You'll see how this seemingly simple clause unlocks powerful analytical queries.
To understand HAVING, we must first understand why WHERE cannot filter aggregated results. This isn't an arbitrary SQL limitation—it reflects the fundamental order of query processing.
Consider this scenario:
You have an orders table and want to find customers who have placed more than 5 orders. Intuitively, you might try:
12345
-- ❌ This query is INVALID! It will produce an error.SELECT customer_id, COUNT(*) AS order_countFROM ordersGROUP BY customer_idWHERE COUNT(*) > 5; -- ERROR: Cannot use aggregate in WHERE clauseThis query fails. The SQL engine rejects it outright. But why?
The answer lies in SQL's logical execution order:
The WHERE clause executes at step 2, before grouping occurs. At that point, COUNT(*) doesn't exist—there are no groups yet, only individual rows. Asking WHERE to filter on COUNT(*) is like asking the score of a game that hasn't been played.
This is not a SQL quirk—it's logically necessary.
Filtering on aggregates requires the aggregates to exist first. Aggregates require groups. Groups require the GROUP BY clause to have executed. Therefore, aggregate filtering must happen after GROUP BY.
This is precisely what HAVING provides: a filter that executes after grouping and aggregation, when aggregate values are actually available.
The HAVING clause is SQL's mechanism for filtering groups after aggregation. It appears after GROUP BY and can reference aggregate functions directly.
Syntax:
123456
SELECT column_list, aggregate_function(column) AS aliasFROM table_name[WHERE row_filter_conditions] -- Optional: filter rows BEFORE groupingGROUP BY grouping_columnsHAVING group_filter_conditions -- Filter groups AFTER aggregation[ORDER BY sort_columns];Now our earlier query works:
12345
-- ✓ Correct: Find customers with more than 5 ordersSELECT customer_id, COUNT(*) AS order_countFROM ordersGROUP BY customer_idHAVING COUNT(*) > 5; -- Valid: HAVING can reference aggregatesThe key insight:
When the database processes this query:
orderscustomer_idCOUNT(*)COUNT(*) is 5 or lessThe aggregate function COUNT(*) exists when HAVING executes because grouping has already occurred.
Think of HAVING as 'WHERE for groups.' WHERE filters individual rows before they're grouped. HAVING filters entire groups after they've been formed and aggregated. Both are filters—they just operate at different granularities and different times in query execution.
To truly master HAVING, you need a solid mental model of what 'filtering groups' actually means. Let's trace through a concrete example step by step.
Scenario:
We have a sales table recording transactions:
| sale_id | product_category | sale_amount |
|---|---|---|
| 1 | Electronics | 500 |
| 2 | Electronics | 750 |
| 3 | Clothing | 120 |
| 4 | Clothing | 80 |
| 5 | Books | 45 |
| 6 | Electronics | 1200 |
| 7 | Clothing | 95 |
Query:
SELECT product_category, SUM(sale_amount) AS total_sales
FROM sales
GROUP BY product_category
HAVING SUM(sale_amount) > 250;
Step-by-Step Execution:
sales tableproduct_category:
SUM(sale_amount) per group:
SUM(sale_amount) > 250:
product_category and total_sales for surviving groupsFinal Result:
| product_category | total_sales |
|---|---|
| Electronics | 2450 |
| Clothing | 295 |
Notice: The 'Books' group is completely absent. It was eliminated as a group because its aggregate failed the HAVING condition.
HAVING treats each group as an indivisible unit. It doesn't remove individual rows from within a group—it removes the entire group or keeps it intact. If a group's aggregate meets the HAVING condition, all its constituent rows contribute to the output. If not, the entire group vanishes.
A subtle but important case: you can use HAVING without an explicit GROUP BY clause. When you do, SQL treats the entire result set as a single group.
Example:
1234
-- Find if total sales exceed one million (returns 1 row or 0 rows)SELECT SUM(sale_amount) AS total_salesFROM salesHAVING SUM(sale_amount) > 1000000;What happens here:
SUM(sale_amount) aggregates across the entire tableThis is valid but uncommon. Most HAVING queries pair with explicit GROUP BY to filter multiple groups selectively.
This pattern is useful for conditional existence checks. 'Return a summary row only if it meets a threshold' is a legitimate business requirement—e.g., 'Show the company's total revenue only if it exceeds the minimum reporting threshold.'
Understanding what expressions are valid in HAVING is crucial. The rule is logically consistent with HAVING's position in query execution:
HAVING can reference:
COUNT(), SUM(), AVG(), MIN(), MAX(), etc.HAVING cannot reference:
HAVING COUNT(*) > 10HAVING AVG(salary) >= 50000HAVING MAX(price) - MIN(price) < 100HAVING department_id = 5 (if in GROUP BY)HAVING SUM(quantity) > 0 AND COUNT(*) > 1HAVING employee_name = 'John' (not in GROUP BY, not aggregate)HAVING total > 100 (alias, not yet computed)HAVING order_date > '2024-01-01' (non-grouped column)Some databases (notably MySQL in certain modes) allow referencing SELECT aliases in HAVING. This is non-standard behavior. For portable, correct SQL, always use the full expression in HAVING rather than relying on aliases. Write HAVING COUNT(*) > 5, not HAVING order_count > 5.
Let's examine several realistic business scenarios where HAVING proves essential.
Schema for examples:
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
total_amount DECIMAL(10,2),
status VARCHAR(20)
);
CREATE TABLE order_items (
item_id INT PRIMARY KEY,
order_id INT REFERENCES orders(order_id),
product_id INT,
quantity INT,
unit_price DECIMAL(10,2)
);
Business Need: Identify customers who have spent more than $5,000 in total.
12345678910
-- Find customers with total spending > $5,000SELECT customer_id, COUNT(*) AS total_orders, SUM(total_amount) AS lifetime_spendingFROM ordersWHERE status = 'completed' -- Only count completed ordersGROUP BY customer_idHAVING SUM(total_amount) > 5000ORDER BY lifetime_spending DESC;Note how WHERE and HAVING work together:
WHERE status = 'completed' filters rows before grouping—cancelled orders don't contributeHAVING SUM(total_amount) > 5000 filters groups after aggregation—only high-spenders appearSeveral misunderstandings about HAVING lead to subtle bugs and inefficient queries. Let's address them directly:
If your condition involves an aggregate function (COUNT, SUM, AVG, MIN, MAX, etc.), it must go in HAVING. If it references raw column values and can be evaluated row-by-row, it should go in WHERE. There's rarely ambiguity once you understand this rule.
The HAVING clause addresses a fundamental need: filtering results based on aggregate computations. Let's consolidate the key concepts:
What's next:
Now that you understand what HAVING does and when to use it, the next page provides a detailed comparison of HAVING and WHERE—clarifying their distinct roles, examining edge cases, and showing how they work together in complex queries.
You now understand group-level filtering with the HAVING clause. You can distinguish it from row-level filtering, understand its position in SQL's execution order, and recognize when aggregate conditions require HAVING. Next, we'll deepen this understanding by comparing HAVING and WHERE in detail.