Exercices SQL corrigés : HAVING

Exercices SQL corrigés : HAVING

Mis à jour le

HAVING filtre les groupes après GROUP BY (WHERE, lui, filtre les lignes avant). Ces 12 exercices vont du niveau 2 au niveau 3 ; ils utilisent la syntaxe de SQLite, le moteur SQL de SpeedQL.

Conseil : Une condition sur COUNT, SUM ou AVG va dans HAVING, jamais dans WHERE.

Relire la fiche « HAVING » de l’aide-mémoire

Exercice 1 · niveau 2

Affiche les départements qui comptent plus de 2 employés, avec leur nombre d'employés.

Table employees (8 lignes)
idnamedepartmentsalary
1AliceFinance32000
2BobIT41000
3ClaireFinance38000
4DavidHR29000
5EmmaIT50000
6FaridIT47000
7GaelleHR33000
8HugoFinance44000
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, HAVING.

Structure de la requête :

SELECT , COUNT(*)
FROM 
GROUP BY 
HAVING COUNT(*) > 
Voir la correction
SELECT department, COUNT(*)
FROM employees
GROUP BY department
HAVING COUNT(*) > 2;

Résultat attendu (2 lignes) :

departmentCOUNT(*)
Finance3
IT3

Exercice 2 · niveau 3

Affiche le nom et le montant total des commandes des clients dont le total dépasse 160.

Table customers (4 lignes)
idnamecity
1AliceParis
2BrunoLyon
3ChloeParis
4DylanNantes
Table orders (6 lignes)
idcustomer_idamount
11120
2180
32200
4350
5375
6330
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Structure de la requête :

SELECT , SUM()
FROM 
INNER JOIN  ON  = 
GROUP BY 
HAVING SUM() > 
Voir la correction
SELECT customers.name, SUM(orders.amount)
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.id
HAVING SUM(orders.amount) > 160;

Résultat attendu (2 lignes) :

nameSUM(orders.amount)
Alice200
Bruno200

Exercice 3 · niveau 3

Affiche chaque ville dont le total des commandes dépasse 250, avec ce total.

Table customers (4 lignes)
idnamecity
1AliceParis
2BrunoLyon
3ChloeParis
4DylanNantes
Table orders (6 lignes)
idcustomer_idamount
11120
2180
32200
4350
5375
6330
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Structure de la requête :

SELECT , SUM()
FROM 
INNER JOIN  ON  = 
GROUP BY 
HAVING SUM() > 
Voir la correction
SELECT customers.city, SUM(orders.amount)
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.city
HAVING SUM(orders.amount) > 250;

Résultat attendu (1 ligne) :

citySUM(orders.amount)
Paris355

Exercice 4 · niveau 3

Affiche le nom des réalisateurs qui ont réalisé au moins 2 films, avec leur nombre de films et la note moyenne de leurs films.

Table movies (10 lignes)
idtitlegenreyeardurationratingdirector_id
1Night TrainThriller20151187.81
2Blue HarborDrama20181027.12
3Paper Moon CityComedy2012956.45
4Silent PeakDrama20201318.23
5Last SignalSci-Fi20191427.51
6Summer KeysComedy2016885.94
7Iron GardenSci-Fi202112583
8Dust and GoldWestern20141106.82
9The Quiet HourDrama2022977.45
10Deep CurrentThriller20171056.9NULL
Table directors (6 lignes)
idnamecountry
1Nora EllisUK
2Paulo ReisBrazil
3Kenji MoriJapan
4Anna BergSweden
5Luc MartinFrance
6Sara DiazSpain
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Structure de la requête :

SELECT , COUNT(*), AVG()
FROM  
JOIN   ON  = 
GROUP BY 
HAVING COUNT(*) >= 
Voir la correction
SELECT d.name, COUNT(*), AVG(m.rating)
FROM directors d
JOIN movies m ON m.director_id = d.id
GROUP BY d.id
HAVING COUNT(*) >= 2;

Résultat attendu (4 lignes) :

nameCOUNT(*)AVG(m.rating)
Nora Ellis27.65
Paulo Reis26.949999999999999
Kenji Mori28.1
Luc Martin26.9

Exercice 5 · niveau 3

Affiche le titre des livres empruntés au moins 2 fois, avec leur nombre d'emprunts.

Table books (10 lignes)
idtitleauthor_idgenrepagesyear
1Cold River1Novel3202011
2Salt Roads2Travel2102016
3The Glass Hive3Sci-Fi4122019
4Winter Ledger1Crime2882014
5Desert Letters2Novel3562020
6Small Engines4Sci-Fi1982022
7Harbor Lights5Novel4452008
8Night Garden3Crime3012017
9Paper Birds5Poetry962012
10Open Maps4Travel2402021
Table loans (12 lignes)
idbook_idmember_idloan_datereturn_date
1112025-01-052025-01-19
2322025-01-102025-02-02
3512025-02-012025-02-10
4732025-02-03NULL
5342025-02-152025-03-01
6222025-03-022025-03-30
7852025-03-05NULL
8132025-03-102025-03-18
9612025-03-202025-04-15
10352025-04-01NULL
11942025-04-052025-04-12
121022025-04-082025-04-20
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Structure de la requête :

SELECT , COUNT(*)
FROM  
JOIN   ON  = 
GROUP BY 
HAVING COUNT(*) >= 
Voir la correction
SELECT b.title, COUNT(*)
FROM books b
JOIN loans l ON l.book_id = b.id
GROUP BY b.id
HAVING COUNT(*) >= 2;

Résultat attendu (2 lignes) :

titleCOUNT(*)
Cold River2
The Glass Hive3

Exercice 6 · niveau 3

Pour chaque équipe, affiche son nom et le nombre de buts marqués à domicile, uniquement pour les équipes qui ont marqué au moins 3 buts à domicile.

Table teams (5 lignes)
idnamecityfounded
1Red FoxesLyon1950
2Blue OwlsParis1962
3Green BullsLille1971
4Gold HawksNantes1988
5Grey WolvesParis1990
Table matches (10 lignes)
idplayed_onhome_idaway_idhome_goalsaway_goals
12025-08-021221
22025-08-033400
32025-08-095113
42025-08-102322
52025-08-164531
62025-08-171310
72025-08-232401
82025-08-243523
92025-08-304111
102025-08-315202
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Structure de la requête :

SELECT , SUM()
FROM  
JOIN   ON  = 
GROUP BY 
HAVING SUM() >= 
Voir la correction
SELECT t.name, SUM(m.home_goals)
FROM teams t
JOIN matches m ON m.home_id = t.id
GROUP BY t.id
HAVING SUM(m.home_goals) >= 3;

Résultat attendu (2 lignes) :

nameSUM(m.home_goals)
Red Foxes3
Gold Hawks4

Exercice 7 · niveau 3

Pour chaque aéroport de départ, affiche son code et le nombre total de places vendues, uniquement pour les aéroports qui totalisent plus de 250 places vendues.

Table flights (12 lignes)
idairlineorigindestdepartsduration_minpriceseats_soldcapacity
1SkyJetCDGMAD2025-06-01 08:1012589150180
2AirNovaCDGLIS2025-06-01 11:40155120160170
3BlueWingLYSFCO2025-06-02 07:309575110150
4SkyJetMADCDG2025-06-02 18:2012095170180
5AirNovaBERCDG2025-06-03 09:05110105140160
6BlueWingNCEBER2025-06-03 13:5013014090150
7SkyJetCDGFCO2025-06-04 06:4513599175180
8AirNovaLISMAD2025-06-04 16:15756560120
9BlueWingFCOLYS2025-06-05 20:3010082130150
10SkyJetCDGBER2025-06-05 10:00105110120180
11AirNovaMADLIS2025-06-06 12:25807095120
12BlueWingLYSMAD2025-06-06 15:4011579100150
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, HAVING.

Structure de la requête :

SELECT , SUM()
FROM 
GROUP BY 
HAVING SUM() > 
Voir la correction
SELECT origin, SUM(seats_sold)
FROM flights
GROUP BY origin
HAVING SUM(seats_sold) > 250;

Résultat attendu (2 lignes) :

originSUM(seats_sold)
CDG605
MAD265

Exercice 8 · niveau 3

Pour chaque ville, affiche la ville et la pluie totale, uniquement pour les villes qui ont reçu au moins 5 mm au total.

Table readings (15 lignes)
idcitydaytemp_maxtemp_minrain_mm
1Paris2025-07-0124150
2Paris2025-07-0227170
3Paris2025-07-0322164.5
4Paris2025-07-04191412
5Paris2025-07-0523130
6Lyon2025-07-0126160
7Lyon2025-07-0229180
8Lyon2025-07-0331190
9Lyon2025-07-0424178.5
10Lyon2025-07-0522153
11Marseille2025-07-0130210
12Marseille2025-07-0232220
13Marseille2025-07-0333230
14Marseille2025-07-0429210
15Marseille2025-07-0528200
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, HAVING.

Structure de la requête :

SELECT , SUM()
FROM 
GROUP BY 
HAVING SUM() >= 
Voir la correction
SELECT city, SUM(rain_mm)
FROM readings
GROUP BY city
HAVING SUM(rain_mm) >= 5;

Résultat attendu (2 lignes) :

citySUM(rain_mm)
Lyon11.5
Paris16.5

Exercice 9 · niveau 3

Affiche le nom des hôtels qui ont au moins 3 réservations (toutes chambres confondues), avec leur nombre de réservations.

Table hotels (5 lignes)
idnamecitystars
1Seaside InnNice3
2Alpine LodgeAnnecy4
3City LoftParis4
4Old MillBordeaux2
5Grand PalaceParis5
Table rooms (10 lignes)
idhotel_idtypeprice
11single70
21double95
32double130
42suite210
53single110
63double150
74double65
85double260
95suite480
105single190
Table bookings (11 lignes)
idroom_idguest_idcheck_incheck_out
1212025-07-012025-07-04
2522025-07-022025-07-05
3832025-07-032025-07-06
4342025-07-052025-07-12
5652025-07-062025-07-08
6122025-07-082025-07-10
7932025-07-102025-07-11
8752025-07-112025-07-15
9412025-07-142025-07-16
10642025-07-152025-07-19
11652025-07-162025-07-18
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Structure de la requête :

SELECT , COUNT(*)
FROM  
JOIN   ON  = 
JOIN   ON  = 
GROUP BY 
HAVING COUNT(*) >= 
Voir la correction
SELECT h.name, COUNT(*)
FROM hotels h
JOIN rooms r ON r.hotel_id = h.id
JOIN bookings b ON b.room_id = r.id
GROUP BY h.id
HAVING COUNT(*) >= 3;

Résultat attendu (1 ligne) :

nameCOUNT(*)
City Loft4

Exercice 10 · niveau 3

Affiche le nom des produits commandés en au moins 3 exemplaires au total, avec la quantité totale.

Table products (7 lignes)
idnamecategoryprice
1Desk LampHome35
2Coffee MugKitchen12
3NotebookOffice6
4Office ChairOffice149
5KettleKitchen45
6CushionHome22
7StaplerOffice9
Table order_items (14 lignes)
order_idproduct_idqty
111
122
241
335
321
451
562
511
624
633
741
761
852
821
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Structure de la requête :

SELECT , SUM()
FROM  
JOIN   ON  = 
GROUP BY 
HAVING SUM() >= 
Voir la correction
SELECT p.name, SUM(oi.qty)
FROM products p
JOIN order_items oi ON oi.product_id = p.id
GROUP BY p.id
HAVING SUM(oi.qty) >= 3;

Résultat attendu (4 lignes) :

nameSUM(oi.qty)
Coffee Mug8
Notebook8
Kettle3
Cushion3

Exercice 11 · niveau 3

Affiche le nom des projets qui ont au moins 2 tâches terminées ('done'), avec ce nombre de tâches.

Table projects (4 lignes)
idnameclientbudgetdeadline
1AtlasAcme200002025-06-30
2BeaconBolt120002025-05-15
3CometAcme80002025-04-30
4DeltaCyan150002025-07-31
Table tasks (10 lignes)
idproject_iddev_idtitlehoursstatusdone_on
111Landing page12done2025-03-10
213Data model20done2025-03-20
312Login8doingNULL
424ETL job16done2025-04-02
525CI pipeline6done2025-03-15
631Dashboard14todoNULL
733Report10done2025-04-25
842API18doingNULL
945Monitoring9todoNULL
1044Forecast22done2025-05-05
Voir l’indice

Notions à utiliser : WHERE (filtres), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Structure de la requête :

SELECT , COUNT(*)
FROM  
JOIN   ON  = 
WHERE  = 
GROUP BY 
HAVING COUNT(*) >= 
Voir la correction
SELECT p.name, COUNT(*)
FROM projects p
JOIN tasks t ON t.project_id = p.id
WHERE t.status = 'done'
GROUP BY p.id
HAVING COUNT(*) >= 2;

Résultat attendu (2 lignes) :

nameCOUNT(*)
Atlas2
Beacon2

Exercice 12 · niveau 3

Pour chaque libellé (label) qui apparaît au moins 3 fois, affiche le libellé, le nombre d'opérations et leur somme.

Table transactions (14 lignes)
idaccount_idmade_onamountlabel
112025-01-022500salary
212025-01-05-60groceries
312025-01-12-800rent
422025-01-15500transfer
532025-01-031900salary
632025-01-20-120groceries
732025-02-01-950rent
842025-01-252100salary
942025-02-03-45restaurant
1012025-02-022600salary
1112025-02-06-75groceries
1252025-02-1030interest
1322025-02-15500transfer
1442025-02-18-600rent
Voir l’indice

Notions à utiliser : COUNT, SUM, AVG, MIN, MAX, GROUP BY, HAVING.

Structure de la requête :

SELECT , COUNT(*), SUM()
FROM 
GROUP BY 
HAVING COUNT(*) >= 
Voir la correction
SELECT label, COUNT(*), SUM(amount)
FROM transactions
GROUP BY label
HAVING COUNT(*) >= 3;

Résultat attendu (3 lignes) :

labelCOUNT(*)SUM(amount)
groceries3-255
rent3-2350
salary49100

S’entraîner avec correction automatique

Dans SpeedQL, tu écris ta requête et elle est vérifiée tout de suite, sur ces tables puis sur un jeu de données caché.

S’entraîner sur les 20 questions « HAVING »