Dinamizar linhas em várias colunas

21

Eu tenho uma instância do SQL Server que possui um servidor vinculado a um servidor Oracle. Há uma tabela no servidor Oracle chamada PersonOptionsque contém os seguintes dados:

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

Eu preciso dinamizar esses dados para que os resultados sejam:

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

Alguma sugestão?

Eu não
fonte

Respostas:

20

Existem algumas maneiras de executar essa transformação de dados. Você tem acesso à PIVOTfunção, então será a mais fácil, mas, se não, poderá usar uma função agregada e a CASE.

Versão agregada / 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;

Veja SQL Fiddle com demonstração

Pivô estático:

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

Veja SQL Fiddle com demonstração

Versão dinâmica:

As duas versões acima funcionam muito bem se você tiver um número conhecido de valores, mas se seus valores forem desconhecidos, você desejará implementar o sql dinâmico e, no Oracle, poderá usar um procedimento:

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;
/

Então você retorna os resultados, você usará:

variable x refcursor
exec dynamic_pivot_po(:x)
print x

Os resultados são os mesmos com todas as versões:

| PERSONID | OPTIONA | OPTIONB | OPTIONC |
------------------------------------------
|        1 |       1 |       1 |       0 |
|        2 |       0 |       0 |       1 |
|        3 |       0 |       1 |       0 |
|        4 |       1 |       0 |       1 |
Taryn
fonte
No entanto, a solução Static Pivot assume que existem apenas três opções. E se você tiver um número potencialmente ilimitado de opções? ABCDEFGHIJK por exemplo? Não existe uma maneira de dinamizar o pivô com o sql regular? Em vez de tornar as opções os cabeçalhos das colunas, poderíamos simplesmente colocá-las nas colunas? Então ficaria assim: | PERSONID | Coluna2 Coluna3 | Coluna4 | ------------------------------------------ | 1 | A N | | 2 C | nulo | nulo | | 3 null | C null |
Matthew
1
@ Matthew, você teria que usar o Dynamic Sql, como demonstro na última parte da resposta.
Taryn
Obrigado pela resposta rápida! Na verdade, faço isso criando uma nova coluna e colocando todas as opções separadas por vírgulas. A coluna gera a partir de uma subconsulta selecionando as mesmas tabelas where a.personId = a2.personId order by a2.personId for xml path(''). a2 é a tabela na subconsulta. Em seguida, separo os dados no Excel usando texto em colunas com vírgula como delimitador. Eu esperava encontrar uma maneira de fazer isso no sql regular sem precisar escrever um procedimento, mas talvez não haja nenhuma maneira. Tem que correr no momento, mas vou tentar postar um exemplo disso para explicar melhor.
6188 Matthew Matthew
9

Isso seria o equivalente na sintaxe do SQL Server. Com base na minha leitura dos documentos do Oracle, NULLIF e PIVOT parecem ter o mesmo formato que seus parentes do SQL Server. O desafio será a lista dinâmica, que precisa ser estática, a menos que você faça a consulta dinâmica, como Itzik demonstra, mas não tenho idéia se isso pode ser traduzido para 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;
billinkc
fonte
5

Prefiro dinamizar a consulta manualmente, mas você também pode usá-lo PIVOT.

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
a1ex07
fonte
1
Sinta-se livre para explicar um pouco mais. O que o pivô fornece que os outros não podem? E quando isso se quebra? Lembre-se de que você está respondendo pela posteridade, não por alguém com conhecimentos específicos de domínio nas mesmas coisas que conhece.
jcolebrand
2
@jcolebrand: É mais sobre preferência pessoal - acho que a PIVOTsintaxe é mais complicada em comparação com a abordagem que uso. No entanto, sei que os dois dão o mesmo resultado e concordo que outras pessoas possam pensar o contrário.
a1ex07
1
Dica: Use o botão de edição ;-) ~ Gostamos de incentivar mais do que uma resposta de resposta de código.
jcolebrand