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
Affiche les départements qui comptent plus de 2 employés, avec leur nombre d'employés.
| id | name | department | salary |
|---|---|---|---|
| 1 | Alice | Finance | 32000 |
| 2 | Bob | IT | 41000 |
| 3 | Claire | Finance | 38000 |
| 4 | David | HR | 29000 |
| 5 | Emma | IT | 50000 |
| 6 | Farid | IT | 47000 |
| 7 | Gaelle | HR | 33000 |
| 8 | Hugo | Finance | 44000 |
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) :
| department | COUNT(*) |
|---|---|
| Finance | 3 |
| IT | 3 |
Exercice 2
Affiche le nom et le montant total des commandes des clients dont le total dépasse 160.
| id | name | city |
|---|---|---|
| 1 | Alice | Paris |
| 2 | Bruno | Lyon |
| 3 | Chloe | Paris |
| 4 | Dylan | Nantes |
| id | customer_id | amount |
|---|---|---|
| 1 | 1 | 120 |
| 2 | 1 | 80 |
| 3 | 2 | 200 |
| 4 | 3 | 50 |
| 5 | 3 | 75 |
| 6 | 3 | 30 |
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) :
| name | SUM(orders.amount) |
|---|---|
| Alice | 200 |
| Bruno | 200 |
Exercice 3
Affiche chaque ville dont le total des commandes dépasse 250, avec ce total.
| id | name | city |
|---|---|---|
| 1 | Alice | Paris |
| 2 | Bruno | Lyon |
| 3 | Chloe | Paris |
| 4 | Dylan | Nantes |
| id | customer_id | amount |
|---|---|---|
| 1 | 1 | 120 |
| 2 | 1 | 80 |
| 3 | 2 | 200 |
| 4 | 3 | 50 |
| 5 | 3 | 75 |
| 6 | 3 | 30 |
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) :
| city | SUM(orders.amount) |
|---|---|
| Paris | 355 |
Exercice 4
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.
| id | title | genre | year | duration | rating | director_id |
|---|---|---|---|---|---|---|
| 1 | Night Train | Thriller | 2015 | 118 | 7.8 | 1 |
| 2 | Blue Harbor | Drama | 2018 | 102 | 7.1 | 2 |
| 3 | Paper Moon City | Comedy | 2012 | 95 | 6.4 | 5 |
| 4 | Silent Peak | Drama | 2020 | 131 | 8.2 | 3 |
| 5 | Last Signal | Sci-Fi | 2019 | 142 | 7.5 | 1 |
| 6 | Summer Keys | Comedy | 2016 | 88 | 5.9 | 4 |
| 7 | Iron Garden | Sci-Fi | 2021 | 125 | 8 | 3 |
| 8 | Dust and Gold | Western | 2014 | 110 | 6.8 | 2 |
| 9 | The Quiet Hour | Drama | 2022 | 97 | 7.4 | 5 |
| 10 | Deep Current | Thriller | 2017 | 105 | 6.9 | NULL |
| id | name | country |
|---|---|---|
| 1 | Nora Ellis | UK |
| 2 | Paulo Reis | Brazil |
| 3 | Kenji Mori | Japan |
| 4 | Anna Berg | Sweden |
| 5 | Luc Martin | France |
| 6 | Sara Diaz | Spain |
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) :
| name | COUNT(*) | AVG(m.rating) |
|---|---|---|
| Nora Ellis | 2 | 7.65 |
| Paulo Reis | 2 | 6.949999999999999 |
| Kenji Mori | 2 | 8.1 |
| Luc Martin | 2 | 6.9 |
Exercice 5
Affiche le titre des livres empruntés au moins 2 fois, avec leur nombre d'emprunts.
| id | title | author_id | genre | pages | year |
|---|---|---|---|---|---|
| 1 | Cold River | 1 | Novel | 320 | 2011 |
| 2 | Salt Roads | 2 | Travel | 210 | 2016 |
| 3 | The Glass Hive | 3 | Sci-Fi | 412 | 2019 |
| 4 | Winter Ledger | 1 | Crime | 288 | 2014 |
| 5 | Desert Letters | 2 | Novel | 356 | 2020 |
| 6 | Small Engines | 4 | Sci-Fi | 198 | 2022 |
| 7 | Harbor Lights | 5 | Novel | 445 | 2008 |
| 8 | Night Garden | 3 | Crime | 301 | 2017 |
| 9 | Paper Birds | 5 | Poetry | 96 | 2012 |
| 10 | Open Maps | 4 | Travel | 240 | 2021 |
| id | book_id | member_id | loan_date | return_date |
|---|---|---|---|---|
| 1 | 1 | 1 | 2025-01-05 | 2025-01-19 |
| 2 | 3 | 2 | 2025-01-10 | 2025-02-02 |
| 3 | 5 | 1 | 2025-02-01 | 2025-02-10 |
| 4 | 7 | 3 | 2025-02-03 | NULL |
| 5 | 3 | 4 | 2025-02-15 | 2025-03-01 |
| 6 | 2 | 2 | 2025-03-02 | 2025-03-30 |
| 7 | 8 | 5 | 2025-03-05 | NULL |
| 8 | 1 | 3 | 2025-03-10 | 2025-03-18 |
| 9 | 6 | 1 | 2025-03-20 | 2025-04-15 |
| 10 | 3 | 5 | 2025-04-01 | NULL |
| 11 | 9 | 4 | 2025-04-05 | 2025-04-12 |
| 12 | 10 | 2 | 2025-04-08 | 2025-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) :
| title | COUNT(*) |
|---|---|
| Cold River | 2 |
| The Glass Hive | 3 |
Exercice 6
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.
| id | name | city | founded |
|---|---|---|---|
| 1 | Red Foxes | Lyon | 1950 |
| 2 | Blue Owls | Paris | 1962 |
| 3 | Green Bulls | Lille | 1971 |
| 4 | Gold Hawks | Nantes | 1988 |
| 5 | Grey Wolves | Paris | 1990 |
| id | played_on | home_id | away_id | home_goals | away_goals |
|---|---|---|---|---|---|
| 1 | 2025-08-02 | 1 | 2 | 2 | 1 |
| 2 | 2025-08-03 | 3 | 4 | 0 | 0 |
| 3 | 2025-08-09 | 5 | 1 | 1 | 3 |
| 4 | 2025-08-10 | 2 | 3 | 2 | 2 |
| 5 | 2025-08-16 | 4 | 5 | 3 | 1 |
| 6 | 2025-08-17 | 1 | 3 | 1 | 0 |
| 7 | 2025-08-23 | 2 | 4 | 0 | 1 |
| 8 | 2025-08-24 | 3 | 5 | 2 | 3 |
| 9 | 2025-08-30 | 4 | 1 | 1 | 1 |
| 10 | 2025-08-31 | 5 | 2 | 0 | 2 |
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) :
| name | SUM(m.home_goals) |
|---|---|
| Red Foxes | 3 |
| Gold Hawks | 4 |
Exercice 7
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.
| id | airline | origin | dest | departs | duration_min | price | seats_sold | capacity |
|---|---|---|---|---|---|---|---|---|
| 1 | SkyJet | CDG | MAD | 2025-06-01 08:10 | 125 | 89 | 150 | 180 |
| 2 | AirNova | CDG | LIS | 2025-06-01 11:40 | 155 | 120 | 160 | 170 |
| 3 | BlueWing | LYS | FCO | 2025-06-02 07:30 | 95 | 75 | 110 | 150 |
| 4 | SkyJet | MAD | CDG | 2025-06-02 18:20 | 120 | 95 | 170 | 180 |
| 5 | AirNova | BER | CDG | 2025-06-03 09:05 | 110 | 105 | 140 | 160 |
| 6 | BlueWing | NCE | BER | 2025-06-03 13:50 | 130 | 140 | 90 | 150 |
| 7 | SkyJet | CDG | FCO | 2025-06-04 06:45 | 135 | 99 | 175 | 180 |
| 8 | AirNova | LIS | MAD | 2025-06-04 16:15 | 75 | 65 | 60 | 120 |
| 9 | BlueWing | FCO | LYS | 2025-06-05 20:30 | 100 | 82 | 130 | 150 |
| 10 | SkyJet | CDG | BER | 2025-06-05 10:00 | 105 | 110 | 120 | 180 |
| 11 | AirNova | MAD | LIS | 2025-06-06 12:25 | 80 | 70 | 95 | 120 |
| 12 | BlueWing | LYS | MAD | 2025-06-06 15:40 | 115 | 79 | 100 | 150 |
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) :
| origin | SUM(seats_sold) |
|---|---|
| CDG | 605 |
| MAD | 265 |
Exercice 8
Pour chaque ville, affiche la ville et la pluie totale, uniquement pour les villes qui ont reçu au moins 5 mm au total.
| id | city | day | temp_max | temp_min | rain_mm |
|---|---|---|---|---|---|
| 1 | Paris | 2025-07-01 | 24 | 15 | 0 |
| 2 | Paris | 2025-07-02 | 27 | 17 | 0 |
| 3 | Paris | 2025-07-03 | 22 | 16 | 4.5 |
| 4 | Paris | 2025-07-04 | 19 | 14 | 12 |
| 5 | Paris | 2025-07-05 | 23 | 13 | 0 |
| 6 | Lyon | 2025-07-01 | 26 | 16 | 0 |
| 7 | Lyon | 2025-07-02 | 29 | 18 | 0 |
| 8 | Lyon | 2025-07-03 | 31 | 19 | 0 |
| 9 | Lyon | 2025-07-04 | 24 | 17 | 8.5 |
| 10 | Lyon | 2025-07-05 | 22 | 15 | 3 |
| 11 | Marseille | 2025-07-01 | 30 | 21 | 0 |
| 12 | Marseille | 2025-07-02 | 32 | 22 | 0 |
| 13 | Marseille | 2025-07-03 | 33 | 23 | 0 |
| 14 | Marseille | 2025-07-04 | 29 | 21 | 0 |
| 15 | Marseille | 2025-07-05 | 28 | 20 | 0 |
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) :
| city | SUM(rain_mm) |
|---|---|
| Lyon | 11.5 |
| Paris | 16.5 |
Exercice 9
Affiche le nom des hôtels qui ont au moins 3 réservations (toutes chambres confondues), avec leur nombre de réservations.
| id | name | city | stars |
|---|---|---|---|
| 1 | Seaside Inn | Nice | 3 |
| 2 | Alpine Lodge | Annecy | 4 |
| 3 | City Loft | Paris | 4 |
| 4 | Old Mill | Bordeaux | 2 |
| 5 | Grand Palace | Paris | 5 |
| id | hotel_id | type | price |
|---|---|---|---|
| 1 | 1 | single | 70 |
| 2 | 1 | double | 95 |
| 3 | 2 | double | 130 |
| 4 | 2 | suite | 210 |
| 5 | 3 | single | 110 |
| 6 | 3 | double | 150 |
| 7 | 4 | double | 65 |
| 8 | 5 | double | 260 |
| 9 | 5 | suite | 480 |
| 10 | 5 | single | 190 |
| id | room_id | guest_id | check_in | check_out |
|---|---|---|---|---|
| 1 | 2 | 1 | 2025-07-01 | 2025-07-04 |
| 2 | 5 | 2 | 2025-07-02 | 2025-07-05 |
| 3 | 8 | 3 | 2025-07-03 | 2025-07-06 |
| 4 | 3 | 4 | 2025-07-05 | 2025-07-12 |
| 5 | 6 | 5 | 2025-07-06 | 2025-07-08 |
| 6 | 1 | 2 | 2025-07-08 | 2025-07-10 |
| 7 | 9 | 3 | 2025-07-10 | 2025-07-11 |
| 8 | 7 | 5 | 2025-07-11 | 2025-07-15 |
| 9 | 4 | 1 | 2025-07-14 | 2025-07-16 |
| 10 | 6 | 4 | 2025-07-15 | 2025-07-19 |
| 11 | 6 | 5 | 2025-07-16 | 2025-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) :
| name | COUNT(*) |
|---|---|
| City Loft | 4 |
Exercice 10
Affiche le nom des produits commandés en au moins 3 exemplaires au total, avec la quantité totale.
| id | name | category | price |
|---|---|---|---|
| 1 | Desk Lamp | Home | 35 |
| 2 | Coffee Mug | Kitchen | 12 |
| 3 | Notebook | Office | 6 |
| 4 | Office Chair | Office | 149 |
| 5 | Kettle | Kitchen | 45 |
| 6 | Cushion | Home | 22 |
| 7 | Stapler | Office | 9 |
| order_id | product_id | qty |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 2 | 2 |
| 2 | 4 | 1 |
| 3 | 3 | 5 |
| 3 | 2 | 1 |
| 4 | 5 | 1 |
| 5 | 6 | 2 |
| 5 | 1 | 1 |
| 6 | 2 | 4 |
| 6 | 3 | 3 |
| 7 | 4 | 1 |
| 7 | 6 | 1 |
| 8 | 5 | 2 |
| 8 | 2 | 1 |
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) :
| name | SUM(oi.qty) |
|---|---|
| Coffee Mug | 8 |
| Notebook | 8 |
| Kettle | 3 |
| Cushion | 3 |
Exercice 11
Affiche le nom des projets qui ont au moins 2 tâches terminées ('done'), avec ce nombre de tâches.
| id | name | client | budget | deadline |
|---|---|---|---|---|
| 1 | Atlas | Acme | 20000 | 2025-06-30 |
| 2 | Beacon | Bolt | 12000 | 2025-05-15 |
| 3 | Comet | Acme | 8000 | 2025-04-30 |
| 4 | Delta | Cyan | 15000 | 2025-07-31 |
| id | project_id | dev_id | title | hours | status | done_on |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | Landing page | 12 | done | 2025-03-10 |
| 2 | 1 | 3 | Data model | 20 | done | 2025-03-20 |
| 3 | 1 | 2 | Login | 8 | doing | NULL |
| 4 | 2 | 4 | ETL job | 16 | done | 2025-04-02 |
| 5 | 2 | 5 | CI pipeline | 6 | done | 2025-03-15 |
| 6 | 3 | 1 | Dashboard | 14 | todo | NULL |
| 7 | 3 | 3 | Report | 10 | done | 2025-04-25 |
| 8 | 4 | 2 | API | 18 | doing | NULL |
| 9 | 4 | 5 | Monitoring | 9 | todo | NULL |
| 10 | 4 | 4 | Forecast | 22 | done | 2025-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) :
| name | COUNT(*) |
|---|---|
| Atlas | 2 |
| Beacon | 2 |
Exercice 12
Pour chaque libellé (label) qui apparaît au moins 3 fois, affiche le libellé, le nombre d'opérations et leur somme.
| id | account_id | made_on | amount | label |
|---|---|---|---|---|
| 1 | 1 | 2025-01-02 | 2500 | salary |
| 2 | 1 | 2025-01-05 | -60 | groceries |
| 3 | 1 | 2025-01-12 | -800 | rent |
| 4 | 2 | 2025-01-15 | 500 | transfer |
| 5 | 3 | 2025-01-03 | 1900 | salary |
| 6 | 3 | 2025-01-20 | -120 | groceries |
| 7 | 3 | 2025-02-01 | -950 | rent |
| 8 | 4 | 2025-01-25 | 2100 | salary |
| 9 | 4 | 2025-02-03 | -45 | restaurant |
| 10 | 1 | 2025-02-02 | 2600 | salary |
| 11 | 1 | 2025-02-06 | -75 | groceries |
| 12 | 5 | 2025-02-10 | 30 | interest |
| 13 | 2 | 2025-02-15 | 500 | transfer |
| 14 | 4 | 2025-02-18 | -600 | rent |
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) :
| label | COUNT(*) | SUM(amount) |
|---|---|---|
| groceries | 3 | -255 |
| rent | 3 | -2350 |
| salary | 4 | 9100 |
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é.