Exercices SQL corrigés : les fonctions de fenêtre
Mis à jour le
Les fonctions de fenêtre calculent sur un groupe de lignes sans les regrouper : ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, SUM() OVER… Ces 15 exercices vont du niveau 6 au niveau 8 ; ils utilisent la syntaxe de SQLite, le moteur SQL de SpeedQL.
Conseil : PARTITION BY découpe les groupes, ORDER BY fixe l’ordre à l’intérieur de chaque groupe.
Relire la fiche « Fonctions de fenêtre (OVER) » de l’aide-mémoire
Exercice 1
Numérote les employés du mieux payé au moins bien payé (en cas d'égalité, par ordre alphabétique du nom). Affiche name, salary et le numéro. Utilise ROW_NUMBER().
| 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 |
Voir l’indice
Notions à utiliser : Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT …, …, ROW_NUMBER() OVER (ORDER BY … DESC, …)
FROM …Voir la correction
SELECT name, salary, ROW_NUMBER() OVER (ORDER BY salary DESC, name)
FROM staff;Résultat attendu (10 lignes) :
| name | salary | ROW_NUMBER() OVER (ORDER BY salary DESC, name) |
|---|---|---|
| Alice | 52000 | 1 |
| Emma | 50000 | 2 |
| Farid | 47000 | 3 |
| Iris | 47000 | 4 |
| Hugo | 44000 | 5 |
| Jules | 44000 | 6 |
| Bob | 41000 | 7 |
| Claire | 38000 | 8 |
| Gaelle | 33000 | 9 |
| David | 29000 | 10 |
Exercice 2
Dans chaque département, numérote les employés du mieux au moins bien payé (égalité : ordre alphabétique du nom). Affiche name, department et le numéro. Utilise ROW_NUMBER().
| 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 |
Voir l’indice
Notions à utiliser : Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT …, …, ROW_NUMBER() OVER (PARTITION BY … ORDER BY … DESC, …)
FROM …Voir la correction
SELECT name, department, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC, name)
FROM staff;Résultat attendu (10 lignes) :
| name | department | ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC, name) |
|---|---|---|
| Alice | Finance | 1 |
| Hugo | Finance | 2 |
| Jules | Finance | 3 |
| Claire | Finance | 4 |
| Gaelle | HR | 1 |
| David | HR | 2 |
| Emma | IT | 1 |
| Farid | IT | 2 |
| Iris | IT | 3 |
| Bob | IT | 4 |
Exercice 3
Affiche le titre, la note et l'écart entre la note du film et la note moyenne de tous les films, arrondi à 2 décimales. Utilise une fonction de fenêtre.
| 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 |
Voir l’indice
Notions à utiliser : Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT …, …, ROUND(… - AVG(…) OVER (), …)
FROM …Voir la correction
SELECT title, rating, ROUND(rating - AVG(rating) OVER (), 2)
FROM movies;Résultat attendu (10 lignes) :
| title | rating | ROUND(rating - AVG(rating) OVER (), 2) |
|---|---|---|
| Night Train | 7.8 | 0.6 |
| Blue Harbor | 7.1 | -0.1 |
| Paper Moon City | 6.4 | -0.8 |
| Silent Peak | 8.2 | 1 |
| Last Signal | 7.5 | 0.3 |
| Summer Keys | 5.9 | -1.3 |
| Iron Garden | 8 | 0.8 |
| Dust and Gold | 6.8 | -0.4 |
| The Quiet Hour | 7.4 | 0.2 |
| Deep Current | 6.9 | -0.3 |
Exercice 4
Affiche la date de chaque match et le nombre cumulé de buts marqués dans le championnat jusqu'à cette date incluse (fonction de fenêtre).
| 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 : Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT …, SUM(… + …) OVER (ORDER BY …)
FROM …Voir la correction
SELECT played_on, SUM(home_goals + away_goals) OVER (ORDER BY played_on)
FROM matches;Résultat attendu (10 lignes) :
| played_on | SUM(home_goals + away_goals) OVER (ORDER BY played_on) |
|---|---|
| 2025-08-02 | 3 |
| 2025-08-03 | 3 |
| 2025-08-09 | 7 |
| 2025-08-10 | 11 |
| 2025-08-16 | 15 |
| 2025-08-17 | 16 |
| 2025-08-23 | 17 |
| 2025-08-24 | 22 |
| 2025-08-30 | 24 |
| 2025-08-31 | 26 |
Exercice 5
Affiche l'id, la destination, le prix et le prix moyen des vols vers la même destination, arrondi à 1 décimale (fonction de fenêtre).
| 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 : Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT …, …, …, ROUND(AVG(…) OVER (PARTITION BY …), …)
FROM …Voir la correction
SELECT id, dest, price, ROUND(AVG(price) OVER (PARTITION BY dest), 1)
FROM flights;Résultat attendu (12 lignes) :
| id | dest | price | ROUND(AVG(price) OVER (PARTITION BY dest), 1) |
|---|---|---|---|
| 6 | BER | 140 | 125 |
| 10 | BER | 110 | 125 |
| 4 | CDG | 95 | 100 |
| 5 | CDG | 105 | 100 |
| 3 | FCO | 75 | 87 |
| 7 | FCO | 99 | 87 |
| 2 | LIS | 120 | 95 |
| 11 | LIS | 70 | 95 |
| 9 | LYS | 82 | 82 |
| 1 | MAD | 89 | 77.7 |
| 8 | MAD | 65 | 77.7 |
| 12 | MAD | 79 | 77.7 |
Exercice 6
Affiche la ville, le jour, la pluie du jour et la pluie du lendemain dans la même ville (NULL pour le dernier jour). Utilise LEAD().
| 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 : Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT …, …, …, LEAD(…) OVER (PARTITION BY … ORDER BY …)
FROM …Voir la correction
SELECT city, day, rain_mm, LEAD(rain_mm) OVER (PARTITION BY city ORDER BY day)
FROM readings;Résultat attendu (15 lignes) :
| city | day | rain_mm | LEAD(rain_mm) OVER (PARTITION BY city ORDER BY day) |
|---|---|---|---|
| Lyon | 2025-07-01 | 0 | 0 |
| Lyon | 2025-07-02 | 0 | 0 |
| Lyon | 2025-07-03 | 0 | 8.5 |
| Lyon | 2025-07-04 | 8.5 | 3 |
| Lyon | 2025-07-05 | 3 | NULL |
| Marseille | 2025-07-01 | 0 | 0 |
| Marseille | 2025-07-02 | 0 | 0 |
| Marseille | 2025-07-03 | 0 | 0 |
| Marseille | 2025-07-04 | 0 | 0 |
| Marseille | 2025-07-05 | 0 | NULL |
| Paris | 2025-07-01 | 0 | 0 |
| Paris | 2025-07-02 | 0 | 4.5 |
| Paris | 2025-07-03 | 4.5 | 12 |
| Paris | 2025-07-04 | 12 | 0 |
| Paris | 2025-07-05 | 0 | NULL |
Exercice 7
Pour chaque commande non annulée, affiche son numéro, sa date, son montant et le chiffre d'affaires cumulé (par date, puis par numéro).
| id | customer_id | order_date | status |
|---|---|---|---|
| 1 | 1 | 2025-01-05 | shipped |
| 2 | 2 | 2025-01-12 | shipped |
| 3 | 1 | 2025-02-03 | paid |
| 4 | 3 | 2025-02-10 | cancelled |
| 5 | 4 | 2025-02-20 | shipped |
| 6 | 5 | 2025-03-02 | shipped |
| 7 | 2 | 2025-03-15 | paid |
| 8 | 3 | 2025-03-28 | shipped |
| 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 |
| 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 |
Voir l’indice
Notions à utiliser : WHERE (filtres), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CTE (WITH), Fonctions de fenêtre (OVER).
Structure de la requête :
WITH … AS (
SELECT …, …, SUM(… * …) AS …
FROM … …
JOIN … … ON … = …
JOIN … … ON … = …
WHERE … <> …
GROUP BY …)
SELECT …, …, …, SUM(…) OVER (ORDER BY …, …)
FROM …Voir la correction
WITH t AS (
SELECT o.id, o.order_date, SUM(oi.qty * p.price) AS amount
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.status <> 'cancelled'
GROUP BY o.id)
SELECT id, order_date, amount, SUM(amount) OVER (ORDER BY order_date, id)
FROM t;Résultat attendu (7 lignes) :
| id | order_date | amount | SUM(amount) OVER (ORDER BY order_date, id) |
|---|---|---|---|
| 1 | 2025-01-05 | 59 | 59 |
| 2 | 2025-01-12 | 149 | 208 |
| 3 | 2025-02-03 | 42 | 250 |
| 5 | 2025-02-20 | 79 | 329 |
| 6 | 2025-03-02 | 66 | 395 |
| 7 | 2025-03-15 | 171 | 566 |
| 8 | 2025-03-28 | 102 | 668 |
Exercice 8
Pour chaque opération, affiche le compte, la date, le montant et le solde du compte après l'opération (cumul par date, puis par numéro).
| 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 : Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT …, …, …, SUM(…) OVER (PARTITION BY … ORDER BY …, …)
FROM …Voir la correction
SELECT account_id, made_on, amount, SUM(amount) OVER (PARTITION BY account_id ORDER BY made_on, id)
FROM transactions;Résultat attendu (14 lignes) :
| account_id | made_on | amount | SUM(amount) OVER (PARTITION BY account_id ORDER BY made_on, id) |
|---|---|---|---|
| 1 | 2025-01-02 | 2500 | 2500 |
| 1 | 2025-01-05 | -60 | 2440 |
| 1 | 2025-01-12 | -800 | 1640 |
| 1 | 2025-02-02 | 2600 | 4240 |
| 1 | 2025-02-06 | -75 | 4165 |
| 2 | 2025-01-15 | 500 | 500 |
| 2 | 2025-02-15 | 500 | 1000 |
| 3 | 2025-01-03 | 1900 | 1900 |
| 3 | 2025-01-20 | -120 | 1780 |
| 3 | 2025-02-01 | -950 | 830 |
| 4 | 2025-01-25 | 2100 | 2100 |
| 4 | 2025-02-03 | -45 | 2055 |
| 4 | 2025-02-18 | -600 | 1455 |
| 5 | 2025-02-10 | 30 | 30 |
Exercice 9
Calcule la médiane des salaires de tous les employés (la moyenne des deux valeurs centrales quand le nombre d'employés est pair).
| 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 |
Voir l’indice
Notions à utiliser : WHERE (filtres), COUNT, SUM, AVG, MIN, MAX, Sous-requêtes, Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT AVG(…)
FROM (SELECT …, ROW_NUMBER() OVER (ORDER BY …) AS …, COUNT(*) OVER () AS … FROM …)
WHERE … IN ((… + …) / …, (… + …) / …)Voir la correction
SELECT AVG(salary)
FROM (SELECT salary, ROW_NUMBER() OVER (ORDER BY salary) AS rn, COUNT(*) OVER () AS c FROM staff)
WHERE rn IN ((c + 1) / 2, (c + 2) / 2);Résultat attendu (1 ligne) :
| AVG(salary) |
|---|
| 44000 |
Exercice 10
Pour chaque équipe, affiche le nom de son meilleur buteur (colonnes : team_id, name).
| id | name | team_id | position | goals |
|---|---|---|---|---|
| 1 | Alex Moreau | 1 | FW | 9 |
| 2 | Bilal Sow | 1 | MF | 4 |
| 3 | Carl Weber | 2 | FW | 7 |
| 4 | Diego Ruiz | 2 | DF | 1 |
| 5 | Eli Novak | 3 | FW | 6 |
| 6 | Femi Adeyemi | 4 | FW | 11 |
| 7 | Goran Petrov | 4 | GK | 0 |
| 8 | Hugo Lamy | 5 | MF | 5 |
| 9 | Ivan Kral | 5 | DF | 2 |
Voir l’indice
Notions à utiliser : WHERE (filtres), Sous-requêtes, Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT …, …
FROM (SELECT …, …, ROW_NUMBER() OVER (PARTITION BY … ORDER BY … DESC) AS … FROM …)
WHERE … = …Voir la correction
SELECT team_id, name
FROM (SELECT team_id, name, ROW_NUMBER() OVER (PARTITION BY team_id ORDER BY goals DESC) AS rn FROM players)
WHERE rn = 1;Résultat attendu (5 lignes) :
| team_id | name |
|---|---|
| 1 | Alex Moreau |
| 2 | Carl Weber |
| 3 | Eli Novak |
| 4 | Femi Adeyemi |
| 5 | Hugo Lamy |
Exercice 11
Pour chaque ville qui a au moins un jour sec, affiche la ville et le plus grand nombre de jours secs consécutifs (rain_mm = 0).
| 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 : WHERE (filtres), COUNT, SUM, AVG, MIN, MAX, GROUP BY, CTE (WITH), Fonctions de fenêtre (OVER).
Structure de la requête :
WITH … AS (
SELECT …, …, …, ROW_NUMBER() OVER (PARTITION BY … ORDER BY …) - ROW_NUMBER() OVER (PARTITION BY …, … = … ORDER BY …) AS …
FROM …), … AS (
SELECT …, …, COUNT(*) AS …
FROM …
WHERE … = …
GROUP BY …, …)
SELECT …, MAX(…)
FROM …
GROUP BY …Voir la correction
WITH r AS (
SELECT city, day, rain_mm, ROW_NUMBER() OVER (PARTITION BY city ORDER BY day) - ROW_NUMBER() OVER (PARTITION BY city, rain_mm = 0 ORDER BY day) AS grp
FROM readings), s AS (
SELECT city, grp, COUNT(*) AS n
FROM r
WHERE rain_mm = 0
GROUP BY city, grp)
SELECT city, MAX(n)
FROM s
GROUP BY city;Résultat attendu (3 lignes) :
| city | MAX(n) |
|---|---|
| Lyon | 3 |
| Marseille | 5 |
| Paris | 2 |
Exercice 12
Donne le premier mois où le total cumulé des ventes de la région North dépasse 1000 (un seul résultat).
| id | seller | region | month | amount |
|---|---|---|---|---|
| 1 | Ana | North | 2025-01 | 300 |
| 2 | Ana | North | 2025-02 | 450 |
| 3 | Ana | North | 2025-03 | 400 |
| 4 | Ben | North | 2025-01 | 500 |
| 5 | Ben | North | 2025-02 | 350 |
| 6 | Ben | North | 2025-03 | 600 |
| 7 | Cleo | South | 2025-01 | 200 |
| 8 | Cleo | South | 2025-02 | 700 |
| 9 | Cleo | South | 2025-03 | 650 |
| 10 | Dan | South | 2025-01 | 400 |
| 11 | Dan | South | 2025-02 | 400 |
| 12 | Dan | South | 2025-03 | 100 |
Voir l’indice
Notions à utiliser : WHERE (filtres), COUNT, SUM, AVG, MIN, MAX, GROUP BY, CTE (WITH), Fonctions de fenêtre (OVER).
Structure de la requête :
WITH … AS (
SELECT …, SUM(SUM(…)) OVER (ORDER BY …) AS …
FROM …
WHERE … = …
GROUP BY …)
SELECT MIN(…)
FROM …
WHERE … > …Voir la correction
WITH m AS (
SELECT month, SUM(SUM(amount)) OVER (ORDER BY month) AS c
FROM sales
WHERE region = 'North'
GROUP BY month)
SELECT MIN(month)
FROM m
WHERE c > 1000;Résultat attendu (1 ligne) :
| MIN(month) |
|---|
| 2025-02 |
Exercice 13
Pour chaque mois où il y a eu des emprunts (format AAAA-MM), affiche le mois, le nombre d'emprunts du mois et le cumul des emprunts depuis le premier mois, dans l'ordre chronologique.
| 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 : ORDER BY, LIMIT, COUNT, SUM, AVG, MIN, MAX, GROUP BY, Texte et dates, Fonctions de fenêtre (OVER).
Structure de la requête :
SELECT STRFTIME(…, …) AS …, COUNT(*), SUM(COUNT(*)) OVER (ORDER BY STRFTIME(…, …))
FROM …
GROUP BY …
ORDER BY …Voir la correction
SELECT strftime('%Y-%m', loan_date) AS month, COUNT(*), SUM(COUNT(*)) OVER (ORDER BY strftime('%Y-%m', loan_date))
FROM loans
GROUP BY month
ORDER BY month;Résultat attendu (4 lignes, dans cet ordre) :
| month | COUNT(*) | SUM(COUNT(*)) OVER (ORDER BY strftime('%Y-%m', loan_date)) |
|---|---|---|
| 2025-01 | 2 | 2 |
| 2025-02 | 3 | 5 |
| 2025-03 | 4 | 9 |
| 2025-04 | 3 | 12 |
Exercice 14
Pour chaque région qui a des relevés, affiche la région, le nombre de jours de pluie, la pluie totale et le jour le plus pluvieux (NULL s'il n'a jamais plu ; en cas d'égalité, le plus ancien).
| name | region | altitude |
|---|---|---|
| Paris | Ile-de-France | 35 |
| Lyon | Rhone-Alpes | 173 |
| Marseille | Provence | 12 |
| Lille | Hauts-de-France | 20 |
| 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, JOIN, CASE, CTE (WITH), Fonctions de fenêtre (OVER).
Structure de la requête :
WITH … AS (
SELECT …, …, …, ROW_NUMBER() OVER (PARTITION BY … ORDER BY … DESC, …) AS …
FROM … …
JOIN … … ON … = …)
SELECT …, SUM(CASE WHEN … > … THEN … ELSE … END), SUM(…), MAX(CASE WHEN … = … AND … > … THEN … END)
FROM …
GROUP BY …Voir la correction
WITH r AS (
SELECT c.region, x.day, x.rain_mm, ROW_NUMBER() OVER (PARTITION BY c.region ORDER BY x.rain_mm DESC, x.day) AS rn
FROM cities c
JOIN readings x ON x.city = c.name)
SELECT region, SUM(CASE WHEN rain_mm > 0 THEN 1 ELSE 0 END), SUM(rain_mm), MAX(CASE WHEN rn = 1 AND rain_mm > 0 THEN day END)
FROM r
GROUP BY region;Résultat attendu (3 lignes) :
| region | SUM(CASE WHEN rain_mm > 0 THEN 1 ELSE 0 END) | SUM(rain_mm) | MAX(CASE WHEN rn = 1 AND rain_mm > 0 THEN day END) |
|---|---|---|---|
| Ile-de-France | 2 | 16.5 | 2025-07-04 |
| Provence | 0 | 0 | NULL |
| Rhone-Alpes | 2 | 11.5 | 2025-07-04 |
Exercice 15
Pour chaque compte, affiche le compte, la date à laquelle son solde (cumul des opérations par date, puis par numéro) a été le plus bas, et ce solde (en cas d'égalité, la date la plus ancienne).
| 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 : WHERE (filtres), CTE (WITH), Fonctions de fenêtre (OVER).
Structure de la requête :
WITH … AS (
SELECT …, …, …, SUM(…) OVER (PARTITION BY … ORDER BY …, …) AS …
FROM …), … AS (
SELECT …, …, …, ROW_NUMBER() OVER (PARTITION BY … ORDER BY …, …, …) AS …
FROM …)
SELECT …, …, …
FROM …
WHERE … = …Voir la correction
WITH b AS (
SELECT account_id, made_on, id, SUM(amount) OVER (PARTITION BY account_id ORDER BY made_on, id) AS bal
FROM transactions), k AS (
SELECT account_id, made_on, bal, ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY bal, made_on, id) AS rn
FROM b)
SELECT account_id, made_on, bal
FROM k
WHERE rn = 1;Résultat attendu (5 lignes) :
| account_id | made_on | bal |
|---|---|---|
| 1 | 2025-01-12 | 1640 |
| 2 | 2025-01-15 | 500 |
| 3 | 2025-02-01 | 830 |
| 4 | 2025-02-18 | 1455 |
| 5 | 2025-02-10 | 30 |
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 111 questions « Fonctions de fenêtre (OVER) »