Pivot ou CrossTab (invertendo linhas em colunas)

4

Posted on : 02-06-2008 | By : .rafa | In : Banco de Dados, Oracle, PL/SQL, Programação

Imagine você está tentando para criar um jogo de resultados onde os registros precisam ser colunas, ou vice-versa.
A essência, você precisa "girar" os registros em colunas, ou vice-versa.
Muitas vezes há relatorios que exigêm isto e para que não precisarmos de criar functions ou qualquer outra coisa para solucionar isso podemos usar o chamado pivot ou crosstab queries.

Uma questão de pivot simples é realizada fazendo o seguinte:
1. Acrescente algum tipo de conta ou número de fila ao seu questionamento, se necessário para o agrupamento;
2. Então use sua query original como uma sub-query;
3. Use "decode" para se transformar os registros em colunas;
4. Use "MAX ou SUM" para "agrupar" os registros múltiplos em únicas coluna. E não pode ser esquecido de agrupar.

Exemplo prático:

1. Criar uma tabela:

CREATE TABLE pontuacao (
ano NUMBER(4),
time VARCHAR2(16),
pontos NUMBER(3)
);

2. Inserir alguns registros para simular o pivot:

INSERT INTO pontuacao (ano, time, pontos) VALUES (2004, 'Flamengo', 21);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2004, 'Sao Paulo', 28);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2004, 'Palmeiras', 19);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2004, 'Vasco', 10);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2003, 'Flamengo', 18);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2003, 'Sao Paulo', 26);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2003, 'Palmeiras', 2);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2003, 'Vasco', 14);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2002, 'Flamengo', 16);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2002, 'Sao Paulo', 27);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2002, 'Palmeiras', 15);
INSERT INTO pontuacao (ano, time, pontos) VALUES (2002, 'Vasco', 10);

3. Selecionando todos os registros com um subselect e já mostrando no formado de colunas:

SELECT time,
DECODE (ano, 2002, pontos, NULL) Ano2002,
DECODE (ano, 2003, pontos, NULL) Ano2003,
DECODE (ano, 2004, pontos, NULL) Ano2004
FROM (SELECT ano, time, pontos
FROM pontuacao);
 
TIME                ANO2002    ANO2003    ANO2004
---------------- ---------- ---------- ----------
Flamengo                                       21
Sao Paulo                                      28
Palmeiras                                      19
Vasco                                          10
Flamengo                            18
Sao Paulo                           26
Palmeiras                            2
Vasco                               14
Flamengo                 16
Sao Paulo                27
Palmeiras                15
Vasco                    10
 
12 rows selected

Obs.: Deve-se notar que neste resultado não irá apresentar agrupado os valores com seu determinado time, isso por causa que há 3 linhas para cada ano e o decode passa 3 vezes por time assim gerando 3 vezes de cada time.

4. Outro exemplo, porem agrupando os valores e mostrando os maiores valores de cada agrupamento no caso "time":

SELECT time,
MAX (DECODE (ano, 2002, pontos, NULL)) Ano2002,
MAX (DECODE (ano, 2003, pontos, NULL)) Ano2003,
MAX (DECODE (ano, 2004, pontos, NULL)) Ano2004
FROM (SELECT ano, time, pontos
FROM pontuacao)
GROUP BY time;
 
TIME                ANO2002    ANO2003    ANO2004
---------------- ---------- ---------- ----------
Flamengo                 16         18         21
Palmeiras                15          2         19
Sao Paulo                27         26         28
Vasco                    10         14         10

5. Um exemplo totalizando os times por ano, utilizando agora a função de agrupamento rollup:

SELECT decode(GROUPING(time),1,'Total',time) AS time,
sum (DECODE (ano, 2002, pontos, NULL)) Ano2002,
sum (DECODE (ano, 2003, pontos, NULL)) Ano2003,
sum (DECODE (ano, 2004, pontos, NULL)) Ano2004
FROM (SELECT ano, time, pontos
FROM pontuacao)
GROUP BY rollup(time);
 
TIME                ANO2002    ANO2003    ANO2004
---------------- ---------- ---------- ----------
Flamengo                 16         18         21
Palmeiras                15          2         19
Sao Paulo                27         26         28
Vasco                    10         14         10
Total                    68         60         78

Obs.: Lembrando que no Oracle11g tem uma função específica para o pivot ou crosstab, mais detalhes otn.oracle.com

Comments

Meu! parabéns pelo post!
Você nem imagina o que esse esquema do Pivot abriu de possibilidades pra mim aqui no trampo! Antes, pra eu fazer a quantidade de estoque de todas as 12 lojas, por exemplo, eu criava 12 sub_selects (uma pra cada loja) e assim conseguia o resultado que eu queria:
exemplo:

select loja1.codigo, loja1.qtde, loja2.qtde, loja3.qtde, etc .. .
from (select codigo, sum(qtde) qtde
from estoque e
where e.loja = 1
group by codigo) loja1,

(select codigo, sum(qtde) qtde
from estoque e
where e.loja = 1
group by codigo) loja2,

(select codigo, sum(qtde) qtde
from estoque e
where e.loja = 1
group by codigo) loja3,

etc ..

where loja1.codigo = loja2.codigo(+)
and loja1.codigo = loja3.codigo(+) etc…

Nem preciso falar que essa gambiarra que a gente fazia demorava mais de 100 vezes do que esse sistema que você ensinou!

Agora, o que demorava 20 minutos pra ficar pronto, leva apenas 5 segundos! hehehehe!

Mais uma vez, muito obrigado e tomara que você sempre tenha vários coelhos como esse em sua enorme cartola!

Parabéns !!! Muito útil de modo geral. Especificamente irá facilitar muito a geração de gráficos para ferramentas que usam fontes de dados tabulares.

Nossa cara, isso era uma coisa que sempre procurei e nunca consegui encontrar. Ótimo post. Coisa super simples e funcional pra caramba. É o tipo de coisa que muitas vezes precisamos para desenvolver relatórios e temos que fazer muita coisa pra chegar ao resultado.

Muito bom mesmo esse post. Obrigado pela ajuda.

Post a comment

*