Przestaw wiersze na wiele kolumn

21

Mam instancję programu SQL Server, która ma połączony serwer z serwerem Oracle. Na nazwie serwera Oracle znajduje się tabela, PersonOptionsktóra zawiera następujące dane:

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

Muszę przestawić te dane, aby wyniki były:

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

Jakieś sugestie?

Nie ja
źródło

Odpowiedzi:

20

Istnieje kilka sposobów przeprowadzenia tej transformacji danych. Masz dostęp do PIVOTfunkcji, to będzie najłatwiejsze, ale jeśli nie, możesz użyć funkcji agregującej i CASE.

Wersja zbiorcza / sprawy:

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;

Zobacz SQL Fiddle with Demo

Pivot statyczny:

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

Zobacz SQL Fiddle with Demo

Wersja dynamiczna:

Dwie powyższe wersje działają świetnie, jeśli masz znaną liczbę wartości, ale jeśli twoje wartości są nieznane, to chcesz zaimplementować dynamiczne narzędzie SQL, aw Oracle możesz użyć procedury:

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

Następnie zwrócisz wyniki, użyjesz:

variable x refcursor
exec dynamic_pivot_po(:x)
print x

Wyniki są takie same dla wszystkich wersji:

| PERSONID | OPTIONA | OPTIONB | OPTIONC |
------------------------------------------
|        1 |       1 |       1 |       0 |
|        2 |       0 |       0 |       1 |
|        3 |       0 |       1 |       0 |
|        4 |       1 |       0 |       1 |
Taryn
źródło
Jednak rozwiązanie Static Pivot zakłada, że ​​istnieją tylko trzy opcje. Co jeśli masz potencjalnie nieograniczoną liczbę opcji? ABCDEFGHIJK na przykład? Czy nie ma sposobu, aby uczynić pivot dynamicznym za pomocą zwykłego SQL? Czy zamiast umieścić opcje w nagłówkach kolumn, czy moglibyśmy po prostu umieścić je w kolumnach? Więc wyglądałoby to tak: | PERSONID | Kolumna 2 | Kolumna3 | Kolumna4 | ------------------------------------------ | 1 | A | B | null | | 2 | C | null | null | | 3 | null | C | null |
Matthew
1
@ Matthew, musisz użyć Dynamic Sql, jak pokazałem w ostatniej części odpowiedzi.
Taryn
Dziękuję za szybką odpowiedź! Właściwie to robię, tworząc nową kolumnę i upychając wszystkie tam opcje oddzielone przecinkami. Kol generuje z podzapytania, wybierając z tych samych tabel where a.personId = a2.personId order by a2.personId for xml path(''). a2 to tabela w podzapytaniu. Następnie rozdzielam dane w programie Excel, używając tekstu do kolumn z przecinkiem jako separatorem. Miałem nadzieję znaleźć sposób na zrobienie tego w zwykłym sql bez konieczności pisania procedury, ale może nie ma takiej możliwości. W tej chwili muszę biec, ale postaram się opublikować przykład tego, aby lepiej to wyjaśnić.
Matthew
9

Byłoby to równoważne w składni SQL Server. Na podstawie mojej lektury dokumentów Oracle wydaje się , że NULLIF i PIVOT mają ten sam format, co ich rodziny SQL Server. Wyzwaniem będzie lista przestawna, która musi być statyczna, chyba że zapytanie zostanie dynamiczne, jak pokazuje Itzik , ale nie mam pojęcia, czy można to przetłumaczyć na 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
źródło
5

Wolę ręcznie przestawiać zapytania, ale możesz także użyć 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
źródło
1
Wyjaśnij to jeszcze trochę. Co zapewnia oś obrotu, której inni nie mogą? A kiedy to się psuje? Pamiętaj, że odpowiadasz za potomność, a nie za kogoś, kto ma specjalistyczną wiedzę w dziedzinie w tych samych sprawach, które znasz.
jcolebrand
2
@jcolebrand: Chodzi bardziej o osobiste preferencje - osobiście uważam, że PIVOTskładnia jest bardziej skomplikowana w porównaniu do podejścia, którego używam. Wiem jednak, że oba dają ten sam rezultat i zgadzam się, że inni ludzie mogą myśleć inaczej.
a1ex07
1
Wskazówka: użyj przycisku edycji ;-) ~ Chcemy zachęcić więcej niż odpowiedź na kod.
jcolebrand