Guide pratique · Niveau intermédiaire · Lecture : ~15 min Pratique sur modai-lab.com
Le problĂšme que personne ne voit venir Une erreur de syntaxe, SQL te la signale. Un LEFT JOIN silencieux, il lâavale sans broncher â et te retourne des chiffres qui ont lâair parfaitement vrais.
Sur 41 requĂȘtes SQL auditĂ©es, 31 contenaient ce pattern : un LEFT JOIN transformĂ© en INNER JOIN par un WHERE mal placĂ©. Aucune erreur. Aucun warning. Juste des dĂ©cisions prises sur du vide.
Ce guide couvre :
Les 6 patterns qui détruisent tes LEFT JOINs sans alerte
Les requĂȘtes de diagnostic Ă lancer sur ta base aujourdâhui
La checklist dâaudit pour valider nâimporte quelle requĂȘte en 2 minutes
3 cas réels avec les écarts de données corrigés
Comprendre le mĂ©canisme Avant dâaller dans les patterns, voici pourquoi ça arrive.
Un LEFT JOIN retourne toutes les lignes de la table de gauche , mĂȘme sans correspondance dans la table de droite. Les colonnes de droite sont alors NULL.
-- Ce que tu veux : tous les clients, mĂȘme sans commande
SELECT u.id, u.nom, o.montant
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
left-join-silencieux-bug-analyses · 01 u.id
u.nom
o.montant
1
Alice
150
2
Bob
NULL â client sans commande
3
Claire
90
Le piÚge : dÚs que tu filtres sur o.montant dans un WHERE, les lignes NULL sont éliminées. Bob disparaßt. Silencieusement.
đ§Ș Test de niveau â Avant de commencer Sais-tu dĂ©jĂ repĂ©rer un LEFT JOIN silencieux ? Teste ton niveau SQL en conditions rĂ©elles et reçois ton score instantanĂ©ment. đ Faire le test sur modai-lab.com Exercices interactifs · RĂ©sultat en direct · Parcours personnalisĂ© selon ton niveau
PARTIE 1 â Les 6 patterns destructeurs đŽ Pattern 1 â WHERE sur la table de droite (le classique) Le plus frĂ©quent. PrĂ©sent dans 28 des 31 requĂȘtes auditĂ©es.
-- â INNER JOIN dĂ©guisĂ©
SELECT u.id, u.nom, o.montant
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.statut = 'livrĂ©' ; -- Ă©limine tous les NULL â INNER JOIN
left-join-silencieux-bug-analyses · 02 · Le plus frĂ©quent. PrĂ©sent dans 28 des 31 requĂȘtes auditĂ©es. Le WHERE o.statut = 'livrĂ©' est Ă©valuĂ© APRĂS le JOIN. Les clients sans commande ont o.statut = NULL. NULL = 'livrĂ©' â UNKNOWN â ligne exclue.
-- â
Filtre déplacé dans le ON
SELECT u.id, u.nom, o.montant
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
AND o.statut = 'livré' ; -- filtre AVANT le JOIN, les NULL sont préservés
left-join-silencieux-bug-analyses · 03 Résultat avant correction : 1 240 clients analysés
Résultat aprÚs correction : 4 830 clients (les 3 590 sans commande livrée étaient invisibles)
đĄ RĂšgle dâor : tout filtre sur la table de droite va dans le ON, pas dans le WHERE.
đŽ Pattern 2 â IS NOT NULL implicite via comparaison -- â La comparaison Ă©limine les NULL
SELECT u.nom, o.montant
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.montant > ; -- NULL > 0 â UNKNOWN â exclu
left-join-silencieux-bug-analyses · 04 -- â
Gérer les NULL explicitement
SELECT u.nom, COALESCE (o.montant, ) AS montant
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.montant >
OR o.montant IS NULL ; -- garder les clients sans commande
Â
-- Ou mieux : filtrer dans le ON
SELECT u.nom, COALESCE (o.montant, ) AS montant
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
AND o.montant > ;
left-join-silencieux-bug-analyses · 05 đŽ Pattern 3 â BETWEEN sur la table de droite -- â BETWEEN Ă©limine les NULL
SELECT u.nom, o.created_at, o.montant
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.created_at BETWEEN '2024-01-01' AND '2024-12-31' ;
left-join-silencieux-bug-analyses · 06 -- â
Filtre dans le ON
SELECT u.nom, o.created_at, o.montant
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
AND o.created_at BETWEEN '2024-01-01' AND '2024-12-31' ;
left-join-silencieux-bug-analyses · 07 đŽ Pattern 4 â NOT IN avec sous-requĂȘte sur la table de droite -- â Ălimine les lignes dont o.statut est NULL
SELECT u.nom
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.statut NOT IN ('annulé' , 'remboursé' );
left-join-silencieux-bug-analyses · 08 -- â
Gérer les NULL explicitement
SELECT u.nom
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.statut IS NULL
OR o.statut NOT IN ('annulé' , 'remboursé' );
left-join-silencieux-bug-analyses · 09 đŽ Pattern 5 â ChaĂźne de LEFT JOINs avec filtre au milieu Câest le plus vicieux. Le filtre sur la deuxiĂšme table convertit tout en INNER JOIN pour les tables suivantes.
-- â Le WHERE sur orders annule les LEFT JOINs suivants
SELECT u.nom, o.order_id, s.statut_livraison
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
LEFT JOIN shipments s ON o.order_id = s.order_id
WHERE o.created_at > '2024-01-01' ; -- détruit les deux LEFT JOINs
left-join-silencieux-bug-analyses · 10 -- â
Tous les filtres sur les tables de droite dans leur ON
SELECT u.nom, o.order_id, s.statut_livraison
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
AND o.created_at > '2024-01-01'
LEFT JOIN shipments s
ON o.order_id = s.order_id;
left-join-silencieux-bug-analyses · 11 đŽ Pattern 6 â HAVING sur agrĂ©gat de la table de droite -- â Les clients avec COUNT = 0 sont exclus par HAVING
SELECT u.nom, COUNT (o.id) AS nb_commandes
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.nom
HAVING COUNT (o.id) >= ; -- élimine les clients sans commande = INNER JOIN
left-join-silencieux-bug-analyses · 12 -- â
Si tu veux tous les clients avec leur nombre de commandes
SELECT u.nom, COUNT (o.id) AS nb_commandes
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.nom; -- pas de HAVING, ou HAVING COUNT(*) >= 0
Â
-- Si tu veux vraiment filtrer, sois explicite sur l'intention :
SELECT u.nom, COUNT (o.id) AS nb_commandes
FROM users u
INNER JOIN orders o ON u.id = o.user_id -- assume explicitement que tu veux les actifs
GROUP BY u.id, u.nom
HAVING COUNT (o.id) >= ;
left-join-silencieux-bug-analyses · 13 đĄ Si ton intention est de filtrer uniquement les utilisateurs ayant des commandes, utilise un INNER JOIN explicite. Le code dit alors exactement ce quâil fait.
đ§Ș Test intermĂ©diaire â Les patterns, tu les vois ? Tu viens de lire les 6 patterns. Est-ce que tu les reconnais dans une vraie requĂȘte ? Modai Lab te soumet des requĂȘtes rĂ©elles : Ă toi de repĂ©rer le LEFT JOIN silencieux en moins de 2 minutes. đ Tester mes rĂ©flexes sur modai-lab.com Score immĂ©diat · Explication de chaque erreur · Niveau calibrĂ© automatiquement
PARTIE 2 â RequĂȘtes de diagnostic Ă lancer aujourdâhui Avant de corriger quoi que ce soit, mesure lâimpact rĂ©el sur ta base.
Diagnostic 1 â DĂ©tecter les LEFT JOINs dĂ©guisĂ©s en INNER JOIN -- Compte combien de lignes sont exclues par ton filtre actuel
-- RequĂȘte originale (probablement fausse)
SELECT COUNT (*) AS avec_filtre_where
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.statut = 'livré' ;
Â
-- RequĂȘte corrigĂ©e (filtre dans le ON)
SELECT COUNT (*) AS avec_filtre_on
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
AND o.statut = 'livré' ;
Â
-- Si les deux nombres diffĂšrent : tu avais un LEFT JOIN silencieux.
-- L'écart = le nombre de clients que tu ne voyais pas.
left-join-silencieux-bug-analyses · 14 Diagnostic 2 â Identifier les utilisateurs manquants -- Quels utilisateurs disparaissaient avec le mauvais filtre ?
WITH
avec_where AS (
SELECT DISTINCT u.id
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.statut = 'livré'
),
avec_on AS (
SELECT DISTINCT u.id
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
AND o.statut = 'livré'
)
SELECT a.id, u.nom, u.email
FROM avec_on a
LEFT JOIN avec_where w ON a.id = w.id
JOIN users u ON a.id = u.id
WHERE w.id IS NULL ; -- ces utilisateurs Ă©taient invisibles dans ta requĂȘte
left-join-silencieux-bug-analyses · 15 Diagnostic 3 â VĂ©rifier le ratio de lignes NULL aprĂšs un LEFT JOIN -- Si ce ratio est 0%, tu as probablement un INNER JOIN dĂ©guisĂ©
SELECT
COUNT (*) AS total_lignes,
COUNT (o.id) AS avec_correspondance,
COUNT (*) - COUNT (o.id) AS sans_correspondance,
ROUND(. * (COUNT (*) - COUNT (o.id)) / COUNT (*), ) AS pct_null
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
left-join-silencieux-bug-analyses · 16 đĄ Si pct_null = 0 sur une jointure LEFT JOIN, posez-vous la question : est-ce vraiment logique que 100% des utilisateurs aient une commande ? Si non, un filtre a probablement Ă©liminĂ© les NULL quelque part.
Diagnostic 4 â Scanner tes requĂȘtes pour les patterns dangereux -- PostgreSQL : inspecter les requĂȘtes longues rĂ©centes
-- (nécessite pg_stat_statements)
SELECT
query,
calls,
mean_exec_time
FROM pg_stat_statements
WHERE query ILIKE '%LEFT JOIN%'
AND query ILIKE '%WHERE%'
ORDER BY calls DESC
LIMIT ;
-- Revue manuelle : cherche les WHERE sur des colonnes de la table de droite
left-join-silencieux-bug-analyses · 17 Diagnostic 5 â Comparer les mĂ©triques avant/aprĂšs correction -- Template de validation : toujours lancer les deux et comparer
WITH
metrique_avant AS (
SELECT
'AVANT (WHERE)' AS version,
COUNT (DISTINCT u.id) AS nb_users,
SUM (COALESCE (o.montant, )) AS ca_total,
AVG (COALESCE (o.montant, )) AS panier_moyen
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.created_at >= '2024-01-01' -- filtre dangereux
),
metrique_apres AS (
SELECT
'APRĂS (ON)' AS version,
COUNT (DISTINCT u.id) AS nb_users,
SUM (COALESCE (o.montant, )) AS ca_total,
AVG (COALESCE (o.montant, )) AS panier_moyen
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
AND o.created_at >= '2024-01-01' -- filtre corrigé
)
SELECT * FROM metrique_avant
UNION ALL
SELECT * FROM metrique_apres;
left-join-silencieux-bug-analyses · 18 đ§Ș Test diagnostic â Et sur ta propre base ? Les requĂȘtes de diagnostic, câest bien. Savoir les Ă©crire sans les copier, câest mieux. Modai Lab te met en situation : un dataset rĂ©el, une requĂȘte cassĂ©e, un rĂ©sultat Ă corriger. đ Pratiquer le diagnostic SQL sur modai-lab.com Datasets rĂ©alistes · Feedback ligne par ligne · Progression mesurĂ©e
PARTIE 3 â 3 cas rĂ©els avec Ă©carts corrigĂ©s Cas rĂ©el 1 â Rapport dâactivation utilisateurs Contexte : Une Ă©quipe produit mesurait le taux dâactivation (% dâutilisateurs ayant complĂ©tĂ© une action dans les 7 jours aprĂšs inscription).
-- â RequĂȘte en production
SELECT
DATE_TRUNC('week' , u.created_at) AS semaine,
COUNT (DISTINCT u.id) AS inscrits,
COUNT (DISTINCT a.user_id) AS actives,
ROUND(. * COUNT (DISTINCT a.user_id) / COUNT (DISTINCT u.id), ) AS taux_activation
FROM users u
LEFT JOIN actions a
ON u.id = a.user_id
WHERE a.created_at <= u.created_at + INTERVAL '7 days' ; -- élimine les NULL
left-join-silencieux-bug-analyses · 19 RĂ©sultat faux : taux dâactivation = 78%
-- â
RequĂȘte corrigĂ©e
SELECT
DATE_TRUNC('week' , u.created_at) AS semaine,
COUNT (DISTINCT u.id) AS inscrits,
COUNT (DISTINCT a.user_id) AS actives,
ROUND(. * COUNT (DISTINCT a.user_id) / COUNT (DISTINCT u.id), ) AS taux_activation
FROM users u
LEFT JOIN actions a
ON u.id = a.user_id
AND a.created_at <= u.created_at + INTERVAL '7 days' ; -- filtre dans le ON
left-join-silencieux-bug-analyses · 20 RĂ©sultat rĂ©el : taux dâactivation = 34%
Ăcart : â44 points. La moitiĂ© des utilisateurs inactifs Ă©taient invisibles.
Cas rĂ©el 2 â Dashboard revenus par segment client Contexte : Un rapport de CA mensuel par segment (Premium, Standard, Freemium).
-- â RequĂȘte en production
SELECT
u.segment,
DATE_TRUNC('month' , o.created_at) AS mois,
SUM (o.montant) AS ca,
COUNT (DISTINCT u.id) AS clients_actifs
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.created_at >= '2024-01-01'
AND o.statut != 'annulé' ;
left-join-silencieux-bug-analyses · 21 -- â
RequĂȘte corrigĂ©e
SELECT
u.segment,
DATE_TRUNC('month' , o.created_at) AS mois,
SUM (o.montant) AS ca,
COUNT (DISTINCT u.id) AS clients_actifs,
COUNT (DISTINCT CASE WHEN o.id IS NOT NULL THEN u.id END ) AS clients_avec_commande,
COUNT (DISTINCT CASE WHEN o.id IS NULL THEN u.id END ) AS clients_sans_commande
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
AND o.created_at >= '2024-01-01'
AND o.statut != 'annulé'
GROUP BY u.segment, DATE_TRUNC('month' , o.created_at);
left-join-silencieux-bug-analyses · 22 Ăcart dĂ©couvert : 2 300 clients Premium nâavaient passĂ© aucune commande en 2024. La requĂȘte dâorigine les ignorait complĂštement, faussant le taux de churn calculĂ© en aval.
Cas rĂ©el 3 â Analyse de cohorte rĂ©tention Contexte : Calcul du taux de rĂ©tention Ă J+30 par cohorte dâacquisition.
-- â RequĂȘte en production
WITH cohortes AS (
SELECT user_id, DATE_TRUNC('month' , MIN (created_at)) AS mois_acq
FROM orders GROUP BY user_id
)
SELECT
c.mois_acq,
COUNT (DISTINCT c.user_id) AS cohorte,
COUNT (DISTINCT o.user_id) AS retenus,
ROUND(. * COUNT (DISTINCT o.user_id) / COUNT (DISTINCT c.user_id), ) AS retention
FROM cohortes c
LEFT JOIN orders o
ON c.user_id = o.user_id
WHERE o.created_at >= c.mois_acq + INTERVAL '30 days' -- silencieux
AND o.created_at < c.mois_acq + INTERVAL '60 days' ;
left-join-silencieux-bug-analyses · 23 -- â
RequĂȘte corrigĂ©e
WITH cohortes AS (
SELECT user_id, DATE_TRUNC('month' , MIN (created_at)) AS mois_acq
FROM orders GROUP BY user_id
)
SELECT
c.mois_acq,
COUNT (DISTINCT c.user_id) AS cohorte,
COUNT (DISTINCT o.user_id) AS retenus,
ROUND(. * COUNT (DISTINCT o.user_id) / COUNT (DISTINCT c.user_id), ) AS retention
FROM cohortes c
LEFT JOIN orders o
ON c.user_id = o.user_id
AND o.created_at >= c.mois_acq + INTERVAL '30 days'
AND o.created_at < c.mois_acq + INTERVAL '60 days' ;
left-join-silencieux-bug-analyses · 24 Ăcart dĂ©couvert : rĂ©tention rĂ©elle = 18% contre 67% calculĂ©e. Le dashboard de rĂ©tention ne comptait que les utilisateurs qui avaient re-commandĂ© â en excluant silencieusement tous ceux qui nâavaient pas.
đ§Ș Test avancĂ© â Reproduis ces cas sur de vraies donnĂ©es Les 3 cas rĂ©els tâont montrĂ© des Ă©carts de â44 pts, â49 pts de rĂ©tention, 2 300 clients invisibles. Sur Modai Lab, des exercices reproduisent exactement ces scĂ©narios â avec un jeu de donnĂ©es complet et un rĂ©sultat attendu Ă atteindre. đ Reproduire ces cas sur modai-lab.com ScĂ©narios data rĂ©els · Avant/aprĂšs interactif · Validation automatique
PARTIE 4 â Checklist dâaudit en 2 minutes Utilise cette checklist sur nâimporte quelle requĂȘte avec un LEFT JOIN.
Ătape 1 â Identifier les tables de droite Pour chaque LEFT JOIN de ta requĂȘte :
â Quelle est la table de droite ?
â Quelles sont ses colonnes ?
left-join-silencieux-bug-analyses · 25 Ătape 2 â Scanner le WHERE -- Cherche dans ton WHERE :
-- â
OK : filtre sur la table de GAUCHE
WHERE u.statut = 'actif'
Â
-- â
OK : IS NULL explicite
WHERE o.id IS NULL
Â
-- âDANGER : comparaison sur colonne de la table de DROITE
WHERE o.statut = 'livré'
WHERE o.montant >
WHERE o.created_at > '2024-01-01'
WHERE o.statut NOT IN ('annulé' )
WHERE o.colonne BETWEEN x AND y
left-join-silencieux-bug-analyses · 26 Ătape 3 â VĂ©rifier les NULL aprĂšs JOIN -- Lance cette requĂȘte de contrĂŽle
SELECT
COUNT (*) AS total,
COUNT (o.id) AS avec_match,
COUNT (*) - COUNT (o.id) AS sans_match
FROM table_gauche u
LEFT JOIN table_droite o ON u.id = o.user_id;
Â
-- Si sans_match = 0 â suspect. Est-ce logique dans ton contexte ?
left-join-silencieux-bug-analyses · 27 Ătape 4 â Comparer les comptages -- Avant toute correction en prod, mesure l'Ă©cart
SELECT 'version_actuelle' AS v, COUNT (DISTINCT u.id) AS n
FROM ... [ta requĂȘte actuelle]
UNION ALL
SELECT 'version_corrigee' AS v, COUNT (DISTINCT u.id) AS n
FROM ... [ta requĂȘte corrigĂ©e]
left-join-silencieux-bug-analyses · 28 Ătape 5 â Valider lâintention Pose-toi la question avant de âcorrigerâ :
Question
Réponse attendue
Type de JOIN
Je veux TOUS les users, mĂȘme sans commande ?
Oui
LEFT JOIN + filtre dans ON
Je veux uniquement les users avec commande ?
Oui
INNER JOIN explicite
Je veux les users SANS commande uniquement ?
Oui
LEFT JOIN + WHERE IS NULL
đĄ Un INNER JOIN explicite vaut mieux quâun LEFT JOIN avec WHERE qui fait un INNER JOIN en secret. Le code doit dire ce quâil fait.
đ§Ș Test final â Valide tes acquis en conditions rĂ©elles Tu as les patterns, les diagnostics, les cas rĂ©els, la checklist. Il ne reste plus quâune chose : Ă©crire les requĂȘtes toi-mĂȘme sous contrainte de temps. Câest exactement ce que Modai Lab simule â les mĂȘmes conditions quâen entretien data. đ Valider mes acquis sur modai-lab.com Mode entretien · Timer · Score et feedback dĂ©taillĂ© Ă la fin
RĂ©capitulatif â Les 6 patterns #
Pattern
Fréquence
Impact
1
WHERE sur colonne de la table de droite
âââââ
Critique
2
Comparaison numérique qui élimine NULL
ââââ
Critique
3
BETWEEN sur la table de droite
âââ
ĂlevĂ©
4
NOT IN sur la table de droite
âââ
ĂlevĂ©
5
ChaĂźne de LEFT JOINs avec WHERE au milieu
ââââ
Critique
6
HAVING sur agrégat de la table de droite
âââ
ĂlevĂ©
â
Checklist rapide (Ă imprimer) â Tous les filtres sur les tables de droite sont dans le ON, pas dans le WHERE
â Jâai vĂ©rifiĂ© que le ratio de NULL aprĂšs JOIN est cohĂ©rent avec la rĂ©alitĂ©
â Jâai comparĂ© COUNT avant et aprĂšs la correction
â Les INNER JOINs implicites ont Ă©tĂ© remplacĂ©s par des INNER JOINs explicites
â Les BETWEEN et NOT IN sur tables de droite sont dans le ON
â Les chaĂźnes de LEFT JOINs nâont aucun WHERE sur les tables intermĂ©diaires
đ SâentraĂźner sur Modai Lab Ces patterns, tu ne les retiendras vraiment quâen les pratiquant sur des cas rĂ©els.
modai-lab.com â Exercices SQL data rĂ©els · Feedback immĂ©diat · Parcours entretien
Modai Lab propose des exercices spĂ©cifiquement construits autour des piĂšges SQL comme celui-ci : des datasets rĂ©alistes, des requĂȘtes Ă dĂ©boguer, et une correction expliquĂ©e pour chaque cas.
Guide offert suite au post LinkedIn · EntraĂźnez-vous sur modai-lab.com · Partagez librement â»ïž