🧚 Écoute ! Beekeeper Studio est une interface de base de données rapide, moderne et open source Télécharger
7 janvier 2026 Par Matthew Rathbone

Un tableau croisé dynamique (pivot) transforme des lignes en colonnes : une ligne par groupe, et une colonne pour chaque valeur d’un autre champ, par exemple une colonne par trimestre. En SQL, tu le construis avec CASE à l’intérieur d’un agrégat comme SUM, groupé selon ce que chaque ligne doit représenter. Les exemples fonctionnent dans PostgreSQL, MySQL, SQLite et SQL Server.

Comment faire un tableau croisé dynamique en SQL

Écris un agrégat par colonne de sortie. Le CASE à l’intérieur ne laisse passer que les lignes qui appartiennent à cette colonne, et GROUP BY donne une ligne par groupe :

SELECT
  row_column,
  SUM(CASE WHEN pivot_column = 'value 1' THEN amount ELSE 0 END) AS value_1,
  SUM(CASE WHEN pivot_column = 'value 2' THEN amount ELSE 0 END) AS value_2
FROM table_name
GROUP BY row_column;

Ici, chaque produit obtient une ligne, avec ses unités pour chaque trimestre côte à côte :

-- Input: sales
-- product  | quarter | units
-- ---------+---------+------
-- Keyboard | Q1      | 120
-- Keyboard | Q2      | 95
-- Monitor  | Q1      | 40
-- Monitor  | Q2      | 55
-- Mouse    | Q2      | 210

SELECT
  product,
  SUM(CASE WHEN quarter = 'Q1' THEN units ELSE 0 END) AS q1,
  SUM(CASE WHEN quarter = 'Q2' THEN units ELSE 0 END) AS q2
FROM sales
GROUP BY product
ORDER BY product;

-- Output:
-- product  | q1  | q2
-- ---------+-----+----
-- Keyboard | 120 | 95
-- Monitor  | 40  | 55
-- Mouse    | 0   | 210

Les règles à connaître

  • Tu nommes chaque colonne toi-même. SQL ne peut pas créer de colonnes à partir des données, donc un troisième trimestre demande une troisième ligne CASE. Si la liste des valeurs change sans cesse, construis la requête dans le code de ton application.
  • ELSE 0 ne sert que pour SUM et les comptages. Il transforme une combinaison absente en 0 au lieu de NULL. Avec AVG, MIN ou MAX, omets ELSE, sinon les zéros comptent comme de vraies valeurs :
-- Input: the sales table above

SELECT
  product,
  ROUND(AVG(CASE WHEN quarter = 'Q1' THEN units ELSE 0 END), 1) AS q1_else_0,
  ROUND(AVG(CASE WHEN quarter = 'Q1' THEN units END), 1) AS q1_no_else
FROM sales
GROUP BY product
ORDER BY product;

-- Output:
-- product  | q1_else_0 | q1_no_else
-- ---------+-----------+-----------
-- Keyboard | 60.0      | 120.0
-- Monitor  | 20.0      | 40.0
-- Mouse    | 0.0       | NULL

Syntaxe alternative

Tu croiseras aussi FILTER dans PostgreSQL et SQLite, et l’opérateur PIVOT dans SQL Server. Les deux laissent une combinaison absente à NULL plutôt qu’à 0 :

-- Input: the sales table above

-- PostgreSQL and SQLite
SELECT
  product,
  SUM(units) FILTER (WHERE quarter = 'Q1') AS q1,
  SUM(units) FILTER (WHERE quarter = 'Q2') AS q2
FROM sales
GROUP BY product
ORDER BY product;

-- SQL Server
SELECT product, [Q1] AS q1, [Q2] AS q2
FROM (SELECT product, quarter, units FROM sales) AS src
PIVOT (SUM(units) FOR quarter IN ([Q1], [Q2])) AS p
ORDER BY product;

-- Output:
-- product  | q1   | q2
-- ---------+------+----
-- Keyboard | 120  | 95
-- Monitor  | 40   | 55
-- Mouse    | NULL | 210

Dans du code MySQL, tu verras souvent SUM(IF(quarter = 'Q1', units, 0)), une façon plus courte d’écrire la version avec CASE. L’extension tablefunc de PostgreSQL propose aussi une fonction crosstab() ; consulte créer des tableaux croisés dynamiques dans PostgreSQL avec crosstab().

Quand utiliser un tableau croisé dynamique ?

Fais un pivot quand une liste de lignes serait plus lisible sous forme de grille. Voici les raisons les plus courantes.

Comparer une mesure côte à côte

Place les valeurs à comparer (jours, trimestres, régions) les unes à côté des autres sur une seule ligne, au lieu de les éparpiller dans une longue liste.

Afficher plusieurs mesures par colonne

Chaque colonne de sortie est un agrégat à part, tu peux donc les associer : sessions et pages vues par appareil, ou commandes et chiffre d’affaires par canal.

Compter les lignes de chaque catégorie

Pour compter au lieu d’additionner, fais la somme de 1 pour chaque ligne correspondante, ou utilise COUNT(CASE WHEN ... THEN 1 END) : COUNT ignore les NULL produits par les autres lignes. C’est ainsi que tu obtiens les tâches par statut ou les tickets par priorité.

Transformer des lignes clé-valeur en colonnes

Les paramètres et les attributs produit sont souvent stockés sous forme d’une ligne par nom et valeur. Un pivot avec MAX(CASE ...) donne une ligne par élément avec une colonne par attribut. MAX fonctionne sur du texte et récupère l’unique valeur de chaque groupe.

Exemples concrets de tableaux croisés dynamiques en SQL

Note moyenne d’une app par plateforme

Omets ELSE quand tu calcules une moyenne, pour qu’une app sans avis sur une plateforme affiche NULL au lieu d’une note trompeuse :

-- Input: reviews
-- app      | platform | rating
-- ---------+----------+-------
-- Budgetly | iOS      | 5
-- Budgetly | iOS      | 4
-- Budgetly | Android  | 3
-- FitTrack | Android  | 4
-- FitTrack | Android  | 5

-- In SQL Server, AVG of an integer column drops the decimals: use rating * 1.0
SELECT
  app,
  ROUND(AVG(CASE WHEN platform = 'iOS' THEN rating END), 1) AS ios,
  ROUND(AVG(CASE WHEN platform = 'Android' THEN rating END), 1) AS android
FROM reviews
GROUP BY app
ORDER BY app;

-- Output:
-- app      | ios  | android
-- ---------+------+--------
-- Budgetly | 4.5  | 3.0
-- FitTrack | NULL | 4.5

Commandes et ventes par canal pour chaque région

Deux mesures pour chaque canal, un nombre de commandes et un total :

-- Input: orders
-- id | region | channel | amount
-- ---+--------+---------+-------
-- 1  | North  | web     | 120.00
-- 2  | North  | store   | 80.00
-- 3  | North  | web     | 45.50
-- 4  | South  | store   | 210.00
-- 5  | South  | store   | 64.00

SELECT
  region,
  COUNT(CASE WHEN channel = 'web' THEN 1 END) AS web_orders,
  SUM(CASE WHEN channel = 'web' THEN amount ELSE 0 END) AS web_sales,
  COUNT(CASE WHEN channel = 'store' THEN 1 END) AS store_orders,
  SUM(CASE WHEN channel = 'store' THEN amount ELSE 0 END) AS store_sales
FROM orders
GROUP BY region
ORDER BY region;

-- Output:
-- region | web_orders | web_sales | store_orders | store_sales
-- -------+------------+-----------+--------------+------------
-- North  | 2          | 165.50    | 1            | 80.00
-- South  | 0          | 0         | 2            | 274.00

Tickets de support par agent et par priorité

Un comptage pour chaque priorité, plus un total. COUNT(*) compte toujours toutes les lignes du groupe :

-- Input: tickets
-- id | agent | priority
-- ---+-------+---------
-- 1  | Priya | urgent
-- 2  | Priya | normal
-- 3  | Marco | normal
-- 4  | Priya | normal
-- 5  | Marco | low

SELECT
  agent,
  COUNT(CASE WHEN priority = 'low' THEN 1 END) AS low,
  COUNT(CASE WHEN priority = 'normal' THEN 1 END) AS normal,
  COUNT(CASE WHEN priority = 'urgent' THEN 1 END) AS urgent,
  COUNT(*) AS total
FROM tickets
GROUP BY agent
ORDER BY agent;

-- Output:
-- agent | low | normal | urgent | total
-- ------+-----+--------+--------+------
-- Marco | 1   | 1      | 0      | 2
-- Priya | 0   | 2      | 1      | 3

Une ligne de paramètres par utilisateur

Chaque paramètre est stocké sous forme de nom et de valeur. Un utilisateur qui ne l’a jamais défini obtient NULL dans cette colonne :

-- Input: user_settings
-- user_id | setting  | value
-- --------+----------+--------------
-- 1       | theme    | dark
-- 1       | language | en
-- 2       | language | de
-- 2       | timezone | Europe/Berlin
-- 3       | theme    | light

SELECT
  user_id,
  MAX(CASE WHEN setting = 'theme' THEN value END) AS theme,
  MAX(CASE WHEN setting = 'language' THEN value END) AS language,
  MAX(CASE WHEN setting = 'timezone' THEN value END) AS timezone
FROM user_settings
GROUP BY user_id
ORDER BY user_id;

-- Output:
-- user_id | theme | language | timezone
-- --------+-------+----------+--------------
-- 1       | dark  | en       | NULL
-- 2       | NULL  | de       | Europe/Berlin
-- 3       | light | NULL     | NULL

Pour aller plus loin sur les tableaux croisés dynamiques

Sur ce blog :

Documentation officielle :

Beekeeper Studio est une interface de base de données gratuite & open source

Le meilleur outil de requêtes & éditeur SQL que j’aie jamais utilisé. Il m’offre tout ce dont j’ai besoin pour gérer ma base de données. - ⭐⭐⭐⭐⭐ Mit

Beekeeper Studio est rapide, intuitif et facile à utiliser. Beekeeper prend en charge plein de bases de données et fonctionne parfaitement sous Windows, Mac et Linux.

La version Linux de Beekeeper est 100% complète : rien de bâclé, aucun compromis sur les fonctionnalités.

Ce que les utilisateurs disent de Beekeeper Studio

★★★★★
« Beekeeper Studio a complètement remplacé mon ancien workflow SQL. C’est rapide, intuitif, et ça rend le travail sur les bases de données à nouveau agréable. »
— Alex K., développeur de bases de données
★★★★★
« J’ai essayé beaucoup d’interfaces de bases de données, mais Beekeeper trouve l’équilibre parfait entre fonctionnalités et simplicité. Ça marche, tout simplement. »
— Sarah M., ingénieure Full Stack