Question 1Quelle est la meilleure correction pour une requête qui écrit la même agrégation GROUP BY deux fois — une dans la requête principale et une dans une sous-requête HAVING ?
Éliminer le travail répété et optimiser les sous-requêtes — Exemples pratiques
Pratique la réécriture de requêtes pour que la même agrégation ne soit pas calculée deux fois, que les sous-requêtes corrélées s'effondrent en un seul GROUP BY, et qu'une table dérivée soit matérialisée une fois via un CTE — en comparant les plans de requête avant et après.
Les données utilisées dans cet article — perf_sales et employee
Dans l'article précédent tu as étudié la réécriture de IN / EXISTS et des sous-requêtes corrélées.
Cet article s'appuie là-dessus avec des réécritures qui évitent de calculer la même agrégation deux fois (replier les agrégations répétées en un seul résultat intermédiaire) et matérialiser les tables dérivées (construire une sous-requête dans une clause FROM une fois et la tenir temporairement) — pratiqué à travers des exemples concrets.
N'écris pas la même agrégation deux fois — replie la duplication en un seul résultat intermédiaire
Quand tu veux demander « le total des ventes de chaque région est-il supérieur au total moyen sur toutes les régions ? », écrire le total et la moyenne comme deux sous-requêtes séparées scanne perf_sales deux fois.
Si tu replies la même agrégation dans un CTE et la calcules une fois, puis réutilises le résultat, tu ne scannes qu'une fois.
Le motif de réécriture est : repère le cas où « la même agrégation GROUP BY apparaît à la fois dans la requête principale et dans une sous-requête », hisse cette agrégation dans un seul CTE, et fais que la requête principale référence le CTE à la place.
Le résultat est identique, et le scan en double disparaît du plan.
-- Avant : total et « moyenne sur les régions » agrégés séparément (perf_sales scanné deux fois)
SELECT region, SUM(amount) AS total
FROM perf_sales
GROUP BY region
HAVING SUM(amount) > (
SELECT AVG(t) FROM (
SELECT SUM(amount) AS t FROM perf_sales GROUP BY region
)
);
-- Après : agréger par région une fois dans un CTE
WITH region_total AS (
SELECT region, SUM(amount) AS total
FROM perf_sales
GROUP BY region
)
SELECT region, total
FROM region_total
WHERE total > (SELECT AVG(total) FROM region_total)
ORDER BY region;
Effondrer les sous-requêtes corrélées en une agrégation en un seul coup
Quand une condition comme « le total des ventes de chaque région est-il supérieur au total maximum par employé dans cette région ? » est calculée comme une sous-requête corrélée par ligne externe de région, la requête interne est ré-évaluée une fois pour chaque région.
Cela peut aussi être replié en une agrégation unique clé par région, puis jointe en retour.
Le motif de réécriture est : prends un (SELECT agrégat ... WHERE child.key = parent.key) appelé par ligne externe et transforme-le en « construis une table d'agrégat GROUP BY key une fois, puis joins en retour à la parente ».
Les ré-évaluations par région sont remplacées par une seule agrégation plus une jointure.
-- Avant : corréler par région pour trouver le « total max par employé »
SELECT region,
(SELECT MAX(s) FROM (
SELECT SUM(amount) AS s FROM perf_sales p2
WHERE p2.region = p1.region GROUP BY emp_id
)) AS max_emp_total
FROM perf_sales p1
GROUP BY region;
-- Après : agréger une fois par (region, emp_id), puis prendre le MAX par région
WITH emp_region AS (
SELECT region, emp_id, SUM(amount) AS s
FROM perf_sales
GROUP BY region, emp_id
)
SELECT region, MAX(s) AS max_emp_total
FROM emp_region
GROUP BY region
ORDER BY region;
Matérialiser les tables dérivées — construire la sous-requête de clause FROM une fois
Une table dérivée — une sous-requête écrite dans une clause FROM — référencée en deux endroits peut parfois être recalculée deux fois par l'optimiseur.
Si tu la construis une fois et la tiens temporairement (la matérialise), les références ne la reconstruisent pas à chaque fois.
En SQLite, référencer un CTE plusieurs fois fait apparaître EXPLAIN QUERY PLAN une ligne MATERIALIZE — confirmant que le CTE est matérialisé exactement une fois.
Dans le dernier exercice de cet article tu prendras une table dérivée lourde qui agrège perf_sales par emp_id et est référencée deux fois, la transformeras en CTE matérialisé une fois, et confirmeras que MATERIALIZE apparaît dans le plan et que le scan ne se produit qu'une fois.
-- Avant : la même table dérivée référencée dans deux clauses FROM
SELECT a.emp_id, a.tot, b.avg_tot
FROM (SELECT emp_id, SUM(amount) AS tot FROM perf_sales GROUP BY emp_id) a
CROSS JOIN (
SELECT AVG(tot) AS avg_tot FROM (
SELECT emp_id, SUM(amount) AS tot FROM perf_sales GROUP BY emp_id
)
) b
ORDER BY a.emp_id;
-- Après : table dérivée en CTE référencé deux fois (matérialisé une fois)
WITH emp_total AS (
SELECT emp_id, SUM(amount) AS tot
FROM perf_sales
GROUP BY emp_id
)
SELECT e.emp_id, e.tot, (SELECT AVG(tot) FROM emp_total) AS avg_tot
FROM emp_total e
ORDER BY e.emp_id;
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Quelle réécriture effondre une corrélation qui appelle (SELECT agrégat ... WHERE child.key = parent.key) pour chaque ligne de groupe externe ?
Question 3Que signifie MATERIALIZE dans EXPLAIN QUERY PLAN ?