Зведення рядків у кілька стовпців


21

У мене є екземпляр SQL Server, який має пов'язаний сервер із сервером Oracle. На сервері Oracle є таблиця, PersonOptionsяка містить такі дані:

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

Мені потрібно повернути ці дані, щоб результати були:

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

Будь-які пропозиції?

Відповіді:


20

Існує кілька способів здійснення цієї трансформації даних. PIVOTТоді у вас є доступ до функції, яка буде найпростішою, але якщо ні, то ви можете використовувати сукупну функцію і CASE.

Версія сукупності / випадку:

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;

Див. SQL Fiddle with Demo

Статичний зведення:

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

Див. SQL Fiddle with Demo

Динамічна версія:

Дві версії вище працюють чудово, якщо у вас є відома кількість значень, але якщо ваші значення невідомі, ви хочете реалізувати динамічний sql і в Oracle ви можете використовувати процедуру:

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

Потім ви повернете результати, ви будете використовувати:

variable x refcursor
exec dynamic_pivot_po(:x)
print x

Результати однакові для всіх версій:

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

Однак рішення Static Pivot передбачає, що існує лише три варіанти. Що робити, якщо у вас є потенційно необмежена кількість варіантів? ABCDEFGHIJK наприклад? Чи не існує способу зробити стрижневий динамічний звичайний sql? Замість того, щоб робити параметри заголовків стовпців, чи можемо ми просто поставити їх у стовпці? Так би виглядало так: | ОСОБА | Стовпчик2 | Стовпчик3 | Стовпець4 | ------------------------------------------ | 1 | А | B | null | | 2 | C | null | null | | 3 | нульовий | C | нульовий |
Метью

1
@Matthew вам доведеться використовувати Dynamic Sql, як я демонструю в останній частині відповіді.
Taryn

Дякуємо за швидку відповідь! Я фактично роблю це, створюючи новий стовпець і вкладаючи всі параметри туди, розділені комами. Коло генерується з підзапиту, що вибирається з тих же таблиць where a.personId = a2.personId order by a2.personId for xml path(''). a2 - таблиця в підзапиті. Потім я відокремлюю дані в excel, використовуючи текст, у стовпці з комою в якості роздільника. Я сподівався знайти спосіб зробити це в звичайному sql, не вимагаючи процедури, але, можливо, немає способу. Треба бігти на даний момент, але я спробую розмістити приклад, щоб краще пояснити.
Метью

9

Це було б еквівалентом у синтаксисі SQL Server. На основі мого читання документів Oracle, схоже , NULLIF та PIVOT мають такий самий формат, що і їхній родич SQL Server. Викликом буде перехідний список, який повинен стати статичним, якщо ви не зробите запит динамічним, як це демонструє Ітзік, але я не маю уявлення, чи можна це перекласти на 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

Я вважаю за краще поворотний запит вручну, але ви також можете використовувати 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

1
Сміливо пояснюйте це лише трохи більше. Що забезпечує стрижень, щоб інші не могли? А коли це ламається? Пам’ятайте, що ви відповідаєте за нащадки, а не для того, хто має специфічну експертизу домену в тих самих речах, які ви знаєте.
jcolebrand

2
@jcolebrand: Це більше стосується особистих уподобань - я сам вважаю, що PIVOTсинтаксис є більш перекрученим порівняно з підходом, який я використовую. Однак я знаю, що обидва вони дають однаковий результат, і я згоден, що інші люди можуть думати протилежне.
a1ex07

1
Підказка: Використовуйте кнопку редагування ;-) ~ Нам подобається заохочувати більше, ніж відповідь на код.
jcolebrand
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.