Voir ce que le moteur fait d'une requête
Un plan de requête est la liste des étapes que le moteur suit pour répondre : lire les tables, filtrer, relier, additionner. La commande explain l'affiche sans exécuter la requête.
Deux étudiants écrivent la même question de deux façons. Le plan dit si DuckDB fait vraiment un travail différent.
À la fin de cette leçon
Remarque
Je peux lire un plan avec EXPLAIN et dire pourquoi une requête coûte moins cher qu'une autre.
Mots clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Plan de requête | les étapes que le moteur suit pour répondre | |
| EXPLAIN | affiche le plan sans exécuter la requête | |
| Lecture (scan) | l'étape qui lit une table | Seq Scan |
| Filtre | l'étape qui garde seulement certaines lignes | |
| Optimiseur | la partie du moteur qui choisit le plan |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un plan se lit de bas en haut
Un plan est une pile de boîtes. Les données entrent par les boîtes du bas, qui lisent les tables, et remontent vers celle du haut, qui donne le résultat.
Chaque boîte porte un nom : lecture, filtre, jointure, agrégat. Les noms exacts changent un peu d'une version à l'autre.
Exemple : les boîtes d'un plan DuckDB
| Nom affiché | Ce que fait la boîte | Étape de la requête |
|---|---|---|
| Seq Scan | Lit une table, avec ses colonnes et parfois un filtre | from |
| Filter | Garde les lignes qui réussissent un test | where, qualify |
| Hash Join | Relie deux entrées sur une clé | join ... on |
| Hash Group By | Regroupe et additionne | group by, sum |
| Window | Calcule une fonction de fenêtre | over (...) |
| Projection | Garde ou calcule des colonnes | select |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
À vous : lire le plan d'une jointure
-- Le plan d'une jointure suivie d'un total. Lisez-le de bas en haut.
explain
select clients.nom, sum(commandes.montant) as total
from clients
join commandes on commandes.client_id = clients.id
where commandes.annee = 2026
group by clients.nom;
-- Cherchez la boîte Seq Scan de commandes : quelle ligne mentionne annee = 2026 ?
Filtrer tôt réduit le travail
Une jointure compare des lignes. Moins il y a de lignes à relier, moins le travail est grand.
Si on veut seulement les commandes de 2026, mieux vaut les garder avant la jointure que de relier toutes les années puis de jeter.
Exemple : les lignes à relier
| Version | Commandes envoyées à la jointure | Lignes jetées après |
|---|---|---|
| Relier d'abord, filtrer ensuite | 8 | 3 |
| Filtrer d'abord, relier ensuite | 5 | 0 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
À vous : le filtre écrit avant la jointure
-- Le même calcul, avec le filtre écrit avant la jointure.
explain
with recentes as (
select client_id, montant from commandes where annee = 2026
)
select clients.nom, sum(recentes.montant) as total
from clients
join recentes on recentes.client_id = clients.id
group by clients.nom;
-- Comparez ce plan à celui du bloc précédent : voyez-vous une différence de forme ?
Une erreur fréquente
Attention
Beaucoup pensent que réécrire une requête change toujours son plan. L'optimiseur de DuckDB descend souvent le filtre tout seul : ici, les deux écritures ont la même forme. Lisez le plan avant de réécrire, et réécrivez pour la clarté d'abord.
Certains filtres ne peuvent pas descendre
Un filtre sur une colonne d'une table peut descendre jusqu'à la lecture. Un filtre sur le résultat d'un calcul ne le peut pas : il faut avoir calculé avant.
C'est le cas du rang d'une fenêtre. La boîte Filter reste au-dessus de Window, qui reste au-dessus de Hash Group By.
À vous : le plan d'un filtre de rang
-- Un filtre sur un rang : il ne peut pas descendre sous la fenêtre.
explain
select nom, total, rank() over (order by total desc) as rang
from (
select clients.nom, sum(commandes.montant) as total
from clients join commandes on commandes.client_id = clients.id
group by clients.nom
)
qualify rang <= 2;
-- Dans quel ordre, de bas en haut, voyez-vous Hash Group By, Window et Filter ?