Uma tabela dinâmica (pivot) transforma linhas em colunas: uma linha por grupo e uma coluna para cada valor de outro campo, como uma coluna por trimestre. Em SQL, você monta uma usando CASE dentro de uma agregação como SUM, agrupando pelo que cada linha deve representar. Os exemplos funcionam em PostgreSQL, MySQL, SQLite e SQL Server.
Como fazer uma tabela dinâmica em SQL
Escreva uma agregação para cada coluna de saída. O CASE dentro dela deixa passar só as linhas que pertencem àquela coluna, e o GROUP BY gera uma linha 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;
Aqui cada produto ganha uma linha, com as unidades de cada trimestre lado a lado:
-- 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
Regras importantes
-
Você mesmo nomeia cada coluna. O SQL não consegue criar colunas a partir dos dados, então um terceiro trimestre precisa de uma terceira linha com
CASE. Se a lista de valores muda com frequência, monte a consulta no código da sua aplicação. -
ELSE 0serve só paraSUMe contagens. Ele transforma uma combinação ausente em 0 em vez de NULL. ComAVG,MINouMAX, omita oELSE, senão os zeros contam como valores reais:
-- 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
Sintaxe alternativa
Você também vai encontrar FILTER no PostgreSQL e no SQLite, e o operador PIVOT no SQL Server. Ambos deixam uma combinação ausente como NULL em 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
Em código MySQL é comum ver SUM(IF(quarter = 'Q1', units, 0)), uma forma mais curta de escrever a versão com CASE. A extensão tablefunc do PostgreSQL também tem a função crosstab(); veja como criar tabelas dinâmicas no PostgreSQL com crosstab().
Quando usar uma tabela dinâmica?
Use pivot quando uma lista de linhas ficaria mais fácil de ler como uma grade. Estes são os motivos mais comuns.
Comparar uma medida lado a lado
Coloque os valores que você quer comparar (dias, trimestres, regiões) lado a lado em uma linha, em vez de espalhados por uma lista longa.
Mostrar mais de uma medida por coluna
Cada coluna de saída é uma agregação própria, então você pode combiná-las: sessões e visualizações de página por dispositivo, ou pedidos e receita por canal.
Contar linhas em cada categoria
Para contar em vez de somar, some 1 para cada linha correspondente ou use COUNT(CASE WHEN ... THEN 1 END): o COUNT ignora os NULLs gerados pelas outras linhas. É assim que você obtém tarefas por status ou tickets por prioridade.
Transformar linhas chave-valor em colunas
Configurações e atributos de produtos costumam ser armazenados como uma linha por nome e valor. Fazer o pivot com MAX(CASE ...) gera uma linha por item, com uma coluna por atributo. O MAX funciona com texto e pega o único valor de cada grupo.
Exemplos reais de tabelas dinâmicas em SQL
Avaliação média de apps por plataforma
Omita o ELSE ao calcular médias, para que um app sem avaliações em uma plataforma mostre NULL em vez de uma nota enganosa:
-- 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 e vendas por canal em cada região
Duas medidas para cada canal, uma contagem de pedidos e um 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 suporte por atendente e prioridade
Uma contagem para cada prioridade, mais um total. O COUNT(*) continua contando todas as linhas do 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
Uma linha de configurações por usuário
Cada configuração é armazenada como um nome e um valor. Um usuário que nunca definiu uma recebe NULL naquela coluna:
-- 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
Leitura complementar sobre tabelas dinâmicas
Neste blog:
-
Como fazer pivot no PostgreSQL:
CASEecrosstab()no PostgreSQL -
Criando tabelas dinâmicas no PostgreSQL com crosstab(): a extensão
tablefuncem detalhes - Guia da instrução CASE em SQL: as condições dentro de cada coluna do pivot
- Guia de GROUP BY em SQL: agrupando linhas antes de fazer o pivot
Documentação oficial:
- PostgreSQL: expressões de agregação e FILTER e crosstab()
- MySQL: operador CASE
- SQLite: funções de agregação e FILTER
- SQL Server: PIVOT e UNPIVOT
Beekeeper Studio é um cliente de banco de dados gratuito e open source
A melhor ferramenta de consultas e edição de SQL que já usei. Tem tudo o que preciso para gerenciar meu banco de dados. - ⭐⭐⭐⭐⭐ Mit
O Beekeeper Studio é rápido, intuitivo e fácil de usar. Ele suporta vários bancos de dados e funciona muito bem no Windows, Mac e Linux.
O que os usuários dizem sobre o Beekeeper Studio
"O Beekeeper Studio substituiu completamente meu antigo fluxo de trabalho com SQL. É rápido, intuitivo e torna o trabalho com bancos de dados prazeroso de novo."
"Já testei muitas GUIs de banco de dados, mas o Beekeeper encontra o equilíbrio perfeito entre recursos e simplicidade. Simplesmente funciona."