SQL exercises with solutions: subqueries

SQL exercises with solutions: subqueries

Updated on

A subquery is a query inside a query: a value, a list (IN) or a table. These 15 exercises range from level 2 to level 8; they use the syntax of SQLite, SpeedQL’s SQL engine.

Tip: Write the subquery on its own first to check its result, then plug it in.

Read the “Subqueries” card in the cheat sheet

Exercise 1 · level 2

Show the name of the employees whose salary is above the average salary of all employees.

Table employees (8 rows)
idnamedepartmentsalary
1AliceFinance32000
2BobIT41000
3ClaireFinance38000
4DavidHR29000
5EmmaIT50000
6FaridIT47000
7GaelleHR33000
8HugoFinance44000
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT 
FROM 
WHERE  > (SELECT AVG() FROM )
Show the solution
SELECT name
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

Expected result (4 rows):

name
Bob
Emma
Farid
Hugo

Exercise 2 · level 3

Show the name of the customers who have not placed any order.

Table customers (4 rows)
idnamecity
1AliceParis
2BrunoLyon
3ChloeParis
4DylanNantes
Table orders (6 rows)
idcustomer_idamount
11120
2180
32200
4350
5375
6330
Show the hint

Topics to use: WHERE (filters), Subqueries.

Query structure:

SELECT 
FROM 
WHERE  NOT IN (SELECT  FROM )
Show the solution
SELECT name
FROM customers
WHERE id NOT IN (SELECT customer_id FROM orders);

Expected result (1 row):

name
Dylan

Exercise 3 · level 3

Show the name of the employees who earn more than the average salary of their own department.

Table employees (8 rows)
idnamedepartmentsalary
1AliceFinance32000
2BobIT41000
3ClaireFinance38000
4DavidHR29000
5EmmaIT50000
6FaridIT47000
7GaelleHR33000
8HugoFinance44000
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT 
FROM  
WHERE  > (SELECT AVG() FROM  WHERE  = )
Show the solution
SELECT name
FROM employees e
WHERE salary > (SELECT AVG(salary) FROM employees WHERE department = e.department);

Expected result (4 rows):

name
Emma
Farid
Gaelle
Hugo

Exercise 4 · level 3

Show the code and city of the airports where no flight arrives.

Table airports (7 rows)
codecitycountry
CDGParisFrance
LYSLyonFrance
MADMadridSpain
LISLisbonPortugal
FCORomeItaly
BERBerlinGermany
NCENiceFrance
Table flights (12 rows)
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
Show the hint

Topics to use: WHERE (filters), Subqueries.

Query structure:

SELECT , 
FROM 
WHERE  NOT IN (SELECT  FROM )
Show the solution
SELECT code, city
FROM airports
WHERE code NOT IN (SELECT dest FROM flights);

Expected result (1 row):

codecity
NCENice

Exercise 5 · level 5

Show the name and department of the highest-paid employee of each department (correlated subquery).

Table staff (10 rows)
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
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT , 
FROM 
WHERE  = (SELECT MAX() FROM   WHERE  = )
Show the solution
SELECT name, department
FROM staff
WHERE salary = (SELECT MAX(salary) FROM staff s WHERE s.department = staff.department);

Expected result (3 rows):

namedepartment
AliceFinance
EmmaIT
GaelleHR

Exercise 6 · level 5

Show the title of the movies whose rating is above the average rating of the movies of their own genre.

Table movies (10 rows)
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
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT 
FROM  
WHERE  > (SELECT AVG() FROM   WHERE  = )
Show the solution
SELECT title
FROM movies m
WHERE rating > (SELECT AVG(rating) FROM movies x WHERE x.genre = m.genre);

Expected result (4 rows):

title
Night Train
Paper Moon City
Silent Peak
Iron Garden

Exercise 7 · level 5

Show the name and number of goals of the players who scored more than the average of the players of their team.

Table players (9 rows)
idnameteam_idpositiongoals
1Alex Moreau1FW9
2Bilal Sow1MF4
3Carl Weber2FW7
4Diego Ruiz2DF1
5Eli Novak3FW6
6Femi Adeyemi4FW11
7Goran Petrov4GK0
8Hugo Lamy5MF5
9Ivan Kral5DF2
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT , 
FROM  
WHERE  > (SELECT AVG() FROM   WHERE  = )
Show the solution
SELECT name, goals
FROM players p
WHERE goals > (SELECT AVG(goals) FROM players x WHERE x.team_id = p.team_id);

Expected result (4 rows):

namegoals
Alex Moreau9
Carl Weber7
Femi Adeyemi11
Hugo Lamy5

Exercise 8 · level 5

Show the title of the songs that are longer than the average duration of their artist's songs.

Table songs (10 rows)
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
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT 
FROM  
WHERE  > (SELECT AVG() FROM   WHERE  = )
Show the solution
SELECT title
FROM songs s
WHERE duration_s > (SELECT AVG(duration_s) FROM songs x WHERE x.artist_id = s.artist_id);

Expected result (5 rows):

title
Glass Heart
Wires
Palm Wine
Brisa
Night Drive

Exercise 9 · level 5

Show the city, day and maximum temperature of the readings hotter than the average maximum of their city.

Table readings (15 rows)
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
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT , , 
FROM  
WHERE  > (SELECT AVG() FROM   WHERE  = )
Show the solution
SELECT city, day, temp_max
FROM readings r
WHERE temp_max > (SELECT AVG(temp_max) FROM readings x WHERE x.city = r.city);

Expected result (6 rows):

citydaytemp_max
Paris2025-07-0124
Paris2025-07-0227
Lyon2025-07-0229
Lyon2025-07-0331
Marseille2025-07-0232
Marseille2025-07-0333

Exercise 10 · level 5

Show the name, category and price of the products that cost more than the average product of their category.

Table products (7 rows)
idnamecategoryprice
1Desk LampHome35
2Coffee MugKitchen12
3NotebookOffice6
4Office ChairOffice149
5KettleKitchen45
6CushionHome22
7StaplerOffice9
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT , , 
FROM  
WHERE  > (SELECT AVG() FROM   WHERE  = )
Show the solution
SELECT name, category, price
FROM products p
WHERE price > (SELECT AVG(x.price) FROM products x WHERE x.category = p.category);

Expected result (3 rows):

namecategoryprice
Desk LampHome35
Office ChairOffice149
KettleKitchen45

Exercise 11 · level 5

Show the name, team and rate of the developers whose rate is above their team's average.

Table devs (6 rows)
idnameteamrate
1AnaWeb55
2BoWeb48
3CleoData62
4DanData58
5EveOps50
6FinnOps45
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT , , 
FROM  
WHERE  > (SELECT AVG() FROM   WHERE  = )
Show the solution
SELECT name, team, rate
FROM devs d
WHERE rate > (SELECT AVG(x.rate) FROM devs x WHERE x.team = d.team);

Expected result (3 rows):

nameteamrate
AnaWeb55
CleoData62
EveOps50

Exercise 12 · level 5

Show the id, account and amount of the transactions whose amount is above the average transaction of their account.

Table transactions (14 rows)
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
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries.

Query structure:

SELECT , , 
FROM  
WHERE  > (SELECT AVG() FROM   WHERE  = )
Show the solution
SELECT id, account_id, amount
FROM transactions t
WHERE amount > (SELECT AVG(x.amount) FROM transactions x WHERE x.account_id = t.account_id);

Expected result (4 rows):

idaccount_idamount
112500
531900
842100
1012600

Exercise 13 · level 7

For each account with expenses, show the account id, the owner, and the date, amount and label of its biggest expense (on ties, all the tied expenses).

Table accounts (6 rows)
idownercityopenedkind
1AliceParis2021-03-01current
2AliceParis2022-06-15savings
3BrunoLyon2020-09-10current
4ChloeLyon2023-01-20current
5DavidNice2019-11-05savings
6EmmaNice2024-04-01current
Table transactions (14 rows)
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
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, JOIN, Subqueries.

Query structure:

SELECT , , , , 
FROM  
JOIN   ON  = 
WHERE  <  AND  = (SELECT MIN() FROM   WHERE  = )
Show the solution
SELECT a.id, a.owner, t.made_on, t.amount, t.label
FROM transactions t
JOIN accounts a ON a.id = t.account_id
WHERE t.amount < 0 AND t.amount = (SELECT MIN(x.amount) FROM transactions x WHERE x.account_id = t.account_id);

Expected result (3 rows):

idownermade_onamountlabel
1Alice2025-01-12-800rent
3Bruno2025-02-01-950rent
4Chloe2025-02-18-600rent

Exercise 14 · level 8

For each director who has at least one movie, show their name, the title of their longest movie and that movie's duration.

Table movies (10 rows)
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 rows)
idnamecountry
1Nora EllisUK
2Paulo ReisBrazil
3Kenji MoriJapan
4Anna BergSweden
5Luc MartinFrance
6Sara DiazSpain
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, JOIN, Subqueries.

Query structure:

SELECT , , 
FROM  
JOIN   ON  = 
WHERE  = (SELECT MAX() FROM   WHERE  = )
Show the solution
SELECT d.name, m.title, m.duration
FROM directors d
JOIN movies m ON m.director_id = d.id
WHERE m.duration = (SELECT MAX(duration) FROM movies x WHERE x.director_id = d.id);

Expected result (5 rows):

nametitleduration
Nora EllisLast Signal142
Paulo ReisDust and Gold110
Kenji MoriSilent Peak131
Anna BergSummer Keys88
Luc MartinThe Quiet Hour97

Exercise 15 · level 8

Show the player's name, their team's name and their number of goals, for the players who scored more goals than every Blue Owls player, from the most goals to the fewest.

Table players (9 rows)
idnameteam_idpositiongoals
1Alex Moreau1FW9
2Bilal Sow1MF4
3Carl Weber2FW7
4Diego Ruiz2DF1
5Eli Novak3FW6
6Femi Adeyemi4FW11
7Goran Petrov4GK0
8Hugo Lamy5MF5
9Ivan Kral5DF2
Table teams (5 rows)
idnamecityfounded
1Red FoxesLyon1950
2Blue OwlsParis1962
3Green BullsLille1971
4Gold HawksNantes1988
5Grey WolvesParis1990
Show the hint

Topics to use: WHERE (filters), ORDER BY, LIMIT, COUNT, SUM, AVG, MIN, MAX, JOIN, Subqueries.

Query structure:

SELECT , , 
FROM  
JOIN   ON  = 
WHERE  > (SELECT MAX() FROM   JOIN   ON  =  WHERE  = )
ORDER BY  DESC
Show the solution
SELECT p.name, t.name, p.goals
FROM players p
JOIN teams t ON t.id = p.team_id
WHERE p.goals > (SELECT MAX(x.goals) FROM players x JOIN teams y ON y.id = x.team_id WHERE y.name = 'Blue Owls')
ORDER BY p.goals DESC;

Expected result (2 rows, in this order):

namenamegoals
Femi AdeyemiGold Hawks11
Alex MoreauRed Foxes9

Practice with automatic checking

In SpeedQL, you write your query and it is checked straight away, on these tables and then on a hidden dataset.

Practice on the 34 “Subqueries” questions