“Dinamização” uma tabela em SQL (isto é, transversal tabulação / tabulação cruzada)

StackOverflow https://stackoverflow.com/questions/213476

  •  03-07-2019
  •  | 
  •  

Pergunta

Eu estou trabalhando na tentativa de gerar um relatório a partir de um par de tabelas de banco de dados. A versão olhares simplificados como este

Campaign
----------
CampaignID

Source
-----------------------
Source_ID | Campaign_ID

Content
---------------------------------------------------------
Content_ID | Campaign_ID | Content_Row_ID | Content_Value

O relatório precisa ler como este:

CampaignID - SourceID - ContentRowID(Value(A)) - ContentRowID(Value(B))

Onde ContentRowID (Valor (A)) significa "Encontre uma linha a tem um determinado campaignId, e uma ContentRowId de "A" e, em seguida, obter o ContentValue para essa linha"

Essencialmente, eu tenho que "pivot" (eu acho que é o termo correto) as linhas em colunas ...

É banco de dados de um Oracle 10g ...

Todas as sugestões?

Foi útil?

Solução

Esta é minha primeira facada nele. Refinamento vindo uma vez eu sei mais sobre o conteúdo da tabela de conteúdo.

Em primeiro lugar, você precisa de uma tabela temporária:

CREATE TABLE pivot (count integer);
INSERT INTO pivot VALUES (1);
INSERT INTO pivot VALUES (2);

Agora estamos prontos para consulta.

SELECT campaignid, sourceid, a.contentvalue, b.contentvalue
FROM content a, content b, pivot, source
WHERE source.campaignid = content.campaignid
AND pivot = 1 AND a.contentrowid = 'A'
AND pivot = 2 AND b.contentrowid = 'B'

Outras dicas

Bill Karwin menciona isso, mas acho que isso merece ser salientado muito claramente:

O SQL não fazer o que você está pedindo, portanto, qualquer "solução" que você começa vai ser um truque.

Se você sei , com certeza, ele sempre vai ser executado em um Oracle 10, em seguida, com certeza, tabulação cruzada de Walter Mitty pode fazê-lo. A maneira certa de fazê-lo é trabalhar a combinação mais fácil de ordem de classificação no código de consulta e aplicação para colocá-lo direito.

  • Ele funciona em outros sistemas de banco de dados,
  • não arriscar quaisquer outras camadas crapping para fora (eu me lembro MySQL tendo um problema com> 255 colunas por exemplo. Tem certeza de que biblioteca de interface lida, bem como o próprio db?)
  • -lo de (geralmente) não muito mais difícil.

Se você precisar, você pode simplesmente pedir para os Content_Row_IDs primeiro, em seguida, pedir o que quer que as linhas que você precisa, ordenados por CampaignID, ContentRowID, que lhe daria cada célula (povoado) na esquerda para a direita, linha por fim -line.


Ps.

Há um monte de coisas que o homem moderno pensa SQL deve ter / fazer isso simplesmente não está lá. Este é um, faixas gerado é outro, o fechamento recursiva, ORDER BY paramétrico, linguagem de programação padronizada ... a lista continua. (Embora, na verdade, há um truque para ORDER BY)

Se você não tem um número dinâmico de colunas e seu conjunto de dados não é muito grande você poderia fazer isso ...

SELECT CampaignID, SourceID, 
   (SELECT Content_Value FROM Content c 
      WHERE c.Campaign_ID=s.Campaign_ID 
      AND Content_Row_ID = 39100 
      AND rownum<=1) AS Value39100,
   (SELECT Content_Value FROM Content c 
      WHERE c.Campaign_ID=s.Campaign_ID 
      AND Content_Row_ID = 39200 
      AND rownum<=1) AS Value39200
FROM Source s;

Repita a subconsulta para cada Content_Row_ID adicional.

Para fazer isso no SQL padrão, você precisa saber todos os valores distintos de Content_Row_ID, e fazer uma junção por valor distinto. Então você precisa de uma coluna por valor distinto de Content_Row_ID.

SELECT CA.Campaign_ID, 
  C1.Content_Value AS "39100",
  C2.Content_Value AS "39200",
  C3.Content_Value AS "39300"
FROM Campaign CA
  LEFT OUTER JOIN Content C1 ON (CA.Campaign_ID = C1.Campaign_ID 
    AND C1.Content_Row_ID = 39100)
  LEFT OUTER JOIN Content C2 ON (CA.Campaign_ID = C2.Campaign_ID 
    AND C2.Content_Row_ID = 39200)
  LEFT OUTER JOIN Content C3 ON (CA.Campaign_ID = C3.Campaign_ID 
    AND C3.Content_Row_ID = 39300);

Como o número de valores distintos cresce, esta consulta se torna muito caro para executar de forma eficiente. É provavelmente mais fácil de buscar os dados de forma mais simples e reformatá-lo em PL / SQL ou no código do aplicativo.

Bill Karwin e Anders Eurenius está correto que não há uma solução que é simples, nem há qualquer solução em tudo quando o número de valores da coluna resultante não é conhecido antecipadamente. Oracle 11g faz simplificá-lo um pouco com operador PIVOT, mas as colunas ainda têm de ser conhecidos com antecedência e que não cumpre os critérios de 10g de sua pergunta.

Se você precisa de um número dinâmico de colunas, eu não acredito que isso pode ser feito em SQL padrão, que, infelizmente, excede meu conhecimento. Mas há recursos do Oracle que podem fazer isso. Eu encontrei alguns recursos:

http://www.sqlsnippets.com/en/topic-12200.html

http: //asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:124812348063#41097616566309

Se você tem "Oracle, a referência completa" olhar para uma seção intitulada "Transformar uma tabela em seu lado". Isto dá exemplos detalhados e instruções para a realização de um pivô, embora a edição que eu tenho não chamá-lo de um pivô.

Outro termo para "girando uma mesa" é tabulação cruzada.

Uma das ferramentas mais fáceis de usar para a realização de tabulação cruzada é MS Access. Se você tiver MS Access, e você pode estabelecer uma ligação tabela a partir de um banco de dados para a sua mesa de origem, você já é meio caminho andado.

Nesse ponto, você pode pôr em marcha o "Assistente de consulta", e pedir-lhe para construir uma consulta de referência cruzada para você. É realmente tão fácil como responder às perguntas do assistente pede-lhe. O lado triste desta solução é que se olhar para a consulta resultante na vista SQL, você vai ver alguns SQL desse peculiar ao dialeto acesso de SQL, e não pode ser utilizado, em geral, através de outras plataformas.

Você também pode ser capaz de baixar algumas ferramentas de análise simples do site da Oracle, e usar uma dessas ferramentas para executar uma tabulação cruzada para você.

Mais uma vez, se você realmente quer fazer isso em SQL, "Oracle, a referência completa" deve ajudá-lo.

Se você não sabe o número de colunas na frente apenas trazer de volta um código lado normal de consulta SQL e servidor uso como eu aqui: Encher DataGrid e SQL query

Eu fiz uma solução com este SQL. I necessário que as linhas sejam o número de classes e as colunas ser o sumary de cada classe por mês, por isso, a primeira coluna é o sumary de linha e cada ohters colunas são o sumary de cada mês, ea última linha é o sumary do mês coluna completa por mês.

Boa sorte

Select DS.Cla,
Sum(case
when (Extract(year from DS.Data) =:intYear) then DS.PRE
else 0
end) as ToTal,
Sum(case
when (Extract(month from DS.Data) =1) then DS.PRE
else 0
end) as Jan,
Sum(case
when (Extract(month from DS.Data) =2) then DS.PRE
else 0
end) as FEV,
Sum(case
when (Extract(month from DS.Data) =3) then DS.PRE
else 0
end) as MAR,
Sum(case
when (Extract(month from DS.Data) =4) then DS.PRE
else 0
end) as ABR,
Sum(case
when (Extract(month from DS.Data) =5) then DS.PRE
else 0
end) as MAI,
Sum(case
when (Extract(month from DS.Data) =6) then DS.PRE
else 0
end) as JUN,
Sum(case
when (Extract(month from DS.Data) =7) then DS.PRE
else 0
end) as JUL,
Sum(case
when (Extract(month from DS.Data) =8) then DS.PRE
else 0
end) as AGO,
Sum(case
when (Extract(month from DS.Data) =9) then DS.PRE
else 0
end) as SETE,
Sum(case
when (Extract(month from DS.Data) =10) then DS.PRE
else 0
end) as OUT,
Sum(case
when (Extract(month from DS.Data) =11) then DS.PRE
else 0
end) as NOV,
Sum(case
when (Extract(month from DS.Data) =12) then DS.PRE
else 0
end) as DEZ
from Dados DS
Where DS.Cla > 0
And Extract(Year from DS.Data) = :intYear
group by DS.CLA

Union All

Select 0*count(DS.cla),  0*count(DS.cla),
Sum(case
when (Extract(month from DS.Data) =1) then DS.PRE
else 0
end) as JAN,
Sum(case
when (Extract(month from DS.Data) =2) then DS.PRE
else 0
end) as FEV,
Sum(case
when (Extract(month from DS.Data) =3) then DS.PRE
else 0
end) as MAR,
Sum(case
when (Extract(month from DS.Data) =4) then DS.PRE
else 0
end) as ABR,
Sum(case
when (Extract(month from DS.Data) =5) then DS.PRE
else 0
end) as MAI,
Sum(case
when (Extract(month from DS.Data) =6) then DS.PRE
else 0
end) as JUN,
Sum(case
when (Extract(month from DS.Data) =7) then DS.PRE
else 0
end) as JUL,
Sum(case
when (Extract(month from DS.Data) =8) then DS.PRE
else 0
end) as AGO,
Sum(case
when (Extract(month from DS.Data) =9) then DS.PRE
else 0
end) as SETE,
Sum(case
when (Extract(month from DS.Data) =10) then DS.PRE
else 0
end) as OUT,
Sum(case
when (Extract(month from DS.Data) =11) then DS.PRE
else 0
end) as NOV,
Sum(case
when (Extract(month from DS.Data) =12) then DS.PRE
else 0
end) as DEZ
from Dados DS
Where DS.Cla > 0
And Extract(Year from DS.Data) = :intYear
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top