Réécrire les sous-requêtes corrélées avec des jointures
Transformer une logique corrélée en jointures ou en fonctions de fenêtre pour améliorer les performances
Réécrire les sous-requêtes corrélées avec des jointures est une leçon Coding Interview Prep gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Coding Interview Prep, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Coding Interview Prep comprend 4 leçons au total.
Pourquoi réécrire
Les sous-requêtes corrélées sont lisibles, mais peuvent être lentes : la requête interne peut s’exécuter une fois par ligne externe. Les personnes qui vous interrogent vous demandent souvent d’en réécrire une sous la forme d’une jointure ou d’une fonction de fenêtre pour améliorer les performances.
Le but est d’obtenir le même résultat en parcourant les données une seule fois plutôt qu’en répétant les analyses internes.
Connaître deux ou trois modèles de réécriture, et savoir quand chacun préserve la justesse du résultat, est une compétence essentielle de niveau intermédiaire.
Modèle 1 : EXISTS vers INNER JOIN
Un EXISTS corrélé qui vérifie l’existence d’au moins une correspondance peut souvent devenir une INNER JOIN.
Mais attention : une jointure peut produire des lignes externes en double si plusieurs lignes internes correspondent. Ajoutez DISTINCT ou effectuez une agrégation pour retrouver une ligne par clé externe.
-- Correlated EXISTS
SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id);
-- Join rewrite (DISTINCT avoids dupes from fan-out)
SELECT DISTINCT c.customer_id, c.name
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id;Piège de la multiplication des lignes
L’erreur de réécriture la plus courante consiste à oublier la multiplication des lignes. EXISTS renvoie chaque client une seule fois, quel que soit le nombre de commandes qu’il possède. Une jointure naïve renvoie une ligne par commande, ce qui gonfle les décomptes.
Si une étape en aval effectue un COUNT(*) ou un SUM(amount) sur ce résultat joint sans regroupement approprié, les nombres seront incorrects.
Demandez-vous toujours : la jointure peut-elle multiplier les lignes ? Si oui, utilisez DISTINCT ou un GROUP BY pour les regrouper à nouveau.
Modèle 2 : NOT EXISTS vers LEFT JOIN / IS NULL
La réécriture en anti-jointure est un modèle incontournable des entretiens. Un NOT EXISTS corrélé devient une LEFT JOIN dont le côté droit est NULL.
Les lignes externes sans correspondance reçoivent des valeurs NULL du côté droit ; filtrer ces valeurs NULL conserve exactement les lignes sans correspondance.
-- Correlated NOT EXISTS
SELECT c.customer_id FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id);
-- LEFT JOIN / IS NULL rewrite
SELECT c.customer_id
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.customer_id IS NULL;Choisir une colonne non NULL à tester
Dans la réécriture LEFT JOIN / IS NULL, testez une colonne du côté droit qui n’est jamais NULL lorsqu’une correspondance réelle existe, idéalement la clé de jointure ou la clé primaire.
Si vous testez une colonne pouvant être NULL, vous ne pouvez pas distinguer une véritable absence de correspondance (aucune ligne) d’une ligne correspondante qui contient simplement NULL à cet endroit. Cette erreur renvoie des lignes incorrectes.
Utiliser la clé de jointure (ici o.customer_id) ou o.order_id garantit que NULL signifie « aucune ligne correspondante ».
Modèle 3 : agrégat scalaire vers JOIN + GROUP BY
Un agrégat corrélé dans SELECT peut devenir une jointure avec une sous-requête regroupée (une table dérivée).
Calculez une fois l’agrégat pour chaque groupe, puis joignez-le aux lignes détaillées. La requête interne s’exécute une seule fois au lieu de s’exécuter pour chaque ligne.
-- Correlated scalar aggregate
SELECT e1.name,
(SELECT MAX(e2.salary) FROM employees e2
WHERE e2.dept_id = e1.dept_id) AS dept_max
FROM employees e1;
-- Join + GROUP BY rewrite
SELECT e.name, m.dept_max
FROM employees e
JOIN (SELECT dept_id, MAX(salary) AS dept_max
FROM employees GROUP BY dept_id) m
ON m.dept_id = e.dept_id;Modèle 4 : réécriture avec une fonction de fenêtre
Souvent, la réécriture la plus claire utilise une fonction de fenêtre. MAX(salary) OVER (PARTITION BY dept_id) remplace entièrement l’agrégat corrélé, sans jointure nécessaire.
Elle calcule la valeur du groupe en un seul parcours et conserve chaque ligne détaillée. C’est généralement la réponse que les personnes qui vous interrogent souhaitent le plus voir pour les requêtes analytiques.
SELECT name,
dept_id,
salary,
MAX(salary) OVER (PARTITION BY dept_id) AS dept_max
FROM employees;Réécriture des N premiers éléments par groupe
Une sous-requête corrélée qui sélectionne la première ligne de chaque groupe (salary = MAX per dept) se réécrit élégamment avec ROW_NUMBER.
Partitionnez par groupe, triez selon la mesure et gardez le rang 1. Utilisez RANK à la place si vous souhaitez conserver toutes les premières lignes ex æquo.
SELECT name, dept_id, salary
FROM (
SELECT name, dept_id, salary,
ROW_NUMBER() OVER (PARTITION BY dept_id
ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn = 1;Quand ne pas réécrire
La réécriture n’est pas toujours avantageuse. Conservez la sous-requête corrélée lorsque :
- l’ensemble externe est très petit, de sorte que le coût par ligne est négligeable ;
- la colonne corrélée est bien indexée et l’optimiseur la transforme déjà en semi-jointure efficace ;
- la lisibilité compte davantage qu’une micro-optimisation dans du code maintenu.
Les optimiseurs modernes transforment souvent automatiquement EXISTS en semi-jointure. Dites que vous mesureriez avec EXPLAIN avant de supposer qu’une réécriture est utile.
Vérifier l’équivalence
Après toute réécriture, vérifiez qu’elle renvoie les mêmes lignes et la même cardinalité que la version originale.
- Vérifiez que le nombre de lignes concorde.
- Vérifiez qu’aucun doublon n’a été créé par la multiplication des lignes d’une jointure.
- Vérifiez que les cas limites liés à NULL et aux groupes vides se comportent toujours correctement.
Une méthode rapide consiste à exécuter les deux versions et à leur appliquer EXCEPT dans les deux sens ; un résultat vide signifie qu’elles concordent. Les personnes qui vous interrogent apprécient que vous vérifiiez plutôt que de supposer.
SELECT customer_id FROM query_a
EXCEPT
SELECT customer_id FROM query_b;
-- and the reverse; both empty => equivalentRéécrire IN en JOIN
Une sous-requête IN non corrélée peut souvent être réécrite sous la forme d’une jointure, mais le même avertissement concernant la multiplication des lignes s’applique. IN élimine les doublons d’appartenance ; une jointure ne le fait pas.
Si la liste interne contient des clés en double, la jointure répète les lignes externes. Utilisez DISTINCT du côté interne ou sur le résultat final pour retrouver la sémantique de IN.
-- IN subquery
SELECT c.name FROM customers c
WHERE c.customer_id IN (SELECT o.customer_id FROM orders o);
-- Join rewrite, de-duplicated to match IN
SELECT DISTINCT c.name
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id;Vérification rapide
Choisissez la réécriture correcte avec une jointure pour une anti-jointure corrélée NOT EXISTS.
Récapitulatif : réécrire les sous-requêtes corrélées sous forme de jointures
Points essentiels :
EXISTS→INNER JOIN(ajoutez DISTINCT pour éviter les doublons dus à la multiplication des lignes).NOT EXISTS→LEFT JOIN ... WHERE key IS NULL(testez une colonne qui ne peut pas être NULL).- Agrégat scalaire corrélé →
JOINavec une table dérivée regroupée ou, mieux encore, une fonction de fenêtre. - Premiers éléments par groupe →
ROW_NUMBER(ouRANKpour les valeurs ex æquo). - Vérifiez l’équivalence et contrôlez avec
EXPLAINavant de supposer qu’une réécriture est plus rapide.
Connaître les deux formes et le piège de la multiplication des lignes est exactement ce que les entretiens de niveau intermédiaire cherchent à évaluer.
Questions Fréquemment Posées
La leçon « Réécrire les sous-requêtes corrélées avec des jointures » est-elle gratuite ?
Oui — le texte complet de « Réécrire les sous-requêtes corrélées avec des jointures » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Coding Interview Prep, passe à CoddyKit PRO. Le cours Coding Interview Prep comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Réécrire les sous-requêtes corrélées avec des jointures » ?
Transformer une logique corrélée en jointures ou en fonctions de fenêtre pour améliorer les performances Tu pratiques Coding Interview Prep avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Coding Interview Prep ?
Aucune expérience préalable n'est requise. Coding Interview Prep sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Réécrire les sous-requêtes corrélées avec des jointures » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Coding Interview Prep ?
Oui. Chaque leçon Coding Interview Prep inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Anatomie d’une sous-requête corrélée
- Agrégats par groupe sans GROUP BY
- EXISTS et NOT EXISTS corrélés
- Réécrire les sous-requêtes corrélées avec des jointures