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

SQL GROUP BY: compute per group

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

Aggregate functions summarize a whole table into a single value. But you often want a result per category: the number of customers per city, the revenue per month, the average salary per department. That is the job of GROUP BY.

Syntax

SELECT group_column, FUNCTION(column)
FROM table
GROUP BY group_column;

How it works

GROUP BY gathers the rows that have the same value in the given column, then applies the aggregate function to each group. The result contains one row per group.

The golden rule

Every column in the SELECT must either appear in the GROUP BY or be inside an aggregate function. Otherwise, SQL would not know which value to show for a group that contains several different rows.

The execution order of a query

SQL does not run the clauses in the order you write them:

  1. FROM (and the JOINs): start from the tables;
  2. WHERE: filter the rows;
  3. GROUP BY: build the groups;
  4. HAVING: filter the groups;
  5. SELECT: compute the columns;
  6. ORDER BY, then LIMIT.

Understanding this order explains most errors: for example, you cannot use a COUNT in the WHERE, because the groups do not exist yet at that point.

Diagram

clients
prenomville
LéaLyon
InèsLyon
HugoParis
AdamLyon
ZoéParis
result
villeCOUNT(*)
Lyon3
Paris2
GROUP BY ville: rows with the same city form a group, and COUNT(*) counts each group.

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. Count per group

The number of customers per city, from the largest to the smallest.

SELECT ville, COUNT(*) AS nb_clients
FROM clients
GROUP BY ville
ORDER BY nb_clients DESC;
Try it in the sandbox
Result
villenb_clients
Bordeaux14
Paris13
Lyon12
Strasbourg11
Nantes10
Montréal10
Marseille10
Lille10
Casablanca10
Bruxelles9

12 rows in total · 10 shown

2. Several aggregates per group

For each category: the number of products and the average price.

SELECT categorie, COUNT(*) AS nb_produits, ROUND(AVG(prix), 2) AS prix_moyen
FROM produits
GROUP BY categorie;
Try it in the sandbox
Result
categorienb_produitsprix_moyen
Alimentation524.74
Informatique7146.43
Livres518.17
Maison636.45
Sport627.81

5 rows in total

3. Group on an expression

The number of orders per year, using strftime to extract the year from a date.

SELECT strftime('%Y', date_commande) AS annee, COUNT(*) AS nb_commandes
FROM commandes
GROUP BY annee;
Try it in the sandbox
Result
anneenb_commandes
2023190
2024214
2025196

3 rows in total

4. Group on two columns

The number of orders per year and per status.

SELECT strftime('%Y', date_commande) AS annee, statut, COUNT(*) AS nb
FROM commandes
GROUP BY annee, statut
ORDER BY annee, nb DESC
LIMIT 8;
Try it in the sandbox
Result
anneestatutnb
2023livrée85
2023annulée39
2023en cours34
2023expédiée32
2024livrée117
2024expédiée35
2024en cours34
2024annulée28

8 rows in total

Common mistakes

Showing a column that is not grouped

nom is neither in the GROUP BY nor in an aggregate: SQLite shows a random name from the group, other databases return an error.

Avoid
SELECT ville, nom, COUNT(*) FROM clients GROUP BY ville;
Correct
SELECT ville, COUNT(*) FROM clients GROUP BY ville;

Forgetting the GROUP BY

Without GROUP BY, the query returns a single row for the whole table instead of one row per city.

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

Grouping on a column that is too precise

Grouping by id creates one group per row: the grouping is useless.

Avoid
SELECT id, ville, COUNT(*) FROM clients GROUP BY id;
Correct
SELECT ville, COUNT(*) FROM clients GROUP BY ville;

Exercises

Try writing the query yourself before opening the solution.

Challenge 1. Show the average salary per department, rounded to the euro.

Show a solution
SELECT departement, ROUND(AVG(salaire)) AS salaire_moyen FROM employes GROUP BY departement;
departementsalaire_moyen
Direction9,500
IT4,760
RH4,233
Ventes4,554

4 rows in total

Try it in the sandbox

Challenge 2. Show the number of products per supplier (fournisseur_id, count).

Show a solution
SELECT fournisseur_id, COUNT(*) AS nb FROM produits GROUP BY fournisseur_id;
fournisseur_idnb
11
22
34
413
51
65
73

7 rows in total

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

What is the difference between GROUP BY and ORDER BY?

GROUP BY gathers rows to compute one value per group (the number of rows goes down). ORDER BY only sorts the rows, without changing how many there are.

Can I use an alias in GROUP BY?

SQLite, MySQL and PostgreSQL accept it (GROUP BY annee). Standard SQL and SQL Server require you to repeat the expression.

How do I filter groups?

With HAVING, placed after GROUP BY: HAVING COUNT(*) > 10 only keeps the groups with more than 10 rows.

Read next