Exercices SQL corrigés : NULL et COALESCE
Mis à jour le
NULL = valeur absente : on la teste avec IS NULL / IS NOT NULL ; COALESCE la remplace. Ces 14 exercices vont du niveau 4 au niveau 8 ; ils utilisent la syntaxe de SQLite, le moteur SQL de SpeedQL.
Conseil : col = NULL ne marche jamais : il faut écrire col IS NULL.
Relire la fiche « NULL, COALESCE » de l’aide-mémoire
Exercice 1
Affiche le nom et l'email de chaque contact, en remplaçant les emails manquants par 'unknown'. Utilise COALESCE.
| id | name | phone | city | |
|---|---|---|---|---|
| 1 | Alice Martin | alice@mail.com | 0612345678 | Paris |
| 2 | bob durand | NULL | 0698765432 | Lyon |
| 3 | CLAIRE ROUX | claire@work.org | NULL | NULL |
| 4 | David Lefevre | NULL | NULL | Nantes |
| 5 | Emma Petit | emma@mail.com | 0611223344 | NULL |
Voir l’indice
Notions à utiliser : NULL, COALESCE.
Structure de la requête :
SELECT …, COALESCE(…, …)
FROM …Voir la correction
SELECT name, COALESCE(email, 'unknown')
FROM contacts;Résultat attendu (5 lignes) :
| name | COALESCE(email, 'unknown') |
|---|---|
| Alice Martin | alice@mail.com |
| bob durand | unknown |
| CLAIRE ROUX | claire@work.org |
| David Lefevre | unknown |
| Emma Petit | emma@mail.com |
Exercice 2
Affiche le nom de chaque contact et, dans une colonne contact, son téléphone s'il existe, sinon son email, sinon 'none'. Utilise COALESCE.
| id | name | phone | city | |
|---|---|---|---|---|
| 1 | Alice Martin | alice@mail.com | 0612345678 | Paris |
| 2 | bob durand | NULL | 0698765432 | Lyon |
| 3 | CLAIRE ROUX | claire@work.org | NULL | NULL |
| 4 | David Lefevre | NULL | NULL | Nantes |
| 5 | Emma Petit | emma@mail.com | 0611223344 | NULL |
Voir l’indice
Notions à utiliser : NULL, COALESCE.
Structure de la requête :
SELECT …, COALESCE(…, …, …) AS …
FROM …Voir la correction
SELECT name, COALESCE(phone, email, 'none') AS contact
FROM contacts;Résultat attendu (5 lignes) :
| name | contact |
|---|---|
| Alice Martin | 0612345678 |
| bob durand | 0698765432 |
| CLAIRE ROUX | claire@work.org |
| David Lefevre | none |
| Emma Petit | 0611223344 |
Exercice 3
Affiche le nom des contacts qui n'ont pas d'email.
| id | name | phone | city | |
|---|---|---|---|---|
| 1 | Alice Martin | alice@mail.com | 0612345678 | Paris |
| 2 | bob durand | NULL | 0698765432 | Lyon |
| 3 | CLAIRE ROUX | claire@work.org | NULL | NULL |
| 4 | David Lefevre | NULL | NULL | Nantes |
| 5 | Emma Petit | emma@mail.com | 0611223344 | NULL |
Voir l’indice
Notions à utiliser : WHERE (filtres), NULL, COALESCE.
Structure de la requête :
SELECT …
FROM …
WHERE … IS NULLVoir la correction
SELECT name
FROM contacts
WHERE email IS NULL;Résultat attendu (2 lignes) :
| name |
|---|
| bob durand |
| David Lefevre |
Exercice 4
Affiche le nombre de contacts pour lesquels il manque l'email, le téléphone, ou les deux.
| id | name | phone | city | |
|---|---|---|---|---|
| 1 | Alice Martin | alice@mail.com | 0612345678 | Paris |
| 2 | bob durand | NULL | 0698765432 | Lyon |
| 3 | CLAIRE ROUX | claire@work.org | NULL | NULL |
| 4 | David Lefevre | NULL | NULL | Nantes |
| 5 | Emma Petit | emma@mail.com | 0611223344 | NULL |
Voir l’indice
Notions à utiliser : WHERE (filtres), COUNT, SUM, AVG, MIN, MAX, NULL, COALESCE.
Structure de la requête :
SELECT COUNT(*)
FROM …
WHERE … IS NULL OR … IS NULLVoir la correction
SELECT COUNT(*)
FROM contacts
WHERE email IS NULL OR phone IS NULL;Résultat attendu (1 ligne) :
| COUNT(*) |
|---|
| 3 |
Exercice 5
Pour les contacts qui ont un email, affiche l'email et son domaine (la partie après le @).
| id | name | phone | city | |
|---|---|---|---|---|
| 1 | Alice Martin | alice@mail.com | 0612345678 | Paris |
| 2 | bob durand | NULL | 0698765432 | Lyon |
| 3 | CLAIRE ROUX | claire@work.org | NULL | NULL |
| 4 | David Lefevre | NULL | NULL | Nantes |
| 5 | Emma Petit | emma@mail.com | 0611223344 | NULL |
Voir l’indice
Notions à utiliser : WHERE (filtres), NULL, COALESCE, Texte et dates.
Structure de la requête :
SELECT …, SUBSTR(…, INSTR(…, …) + …)
FROM …
WHERE … IS NOT NULLVoir la correction
SELECT email, SUBSTR(email, INSTR(email, '@') + 1)
FROM contacts
WHERE email IS NOT NULL;Résultat attendu (3 lignes) :
| SUBSTR(email, INSTR(email, '@') + 1) | |
|---|---|
| alice@mail.com | mail.com |
| claire@work.org | work.org |
| emma@mail.com | mail.com |
Exercice 6
Affiche le titre de chaque film et le nom de son réalisateur, avec 'unknown' à la place du nom quand le réalisateur est inconnu. Utilise COALESCE.
| 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 : JOIN, NULL, COALESCE.
Structure de la requête :
SELECT …, COALESCE(…, …)
FROM … …
LEFT JOIN … … ON … = …Voir la correction
SELECT m.title, COALESCE(d.name, 'unknown')
FROM movies m
LEFT JOIN directors d ON d.id = m.director_id;Résultat attendu (10 lignes) :
| title | COALESCE(d.name, 'unknown') |
|---|---|
| Night Train | Nora Ellis |
| Blue Harbor | Paulo Reis |
| Paper Moon City | Luc Martin |
| Silent Peak | Kenji Mori |
| Last Signal | Nora Ellis |
| Summer Keys | Anna Berg |
| Iron Garden | Kenji Mori |
| Dust and Gold | Paulo Reis |
| The Quiet Hour | Luc Martin |
| Deep Current | unknown |
Exercice 7
Pour chaque emprunt rendu, affiche l'id de l'emprunt et le nombre de jours entre l'emprunt et le retour.
| 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 : WHERE (filtres), NULL, COALESCE, Texte et dates.
Structure de la requête :
SELECT …, CAST(JULIANDAY(…) - JULIANDAY(…) AS INTEGER)
FROM …
WHERE … IS NOT NULLVoir la correction
SELECT id, CAST(julianday(return_date) - julianday(loan_date) AS INTEGER)
FROM loans
WHERE return_date IS NOT NULL;Résultat attendu (9 lignes) :
| id | CAST(julianday(return_date) - julianday(loan_date) AS INTEGER) |
|---|---|
| 1 | 14 |
| 2 | 23 |
| 3 | 9 |
| 5 | 14 |
| 6 | 28 |
| 8 | 8 |
| 9 | 26 |
| 11 | 7 |
| 12 | 12 |
Exercice 8
Affiche le titre des livres actuellement empruntés (emprunts sans date de retour).
| 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 : WHERE (filtres), JOIN, NULL, COALESCE.
Structure de la requête :
SELECT …
FROM … …
JOIN … … ON … = …
WHERE … IS NULLVoir la correction
SELECT b.title
FROM books b
JOIN loans l ON l.book_id = b.id
WHERE l.return_date IS NULL;Résultat attendu (3 lignes) :
| title |
|---|
| Harbor Lights |
| Night Garden |
| The Glass Hive |
Exercice 9
Avec une jointure externe, affiche le nom et le prix des produits qui n'ont jamais été commandés.
| 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 : WHERE (filtres), JOIN, NULL, COALESCE.
Structure de la requête :
SELECT …, …
FROM … …
LEFT JOIN … … ON … = …
WHERE … IS NULLVoir la correction
SELECT p.name, p.price
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.id
WHERE oi.product_id IS NULL;Résultat attendu (1 ligne) :
| name | price |
|---|---|
| Stapler | 9 |
Exercice 10
Avec COALESCE, affiche le titre de chaque tâche et sa date de fin, ou 'pending' si elle n'est pas terminée (done_on vide).
| 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 : NULL, COALESCE.
Structure de la requête :
SELECT …, COALESCE(…, …)
FROM …Voir la correction
SELECT title, COALESCE(done_on, 'pending')
FROM tasks;Résultat attendu (10 lignes) :
| title | COALESCE(done_on, 'pending') |
|---|---|
| Landing page | 2025-03-10 |
| Data model | 2025-03-20 |
| Login | pending |
| ETL job | 2025-04-02 |
| CI pipeline | 2025-03-15 |
| Dashboard | pending |
| Report | 2025-04-25 |
| API | pending |
| Monitoring | pending |
| Forecast | 2025-05-05 |
Exercice 11
Pour chaque tâche terminée, affiche son titre, le nom du projet et le nombre de jours entre sa date de fin et la date limite du projet (deadline − done_on).
| 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 | 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 |
Voir l’indice
Notions à utiliser : WHERE (filtres), JOIN, NULL, COALESCE, Texte et dates.
Structure de la requête :
SELECT …, …, CAST(JULIANDAY(…) - JULIANDAY(…) AS INTEGER)
FROM … …
JOIN … … ON … = …
WHERE … IS NOT NULLVoir la correction
SELECT t.title, p.name, CAST(julianday(p.deadline) - julianday(t.done_on) AS INTEGER)
FROM tasks t
JOIN projects p ON p.id = t.project_id
WHERE t.done_on IS NOT NULL;Résultat attendu (6 lignes) :
| title | name | CAST(julianday(p.deadline) - julianday(t.done_on) AS INTEGER) |
|---|---|---|
| Landing page | Atlas | 112 |
| Data model | Atlas | 102 |
| ETL job | Beacon | 43 |
| CI pipeline | Beacon | 61 |
| Report | Comet | 5 |
| Forecast | Delta | 87 |
Exercice 12
Pour chaque facture, affiche son id et son nombre de jours de retard : jours entre l'échéance et la date de paiement (ou le 2025-04-15 si elle n'est pas payée), et 0 s'il n'y a pas de retard. Utilise COALESCE.
| 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 |
Voir l’indice
Notions à utiliser : NULL, COALESCE, Texte et dates.
Structure de la requête :
SELECT …, MAX(…, CAST(JULIANDAY(COALESCE(…, …)) - JULIANDAY(…) AS INTEGER))
FROM …Voir la correction
SELECT id, MAX(0, CAST(julianday(COALESCE(paid_on, '2025-04-15')) - julianday(due) AS INTEGER))
FROM invoices;Résultat attendu (6 lignes) :
| id | MAX(0, CAST(julianday(COALESCE(paid_on, '2025-04-15')) - julianday(due) AS INTEGER)) |
|---|---|
| 1 | 0 |
| 2 | 30 |
| 3 | 0 |
| 4 | 6 |
| 5 | 0 |
| 6 | 6 |
Exercice 13
Affiche les clients qui ont au moins une facture impayée, sauf ceux qui ont déjà payé une facture en retard (paiement après l'échéance).
| 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 |
Voir l’indice
Notions à utiliser : WHERE (filtres), NULL, COALESCE, UNION, INTERSECT, EXCEPT.
Structure de la requête :
SELECT …
FROM …
WHERE … IS NULL
EXCEPT SELECT …
FROM …
WHERE … > …Voir la correction
SELECT client
FROM invoices
WHERE paid_on IS NULL
EXCEPT SELECT client
FROM invoices
WHERE paid_on > due;Résultat attendu (1 ligne) :
| client |
|---|
| Acme |
Exercice 14
Pour chaque projet qui a au moins 2 tâches terminées, affiche son nom, la première et la dernière date de fin, et le nombre de jours entre les deux.
| 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, NULL, COALESCE, Texte et dates.
Structure de la requête :
SELECT …, MIN(…), MAX(…), CAST(JULIANDAY(MAX(…)) - JULIANDAY(MIN(…)) AS INTEGER)
FROM … …
JOIN … … ON … = …
WHERE … IS NOT NULL
GROUP BY …
HAVING COUNT(*) >= …Voir la correction
SELECT p.name, MIN(t.done_on), MAX(t.done_on), CAST(julianday(MAX(t.done_on)) - julianday(MIN(t.done_on)) AS INTEGER)
FROM projects p
JOIN tasks t ON t.project_id = p.id
WHERE t.done_on IS NOT NULL
GROUP BY p.id
HAVING COUNT(*) >= 2;Résultat attendu (2 lignes) :
| name | MIN(t.done_on) | MAX(t.done_on) | CAST(julianday(MAX(t.done_on)) - julianday(MIN(t.done_on)) AS INTEGER) |
|---|---|---|---|
| Atlas | 2025-03-10 | 2025-03-20 | 10 |
| Beacon | 2025-03-15 | 2025-04-02 | 18 |
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é.