Exercices SQL corrigés : les jointures (JOIN)

Exercices SQL corrigés : les jointures (JOIN)

Mis à jour le

JOIN relie deux tables par une colonne commune ; LEFT JOIN garde aussi les lignes sans correspondance. Ces 15 exercices vont du niveau 2 au niveau 8 ; ils utilisent la syntaxe de SQLite, le moteur SQL de SpeedQL.

Conseil : Donne un alias court à chaque table (c, o) et précise toujours la condition ON.

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

Exercice 1 · niveau 2

Affiche le nom de chaque client avec le montant de chacune de ses commandes.

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 : JOIN.

Structure de la requête :

SELECT , 
FROM 
INNER JOIN  ON  = 
Voir la correction
SELECT customers.name, orders.amount
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id;

Résultat attendu (6 lignes) :

nameamount
Alice80
Alice120
Bruno200
Chloe30
Chloe50
Chloe75

Exercice 2 · niveau 2

Affiche l'id de chaque vol avec la ville de l'aéroport de départ.

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
Table airports (7 lignes)
codecitycountry
CDGParisFrance
LYSLyonFrance
MADMadridSpain
LISLisbonPortugal
FCORomeItaly
BERBerlinGermany
NCENiceFrance
Voir l’indice

Notions à utiliser : JOIN.

Structure de la requête :

SELECT , 
FROM  
JOIN   ON  = 
Voir la correction
SELECT f.id, a.city
FROM flights f
JOIN airports a ON a.code = f.origin;

Résultat attendu (12 lignes) :

idcity
1Paris
2Paris
3Lyon
4Madrid
5Berlin
6Nice
7Paris
8Lisbon
9Rome
10Paris
11Madrid
12Lyon

Exercice 3 · niveau 3

Affiche chaque ville avec le nombre total de commandes passées par les clients de cette ville.

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.

Structure de la requête :

SELECT , COUNT(*)
FROM 
INNER JOIN  ON  = 
GROUP BY 
Voir la correction
SELECT customers.city, COUNT(*)
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.city;

Résultat attendu (2 lignes) :

cityCOUNT(*)
Lyon1
Paris5

Exercice 4 · niveau 3

Affiche le nom de chaque réalisateur et son nombre de films, y compris 0 pour ceux qui n'en ont aucun.

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.

Structure de la requête :

SELECT , COUNT()
FROM  
LEFT JOIN   ON  = 
GROUP BY 
Voir la correction
SELECT d.name, COUNT(m.id)
FROM directors d
LEFT JOIN movies m ON m.director_id = d.id
GROUP BY d.id;

Résultat attendu (6 lignes) :

nameCOUNT(m.id)
Nora Ellis2
Paulo Reis2
Kenji Mori2
Anna Berg1
Luc Martin2
Sara Diaz0

Exercice 5 · niveau 3

Affiche le nom de chaque membre et son nombre d'emprunts, y compris 0 pour ceux qui n'ont rien emprunté.

Table members (6 lignes)
idnamecityjoined
1LenaLyon2022-01-15
2MarcParis2021-06-03
3NadiaLyon2023-03-20
4OscarLille2020-11-11
5PaulaParis2024-02-01
6QuentinNantes2023-09-09
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.

Structure de la requête :

SELECT , COUNT()
FROM  
LEFT JOIN   ON  = 
GROUP BY 
Voir la correction
SELECT m.name, COUNT(l.id)
FROM members m
LEFT JOIN loans l ON l.member_id = m.id
GROUP BY m.id;

Résultat attendu (6 lignes) :

nameCOUNT(l.id)
Lena3
Marc3
Nadia2
Oscar2
Paula2
Quentin0

Exercice 6 · niveau 3

Affiche la date de chaque match avec le nom de l'équipe à domicile et le nom de l'équipe à l'extérieur.

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
Table teams (5 lignes)
idnamecityfounded
1Red FoxesLyon1950
2Blue OwlsParis1962
3Green BullsLille1971
4Gold HawksNantes1988
5Grey WolvesParis1990
Voir l’indice

Notions à utiliser : JOIN.

Structure de la requête :

SELECT , , 
FROM  
JOIN   ON  = 
JOIN   ON  = 
Voir la correction
SELECT m.played_on, h.name, a.name
FROM matches m
JOIN teams h ON h.id = m.home_id
JOIN teams a ON a.id = m.away_id;

Résultat attendu (10 lignes) :

played_onnamename
2025-08-02Red FoxesBlue Owls
2025-08-03Green BullsGold Hawks
2025-08-09Grey WolvesRed Foxes
2025-08-10Blue OwlsGreen Bulls
2025-08-16Gold HawksGrey Wolves
2025-08-17Red FoxesGreen Bulls
2025-08-23Blue OwlsGold Hawks
2025-08-24Green BullsGrey Wolves
2025-08-30Gold HawksRed Foxes
2025-08-31Grey WolvesBlue Owls

Exercice 7 · niveau 3

Affiche le nom de chaque artiste et son nombre de chansons, y compris 0 pour ceux qui n'en ont aucune.

Table artists (6 lignes)
idnamecountrydebut_year
1Luna ValeUSA2012
2The StaticUK2005
3Kofi MensahGhana2016
4Mira SolSpain2010
5Neon HarborCanada2018
6Iris NovaFrance2020
Table songs (10 lignes)
idtitleartist_idgenreduration_sreleased
1Glass Heart1Pop2142019-04-12
2Low Tide1Pop1982021-06-01
3Rust2Rock2562008-09-30
4Wires2Rock3012015-02-14
5Sunday Market3Afrobeat2332020-11-20
6Palm Wine3Afrobeat2452022-03-03
7Alma4Latin1892013-07-07
8Brisa4Pop2052018-05-25
9Night Drive5Electro2762021-10-10
10Pulse5Electro2302023-01-15
Voir l’indice

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

Structure de la requête :

SELECT , COUNT()
FROM  
LEFT JOIN   ON  = 
GROUP BY 
Voir la correction
SELECT a.name, COUNT(s.id)
FROM artists a
LEFT JOIN songs s ON s.artist_id = a.id
GROUP BY a.id;

Résultat attendu (6 lignes) :

nameCOUNT(s.id)
Luna Vale2
The Static2
Kofi Mensah2
Mira Sol2
Neon Harbor2
Iris Nova0

Exercice 8 · niveau 3

Affiche le nom de chaque client et son nombre de réservations, y compris 0 pour ceux qui n'ont jamais réservé.

Table guests (6 lignes)
idnamecountry
1Ana SilvaPortugal
2Ben FordUSA
3Chen LiChina
4Dana WeissGermany
5Emma RoyFrance
6Farid NasserMorocco
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.

Structure de la requête :

SELECT , COUNT()
FROM  
LEFT JOIN   ON  = 
GROUP BY 
Voir la correction
SELECT g.name, COUNT(b.id)
FROM guests g
LEFT JOIN bookings b ON b.guest_id = g.id
GROUP BY g.id;

Résultat attendu (6 lignes) :

nameCOUNT(b.id)
Ana Silva2
Ben Ford2
Chen Li2
Dana Weiss2
Emma Roy3
Farid Nasser0

Exercice 9 · niveau 3

Pour chaque commande, affiche son numéro et son montant total (quantité × prix), du plus grand montant au plus petit (en cas d'égalité, par numéro).

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

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

Structure de la requête :

SELECT , SUM( * )
FROM  
JOIN   ON  = 
GROUP BY 
ORDER BY SUM( * ) DESC, 
Voir la correction
SELECT oi.order_id, SUM(oi.qty * p.price)
FROM order_items oi
JOIN products p ON p.id = oi.product_id
GROUP BY oi.order_id
ORDER BY SUM(oi.qty * p.price) DESC, oi.order_id;

Résultat attendu (8 lignes, dans cet ordre) :

order_idSUM(oi.qty * p.price)
7171
2149
8102
579
666
159
445
342

Exercice 10 · niveau 3

Pour chaque projet qui a des tâches, affiche son nom et le total d'heures de ses tâches, du plus grand total au plus petit (en cas d'égalité, par nom).

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 : ORDER BY, LIMIT, COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN.

Structure de la requête :

SELECT , SUM()
FROM  
JOIN   ON  = 
GROUP BY 
ORDER BY SUM() DESC, 
Voir la correction
SELECT p.name, SUM(t.hours)
FROM projects p
JOIN tasks t ON t.project_id = p.id
GROUP BY p.id
ORDER BY SUM(t.hours) DESC, p.name;

Résultat attendu (4 lignes, dans cet ordre) :

nameSUM(t.hours)
Delta49
Atlas40
Comet24
Beacon22

Exercice 11 · niveau 3

Pour chaque titulaire qui a des opérations, affiche son nom et son solde total (tous ses comptes), du plus grand au plus petit (en cas d'égalité, par nom).

Table accounts (6 lignes)
idownercityopenedkind
1AliceParis2021-03-01current
2AliceParis2022-06-15savings
3BrunoLyon2020-09-10current
4ChloeLyon2023-01-20current
5DavidNice2019-11-05savings
6EmmaNice2024-04-01current
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 : ORDER BY, LIMIT, COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN.

Structure de la requête :

SELECT , SUM()
FROM  
JOIN   ON  = 
GROUP BY 
ORDER BY SUM() DESC, 
Voir la correction
SELECT a.owner, SUM(t.amount)
FROM accounts a
JOIN transactions t ON t.account_id = a.id
GROUP BY a.owner
ORDER BY SUM(t.amount) DESC, a.owner;

Résultat attendu (4 lignes, dans cet ordre) :

ownerSUM(t.amount)
Alice5165
Chloe1455
Bruno830
David30

Exercice 12 · niveau 5

Affiche chaque employé avec le nom de son manager (ignore ceux qui n'ont pas de manager).

Table staff (10 lignes)
idnamedepartmentsalarymanager_idhired
1AliceFinance52000NULL2015-03-01
2BobIT4100012018-06-15
3ClaireFinance3800012019-01-10
4DavidHR2900012020-09-01
5EmmaIT5000022017-11-20
6FaridIT4700022021-02-14
7GaelleHR3300042022-05-30
8HugoFinance4400032016-08-08
9IrisIT4700052023-01-09
10JulesFinance4400032024-03-18
Voir l’indice

Notions à utiliser : JOIN.

Structure de la requête :

SELECT , 
FROM  
JOIN   ON  = 
Voir la correction
SELECT e.name, m.name
FROM staff e
JOIN staff m ON e.manager_id = m.id;

Résultat attendu (9 lignes) :

namename
BobAlice
ClaireAlice
DavidAlice
EmmaBob
FaridBob
GaelleDavid
HugoClaire
IrisEmma
JulesClaire

Exercice 13 · niveau 5

Affiche le nom des employés embauchés avant leur propre manager.

Table staff (10 lignes)
idnamedepartmentsalarymanager_idhired
1AliceFinance52000NULL2015-03-01
2BobIT4100012018-06-15
3ClaireFinance3800012019-01-10
4DavidHR2900012020-09-01
5EmmaIT5000022017-11-20
6FaridIT4700022021-02-14
7GaelleHR3300042022-05-30
8HugoFinance4400032016-08-08
9IrisIT4700052023-01-09
10JulesFinance4400032024-03-18
Voir l’indice

Notions à utiliser : WHERE (filtres), JOIN.

Structure de la requête :

SELECT 
FROM  
JOIN   ON  = 
WHERE  < 
Voir la correction
SELECT e.name
FROM staff e
JOIN staff m ON e.manager_id = m.id
WHERE e.hired < m.hired;

Résultat attendu (2 lignes) :

name
Emma
Hugo

Exercice 14 · niveau 5

Affiche les paires de joueurs de la même équipe (colonnes : nom 1, nom 2) où le joueur 1 a marqué plus de buts que le joueur 2.

Table players (9 lignes)
idnameteam_idpositiongoals
1Alex Moreau1FW9
2Bilal Sow1MF4
3Carl Weber2FW7
4Diego Ruiz2DF1
5Eli Novak3FW6
6Femi Adeyemi4FW11
7Goran Petrov4GK0
8Hugo Lamy5MF5
9Ivan Kral5DF2
Voir l’indice

Notions à utiliser : JOIN.

Structure de la requête :

SELECT , 
FROM  
JOIN   ON  =  AND  > 
Voir la correction
SELECT a.name, b.name
FROM players a
JOIN players b ON a.team_id = b.team_id AND a.goals > b.goals;

Résultat attendu (4 lignes) :

namename
Alex MoreauBilal Sow
Carl WeberDiego Ruiz
Femi AdeyemiGoran Petrov
Hugo LamyIvan Kral

Exercice 15 · niveau 8

Affiche les paires de produits achetés ensemble dans une même commande (nom du produit au plus petit id d'abord), avec le nombre de commandes où ils apparaissent ensemble.

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

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

Structure de la requête :

SELECT , , COUNT(*)
FROM  
JOIN   ON  =  AND  < 
JOIN   ON  = 
JOIN   ON  = 
GROUP BY , 
Voir la correction
SELECT p1.name, p2.name, COUNT(*)
FROM order_items a
JOIN order_items b ON b.order_id = a.order_id AND a.product_id < b.product_id
JOIN products p1 ON p1.id = a.product_id
JOIN products p2 ON p2.id = b.product_id
GROUP BY a.product_id, b.product_id;

Résultat attendu (5 lignes) :

namenameCOUNT(*)
Desk LampCoffee Mug1
Desk LampCushion1
Coffee MugNotebook2
Coffee MugKettle1
Office ChairCushion1

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 60 questions « JOIN »