SQL Quest
Sign in Français 0 XP Lvl 1
Intermediate

SQL HAVING: filter groups

4 min read · 3 runnable examples · updated October 9, 2026

After a GROUP BY, you sometimes want to keep only some groups: the cities with more than 10 customers, the categories whose average price exceeds €20, the customers who placed at least 5 orders. WHERE cannot do this, because it acts before the grouping. You need HAVING.

Syntax

SELECT column, FUNCTION(column)
FROM table
WHERE condition_on_rows
GROUP BY column
HAVING condition_on_groups;

How it works

WHEREHAVING
Acts onrowsgroups
Whenbefore the GROUP BYafter the GROUP BY
Can contain COUNT, SUM…noyes

You can use both in the same query: WHERE first removes rows, GROUP BY builds the groups from the remaining ones, then HAVING removes groups.

When a condition is on a plain column (not on an aggregate), put it in the WHERE instead: the database then works on fewer rows.

Diagram

groups
villeCOUNT(*)
Lyon5
Paris2
Nantes3
result
villeCOUNT(*)
Lyon5
Nantes3
HAVING COUNT(*) >= 3: after grouping, only the groups with at least 3 rows are kept.

Simplified diagram to show the idea. The examples below use the real SQL Quest practice database (a fictional online shop: see the full schema).

Glossary: the sample database uses French names
clients
customers
commandes
orders
lignes_commande
order lines
produits
products
fournisseurs
suppliers
employes
employees
prenom / nom
first name / last name (or name)
ville
city
pays
country
prix
price
prix_unitaire
unit price
quantite
quantity
categorie
category
date_commande
order date
poste
job title
departement
department
salaire
salary
statut
status: livrée = delivered, expédiée = shipped, en cours = in progress, annulée = cancelled

Examples with their results

1. Keep the large groups

The cities with more than 10 customers.

SELECT ville, COUNT(*) AS nb_clients
FROM clients
GROUP BY ville
HAVING COUNT(*) > 10;
Try it in the sandbox
Result
villenb_clients
Bordeaux14
Lyon12
Paris13
Strasbourg11

4 rows in total

2. Filter on an average

The categories whose average price exceeds €30.

SELECT categorie, ROUND(AVG(prix), 2) AS prix_moyen
FROM produits
GROUP BY categorie
HAVING AVG(prix) > 30;
Try it in the sandbox
Result
categorieprix_moyen
Informatique146.43
Maison36.45

2 rows in total

3. WHERE and HAVING together

Among delivered orders only, the customers who have at least 6.

SELECT client_id, COUNT(*) AS livrees
FROM commandes
WHERE statut = 'livrée'
GROUP BY client_id
HAVING COUNT(*) >= 6
ORDER BY livrees DESC;
Try it in the sandbox
Result
client_idlivrees
398
108
746

3 rows in total

Common mistakes

Putting the aggregate in WHERE

SQLite answers misuse of aggregate: at WHERE time, the groups do not exist yet.

Avoid
SELECT ville, COUNT(*) FROM clients WHERE COUNT(*) > 10 GROUP BY ville;
Correct
SELECT ville, COUNT(*) FROM clients GROUP BY ville HAVING COUNT(*) > 10;

Putting HAVING before GROUP BY

HAVING always comes after GROUP BY when you write the query.

Avoid
SELECT ville, COUNT(*) FROM clients HAVING COUNT(*) > 10 GROUP BY ville;
Correct
SELECT ville, COUNT(*) FROM clients GROUP BY ville HAVING COUNT(*) > 10;

Using HAVING for a simple condition

It works, but it is less efficient: filter the rows in the WHERE.

Avoid
SELECT ville, COUNT(*) FROM clients GROUP BY ville HAVING ville <> 'Paris';
Correct
SELECT ville, COUNT(*) FROM clients WHERE ville <> 'Paris' GROUP BY ville;

Exercises

Try writing the query yourself before opening the solution.

Challenge 1. Show the departments whose average salary exceeds €4,000.

Show a solution
SELECT departement, ROUND(AVG(salaire)) AS moyenne FROM employes GROUP BY departement HAVING AVG(salaire) > 4000;
departementmoyenne
Direction9,500
IT4,760
RH4,233
Ventes4,554

4 rows in total

Try it in the sandbox

Challenge 2. Show the orders (commande_id) that contain at least 4 lines.

Show a solution
SELECT commande_id, COUNT(*) AS nb_lignes FROM lignes_commande GROUP BY commande_id HAVING COUNT(*) >= 4 LIMIT 10;
commande_idnb_lignes
44
54
74
104
144
224
244
274

10 rows in total · 8 shown

Try it in the sandbox
Want more practice?

The interactive exercises are checked automatically, with a tutor who explains your mistakes. A short lesson stays next to the editor.

Do the interactive exercises

Frequently asked questions

Can I use HAVING without GROUP BY?

Yes: the whole table then forms a single group. It is rarely useful in practice.

Can I use an alias in HAVING?

SQLite and MySQL accept it (HAVING nb_clients > 10). For portable code, repeat the expression: HAVING COUNT(*) > 10.

Is HAVING slower than WHERE?

For the same condition on a plain column, yes, because HAVING applies after all the rows have been grouped. Keep HAVING for conditions on aggregates.

Read next