Filtering groups with HAVING
WHERE filters rows. But after grouping, you often want to filter the groups: only the categories with more than three products, only the customers with at least two orders. WHERE cannot do it, because it runs before the groups exist. HAVING can.
The problem
Which categories have an average price above 400 rupees? This is the natural first try.
SELECT category, AVG(price)
FROM products
WHERE AVG(price) > 400
GROUP BY category;What the database says
Error: misuse of aggregate: AVG()The database refuses. At the time WHERE runs, it is looking at single rows. There is no average yet.
HAVING
HAVING goes after GROUP BY. It is a WHERE for groups, and it can use aggregate functions.
SELECT category, ROUND(AVG(price)) AS average
FROM products
GROUP BY category
HAVING AVG(price) > 400;Result
| category | average |
|---|---|
| Electronics | 577.0 |
| Home | 787.0 |
2 rows
The database forms the four groups, works out the average of each, and then keeps only the groups that pass.
Counting and keeping
Which customers have placed more than two orders?
SELECT customer_id, COUNT(*) AS orders
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 2;Result
| customer_id | orders |
|---|---|
| 1 | 4 |
| 2 | 3 |
| 4 | 3 |
3 rows
Which cities have more than one customer?
SELECT city, COUNT(*) AS customers
FROM customers
GROUP BY city
HAVING COUNT(*) > 1
ORDER BY customers DESC;Result
| city | customers |
|---|---|
| Mumbai | 2 |
| Ludhiana | 2 |
| Delhi | 2 |
3 rows
Finding duplicates
HAVING COUNT(*) > 1 is the standard way to find values that appear more than once. It is one of the most useful patterns in the language for checking data. Are any two employees on exactly the same salary?
SELECT salary, COUNT(*) AS people
FROM employees
GROUP BY salary
HAVING COUNT(*) > 1;Result
| salary | people |
|---|---|
| 48000 | 2 |
1 row
In real work the same query finds customers entered twice with the same email address, or invoice numbers used twice.
WHERE and HAVING together
A query can have both. Each does its own job at its own time.
WHEREremoves rows before grouping.HAVINGremoves groups after grouping.
Among delivered orders only, which customers have at least two?
SELECT customer_id, COUNT(*) AS delivered
FROM orders
WHERE status = 'delivered'
GROUP BY customer_id
HAVING COUNT(*) >= 2
ORDER BY delivered DESC;Result
| customer_id | delivered |
|---|---|
| 2 | 3 |
| 1 | 3 |
| 8 | 2 |
3 rows
Read it in the order it runs. Take the orders. Keep the delivered ones. Pile them by customer. Count each pile. Keep the piles of two or more. Sort.
Which one should hold a condition?
Ask one question: is the test about a single row, or about a group as a whole?
| The condition is about | Use | Example |
|---|---|---|
| a value in each row | WHERE | status = 'delivered' |
| a count, sum or average of a group | HAVING | COUNT(*) >= 2 |
A test on a plain column may be legal in HAVING if you grouped by that column, and it returns the same result. Put it in WHERE anyway. Filtering early means fewer rows to group, which is faster, and it keeps each clause to its proper job.
SELECT category, COUNT(*) AS products
FROM products
WHERE category <> 'Home'
GROUP BY category;Result
| category | products |
|---|---|
| Electronics | 4 |
| Grocery | 4 |
| Stationery | 4 |
3 rows
Several conditions
HAVING takes AND and OR like WHERE.
SELECT category, COUNT(*) AS products, SUM(stock) AS stock
FROM products
GROUP BY category
HAVING COUNT(*) = 4 AND SUM(stock) > 150;Result
| category | products | stock |
|---|---|---|
| Electronics | 4 | 253 |
| Grocery | 4 | 175 |
| Stationery | 4 | 515 |
3 rows
The aggregate in HAVING need not be shown
You can filter on a figure that is not in the SELECT list.
SELECT department
FROM employees
GROUP BY department
HAVING MAX(salary) < 50000;Result
| department |
|---|
| Support |
| Warehouse |
2 rows
Departments in which nobody earns 50,000 or more.
Can HAVING use an alias?
In standard SQL, no: write the aggregate again, as the examples here do. MySQL and SQLite allow HAVING orders > 2 with an alias. PostgreSQL, SQL Server and Oracle do not. Repeating the function works everywhere.
HAVING without GROUP BY
It is allowed. The whole table is then one group. It is rarely useful, and seeing it usually means that WHERE was intended.
The full skeleton
You have now met every clause of a query on one table. They are always written in this order:
SELECT columns and aggregates
FROM table
WHERE conditions on rows
GROUP BY columns
HAVING conditions on groups
ORDER BY columns
LIMIT number;A real example with all seven:
SELECT product_id, SUM(quantity) AS units
FROM order_items
WHERE quantity < 10
GROUP BY product_id
HAVING SUM(quantity) >= 4
ORDER BY units DESC
LIMIT 5;Result
| product_id | units |
|---|---|
| 5 | 15 |
| 9 | 12 |
| 6 | 7 |
| 2 | 6 |
| 3 | 5 |
5 rows
Try it yourself
Exercise 1. Show the departments that have more than one employee, with the number of employees in each.
Show one answer
SELECT department, COUNT(*) FROM employees GROUP BY department HAVING COUNT(*) > 1;| department | COUNT(*) |
|---|---|
| Sales | 3 |
| Support | 2 |
| Warehouse | 2 |
3 rows
Exercise 2. Show the states that have at least two customers. Show the state and the count.
Show one answer
SELECT state, COUNT(*) FROM customers GROUP BY state HAVING COUNT(*) >= 2;| state | COUNT(*) |
|---|---|
| Delhi | 2 |
| Maharashtra | 3 |
| Punjab | 3 |
3 rows
Exercise 3. Show the order ids that contain three or more different lines in order_items, with the number of lines.
Show one answer
SELECT order_id, COUNT(*) FROM order_items GROUP BY order_id HAVING COUNT(*) >= 3;| order_id | COUNT(*) |
|---|---|
| 1 | 3 |
| 7 | 3 |
| 11 | 3 |
| 20 | 3 |
4 rows
Examples on this page were run with SQLite 3.50.4 on 12 November 2026. The results shown are the real results.
This is an independent learning site. It is not connected with the makers of any database product. MySQL, PostgreSQL, SQL Server, Oracle and SQLite are named only so that you know where a piece of SQL works. The shop, its customers and its staff in the practice database are made up.