Le Boss de fin de
L'Ancien Monde

Vous pensez maîtriser les bases de données relationnelles ?

Prouvez-le avant de pouvoir accéder au NoSQL.

Avant le combat : votre arène

Tous les défis tournent sur un vrai jeu de données PostgreSQL : 50 clients, 200 000 achats, des fantômes et de la pollution.

Installation (une seule fois)
createdb -h localhost -U postgres sqlfun
psql -h localhost -U postgres -d sqlfun -f jeu-de-donnees-sql-fun.sql

Ou dans pgAdmin 4 : créer la base sqlfun, ouvrir le fichier dans l'outil de requête, puis Exécuter (F5).

📥 Télécharger jeu-de-donnees-sql-fun.sql

Activité 1 : L'Anomalie des Ventes

Votre base contient des millions de transactions. Le directeur exige de trouver tous les clients ayant dépensé plus que la moyenne globale, mais uniquement sur le dernier trimestre.

Mission au tableau

Concevez la structure de la requête. Pas le temps pour le hasard.

🛠️ Armes requises : GROUP BY, HAVING, et une Sous-requête scalaire.

Solution 1 : L'Anomalie des Ventes

La sous-requête renvoie une seule valeur, la moyenne des totaux par client : c'est ce qui la rend scalaire.

Solution
SELECT client_id, SUM(montant) AS total
FROM achats
WHERE date_achat BETWEEN '2026-07-01' AND '2026-09-30'
GROUP BY client_id
HAVING SUM(montant) > (
  SELECT AVG(total_client) FROM (
    SELECT SUM(montant) AS total_client FROM achats
    WHERE date_achat BETWEEN '2026-07-01' AND '2026-09-30'
    GROUP BY client_id) AS totaux);

💀 Mode hardcore : écrivez-la sans répéter le filtre de dates, avec un WITH totaux AS (...).

Activité 2 : Le Chasseur de Fantômes

La base est corrompue. Des factures existent pour des clients qui ont été supprimés du système.

Mission de nettoyage

Écrivez la logique pour isoler ces enregistrements fantômes afin de les purger.

🛠️ Armes requises : LEFT JOIN couplé à une condition IS NULL sur la table de droite.

Solution 2 : Le Chasseur de Fantômes

Le LEFT JOIN garde toutes les factures. Quand le client n'existe plus, ses colonnes reviennent à NULL : c'est la signature du fantôme.

Solution
SELECT factures.*
FROM factures
LEFT JOIN clients ON clients.id = factures.client_id
WHERE clients.id IS NULL;

Dans l'arène : 4 factures fantômes, celles des clients 51, 52 et 53.

💀 Mode hardcore : purgez-les avec un DELETE ... WHERE NOT EXISTS, puis empêchez leur retour : quelle contrainte manquait à la table ?

Activité 3 : La Fenêtre Temporelle

Le marketing veut envoyer une promotion ciblée en se basant uniquement sur le tout dernier achat de chaque client dans la table `achats`.

Mission d'analyse avancée

Un simple `GROUP BY` ne suffit pas pour ramener toutes les colonnes de la ligne la plus récente.

🛠️ Arme fatale : Les Fonctions de fenêtrage ROW_NUMBER() OVER(PARTITION BY ...)

Solution 3 : La Fenêtre Temporelle

ROW_NUMBER() numérote les achats de chaque client, du plus récent au plus ancien. On garde le numéro 1, avec toutes ses colonnes.

Solution
SELECT * FROM (
  SELECT achats.*,
         ROW_NUMBER() OVER (PARTITION BY client_id
                            ORDER BY date_achat DESC, id DESC) AS rang
  FROM achats) AS classement
WHERE rang = 1;

💀 Mode hardcore : retirez id DESC et remplacez ROW_NUMBER() par RANK() : pourquoi certains clients ont-ils soudain deux « derniers » achats ?

Activité 4 : Le Boss Lent

La table `achats` compte 200 000 lignes. Le service client cherche les achats du client 42 à chaque appel... et l'écran gèle.

Mission de chronométrage

Mesurez la requête, accélérez-la, puis mesurez encore. Le gain doit être prouvé par un chiffre, pas par une impression.

🛠️ Armes requises : EXPLAIN ANALYZE et CREATE INDEX.

Solution 4 : Le Boss Lent

Sans index, la base lit tout. Avec un index, elle va droit aux bonnes lignes.

Solution
EXPLAIN ANALYZE SELECT * FROM achats WHERE client_id = 42;
-- Seq Scan : 200 000 lignes lues, 196 004 jetées

CREATE INDEX achats_client_id_index ON achats (client_id);

EXPLAIN ANALYZE SELECT * FROM achats WHERE client_id = 42;
-- Bitmap Index Scan : seulement les 4 086 bonnes lignes

Mesuré sur notre machine de test : de 80 ms à 7 ms, environ 10 fois plus rapide.

💀 Mode hardcore : ouvrez le Simulateur des cubes transitoires : quel ordre de jointure fait descendre le plus gros cube ?

Activité 5 : La Fusion des Mondes

Les achats de 2024 dorment dans `achats_archive`. Le comptable veut le total de tout ce que la cliente 3 a dépensé depuis toujours.

Mission comptable

Réunissez les deux tables et faites la somme. Attention : la cliente 3 a acheté deux souris identiques le 24 décembre 2024.

🛠️ Armes requises : UNION ou UNION ALL ? Un seul des deux donne le bon montant.

Solution 5 : La Fusion des Mondes

UNION élimine les rangées identiques : une des deux souris disparaît. UNION ALL garde tout.

Solution
SELECT SUM(montant) FROM (
  SELECT client_id, montant, date_achat
  FROM achats_archive WHERE client_id = 3
  UNION ALL
  SELECT client_id, montant, date_achat
  FROM achats WHERE client_id = 3) AS tout;

Avec UNION ALL : 822 516,33 $. Avec UNION : 822 491,34 $. Il manque les 24,99 $ de la souris jumelle.

💀 Mode hardcore : quand UNION est-il le bon choix ? Listez les clients qui ont acheté au moins une fois, en 2024 ou après.

L'Épreuve Finale

Ces exercices n'étaient que l'échauffement de vos neurones.

Rendez-vous dans la Salle d'entraînement pour affronter Schemaverse et le SQL Murder Mystery.

Seuls les survivants du SQL accèderont au Big Data.

Flèches gauche/droite pour naviguer