Comprendre le mécanisme
Une jointure relie des lignes ; sa cardinalité peut multiplier un résultat avant agrégation. WHERE filtre les lignes avant GROUP BY, HAVING filtre les groupes après agrégation. Une fonction fenêtre calcule sur un ensemble lié tout en conservant les lignes d’origine. PARTITION BY organise les groupes de fenêtre et ORDER BY leur ordre logique ; il faut aussi expliciter les égalités lorsque le résultat dépend d’un classement déterministe.
Raisonner et vérifier
Une CTE structure une requête mais n’est pas automatiquement une optimisation magique. Définissez d’abord la granularité de sortie : une ligne par réservation, produit ou tenant. Testez avec doublons, groupes vides et valeurs null. NOT IN peut devenir inconnu en présence de null dans la sous-requête ; un NOT EXISTS correctement corrélé exprime souvent plus clairement l’absence recherchée.
Filtrer des lignes ou des groupes
La logique SQL distingue le filtre de lignes effectué par WHERE et les conditions appliquées aux groupes avec HAVING. Commencez par déterminer quelles lignes entrent dans l’agrégation, puis quelle propriété du groupe doit être vérifiée. Déplacer une condition sans analyser son niveau peut changer le résultat. Un jeu d’essai contenant plusieurs groupes, dont un vide de valeurs utiles, rend cette différence visible.
Understand this common mistake
WHERE filtre uniquement les agrégats déjà calculés.
HAVING sert notamment aux conditions sur groupes.
Une fenêtre conserve le contexte des lignes
Une fonction de fenêtre calcule une information sur un ensemble lié à chaque ligne sans nécessairement réduire ce résultat à une ligne par groupe. C’est utile pour afficher une réservation et son rang ou un cumul. Comparez le nombre et l’identité des lignes avant et après le calcul. Une fonction agrégée avec GROUP BY répond à un autre besoin et ne peut pas être substituée sans revoir le contrat.
Understand this common mistake
Toute fenêtre réduit nécessairement le résultat à une ligne par groupe.
Le modèle diffère d’un regroupement agrégé.
L’ordre appartient à chaque calcul concerné
L’ordre utilisé dans une fenêtre définit le sens d’un rang ou d’un cumul. Un ORDER BY final organise l’affichage du résultat, mais ne remplace pas automatiquement cet ordre de calcul. Si plusieurs événements ont la même date, ajoutez un départage pertinent comme un identifiant stable. Sans règle explicite sur les égalités, une sortie observée sur quelques lignes ne démontre pas une sélection reproductible.
Understand this common mistake
ORDER BY global remplace toujours tout ordre de fenêtre.
Les deux niveaux ont des responsabilités distinctes.
Lire le cadre de fenêtre
La frame détermine quelles lignes participent à certains calculs de fenêtre autour de la ligne courante. Un cumul et une valeur de fin de partition ne demandent pas forcément le même cadre. Lisez la définition complète avant de conclure depuis le nom de la fonction. Testez notamment des valeurs de tri égales : elles peuvent révéler une différence que des dates toutes distinctes ne montrent pas.
Understand this common mistake
Toute fenêtre utilise forcément toute la partition pour tout calcul.
La frame doit correspondre au besoin.
Préserver une absence avec LEFT JOIN
Une jointure externe peut conserver une ligne de gauche sans correspondance à droite. Ajouter ensuite dans WHERE une condition qui rejette la valeur nulle de droite peut supprimer cette ligne conservée. Déterminez si le filtre appartient à la condition de jointure ou au résultat final selon le besoin. Vérifiez les cas sans correspondance, avec une correspondance et avec plusieurs correspondances avant de valider le total obtenu.
Understand this common mistake
Une condition restrictive à droite dans WHERE préserve toujours cet effet.
Le filtre peut éliminer les lignes complétées par null.
La logique de NULL change les exclusions
Une comparaison ordinaire avec NULL peut produire un résultat inconnu, que WHERE ne conserve pas comme vrai. Cela explique le piège de NOT IN lorsqu’une sous-requête contient NULL : l’exclusion peut éliminer des lignes attendues. Étudiez les valeurs une par une et choisissez une formulation conforme au contrat, souvent une recherche d’absence avec NOT EXISTS. Filtrer les nulls est une autre décision qui doit être explicitement justifiée.
Understand this common mistake
Comparer NULL avec égal donne toujours vrai pour NULL.
La logique SQL comporte une valeur inconnue.
Stabiliser la sélection avec LIMIT
LIMIT borne le nombre de lignes renvoyées, sans créer leur ordre. Un tri par date seule peut encore laisser une ambiguïté lorsque des dates sont égales. Définissez un ordre total pertinent et testez ces égalités. Pour une pagination concurrente, documentez aussi l’effet des insertions entre appels : une sélection stable à un instant ne promet pas automatiquement un parcours identique sur une base qui évolue.
Understand this common mistake
LIMIT sans ORDER BY garantit les mêmes premières lignes.
Le plan n’impose pas cette stabilité.
La cardinalité des jointures influence les agrégats
Une ligne de gauche peut correspondre à plusieurs lignes de droite. Si vous additionnez ensuite une valeur de gauche, elle peut être comptée plusieurs fois. Dessinez la cardinalité attendue et vérifiez le nombre de lignes après la jointure avant d’interpréter un total. Un résultat numérique plausible n’est pas une preuve de justesse ; préparez un cas où une seule référence possède plusieurs dépendances.
Understand this common mistake
Joindre deux tables conserve toujours la cardinalité de gauche.
Les correspondances multiples affectent le résultat.
Structurer avec un CTE sans promettre la vitesse
Un CTE peut rendre une requête plus lisible en nommant une étape. Il ne garantit pas une amélioration de performance universelle. La version du moteur, la forme de la requête et les options influencent le plan. Validez d’abord le résultat, puis mesurez le comportement sur des données représentatives. Une refactorisation de lisibilité doit conserver les prédicats, cardinalités et conventions de NULL attendus.
Understand this common mistake
Tout CTE accélère nécessairement toute requête.
La performance doit être mesurée sur le plan réel.
Un jeu d’essai qui discrimine réellement
Un petit jeu utile doit inclure les cas qui distinguent deux interprétations possibles : doublons, égalités, lignes sans correspondance, valeurs nulles et limites de sélection. Écrivez à la main le résultat attendu avant d’exécuter la requête. Si deux requêtes incorrectement supposées équivalentes donnent le même résultat sur votre fixture, ajoutez un cas qui exerce la différence ; multiplier des lignes identiques n’améliore pas cette preuve.
Understand this common mistake
Un seul groupe nominal prouve toutes les fenêtres.
Les frontières révèlent des comportements supplémentaires.
Atelier guidé : lire, expliquer et vérifier le code
WITH lab(v) AS (VALUES (1), (NULL), (3))
SELECT count(*) AS row_count, count(v) AS value_count, sum(v) AS total
FROM lab; La fixture de l’atelier comporte trois lignes, dont une valeur nulle. count(*) compte les lignes et donne trois. count(v) compte ici les valeurs non nulles et donne deux. sum(v) additionne les valeurs présentes, un et trois, ce qui donne quatre. Les trois colonnes répondent donc à des questions différentes sur le même ensemble.
Remplacez ensuite toutes les valeurs par NULL et étudiez le contrat de chaque agrégat ; ne généralisez pas le résultat du premier jeu. Ajoutez un groupe supplémentaire et comparez WHERE avec HAVING. Cet exercice construit une méthode de lecture : déterminer les lignes conservées, comprendre la règle de NULL, puis calculer chaque agrégat selon sa définition. Le bon résultat ne doit pas être deviné depuis la taille de la table seule.
SQL et NULL : pourquoi NOT IN peut ne rien retourner
SELECT id FROM invoices
WHERE id NOT IN (SELECT invoice_id FROM blocked); SQL utilise une logique à trois valeurs : TRUE, FALSE et UNKNOWN. WHERE conserve seulement TRUE. Avec l’ensemble interdit {2,NULL}, l’expression 2 NOT IN retourne FALSE ; pour 1 ou 3, l’absence de comparaison déterminée avec NULL empêche d’établir TRUE et le résultat est UNKNOWN.
Dans l’exercice, aucune des factures 1, 2 et 3 n’est donc retenue. La présence d’une seule valeur NULL dans la sous-requête change le raisonnement habituel d’une liste de valeurs toutes connues. Il ne s’agit ni d’un hasard du plan ni d’un problème de casse.
Pour une exclusion par relation, un NOT EXISTS corrélé exprime souvent plus clairement l’absence d’une ligne correspondante. Une autre solution peut filtrer les NULL si cela respecte le contrat. Testez explicitement sous-requête vide, valeur correspondante et présence de NULL, puis choisissez la forme dont la sémantique correspond à vos données.
Points de vigilance : Pour 1 et 3, la présence de NULL rend le prédicat inconnu, donc rejeté par WHERE. L’identifiant 2 est exclu par sa correspondance ; les autres ont un résultat inconnu. 2 produit FALSE ; 1 et 3 produisent UNKNOWN. Aucun résultat ne vaut TRUE. NULL est autorisé ici ; il change la logique à trois valeurs sans provoquer d’erreur.
Reference documentation ↗Worked example
SELECT tenant_id, id, created_at,
row_number() OVER (
PARTITION BY tenant_id
ORDER BY created_at DESC, id DESC
) AS position
FROM lab_reservations;Chaque réservation conserve sa ligne avec un numéro à l’intérieur de son tenant. id tranche les égalités de date pour rendre le classement déterministe.
Practical exercise
Retournez les trois réservations les plus récentes de chaque tenant sans agréger les autres colonnes.
View a suggested solution
Calculez row_number dans une sous-requête ou CTE, puis filtrez position <= 3 à l’extérieur. Testez plusieurs tenants et deux dates identiques.
Verification checklist
- Annoncer la granularité de sortie.
- Tester les multiplications de jointure.
- Définir les égalités de classement.
Knowledge check
Related lesson: Jointures, agrégats, CTE et fenêtres ↗Module validation
101 questions, local practice target of 75%. Suggested duration of 150 minutes, with no enforced deadline. Questions and choices are shuffled; corrections appear after submission. The quick lesson check and the final course exam remain separate exercises.
Start or resume the 101 questions →This records your own progress and does not certify mastery. Complete the exercise before moving on.
