Les expressions de table commune, ou CTE, sont l'une des fonctionnalités les plus puissantes et sous-utilisées de SQL. Elles vous permettent de définir des ensembles de résultats temporaires nommés qui peuvent être référencés dans une requête plus grande. Dans cette leçon, nous explorerons comment les CTE peuvent rendre votre code SQL plus lisible, maintenable et plus facile à déboguer.
Une expression de table commune (CTE) est un ensemble de résultats temporaire défini au début d'une requête à l'aide de la clause WITH. Pensez-y comme une sous-requête nommée qui peut être utilisée plusieurs fois dans la même requête.
Les avantages clés des CTE :
La syntaxe générale d'une CTE est :
WITH nom_cte AS (
SELECT ...
)
SELECT * FROM nom_cte;
Composants:
Commençons par un exemple simple qui calcule les dépenses des clients :
WITH client_dépenses AS (
SELECT
customer_id,
SUM(amount) AS total_dépensé,
COUNT(*) AS nombre_paiements,
AVG(amount) AS paiement_moyen
FROM
payment
GROUP BY
customer_id
)
SELECT
customer_id,
total_dépensé,
nombre_paiements,
paiement_moyen
FROM
client_dépenses
WHERE
total_dépensé > 100
ORDER BY
total_dépensé DESC;
Cette CTE :
client_dépensesLe bénéfice ici est la clarté—l'intention est évidente : nous travaillons avec les données de dépenses des clients.
Comparons la même logique en utilisant une approche traditionnelle de sous-requête :
Utiliser une sous-requête :
SELECT
customer_id,
total_dépensé,
nombre_paiements,
paiement_moyen
FROM (
SELECT
customer_id,
SUM(amount) AS total_dépensé,
COUNT(*) AS nombre_paiements,
AVG(amount) AS paiement_moyen
FROM
payment
GROUP BY
customer_id
) AS données_dépenses
WHERE
total_dépensé > 100
ORDER BY
total_dépensé DESC;
Utiliser une CTE :
WITH client_dépenses AS (
SELECT
customer_id,
SUM(amount) AS total_dépensé,
COUNT(*) AS nombre_paiements,
AVG(amount) AS paiement_moyen
FROM
payment
GROUP BY
customer_id
)
SELECT
customer_id,
total_dépensé,
nombre_paiements,
paiement_moyen
FROM
client_dépenses
WHERE
total_dépensé > 100
ORDER BY
total_dépensé DESC;
Différences clés :
client_dépenses), pas seulement une sous-requête anonymeVous pouvez définir plusieurs CTE dans une seule requête, chacune référençant les précédentes :
WITH client_dépenses AS (
SELECT
customer_id,
SUM(amount) AS total_dépensé
FROM
payment
GROUP BY
customer_id
),
grands_dépensiers AS (
SELECT
customer_id,
total_dépensé
FROM
client_dépenses
WHERE
total_dépensé > 150
),
détails_client AS (
SELECT
gd.customer_id,
gd.total_dépensé,
c.first_name,
c.last_name,
c.email
FROM
grands_dépensiers gd
JOIN
customer c ON gd.customer_id = c.customer_id
)
SELECT
customer_id,
CONCAT(first_name, ' ', last_name) AS nom_client,
email,
total_dépensé
FROM
détails_client
ORDER BY
total_dépensé DESC;
Dans cette requête :
client_dépenses calcule le total dépensé par clientgrands_dépensiers filtre les clients avec un total dépensé > 150détails_client joint les grands dépensiers avec les informations clientCette structure rend le flux logique clair et facile à suivre.
Un aspect puissant des CTE est de vous référencer plusieurs fois :
WITH ventes_mensuelles AS (
SELECT
DATE_TRUNC('month', payment_date) AS mois,
SUM(amount) AS total_mensuel
FROM
payment
GROUP BY
DATE_TRUNC('month', payment_date)
)
SELECT
m1.mois AS mois_actuel,
m1.total_mensuel AS ventes_courantes,
m2.total_mensuel AS ventes_mois_précédent,
ROUND(((m1.total_mensuel - m2.total_mensuel) / m2.total_mensuel * 100), 2) AS pourcentage_changement
FROM
ventes_mensuelles m1
LEFT JOIN
ventes_mensuelles m2 ON m1.mois = m2.mois + INTERVAL '1 month'
WHERE
m1.mois IS NOT NULL
ORDER BY
m1.mois;
Ici, nous référençons ventes_mensuelles deux fois—une fois comme m1 et une fois comme m2. Cela nécessiterait deux sous-requêtes séparées si nous n'utilisions pas une CTE.
Les CTE fonctionnent magnifiquement avec les fonctions de fenêtre :
WITH locations_classées AS (
SELECT
customer_id,
rental_date,
return_date,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY rental_date DESC
) AS rang_location
FROM
rental
),
location_récente AS (
SELECT
customer_id,
rental_date,
return_date
FROM
locations_classées
WHERE
rang_location = 1
)
SELECT
c.customer_id,
CONCAT(c.first_name, ' ', c.last_name) AS nom_client,
lr.rental_date AS date_dernière_location,
DATEDIFF(CURDATE(), lr.rental_date) AS jours_depuis_location
FROM
customer c
LEFT JOIN
location_récente lr ON c.customer_id = lr.customer_id
ORDER BY
jours_depuis_location DESC
LIMIT 20;
Cette requête :
ROW_NUMBER() pour identifier la location la plus récente de chaque clientLa structure modulaire rend facile à comprendre et à modifier.
Les CTE sont excellentes pour les requêtes analytiques complexes comme l'analyse de cohorte :
WITH première_location_client AS (
SELECT
customer_id,
MIN(rental_date) AS date_première_location,
DATE_TRUNC('month', MIN(rental_date)) AS mois_cohorte
FROM
rental
GROUP BY
customer_id
),
historique_location_client AS (
SELECT
flc.customer_id,
flc.mois_cohorte,
DATE_TRUNC('month', r.rental_date) AS mois_location,
COUNT(*) AS locations_du_mois
FROM
première_location_client flc
JOIN
rental r ON flc.customer_id = r.customer_id
GROUP BY
flc.customer_id,
flc.mois_cohorte,
DATE_TRUNC('month', r.rental_date)
)
SELECT
mois_cohorte,
mois_location,
COUNT(DISTINCT customer_id) AS clients,
SUM(locations_du_mois) AS total_locations
FROM
historique_location_client
GROUP BY
mois_cohorte,
mois_location
ORDER BY
mois_cohorte,
mois_location;
Cette analyse complexe devient gérable grâce aux CTE :
| Aspect | CTE | Sous-requête |
|---|---|---|
| Lisibilité | Hautement lisible avec ensembles nommés | Peut être difficile à lire (structures imbriquées) |
| Réutilisabilité | Facile de référencer plusieurs fois | Doit être redéfini pour chaque utilisation |
| Débogage | Peut tester chaque CTE indépendamment | Difficile d'isoler une logique spécifique |
| Organisation | Structure logique, de haut en bas | Linéaire mais parfois encombrant |
| Performance | Identique ou mieux (dépend de l'optimiseur) | Peut être moins efficace avec imbrication profonde |
WITHLes CTE transforment les requêtes complexes de structures imbriquées inintelligibles en code clair, lisible et maintenable. C'est un outil essentiel dans l'outillage de tout analyste de données.
Dans la leçon suivante, nous explorerons les CTE récursives—une fonctionnalité puissante pour travailler avec des données hiérarchiques.