SQL exercises with solutions: CTEs (WITH)
Updated on
WITH names an intermediate query, which you then reuse like a table. These 15 exercises range from level 5 to level 8; they use the syntax of SQLite, SpeedQL’s SQL engine.
Tip: Several CTEs can follow each other, separated by commas, before the final SELECT.
Read the “CTEs (WITH)” card in the cheat sheet
Exercise 1
Using a CTE (WITH), compute the average salary of each department, then show the departments whose average is above 45000.
| id | name | department | salary | manager_id | hired |
|---|---|---|---|---|---|
| 1 | Alice | Finance | 52000 | NULL | 2015-03-01 |
| 2 | Bob | IT | 41000 | 1 | 2018-06-15 |
| 3 | Claire | Finance | 38000 | 1 | 2019-01-10 |
| 4 | David | HR | 29000 | 1 | 2020-09-01 |
| 5 | Emma | IT | 50000 | 2 | 2017-11-20 |
| 6 | Farid | IT | 47000 | 2 | 2021-02-14 |
| 7 | Gaelle | HR | 33000 | 4 | 2022-05-30 |
| 8 | Hugo | Finance | 44000 | 3 | 2016-08-08 |
| 9 | Iris | IT | 47000 | 5 | 2023-01-09 |
| 10 | Jules | Finance | 44000 | 3 | 2024-03-18 |
Show the hint
Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, AVG(…) AS …
FROM …
GROUP BY …)
SELECT …
FROM …
WHERE … > …Show the solution
WITH avg_dept AS (
SELECT department, AVG(salary) AS a
FROM staff
GROUP BY department)
SELECT department
FROM avg_dept
WHERE a > 45000;Expected result (1 row):
| department |
|---|
| IT |
Exercise 2
Using a CTE (WITH), compute the total salaries per department, then show the department with the highest total.
| id | name | department | salary | manager_id | hired |
|---|---|---|---|---|---|
| 1 | Alice | Finance | 52000 | NULL | 2015-03-01 |
| 2 | Bob | IT | 41000 | 1 | 2018-06-15 |
| 3 | Claire | Finance | 38000 | 1 | 2019-01-10 |
| 4 | David | HR | 29000 | 1 | 2020-09-01 |
| 5 | Emma | IT | 50000 | 2 | 2017-11-20 |
| 6 | Farid | IT | 47000 | 2 | 2021-02-14 |
| 7 | Gaelle | HR | 33000 | 4 | 2022-05-30 |
| 8 | Hugo | Finance | 44000 | 3 | 2016-08-08 |
| 9 | Iris | IT | 47000 | 5 | 2023-01-09 |
| 10 | Jules | Finance | 44000 | 3 | 2024-03-18 |
Show the hint
Topics to use: ORDER BY, LIMIT, COUNT, SUM, AVG, MIN, MAX, GROUP BY, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, SUM(…) AS …
FROM …
GROUP BY …)
SELECT …
FROM …
ORDER BY … DESC
LIMIT …Show the solution
WITH t AS (
SELECT department, SUM(salary) AS s
FROM staff
GROUP BY department)
SELECT department
FROM t
ORDER BY s DESC
LIMIT 1;Expected result (1 row):
| department |
|---|
| IT |
Exercise 3
Using a CTE (WITH), count the loans of each member, then show the name of the members who borrowed more than the average (average computed over the members who have at least one loan).
| id | name | city | joined |
|---|---|---|---|
| 1 | Lena | Lyon | 2022-01-15 |
| 2 | Marc | Paris | 2021-06-03 |
| 3 | Nadia | Lyon | 2023-03-20 |
| 4 | Oscar | Lille | 2020-11-11 |
| 5 | Paula | Paris | 2024-02-01 |
| 6 | Quentin | Nantes | 2023-09-09 |
| 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 |
Show the hint
Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, Subqueries, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, COUNT(*) AS …
FROM …
GROUP BY …)
SELECT …
FROM …
JOIN … … ON … = …
WHERE … > (SELECT AVG(…) FROM …)Show the solution
WITH c AS (
SELECT member_id, COUNT(*) AS n
FROM loans
GROUP BY member_id)
SELECT m.name
FROM c
JOIN members m ON m.id = c.member_id
WHERE c.n > (SELECT AVG(n) FROM c);Expected result (2 rows):
| name |
|---|
| Lena |
| Marc |
Exercise 4
Using a CTE (WITH), compute the total number of goals of each match, then show the date of the matches whose total is above the average total.
| 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 |
Show the hint
Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, Subqueries, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, … + … AS …
FROM …)
SELECT …
FROM …
WHERE … > (SELECT AVG(…) FROM …)Show the solution
WITH t AS (
SELECT played_on, home_goals + away_goals AS g
FROM matches)
SELECT played_on
FROM t
WHERE g > (SELECT AVG(g) FROM t);Expected result (5 rows):
| played_on |
|---|
| 2025-08-02 |
| 2025-08-09 |
| 2025-08-10 |
| 2025-08-16 |
| 2025-08-24 |
Exercise 5
Using a CTE (WITH), compute the mean temperature of each reading ((max + min) / 2), then show the city, day and that mean for the readings where it is above 22.
| 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 |
Show the hint
Topics to use: WHERE (filters), CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, …, (… + …) / … AS …
FROM …)
SELECT …, …, …
FROM …
WHERE … > …Show the solution
WITH m AS (
SELECT city, day, (temp_max + temp_min) / 2.0 AS t
FROM readings)
SELECT city, day, t
FROM m
WHERE t > 22;Expected result (7 rows):
| city | day | t |
|---|---|---|
| Lyon | 2025-07-02 | 23.5 |
| Lyon | 2025-07-03 | 25 |
| Marseille | 2025-07-01 | 25.5 |
| Marseille | 2025-07-02 | 27 |
| Marseille | 2025-07-03 | 28 |
| Marseille | 2025-07-04 | 25 |
| Marseille | 2025-07-05 | 24 |
Exercise 6
Using a CTE (WITH), compute the amount of each booking (number of nights × room price), then show the id and amount of the bookings above 500.
| 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 |
Show the hint
Topics to use: WHERE (filters), JOIN, Text and dates, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, (JULIANDAY(…) - JULIANDAY(…)) * … AS …
FROM … …
JOIN … … ON … = …)
SELECT …, CAST(… AS INTEGER)
FROM …
WHERE … > …Show the solution
WITH a AS (
SELECT b.id, (julianday(b.check_out) - julianday(b.check_in)) * r.price AS amount
FROM bookings b
JOIN rooms r ON r.id = b.room_id)
SELECT id, CAST(amount AS INTEGER)
FROM a
WHERE amount > 500;Expected result (3 rows):
| id | CAST(amount AS INTEGER) |
|---|---|
| 3 | 780 |
| 4 | 910 |
| 10 | 600 |
Exercise 7
Using a CTE (WITH), compute the cost of each project (sum of hours × the developer's rate), then show the name and cost of the projects whose cost exceeds 15% of the budget.
| 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 |
| id | name | team | rate |
|---|---|---|---|
| 1 | Ana | Web | 55 |
| 2 | Bo | Web | 48 |
| 3 | Cleo | Data | 62 |
| 4 | Dan | Data | 58 |
| 5 | Eve | Ops | 50 |
| 6 | Finn | Ops | 45 |
Show the hint
Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, SUM(… * …) AS …
FROM … …
JOIN … … ON … = …
GROUP BY …)
SELECT …, …
FROM … …
JOIN … ON … = …
WHERE … > … * …Show the solution
WITH c AS (
SELECT t.project_id, SUM(t.hours * d.rate) AS cost
FROM tasks t
JOIN devs d ON d.id = t.dev_id
GROUP BY t.project_id)
SELECT p.name, c.cost
FROM projects p
JOIN c ON c.project_id = p.id
WHERE c.cost > 0.15 * p.budget;Expected result (2 rows):
| name | cost |
|---|---|
| Comet | 1390 |
| Delta | 2590 |
Exercise 8
Using a CTE (WITH), compute for each owner the salary received in January and in February 2025, then show the name and both amounts of the owners whose February salary is higher than the January one.
| id | owner | city | opened | kind |
|---|---|---|---|---|
| 1 | Alice | Paris | 2021-03-01 | current |
| 2 | Alice | Paris | 2022-06-15 | savings |
| 3 | Bruno | Lyon | 2020-09-10 | current |
| 4 | Chloe | Lyon | 2023-01-20 | current |
| 5 | David | Nice | 2019-11-05 | savings |
| 6 | Emma | Nice | 2024-04-01 | current |
| 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 |
Show the hint
Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CASE, Text and dates, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, SUM(CASE WHEN … LIKE … THEN … ELSE … END) AS …, SUM(CASE WHEN … LIKE … THEN … ELSE … END) AS …
FROM … …
JOIN … … ON … = …
WHERE … = …
GROUP BY …)
SELECT …, …, …
FROM …
WHERE … > …Show the solution
WITH s AS (
SELECT a.owner, SUM(CASE WHEN t.made_on LIKE '2025-01%' THEN t.amount ELSE 0 END) AS jan, SUM(CASE WHEN t.made_on LIKE '2025-02%' THEN t.amount ELSE 0 END) AS feb
FROM accounts a
JOIN transactions t ON t.account_id = a.id
WHERE t.label = 'salary'
GROUP BY a.owner)
SELECT owner, jan, feb
FROM s
WHERE feb > jan;Expected result (1 row):
| owner | jan | feb |
|---|---|---|
| Alice | 2500 | 2600 |
Exercise 9
For each category, show the category, the name of the best-selling product by quantity and that quantity (on ties, all the tied products).
| 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 |
Show the hint
Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, Subqueries, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, …, SUM(…) AS …
FROM … …
JOIN … … ON … = …
GROUP BY …)
SELECT …, …, …
FROM …
WHERE … = (SELECT MAX(…) FROM … … WHERE … = …)Show the solution
WITH q AS (
SELECT p.category, p.name, SUM(oi.qty) AS s
FROM products p
JOIN order_items oi ON oi.product_id = p.id
GROUP BY p.id)
SELECT category, name, s
FROM q
WHERE s = (SELECT MAX(s) FROM q q2 WHERE q2.category = q.category);Expected result (3 rows):
| category | name | s |
|---|---|---|
| Kitchen | Coffee Mug | 8 |
| Office | Notebook | 8 |
| Home | Cushion | 3 |
Exercise 10
Using a CTE (WITH), compute the total invoiced per client, then show client, that total and a column called size that is 'big' if the total is at least 1500, otherwise 'small'. Use CASE.
| id | client | issued | due | amount | paid_on |
|---|---|---|---|---|---|
| 1 | Acme | 2025-01-15 | 2025-02-14 | 1200 | 2025-02-10 |
| 2 | Acme | 2025-03-01 | 2025-03-16 | 800 | NULL |
| 3 | Bolt | 2025-01-20 | 2025-03-06 | 450 | 2025-03-01 |
| 4 | Bolt | 2025-02-25 | 2025-03-27 | 950 | 2025-04-02 |
| 5 | Cyan | 2025-03-05 | 2025-05-04 | 300 | NULL |
| 6 | Cyan | 2024-12-10 | 2024-12-30 | 600 | 2025-01-05 |
Show the hint
Topics to use: COUNT, SUM, AVG, MIN, MAX, GROUP BY, CASE, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, SUM(…) AS …
FROM …
GROUP BY …)
SELECT …, …, CASE WHEN … >= … THEN … ELSE … END AS …
FROM …Show the solution
WITH t AS (
SELECT client, SUM(amount) AS total
FROM invoices
GROUP BY client)
SELECT client, total, CASE WHEN total >= 1500 THEN 'big' ELSE 'small' END AS size
FROM t;Expected result (3 rows):
| client | total | size |
|---|---|---|
| Acme | 2000 | big |
| Bolt | 1400 | small |
| Cyan | 900 | small |
Exercise 11
Using a CTE (WITH), compute the points of each team over all its matches, home and away (win 3 points, draw 1 point, loss 0), then show the team name and its total points, from the highest total to the lowest (on ties, alphabetical order of the name).
| 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 |
Show the hint
Topics to use: ORDER BY, LIMIT, COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CASE, UNION, INTERSECT, EXCEPT, CTEs (WITH).
Query structure:
WITH … AS (
SELECT … AS …, CASE WHEN … > … THEN … WHEN … = … THEN … ELSE … END AS …
FROM …
UNION ALL SELECT …, CASE WHEN … > … THEN … WHEN … = … THEN … ELSE … END
FROM …)
SELECT …, SUM(…)
FROM …
JOIN … … ON … = …
GROUP BY …
ORDER BY SUM(…) DESC, …Show the solution
WITH r AS (
SELECT home_id AS team, CASE WHEN home_goals > away_goals THEN 3 WHEN home_goals = away_goals THEN 1 ELSE 0 END AS pts
FROM matches
UNION ALL SELECT away_id, CASE WHEN away_goals > home_goals THEN 3 WHEN away_goals = home_goals THEN 1 ELSE 0 END
FROM matches)
SELECT t.name, SUM(r.pts)
FROM r
JOIN teams t ON t.id = r.team
GROUP BY t.id
ORDER BY SUM(r.pts) DESC, t.name;Expected result (5 rows, in this order):
| name | SUM(r.pts) |
|---|---|
| Red Foxes | 10 |
| Gold Hawks | 8 |
| Blue Owls | 4 |
| Grey Wolves | 3 |
| Green Bulls | 2 |
Exercise 12
Using a CTE (WITH), compute the fill rate of each flight (seats sold / capacity, in %), then show for each airline its average rate rounded to 1 decimal and the number of its flights more than 80% full.
| 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 |
Show the hint
Topics to use: COUNT, SUM, AVG, MIN, MAX, GROUP BY, CASE, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, … * … / … AS …
FROM …)
SELECT …, ROUND(AVG(…), …), SUM(CASE WHEN … > … THEN … ELSE … END)
FROM …
GROUP BY …Show the solution
WITH r AS (
SELECT airline, 100.0 * seats_sold / capacity AS t
FROM flights)
SELECT airline, ROUND(AVG(t), 1), SUM(CASE WHEN t > 80 THEN 1 ELSE 0 END)
FROM r
GROUP BY airline;Expected result (3 rows):
| airline | ROUND(AVG(t), 1) | SUM(CASE WHEN t > 80 THEN 1 ELSE 0 END) |
|---|---|---|
| AirNova | 77.7 | 2 |
| BlueWing | 71.7 | 1 |
| SkyJet | 85.4 | 3 |
Exercise 13
Using a CTE (WITH), compute for each day the average maximum temperature of all the cities, then show the day, the city and the gap (rounded to 1 decimal) of the readings that exceed that average by more than 3 degrees.
| 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 |
Show the hint
Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, AVG(…) AS …
FROM …
GROUP BY …)
SELECT …, …, ROUND(… - …, …)
FROM … …
JOIN … ON … = …
WHERE … - … > …Show the solution
WITH a AS (
SELECT day, AVG(temp_max) AS m
FROM readings
GROUP BY day)
SELECT r.day, r.city, ROUND(r.temp_max - a.m, 1)
FROM readings r
JOIN a ON a.day = r.day
WHERE r.temp_max - a.m > 3;Expected result (4 rows):
| day | city | ROUND(r.temp_max - a.m, 1) |
|---|---|---|
| 2025-07-01 | Marseille | 3.3 |
| 2025-07-03 | Marseille | 4.3 |
| 2025-07-04 | Marseille | 5 |
| 2025-07-05 | Marseille | 3.7 |
Exercise 14
For each project, show its name, its budget, its cost (hours × rate, 0 without tasks), the remaining budget and the share of the budget used in % rounded to 1 decimal, from the largest share to the smallest (on ties, by name).
| 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 |
| id | name | team | rate |
|---|---|---|---|
| 1 | Ana | Web | 55 |
| 2 | Bo | Web | 48 |
| 3 | Cleo | Data | 62 |
| 4 | Dan | Data | 58 |
| 5 | Eve | Ops | 50 |
| 6 | Finn | Ops | 45 |
Show the hint
Topics to use: ORDER BY, LIMIT, COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, NULL, COALESCE, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, …, …, COALESCE(SUM(… * …), …) AS …
FROM … …
LEFT JOIN … … ON … = …
LEFT JOIN … … ON … = …
GROUP BY …)
SELECT …, …, …, … - …, ROUND(… * … / …, …)
FROM …
ORDER BY … * … / … DESC, …Show the solution
WITH c AS (
SELECT p.id, p.name, p.budget, COALESCE(SUM(t.hours * d.rate), 0) AS cost
FROM projects p
LEFT JOIN tasks t ON t.project_id = p.id
LEFT JOIN devs d ON d.id = t.dev_id
GROUP BY p.id)
SELECT name, budget, cost, budget - cost, ROUND(100.0 * cost / budget, 1)
FROM c
ORDER BY 1.0 * cost / budget DESC, name;Expected result (4 rows, in this order):
| name | budget | cost | budget - cost | ROUND(100.0 * cost / budget, 1) |
|---|---|---|---|---|
| Comet | 8000 | 1390 | 6610 | 17.4 |
| Delta | 15000 | 2590 | 12410 | 17.3 |
| Atlas | 20000 | 2284 | 17716 | 11.4 |
| Beacon | 12000 | 1228 | 10772 | 10.2 |
Exercise 15
Show the name of the owners whose expenses exceed 30% of their income (all accounts together), with that ratio in % rounded to 1 decimal.
| id | owner | city | opened | kind |
|---|---|---|---|---|
| 1 | Alice | Paris | 2021-03-01 | current |
| 2 | Alice | Paris | 2022-06-15 | savings |
| 3 | Bruno | Lyon | 2020-09-10 | current |
| 4 | Chloe | Lyon | 2023-01-20 | current |
| 5 | David | Nice | 2019-11-05 | savings |
| 6 | Emma | Nice | 2024-04-01 | current |
| 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 |
Show the hint
Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CASE, CTEs (WITH).
Query structure:
WITH … AS (
SELECT …, SUM(CASE WHEN … > … THEN … ELSE … END) AS …, -SUM(CASE WHEN … < … THEN … ELSE … END) AS …
FROM … …
JOIN … … ON … = …
GROUP BY …)
SELECT …, ROUND(… * … / …, …)
FROM …
WHERE … > … AND … > … * …Show the solution
WITH s AS (
SELECT a.owner, SUM(CASE WHEN t.amount > 0 THEN t.amount ELSE 0 END) AS income, -SUM(CASE WHEN t.amount < 0 THEN t.amount ELSE 0 END) AS spent
FROM accounts a
JOIN transactions t ON t.account_id = a.id
GROUP BY a.owner)
SELECT owner, ROUND(100.0 * spent / income, 1)
FROM s
WHERE income > 0 AND spent > 0.3 * income;Expected result (2 rows):
| owner | ROUND(100.0 * spent / income, 1) |
|---|---|
| Bruno | 56.3 |
| Chloe | 30.7 |
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.