Объединить строки в несколько столбцов


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функции, тогда это будет проще всего, но если нет, то вы можете использовать агрегатную функцию и a 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 с демо

Статический Пивот:

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 с демо

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

Две вышеупомянутые версии прекрасно работают, если у вас есть известное количество значений, но если ваши значения неизвестны, тогда вы захотите реализовать динамический 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 | A | B | ноль | | 2 | C | ноль | ноль | | 3 | ноль | C | ноль |
Матфея

1
@ Матфе, вам придется использовать Dynamic Sql, как я продемонстрировал в последней части ответа.
Тарын

Спасибо за быстрый ответ! На самом деле я делаю это, создавая новый столбец и вставляя все параметры в них, разделенные запятыми. Col генерируется из подзапроса, выбираемого из тех же таблиц 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.