SQL HAVING: filter groups
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
| WHERE | HAVING | |
|---|---|---|
| Acts on | rows | groups |
| When | before the GROUP BY | after the GROUP BY |
| Can contain COUNT, SUM… | no | yes |
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
| ville | COUNT(*) |
|---|---|
| Lyon | 5 |
| Paris | 2 |
| Nantes | 3 |
| ville | COUNT(*) |
|---|---|
| Lyon | 5 |
| Nantes | 3 |
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| ville | nb_clients |
|---|---|
| Bordeaux | 14 |
| Lyon | 12 |
| Paris | 13 |
| Strasbourg | 11 |
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| categorie | prix_moyen |
|---|---|
| Informatique | 146.43 |
| Maison | 36.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| client_id | livrees |
|---|---|
| 39 | 8 |
| 10 | 8 |
| 74 | 6 |
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.
SELECT ville, COUNT(*) FROM clients WHERE COUNT(*) > 10 GROUP BY ville;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.
SELECT ville, COUNT(*) FROM clients HAVING COUNT(*) > 10 GROUP BY ville;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.
SELECT ville, COUNT(*) FROM clients GROUP BY ville HAVING ville <> 'Paris';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;| departement | moyenne |
|---|---|
| Direction | 9,500 |
| IT | 4,760 |
| RH | 4,233 |
| Ventes | 4,554 |
4 rows in total
Try it in the sandboxChallenge 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_id | nb_lignes |
|---|---|
| 4 | 4 |
| 5 | 4 |
| 7 | 4 |
| 10 | 4 |
| 14 | 4 |
| 22 | 4 |
| 24 | 4 |
| 27 | 4 |
10 rows in total · 8 shown
Try it in the sandboxThe interactive exercises are checked automatically, with a tutor who explains your mistakes. A short lesson stays next to the editor.
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.