Ruota le righe in più colonne


21

Ho un'istanza di SQL Server che ha un server collegato a un server Oracle. C'è una tabella sul server Oracle chiamata PersonOptionsche contiene i seguenti dati:

╔══════════╦══════════╗
║ PersonID ║ OptionID ║
╠══════════╬══════════╣
║        1 ║ A        ║
║        1 ║ B        ║
║        2 ║ C        ║
║        3 ║ B        ║
║        4 ║ A        ║
║        4 ║ C        ║
╚══════════╩══════════╝

Devo ruotare questi dati in modo che i risultati siano:

╔══════════╦═════════╦══════════╦══════════╗
║ PersonID ║ OptionA ║ Option B ║ Option C ║
╠══════════╬═════════╬══════════╬══════════╣
║        1 ║       1 ║        1 ║          ║
║        2 ║         ║          ║        1 ║
║        3 ║         ║        1 ║          ║
║        4 ║       1 ║          ║        1 ║
╚══════════╩═════════╩══════════╩══════════╝

Eventuali suggerimenti?

Risposte:


20

Esistono alcuni modi per eseguire questa trasformazione dei dati. Avrai accesso alla PIVOTfunzione che sarà la più semplice, ma in caso contrario puoi utilizzare una funzione aggregata e a CASE.

Versione aggregata / caso:

select personid,
  max(case when optionid = 'A' then 1 else 0 end) OptionA,
  max(case when optionid = 'B' then 1 else 0 end) OptionB,
  max(case when optionid = 'C' then 1 else 0 end) OptionC
from PersonOptions
group by personid
order by personid;

Vedi SQL Fiddle with Demo

Perno statico:

select *
from
(
  select personid, optionid
  from PersonOptions
) src
pivot
(
  count(optionid)
  for optionid in ('A' as OptionA, 'B' OptionB, 'C' OptionC)
) piv
order by personid

Vedi SQL Fiddle with Demo

Versione dinamica:

Le due versioni precedenti funzionano alla grande se hai un numero noto di valori, ma se i tuoi valori sono sconosciuti, allora vorrai implementare sql dinamico e in Oracle puoi usare una procedura:

CREATE OR REPLACE procedure dynamic_pivot_po(p_cursor in out sys_refcursor)
as
    sql_query varchar2(1000) := 'select personid ';

    begin
        for x in (select distinct OptionID from PersonOptions order by 1)
        loop
            sql_query := sql_query ||
                ' , min(case when OptionID = '''||x.OptionID||''' then 1 else null end) as Option_'||x.OptionID;

                dbms_output.put_line(sql_query);
        end loop;

        sql_query := sql_query || ' from PersonOptions group by personid order by personid';
        dbms_output.put_line(sql_query);

        open p_cursor for sql_query;
    end;
/

Quindi restituisci i risultati, utilizzerai:

variable x refcursor
exec dynamic_pivot_po(:x)
print x

I risultati sono gli stessi con tutte le versioni:

| PERSONID | OPTIONA | OPTIONB | OPTIONC |
------------------------------------------
|        1 |       1 |       1 |       0 |
|        2 |       0 |       0 |       1 |
|        3 |       0 |       1 |       0 |
|        4 |       1 |       0 |       1 |

Tuttavia, la soluzione Pivot statico presuppone che ci siano solo tre opzioni. Cosa succede se si dispone di un numero potenzialmente illimitato di opzioni? ABCDEFGHIJK per esempio? Non c'è un modo per rendere dinamico il perno con sql regolare? Invece di rendere le opzioni le intestazioni di colonna, possiamo semplicemente inserirle nelle colonne? Quindi sarebbe simile a questo: | PERSONID | Colonna2 | Colonna3 | Colonna4 | ------------------------------------------ | 1 | A | B | null | | 2 | C | null | null | | 3 | null | C | null |
Matteo,

1
@Matthew avresti dovuto usare Dynamic Sql come ho dimostrato nell'ultima parte della risposta.
Taryn

Grazie per la risposta rapida! In realtà lo faccio creando una nuova colonna e riempiendo tutte le opzioni lì separate da virgole. Il col viene generato da una sottoquery selezionando dalle stesse tabelle where a.personId = a2.personId order by a2.personId for xml path(''). a2 è la tabella nella sottoquery. Quindi separo i dati in Excel utilizzando il testo in colonne con virgola come delimitatore. Speravo di trovare un modo per farlo in sql normale senza dover scrivere una procedura, ma forse non c'è modo. Devo correre al momento, ma proverò a pubblicare un esempio per spiegarlo meglio.
Matteo,

9

Questo sarebbe l'equivalente nella sintassi di SQL Server. Sulla base della mia lettura dei documenti Oracle, NULLIF e PIVOT sembrano avere lo stesso formato dei loro parenti di SQL Server. La sfida sarà l'elenco pivot che deve essere statico a meno che non si renda dinamica la query, come dimostra Itzik, ma non ho idea se questo possa essere tradotto in P / SQL

WITH PersonOptions(PersonID, OptionId) AS
(
    SELECT 1, 'A'
    UNION ALL SELECT 1, 'B'
    UNION ALL SELECT 2, 'C'
    UNION ALL SELECT 3, 'B'
    UNION ALL SELECT 4, 'A'
    UNION ALL SELECT 4, 'C'
)
SELECT
    P.PersonId
,   NULLIF(P.A, 0) AS OptionA
,   NULLIF(P.B, 0) AS OptionB
,   NULLIF(P.C, 0) AS OptionC
FROM
    PersonOptions  PO
    PIVOT 
    (
        COUNT(PO.OptionId)
        FOR OPtionId IN (A, B, C)
    )  P;

5

Preferisco ruotare manualmente la query, ma è possibile utilizzare PIVOTanche.

SELECT PersonID,
MAX(CASE WHEN OptionId ='A' THEN 1 END) AS OptionA,
MAX(CASE WHEN OptionId ='B' THEN 1 END) AS OptionB, 
MAX(CASE WHEN OptionId ='C' THEN 1 END) AS OptionC
FROM PersonOptions
GROUP BY PersonID

1
Sentiti libero di spiegare questo solo un po 'di più. Cosa prevede il perno che gli altri non possono? E quando si rompe? Ricorda che stai rispondendo ai posteri, non a qualcuno con specifiche competenze di dominio anche nelle cose che conosci.
jcolebrand

2
@jcolebrand: si tratta più di preferenze personali - io stesso penso che la PIVOTsintassi sia più contorta rispetto all'approccio che uso. Tuttavia, sono consapevole del fatto che entrambi danno lo stesso risultato e sono d'accordo che altre persone potrebbero pensare diversamente.
a1ex07,

1
Suggerimento: utilizzare il pulsante Modifica ;-) ~ Ci piace incoraggiare più di una risposta in codice.
jcolebrand
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.