Fazer um “pivot” permite reestruturar dados de uma estrutura de dados tipicamente normalizada para um formato diferente, e assim formatar as informações que você precisa de uma maneira muito mais legível aos olhos do usuário que requisita as informações.
As cláusulas PIVOT e UNPIVOT fazem parte das novas features da versão 11g. Nas versões anteriores era necessário uma codificação mais elaborada e complicda, usando CASE. Agora, além de facilitar na codificação, esta operação foi otimizada (segundo a documentação oficial do Oracle) – e assim a operação ganhou uma boa melhora de desempenho em relação a codificação com CASE, considerando obviamente SELECT’s equivalentes.
PIVOT
Permite “nivelar” uma tabela normalizada. Muito útil para formatar visualmente os dados para o seu aplicativo ou para um relatório. A melhor maneira de entender tanto o funcionamento do PIVOT quanto o seu resultado é mostrar um exemplo.
Vamos imaginar que temos uma tabela de VENDAS, em uma empresa que distribui chocolates – e nós queremos relatórios sumarizados, mostrando totais de cada chocolate em colunas separadas, e também agrupando por cliente.
Vamos ao que interessa!
Tabela criada, agora vamos popular a tabela:
Agora vamos usar o PIVOT para mostrar o total vendido de cada produto em apenas uma linha, com um produto por coluna.
UNPIVOT
A cláusula UNPIVOT faz o caminho inverso: pega uma tabela “pivoteada” e normaliza. Como exemplo, vou usar os mesmos dados para criar uma nova tabela.
E finalmente o uso do UNPIVOT!
Opa! Peraí Milton, mas no primeiro exemplo eram 10 registros. E agora ficaram apenas 9 registros. Por que?
Muito simples. Reparem que no conjunto inicial de registros, o mesmo cliente (10) comprou o mesmo produto (Charge) por duas vezes. Após agrupar os registros em outra tabela, como fizemos na vendas_2, fica impossível o Oracle adivinhar que essa quantidade veio de 2 registros diferentes. Na prática, ou melhor dizendo, em um sistema do mundo real, esta tabela teria mais algum campo para diferenciar essas vendas (um campo com a data da venda, por exemplo), e caso o agrupamento fosse feito por este campo, aí sim seria possível a quebra mais detalhada dos registros.
Milton, estou lendo o Livro Oracle Database 11g do Jason Price e estou justamente na parte que fala sobre PIVOT e UNPIVOT, com certeza sua explicação serviu para clarear mais ainda o estudo sobre este tópico. Muito obrigado pela valiosa colaboração.
Eu que agradeço a visita Daniel!
Abraço
Milton , me corrija se estiver errado, mas uma explicação para o PIVOT é : ele transforma colunas em linhas e UNPIVOT transforma linhas em colunas, certo??
Abrç
Yves, a grosso modo é mais ou menos isso mesmo que eles fazem!
É por isso que chamamos de “pivô” – usamos uma das colunas como ponto de apoio, ou seja, como pivô, para mover as colunas para linhas, e vice-versa.
Grande Milton, ótimo Artigo. Porém tenho uma dúvida. na clásula IN, coloco os valores desde que os saiba. Quando os valores não são conhecidos como proceder ? Pesquisando, vi alguns autores incluirem uma Query selecionando os valores. tentei incluir algo como ‘SELECT DISTINCT produto FROM vendas’, porém ocorreu a seguinte erro na hora da execução: “Expressão não encontrada” e o erro aponta justamente no SELECT dentro do IN. É realmente possível fazer isto ? Posso estar fazendo algo de errado ? Abração. Adrian
Cole o seu select com o erro aqui pra gente!
Boa tarde Milton, estou com o mesmo problema que Adrian. pode me ajudar?
Eu precisaria de mais detalhes. Qual a versão exata do Oracle?
Eu teria que reproduzir o select aqui pra testar.
11G Enterprise Release 11.2.0.1.0.
Olá,
Parabéns pelo artigo, muito bom.
Mas tenho uma dúvida sobre o PIVOT, e gostaria de saber se você pode me ajudar.
É mais ou menos a mesma dúvida do post anterior.
Eu estou fazendo o PIVOT, porém, eu não conheço os resultados retornados na linha pivô, portanto eu necessitaria fazer um sub-select na claúsula IN do PIVOT, o comando seria mais ou menos assim:
SELECT * FROM (TABELA)
PIVOT SUM(COLUNA_1) FOR COLUNA_2 (select coluna_2 from tabela where coluna_3 in (valor1, valor2, valor3)
O exemplo é ficticio, porém, creio que tenha ficado claro.
Em pesquisa na internet, só achei possível fazer sub-select na clausula in, qdo utilizamos o PIVOT XML, mas no meu caso, ele não é util.
Alguma solução amigo?
Obrigado, parabens e abraço.
Bom dia Milton, estou tentando fazer um UNPIVOT mais não consegui, se puder me ajudar lhe agradeço muito, tenho o resultado de um select o seguinte:
DTNEG CODGRUPAI POSIT_PAI
——- ———- ———-
03/2013 100000 721
03/2013 200000 569
01/2013 200000 2428
01/2013 100000 2925
02/2013 200000 2066
02/2013 100000 2474
so que preciso levar ele para o relatorio assim:
CODGRUPAI 01/2013 02/2013 03/2013
—————– ———— ———— ————–
100000 2925 2474 721
200000 2428 2066 569
Segue o Select, essa é so uma partizinha do relatorio, mais com essa logica consigo fazer todo restante, soq ue a quantidade de coluna de Datas é variavel, isso de acordo com o período selecionado.
SELECT * FROM (
SELECT DISTINCT
POSIT_PAI.DTNEG
,POSIT_PAI.GRUPOPAI AS CODGRUPAI
,POSIT_PAI.POSITIVACAO AS POSIT_PAI
FROM
(SELECT PO.DTNEG, PO.CODGRUPAI AS GRUPOPAI,COUNT(PO.CODPARC) AS POSITIVACAO FROM
(SELECT V.DTNEG,V.CODPARC,V.CODGRUPAI,V.POSIT_V,CASE WHEN D.POSIT_D IS NULL THEN 0 ELSE D.POSIT_D END AS POSIT_D FROM
(SELECT DISTINCT TO_CHAR(CAB.DTNEG,’MM/YYYY’) AS DTNEG, CAB.CODPARC, GRUPAI.CODGRUPAI, COUNT(CAB.NUNOTA) AS POSIT_V FROM TGFCAB CAB INNER JOIN TGFTOP TOP ON
(TOP.CODTIPOPER = CAB.CODTIPOPER AND TOP.DHALTER = CAB.DHTIPOPER)
INNER JOIN TGFITE ITE ON (ITE.NUNOTA = CAB.NUNOTA)
INNER JOIN TGFPRO PRO ON (PRO.CODPROD = ITE.CODPROD)
INNER JOIN TGFGRU GRU ON GRU.CODGRUPOPROD = PRO.CODGRUPOPROD
INNER JOIN TGFGRU GRUFIL ON GRUFIL.CODGRUPOPROD = GRU.CODGRUPAI
INNER JOIN TGFGRU GRUPAI ON GRUPAI.CODGRUPOPROD = GRUFIL.CODGRUPAI
INNER JOIN TGFPAR PAR ON (PAR.CODPARC = CAB.CODPARC)
INNER JOIN TSICID CID ON (CID.CODCID = PAR.CODCID)
INNER JOIN TSIUFS UFS ON (UFS.CODUF = CID.UF)
LEFT JOIN TGFVEN VEN ON (VEN.CODVEND = CAB.CODVEND)
WHERE TOP.GRUPO = ‘VENDA’ AND CAB.DTMOV BETWEEN :DT1 AND :DT2 AND (PRO.MARCA = :MARCA OR :MARCA IS NULL)
AND (VEN.CODVEND = :CODVEND OR :CODVEND IS NULL) AND (VEN.AD_EQUIPE = :EQUIPE OR :EQUIPE IS NULL) AND (UFS.UF = :UF OR :UF IS NULL) AND (CID.NOMECID = :NOMECID OR :NOMECID IS NULL) GROUP BY CAB.DTNEG, CAB.CODPARC, GRUPAI.CODGRUPAI) V
LEFT JOIN
(SELECT DISTINCT TO_CHAR(CAB.DTNEG,’MM/YYYY’) AS DTNEG, CAB.CODPARC, GRUPAI.CODGRUPAI, COUNT(CAB.NUNOTA) AS POSIT_D FROM TGFCAB CAB INNER JOIN TGFTOP TOP ON
(TOP.CODTIPOPER = CAB.CODTIPOPER AND TOP.DHALTER = CAB.DHTIPOPER)
INNER JOIN TGFITE ITE ON (ITE.NUNOTA = CAB.NUNOTA)
INNER JOIN TGFPRO PRO ON (PRO.CODPROD = ITE.CODPROD)
INNER JOIN TGFGRU GRU ON GRU.CODGRUPOPROD = PRO.CODGRUPOPROD
INNER JOIN TGFGRU GRUFIL ON GRUFIL.CODGRUPOPROD = GRU.CODGRUPAI
INNER JOIN TGFGRU GRUPAI ON GRUPAI.CODGRUPOPROD = GRUFIL.CODGRUPAI
INNER JOIN TGFPAR PAR ON (PAR.CODPARC = CAB.CODPARC)
INNER JOIN TSICID CID ON (CID.CODCID = PAR.CODCID)
INNER JOIN TSIUFS UFS ON (UFS.CODUF = CID.UF)
LEFT JOIN TGFVEN VEN ON (VEN.CODVEND = CAB.CODVEND)
WHERE (TOP.GRUPO = ‘DEV VENDA’ AND NOT CAB.CODTIPOPER IN (39,41,42)) AND CAB.DTMOV BETWEEN :DT1 AND :DT2
AND (PRO.MARCA = :MARCA OR :MARCA IS NULL) AND (VEN.CODVEND = :CODVEND OR :CODVEND IS NULL)
AND (VEN.AD_EQUIPE = :EQUIPE OR :EQUIPE IS NULL) AND (UFS.UF = :UF OR :UF IS NULL) AND (CID.NOMECID = :NOMECID OR :NOMECID IS NULL) GROUP BY CAB.DTNEG, CAB.CODPARC, GRUPAI.CODGRUPAI) D ON
D.CODPARC = V.CODPARC AND D.CODGRUPAI = V.CODGRUPAI AND D.DTNEG = V.DTNEG) PO
WHERE PO.POSIT_V > PO.POSIT_D GROUP BY PO.DTNEG,PO.CODGRUPAI) POSIT_PAI)
UNPIVOT (POSIT_PAI FOR DTNEG IN (CODGRUPAI))
Emerson:
Por que vc está tentando fazer UNPIVOT? Pelo que entendi, olhando o resultado que vc precisa, o que vc quer é um PIVOT, e não UNPIVOT.
Dê uma lida com mais atenção no artigo, veja a diferença entre os dois.
Repare que no meu exemplo eu transformei alguns valores (nomes de chocolates) em Colunas.
Vc quer fazer a mesma coisa, mas com datas (mês/ano).
Muito bom! Para mim que estou tirando a SQL Expert este artigo esclareceu muita coisa. Obrigado!