SQL Quest
Se connecter English 0 XP Niv. 1
Avancé

SQL WITH : les CTE (Common Table Expressions)

5 min de lecture · 3 exemples exécutables · mis à jour le 9 octobre 2026

Quand une requête devient longue, avec des sous-requêtes imbriquées les unes dans les autres, elle devient vite illisible. Une CTE (expression de table commune), introduite par WITH, permet de découper le problème en étapes nommées, que l'on utilise ensuite comme des tables.

Syntaxe

WITH etape AS (
  SELECT …
)
SELECT …
FROM etape;

Comment ça marche

Une CTE se déclare au début de la requête : WITH nom AS (requête). Dans la requête principale, nom s'utilise exactement comme une table. Elle n'existe que le temps de la requête : rien n'est enregistré dans la base.

Plusieurs étapes

On peut enchaîner plusieurs CTE, séparées par des virgules, et chacune peut utiliser les précédentes : WITH a AS (…), b AS (SELECT … FROM a) SELECT … FROM b. La requête se lit alors de haut en bas, comme une recette.

Les CTE récursives

WITH RECURSIVE permet à une CTE de faire référence à elle-même. C'est l'outil idéal pour parcourir une hiérarchie (un organigramme) ou générer une suite de valeurs.

Schéma

lignes_commande
commandemontant
120
130
215
paniers (étape 1)
commandetotal
150
215
WITH paniers AS (…) : on calcule d'abord le montant de chaque commande, puis on travaille sur ce résultat intermédiaire.

Schéma simplifié pour illustrer le principe. Les exemples ci-dessous utilisent la vraie base d'exercices de SQL Quest (une boutique en ligne fictive : voir le schéma complet).

Exemples avec leurs résultats

1. Une étape intermédiaire

Le montant moyen d'une commande : on calcule d'abord le total de chaque commande, puis la moyenne de ces totaux.

WITH paniers AS (
  SELECT commande_id, SUM(quantite * prix_unitaire) AS montant
  FROM lignes_commande
  GROUP BY commande_id
)
SELECT COUNT(*) AS commandes, ROUND(AVG(montant), 2) AS panier_moyen, ROUND(MAX(montant), 2) AS plus_gros
FROM paniers;
Essayer dans le bac à sable
Résultat
commandespanier_moyenplus_gros
600403,272 337,18

1 ligne au total

2. Deux CTE enchaînées

Les clients dont le total dépensé dépasse la moyenne de tous les clients.

WITH depenses AS (
  SELECT o.client_id, SUM(l.quantite * l.prix_unitaire) AS total
  FROM commandes o JOIN lignes_commande l ON l.commande_id = o.id
  GROUP BY o.client_id
),
moyenne AS (
  SELECT AVG(total) AS m FROM depenses
)
SELECT d.client_id, ROUND(d.total, 2) AS total
FROM depenses d, moyenne
WHERE d.total > moyenne.m
ORDER BY d.total DESC
LIMIT 6;
Essayer dans le bac à sable
Résultat
client_idtotal
226 122,6
395 980,11
105 747,6
645 341,39
464 548,57
24 529,7

6 lignes au total

3. Une CTE récursive

L'organigramme à partir du PDG, avec le niveau hiérarchique de chaque employé.

WITH RECURSIVE organigramme AS (
  SELECT id, prenom, nom, poste, 0 AS niveau
  FROM employes WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.prenom, e.nom, e.poste, o.niveau + 1
  FROM employes e JOIN organigramme o ON e.manager_id = o.id
)
SELECT niveau, prenom, nom, poste
FROM organigramme
ORDER BY niveau, nom
LIMIT 8;
Essayer dans le bac à sable
Résultat
niveauprenomnomposte
0ChloéDuboisPDG
1JadeLefebvreDirecteur commercial
1NathanRouxDirectrice technique
1SamirSimonResponsable RH
2KarimBenaliCommercial
2ChloéBenaliChargé RH
2LouisBernardCommercial
2ChloéBlancDéveloppeur

8 lignes au total

Les erreurs fréquentes

Mettre un point-virgule après la CTE

La CTE et la requête principale forment une seule instruction : pas de point-virgule entre les deux.

À éviter
WITH t AS (SELECT * FROM produits); SELECT * FROM t;
Correct
WITH t AS (SELECT * FROM produits) SELECT * FROM t;

Répéter WITH pour la deuxième CTE

Le mot WITH n'apparaît qu'une fois ; les CTE suivantes sont séparées par des virgules.

À éviter
WITH a AS (SELECT 1 AS x) WITH b AS (SELECT x FROM a) SELECT * FROM b;
Correct
WITH a AS (SELECT 1 AS x), b AS (SELECT x FROM a) SELECT * FROM b;

Utiliser une CTE dans une autre requête

Une CTE n'existe que pour la requête qui la suit. Pour la réutiliser, il faut une vue (CREATE VIEW).

À éviter
WITH t AS (SELECT * FROM produits) SELECT 1; SELECT * FROM t;
Correct
WITH t AS (SELECT * FROM produits) SELECT COUNT(*) FROM t;

Exercices

Essayez d'écrire la requête vous-même avant d'ouvrir la solution.

Défi 1. Avec une CTE, affichez les catégories dont le chiffre d'affaires dépasse 30 000 €.

Voir une solution
WITH ca AS (SELECT p.categorie, SUM(l.quantite * l.prix_unitaire) AS total FROM lignes_commande l JOIN produits p ON p.id = l.produit_id GROUP BY p.categorie) SELECT categorie, ROUND(total, 2) AS total FROM ca WHERE total > 30000;
categorietotal
Informatique151 080
Maison31 502,56

2 lignes au total

Essayer dans le bac à sable

Défi 2. Avec une CTE, calculez le nombre moyen de commandes par client ayant commandé.

Voir une solution
WITH t AS (SELECT client_id, COUNT(*) AS nb FROM commandes GROUP BY client_id) SELECT ROUND(AVG(nb), 2) AS moyenne FROM t;
moyenne
5,45

1 ligne au total

Essayer dans le bac à sable
Envie de vous entraîner davantage ?

Les exercices interactifs sont corrigés automatiquement, avec un prof qui explique vos erreurs. Une leçon résumée reste affichée à côté de l'éditeur.

Faire les exercices interactifs

Questions fréquentes

Une CTE est-elle plus rapide qu'une sous-requête ?

En général, les performances sont équivalentes : l'intérêt principal d'une CTE est la lisibilité. Certaines bases peuvent toutefois la calculer une seule fois si elle est utilisée plusieurs fois.

Une CTE crée-t-elle une table ?

Non, elle n'existe que pendant l'exécution de la requête. Pour une requête réutilisable, créez une vue avec CREATE VIEW.

Toutes les bases supportent-elles WITH ?

Oui : SQLite, PostgreSQL, MySQL 8+, SQL Server et Oracle. Les anciennes versions de MySQL (5.x) ne les supportaient pas.

À lire ensuite