ShareYourGroupShareYourGroupSQL

Lesson 13 of 26 · see the practice database

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

categoryaverage
Electronics577.0
Home787.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_idorders
14
23
43

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

citycustomers
Mumbai2
Ludhiana2
Delhi2

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

salarypeople
480002

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.

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_iddelivered
23
13
82

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 aboutUseExample
a value in each rowWHEREstatus = 'delivered'
a count, sum or average of a groupHAVINGCOUNT(*) >= 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

categoryproducts
Electronics4
Grocery4
Stationery4

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

categoryproductsstock
Electronics4253
Grocery4175
Stationery4515

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_idunits
515
912
67
26
35

5 rows

Try it yourself

Exercise 1. Show the departments that have more than one employee, with the number of employees in each.

Solve it in the practice box

Show one answer
SELECT department, COUNT(*) FROM employees GROUP BY department HAVING COUNT(*) > 1;
departmentCOUNT(*)
Sales3
Support2
Warehouse2

3 rows

Exercise 2. Show the states that have at least two customers. Show the state and the count.

Solve it in the practice box

Show one answer
SELECT state, COUNT(*) FROM customers GROUP BY state HAVING COUNT(*) >= 2;
stateCOUNT(*)
Delhi2
Maharashtra3
Punjab3

3 rows

Exercise 3. Show the order ids that contain three or more different lines in order_items, with the number of lines.

Solve it in the practice box

Show one answer
SELECT order_id, COUNT(*) FROM order_items GROUP BY order_id HAVING COUNT(*) >= 3;
order_idCOUNT(*)
13
73
113
203

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.