Pivoter des données en SQL, c'est transformer des lignes en colonnes pour produire un tableau croisé lisible — une opération que tout data analyst rencontre tôt ou tard en entretien ou en production.
Contrairement à Excel où un tableau croisé dynamique se fait en 3 clics, SQL exige de comprendre la logique sous-jacente. La bonne nouvelle : une fois le mécanisme assimilé, vous pouvez pivoter n'importe quel jeu de données avec élégance, quelle que soit la base de données utilisée.
Dans ce guide, vous allez découvrir toutes les techniques de pivot SQL — de la méthode manuelle avec CASE WHEN aux extensions natives comme PIVOT (SQL Server, Oracle) ou crosstab (PostgreSQL) — avec des exemples chiffrés, des tableaux comparatifs et les pièges à éviter en entretien.
📌 Ce qu'il faut retenir
- Le pivot SQL consiste à transformer des valeurs d'une colonne en en-têtes de colonnes distinctes
- La méthode universelle repose sur
CASE WHENcombiné àGROUP BYet une fonction d'agrégat - SQL Server et Oracle proposent un opérateur
PIVOTnatif ; PostgreSQL utilisecrosstab - Le pivot dynamique (colonnes inconnues à l'avance) nécessite du SQL dynamique ou du code applicatif
Comprendre le problème : données longues vs données larges
En analyse de données, on distingue 2 formats fondamentaux :
- Format long (long format) : chaque ligne représente une observation unique. Une colonne
categoriecontient les libellés, une colonnevaleurcontient les chiffres. - Format large (wide format) : chaque catégorie occupe sa propre colonne. C'est le format attendu pour un rapport ou un tableau de bord.
Prenons un exemple concret. Une table ventes contient les revenus mensuels par région :
| region | mois | revenu |
|-----------|------------|---------|
| Nord | 2025-01 | 42 000 |
| Nord | 2025-02 | 38 500 |
| Sud | 2025-01 | 61 200 |
| Sud | 2025-02 | 57 800 |
| Est | 2025-01 | 29 400 |
| Est | 2025-02 | 33 100 |
Le résultat souhaité après pivot :
| region | 2025-01 | 2025-02 |
|--------|---------|---------|
| Est | 29 400 | 33 100 |
| Nord | 42 000 | 38 500 |
| Sud | 61 200 | 57 800 |
C'est exactement ce que le pivot SQL permet d'obtenir.
Méthode universelle : CASE WHEN + GROUP BY
La technique la plus portable — elle fonctionne sur tous les moteurs SQL — consiste à combiner CASE WHEN avec une fonction d'agrégat et un GROUP BY.
Syntaxe de base
SELECT
region,
SUM(CASE WHEN mois = '2025-01' THEN revenu ELSE 0 END) AS "2025-01",
SUM(CASE WHEN mois = '2025-02' THEN revenu ELSE 0 END) AS "2025-02"
FROM ventes
GROUP BY region
ORDER BY region;
Le principe est simple : pour chaque colonne cible, on crée une expression CASE WHEN qui renvoie la valeur lorsque la condition est vraie, et 0 (ou NULL) sinon. Le SUM — ou MAX, AVG selon le besoin — agrège ensuite les valeurs par groupe.
Choisir entre SUM, MAX et COUNT
Le choix de la fonction d'agrégat est crucial et dépend de la sémantique de vos données :
| Fonction | Quand l'utiliser | Exemple typique |
|---|---|---|
SUM |
Additionner des montants ou quantités | Revenu total par région et mois |
MAX |
Récupérer une valeur unique (sans doublons) | Dernier statut d'une commande |
COUNT |
Compter des occurrences | Nombre de commandes par catégorie |
AVG |
Calculer une moyenne sur plusieurs lignes | Note moyenne par produit et trimestre |
MIN |
Récupérer la valeur minimale | Prix plancher par fournisseur et mois |
💡 Bon à savoir
Utilisez MAX plutôt que SUM quand vous êtes certain qu'il ne peut exister qu'une seule ligne par combinaison (région × mois). Cela évite de cumuler des doublons accidentels et rend l'intention plus lisible.
Exemple complet : tickets de support par statut
Imaginons une table tickets avec les colonnes agent, statut (ouvert, resolu, en_cours) et nb_tickets. On veut un rapport croisé agents × statuts :
SELECT
agent,
SUM(CASE WHEN statut = 'ouvert' THEN nb_tickets ELSE 0 END) AS ouvert,
SUM(CASE WHEN statut = 'en_cours' THEN nb_tickets ELSE 0 END) AS en_cours,
SUM(CASE WHEN statut = 'resolu' THEN nb_tickets ELSE 0 END) AS resolu,
SUM(nb_tickets) AS total
FROM tickets
GROUP BY agent
ORDER BY total DESC;
L'ajout de la colonne total est une bonne pratique : elle permet de vérifier immédiatement la cohérence du pivot (la somme des colonnes pivotées doit égaler total).
La clause FILTER : une alternative plus lisible (PostgreSQL)
PostgreSQL 9.4+ propose la clause FILTER qui rend le pivot plus expressif et légèrement plus performant :
SELECT
agent,
SUM(nb_tickets) FILTER (WHERE statut = 'ouvert') AS ouvert,
SUM(nb_tickets) FILTER (WHERE statut = 'en_cours') AS en_cours,
SUM(nb_tickets) FILTER (WHERE statut = 'resolu') AS resolu
FROM tickets
GROUP BY agent;
La syntaxe aggregat(...) FILTER (WHERE condition) remplace avantageusement le CASE WHEN ... ELSE 0. Le résultat est strictement identique, mais le code est plus facile à lire et à maintenir quand le nombre de colonnes pivotées augmente.
💡 Bon à savoir
La clause FILTER est reconnue dans la norme SQL:2003. Elle est supportée par PostgreSQL, DuckDB et certaines versions récentes de SQLite. En revanche, MySQL, SQL Server et Oracle ne la supportent pas — restez sur CASE WHEN pour du code portable.
L'opérateur PIVOT natif de SQL Server et Oracle
SQL Server (depuis 2005) et Oracle (depuis 11g) disposent d'un opérateur PIVOT intégré qui simplifie la syntaxe pour les cas statiques.
SQL Server : syntaxe PIVOT
SELECT region, [2025-01], [2025-02]
FROM (
SELECT region, mois, revenu
FROM ventes
) AS source
PIVOT (
SUM(revenu)
FOR mois IN ([2025-01], [2025-02])
) AS pivot_result
ORDER BY region;
La sous-requête source prépare les données ; PIVOT effectue la rotation en spécifiant la fonction d'agrégat, la colonne à pivoter (FOR mois) et les valeurs cibles (IN ([2025-01], [2025-02])).
Oracle : même logique, légères différences
SELECT *
FROM ventes
PIVOT (
SUM(revenu)
FOR mois IN ('2025-01' AS "jan_2025", '2025-02' AS "fev_2025")
)
ORDER BY region;
Oracle permet d'aliaser directement les valeurs dans la clause IN, ce qui évite les noms de colonnes numériques awkward.
Comparaison des approches par moteur SQL
| Moteur | Méthode native | CASE WHEN | FILTER | Pivot dynamique |
|---|---|---|---|---|
| PostgreSQL | crosstab() (extension tablefunc) |
✅ | ✅ | SQL dynamique |
| SQL Server | PIVOT natif |
✅ | ❌ | SQL dynamique |
| Oracle | PIVOT natif |
✅ | ❌ | XML PIVOT |
| MySQL / MariaDB | Aucune | ✅ | ❌ | SQL dynamique |
| BigQuery | PIVOT natif (2021+) |
✅ | ❌ | Procédures |
| DuckDB | PIVOT natif |
✅ | ✅ | PIVOT auto |
PostgreSQL crosstab : la fonction tablefunc
PostgreSQL propose la fonction crosstab() via l'extension tablefunc. Elle est plus performante sur de gros volumes mais nécessite une configuration préalable :
-- Activer l'extension (une seule fois par base)
CREATE EXTENSION IF NOT EXISTS tablefunc;
-- Utilisation
SELECT *
FROM crosstab(
'SELECT region, mois, SUM(revenu)
FROM ventes
GROUP BY region, mois
ORDER BY 1, 2',
'VALUES (''2025-01''), (''2025-02'')'
) AS ct(region TEXT, "jan_2025" NUMERIC, "fev_2025" NUMERIC);
La fonction crosstab prend 2 requêtes en argument : la première renvoie les données source triées, la seconde liste les valeurs de pivot attendues. Le typage explicite des colonnes de sortie est obligatoire.
⚠️ Attention
crosstab() est sensible à l'ordre des lignes. Si votre requête source n'est pas triée par (row_identifier, category) dans le bon ordre, les valeurs seront décalées sans qu'aucune erreur ne soit levée. Testez toujours avec des données de référence connues.
Pivot dynamique : quand les colonnes ne sont pas connues à l'avance
Le défi du pivot dynamique survient lorsque les valeurs à pivoter (les mois, les catégories, les statuts) varient et ne sont pas connues au moment de l'écriture de la requête.
Approche en SQL Server avec SQL dynamique
DECLARE @colonnes NVARCHAR(MAX);
DECLARE @sql NVARCHAR(MAX);
-- Récupérer dynamiquement la liste des mois
SELECT @colonnes = STRING_AGG(QUOTENAME(mois), ', ')
FROM (SELECT DISTINCT mois FROM ventes) AS mois_distincts;
-- Construire et exécuter la requête PIVOT
SET @sql = N'
SELECT region, ' + @colonnes + '
FROM (SELECT region, mois, revenu FROM ventes) src
PIVOT (SUM(revenu) FOR mois IN (' + @colonnes + ')) pvt
ORDER BY region;';
EXEC sp_executesql @sql;
Cette technique est puissante mais comporte des risques : injection SQL si les données proviennent d'une source non maîtrisée, et difficulté à typer les colonnes de sortie statiquement. Dans la majorité des projets data réels, le pivot dynamique est délégué à la couche applicatrice (Python pandas, dbt, etc.).
Unpivot : l'opération inverse
L'opération inverse du pivot s'appelle unpivot : elle transforme des colonnes en lignes. C'est utile pour normaliser une table "wide" reçue d'une source externe.
Unpivot avec CROSS JOIN LATERAL (PostgreSQL)
SELECT region, mois, revenu
FROM ventes_wide
CROSS JOIN LATERAL (
VALUES ('2025-01', "2025-01"),
('2025-02', "2025-02")
) AS t(mois, revenu)
ORDER BY region, mois;
SQL Server dispose d'un opérateur UNPIVOT natif avec une syntaxe symétrique à PIVOT. PostgreSQL favorise CROSS JOIN LATERAL ou unnest.
Pour aller plus loin sur les transformations de données, consultez notre guide sur les opérateurs ensemblistes SQL : UNION, INTERSECT, EXCEPT qui permettent de combiner des résultats pivotés issus de plusieurs sources.
Pivot SQL en entretien technique : ce que les recruteurs évaluent
Le pivot SQL est un sujet récurrent dans les entretiens data analyst et data engineer. Voici ce qui est généralement évalué :
- La maîtrise de la méthode universelle (
CASE WHEN+GROUP BY) — attendue de tout candidat - La connaissance des extensions natives du moteur cible (SQL Server
PIVOT, PostgreSQLcrosstab) - La gestion des NULLs — savoir quand utiliser
COALESCE(valeur, 0)pour éviter les NULLs dans les colonnes pivotées - La performance — comprendre pourquoi un pivot sur 100 millions de lignes nécessite une réflexion sur les index et la cardinalité
- L'approche dynamique — expliquer pourquoi on l'évite en SQL pur et comment on le délègue
Les questions d'entretien sur le pivot s'articulent souvent avec les fonctions fenêtre SQL, car les deux compétences sont fréquemment combinées dans les exercices de reporting avancé.
💡 Bon à savoir
En entretien, si le moteur n'est pas précisé, proposez systématiquement la solution CASE WHEN en première approche, puis mentionnez l'existence des opérateurs natifs selon le SGBD. Cela montre à la fois votre maîtrise des fondamentaux et votre connaissance des spécificités des outils.
Gestion des NULLs et valeurs manquantes dans un pivot
Un pivot produit naturellement des NULL pour les combinaisons qui n'existent pas dans les données source. Par exemple, si la région "Est" n'a aucune vente en février, la colonne 2025-02 sera NULL pour cette ligne.
Pour traiter ces cas, enveloppez vos expressions dans COALESCE :
SELECT
region,
COALESCE(SUM(CASE WHEN mois = '2025-01' THEN revenu END), 0) AS "2025-01",
COALESCE(SUM(CASE WHEN mois = '2025-02' THEN revenu END), 0) AS "2025-02"
FROM ventes
GROUP BY region;
La différence entre ELSE 0 et COALESCE(..., 0) est subtile mais importante :
CASE WHEN ... ELSE 0 ENDrenvoie0dès que la condition n'est pas remplie — même si la ligne existeCASE WHEN ... THEN valeur END(sansELSE) renvoieNULLsi la condition est fausse, etCOALESCEle remplace ensuite par0
Les 2 approches donnent le même résultat dans la majorité des cas, mais la seconde est plus explicite sur l'intention sémantique : "il n'y a pas de données" versus "la valeur est zéro".
Optimiser les performances d'un pivot sur grand volume
Sur des tables de plusieurs dizaines de millions de lignes, un pivot mal écrit peut être catastrophiquement lent. Voici les leviers d'optimisation :
- Filtrer avant de pivoter : appliquez les clauses
WHEREdans une CTE ou sous-requête avant la rotation, pas après - Indexer la colonne pivotée : créez un index sur la colonne dont les valeurs deviennent des en-têtes (ici
mois) - Matérialiser les agrégats intermédiaires : si le pivot est exécuté fréquemment, pré-agréger les données dans une vue matérialisée ou une table de staging
- Limiter le nombre de colonnes : au-delà de 20-30 colonnes pivotées, envisagez une représentation alternative (JSON, tableau croisé côté application)
Pour un traitement complet de l'optimisation, référez-vous à notre article sur les techniques d'optimisation des requêtes SQL.
Questions fréquentes
Quelle est la différence entre PIVOT et CASE WHEN pour pivoter des données ?
CASE WHEN est une approche universelle qui fonctionne sur tous les moteurs SQL. L'opérateur PIVOT (SQL Server, Oracle, BigQuery, DuckDB) est une syntaxe native plus concise, mais qui produit le même résultat. En entretien, maîtriser CASE WHEN est indispensable ; connaître PIVOT est un plus apprécié.
Comment pivoter des données quand les colonnes ne sont pas connues à l'avance ?
C'est le cas du pivot dynamique. En SQL pur, il faut construire la requête en chaîne de caractères et l'exécuter via EXEC (SQL Server) ou EXECUTE (PostgreSQL). En pratique, cette logique est souvent déplacée côté application (Python, dbt) pour plus de lisibilité et de sécurité.
Le pivot SQL est-il différent d'un tableau croisé dynamique Excel ?
Fonctionnellement, oui c'est équivalent — les 2 transforment des lignes en colonnes agrégées. La différence est que le pivot SQL est déclaratif et reproductible : la requête peut être versionnée, testée et exécutée à grande échelle. Un tableau croisé Excel dépend d'une interface graphique et ne se prête pas à l'automatisation.
Peut-on pivoter sur plusieurs colonnes simultanément ?
Oui, mais cela complexifie considérablement la requête. Il faut concaténer les valeurs des 2 colonnes pour créer des identifiants uniques, puis pivoter sur ces identifiants composites. Par exemple, region || '_' || mois comme valeur à pivoter. Au-delà de 2 dimensions, envisagez un modèle OLAP ou un outil de BI dédié.
Comment tester qu'un pivot est correct ?
La meilleure vérification est de comparer la somme totale avant et après le pivot. Avant : SELECT SUM(revenu) FROM ventes doit être égale à la somme de toutes les cellules du tableau pivoté. Un écart signale un doublon, un NULL non géré ou une erreur de condition dans le CASE WHEN.
Le pivot affecte-t-il les performances de façon significative ?
Oui, un pivot implique un GROUP BY complet sur la table source. Sur des volumes importants (10M+ lignes), assurez-vous que la colonne pivotée est indexée et que les filtres WHERE sont appliqués en amont. Une vue matérialisée peut réduire drastiquement le temps de réponse pour les rapports récurrents.
BigQuery supporte-t-il l'opérateur PIVOT natif ?
Oui, depuis 2021, BigQuery supporte la syntaxe PIVOT avec une syntaxe similaire à SQL Server. DuckDB — moteur analytique en plein essor — va encore plus loin avec un PIVOT automatique qui déduit les colonnes sans les lister explicitement, ce qui est idéal pour l'exploration rapide de données.
Conclusion
Le pivot SQL est une compétence fondamentale pour tout data analyst ou data engineer qui produit des rapports ou prépare des données pour la visualisation. La maîtrise de la méthode CASE WHEN + GROUP BY vous garantit une solution portable sur tous les moteurs ; la connaissance des opérateurs natifs (PIVOT, crosstab, FILTER) vous permet d'écrire du code plus expressif sur les plateformes qui les supportent.
Retenez les 3 points essentiels : filtrez vos données avant de pivoter, gérez les NULL avec COALESCE, et déléguez le pivot dynamique à la couche applicative. Avec ces principes, vous répondrez à n'importe quelle question de pivot en entretien avec clarté et précision.
Envie de pratiquer ? Retrouvez des exercices SQL interactifs sur le pivot, les fonctions fenêtre et bien d'autres sujets directement sur SQL Pratique.
