Una tabla dinámica (pivot) convierte filas en columnas: una fila por grupo y una columna por cada valor de otro campo, por ejemplo una columna por trimestre. En SQL la construyes con CASE dentro de una función de agregación como SUM, agrupando por lo que deba representar cada fila. Los ejemplos funcionan en PostgreSQL, MySQL, SQLite y SQL Server.
Cómo hacer una tabla dinámica en SQL
Escribe una agregación por cada columna de salida. El CASE que va dentro solo deja pasar las filas que pertenecen a esa columna, y GROUP BY genera una fila por grupo:
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;
Aquí cada producto tiene una fila, con sus unidades de cada trimestre una junto a otra:
-- 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
Reglas que debes conocer
-
Tú nombras cada columna. SQL no puede crear columnas a partir de los datos, así que un tercer trimestre necesita una tercera línea
CASE. Si la lista de valores cambia constantemente, arma la consulta en el código de tu aplicación. -
ELSE 0es solo paraSUMy conteos. Convierte una combinación faltante en 0 en lugar de NULL. ConAVG,MINoMAX, omite elELSE, o los ceros contarán como valores reales:
-- 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
Sintaxis alternativa
También te encontrarás con FILTER en PostgreSQL y SQLite, y con el operador PIVOT en SQL Server. Ambos dejan una combinación faltante como NULL en vez de 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
En código de MySQL verás seguido SUM(IF(quarter = 'Q1', units, 0)), una forma más corta de escribir la versión con CASE. La extensión tablefunc de PostgreSQL también incluye la función crosstab(); consulta cómo crear tablas dinámicas en PostgreSQL con crosstab().
¿Cuándo conviene usar una tabla dinámica?
Usa pivot cuando una lista de filas sería más fácil de leer como cuadrícula. Estas son las razones más comunes.
Comparar una métrica lado a lado
Pon los valores que quieres comparar (días, trimestres, regiones) uno junto a otro en una sola fila, en vez de dispersos en una lista larga.
Mostrar más de una métrica por columna
Cada columna de salida es su propia agregación, así que puedes combinarlas: sesiones y páginas vistas por dispositivo, o pedidos e ingresos por canal.
Contar filas por categoría
Para contar en lugar de sumar, suma 1 por cada fila que coincida, o usa COUNT(CASE WHEN ... THEN 1 END): COUNT ignora los NULL que generan las demás filas. Así obtienes tareas por estado o tickets por prioridad.
Convertir filas clave-valor en columnas
Las configuraciones y los atributos de producto suelen guardarse como una fila por nombre y valor. Hacer pivot con MAX(CASE ...) te da una fila por elemento con una columna por atributo. MAX funciona con texto y toma el único valor de cada grupo.
Ejemplos reales de tablas dinámicas en SQL
Calificación promedio de apps por plataforma
Omite el ELSE cuando calcules promedios, para que una app sin reseñas en una plataforma muestre NULL en lugar de una calificación engañosa:
-- 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
Pedidos y ventas por canal en cada región
Dos métricas por canal, un conteo de pedidos y 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 soporte por agente según prioridad
Un conteo por prioridad, más un total. COUNT(*) sigue contando todas las filas del grupo:
-- 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
Una fila de configuración por usuario
Cada configuración se guarda como un nombre y un valor. Un usuario que nunca definió una obtiene NULL en esa columna:
-- 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
Más lecturas sobre tablas dinámicas
En este blog:
-
Cómo hacer pivot en PostgreSQL:
CASEycrosstab()en PostgreSQL -
Cómo crear tablas dinámicas en PostgreSQL con crosstab(): la extensión
tablefunca detalle - Guía de la sentencia CASE en SQL: las condiciones dentro de cada columna pivot
- Guía de GROUP BY en SQL: agrupar filas antes de hacer pivot
Documentación oficial:
- PostgreSQL: expresiones de agregación y FILTER y crosstab()
- MySQL: operador CASE
- SQLite: funciones de agregación y FILTER
- SQL Server: PIVOT y UNPIVOT
Beekeeper Studio es un cliente de bases de datos gratuito y de código abierto
La mejor herramienta de consultas y edición de SQL que he usado. Tiene todo lo que necesito para administrar mi base de datos. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio es rápido, intuitivo y fácil de usar. Es compatible con muchísimas bases de datos y funciona de maravilla en Windows, Mac y Linux.
Lo que dicen los usuarios de Beekeeper Studio
"Beekeeper Studio reemplazó por completo mi antiguo flujo de trabajo con SQL. Es rápido, intuitivo y hace que trabajar con bases de datos vuelva a ser agradable."
"He probado muchas GUI de bases de datos, pero Beekeeper logra el equilibrio perfecto entre funciones y sencillez. Simplemente funciona."