"Pivot" di una tabella in SQL (ovvero tabulazione incrociata / tabella a campi incrociati)

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

  •  03-07-2019
  •  | 
  •  

Domanda

Sto lavorando per provare a generare un rapporto da un paio di tabelle del database. La versione semplificata si presenta così

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

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

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

Il rapporto deve essere letto in questo modo:

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

Dove ContentRowID (Value (A)) significa " Trova una riga ha un determinato CampaignID e un ContentRowId di " A " e quindi ottieni ContentValue per quella riga "

In sostanza, devo " pivot " (Penso che sia il termine corretto) le righe in colonne ...

È un database Oracle 10g ...

Qualche suggerimento?

È stato utile?

Soluzione

Questa è la mia prima pugnalata. Il perfezionamento verrà una volta che ne saprò di più sul contenuto della tabella dei contenuti.

Innanzitutto, è necessaria una tabella temporanea:

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

Ora siamo pronti per eseguire una query.

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'

Altri suggerimenti

Bill Karwin lo menziona, ma penso che questo meriti di essere sottolineato molto chiaramente:

SQL non fa ciò che stai chiedendo, quindi qualsiasi soluzione "quotata" otterrai sarà un kludge.

Se lo sai , sicuramente, funzionerà sempre su un Oracle 10, quindi sicuramente la tabella a campi incrociati di Walter Mitty potrebbe farlo. Il modo giusto per farlo è lavorare la combinazione più semplice di ordinamento nella query e nel codice dell'applicazione per disporlo nel modo giusto.

  • Funziona su altri sistemi di database,
  • non rischia di spuntare altri livelli (ricordo ad esempio che MySQL ha avuto problemi con le colonne > 255. Sei sicuro di avere libreria di interfacce così come il db stesso?)
  • non è (di solito) molto più difficile.

Se necessario, puoi solo chiedere prima i Content_Row_ID , quindi chiedere le righe necessarie, ordinate per CampaignID , ContentRowID , che ti darebbe ogni cella (popolata) in ordine da sinistra a destra, riga per riga.


Ps.

Ci sono un sacco di cose che l'uomo moderno pensa che SQL dovrebbe avere / fare che proprio non c'è. Questo è uno, gli intervalli generati è un altro, chiusura ricorsiva, ORDER BY parametrico, linguaggio di programmazione standardizzato ... l'elenco continua. (sebbene, è vero, c'è un trucco per ORDER BY )

Se non hai un numero dinamico di colonne e il tuo set di dati non è troppo grande, puoi farlo ...

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;

Ripeti la sottoquery per ogni Content_Row_ID aggiuntivo.

Per fare ciò in SQL standard, è necessario conoscere tutti i valori distinti di Content_Row_ID ed eseguire un join per valore distinto. Quindi è necessaria una colonna per ogni valore distinto di 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);

Man mano che il numero di valori distinti aumenta, questa query diventa troppo costosa per essere eseguita in modo efficiente. Probabilmente è più facile recuperare i dati più semplicemente e riformattarli in PL / SQL o nel codice dell'applicazione.

Bill Karwin e Anders Eurenius hanno ragione sul fatto che non esiste una soluzione semplice, né esiste alcuna soluzione quando il numero di valori di colonna risultanti non è noto in anticipo. Oracle 11g lo semplifica in qualche modo con l'operatore PIVOT , ma le colonne devono ancora essere conosciute in anticipo e questo non soddisfa i criteri di 10 g della tua domanda.

Se hai bisogno di un numero dinamico di colonne, non credo che ciò possa essere fatto in SQL standard che, purtroppo, supera le mie conoscenze. Ma ci sono funzionalità di Oracle che possono farlo. Ho trovato alcune risorse:

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 hai " Oracle, il riferimento completo " cerca una sezione intitolata "Girare una tabella dalla sua parte". Questo fornisce esempi dettagliati e istruzioni per eseguire un pivot, sebbene l'edizione che ho non lo definisca un pivot.

Un altro termine per "ruotare una tabella" è la tavola rotonda.

Uno degli strumenti più semplici da utilizzare per eseguire la tabella a campi incrociati è MS Access. Se si dispone di MS Access e è possibile stabilire un collegamento di tabella da un database di Access alla tabella di origine, ci si trova già a metà strada.

A quel punto, puoi avviare la "Query Wizard", e chiedergli di creare una query a campi incrociati per te. È davvero facile come rispondere alle domande che il mago ti pone. Il lato sfortunato di questa soluzione è che se si guarda alla query risultante nella vista SQL, vedrai alcuni SQL che sono peculiari del dialetto Access di SQL e che non possono essere utilizzati, in generale, su altre piattaforme.

Potresti anche essere in grado di scaricare alcuni semplici strumenti di analisi dal sito Web Oracle e utilizzare uno di questi strumenti per eseguire una tabella a campi incrociati per te.

Ancora una volta, se vuoi davvero farlo in SQL, " Oracle, il riferimento completo " dovrebbe aiutarti.

Se non conosci il numero di colonne in anticipo, riporta semplicemente una normale query sql e usa il codice lato server come ho elencato qui: Compilazione di Datagrid e query SQL

Ho fatto una soluzione con questo SQL. Avevo bisogno che le righe fossero il numero di classi e che le colonne fossero il riepilogo di ogni classe per mese, quindi la prima colonna è il riepilogo della riga e ciascuna delle colonne è il riepilogo di ogni mese e l'ultima riga è il riepilogo della colonna completa mese per mese.

Buona fortuna

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top