Oracle Dynamic Pivoting - PullRequest
       9

Oracle Dynamic Pivoting

0 голосов
/ 09 мая 2018

У меня есть таблица ниже. Мне нужно создать столбцы на основе столбца CCL. Значения в столбце CCL неизвестны. Я не уверен, с чего начать. Любая помощь будет оценена.

TABLEA

ID    CCL    Flag
1     john     x
1     adam     x
1     terry
1     rob      x
2     john     x

Запрос:

SELECT *
FROM TABLEA

Выход:

ID  John  Adam  Terry  Rob
 1    x     x           x
 2    x       

Ответы [ 2 ]

0 голосов
/ 23 сентября 2018

Oracle должен знать все столбцы в списке выбора на этапе PARSING.

Это имеет несколько последствий

  1. Oracle не может изменить список столбцов запроса без его повторного анализа. Независимо от того, что должно на это повлиять - будь то отдельный список значений в каком-то столбце или что-то еще. Другими словами, вы не можете ожидать, что Oracle добавит новые столбцы для вывода, если вы добавили новое значение в столбец CCL в вашем примере.

  2. В каждом запросе вы должны явно указать все столбцы в списке выбора, если вы не используете "*" с псевдонимом таблицы. Если вы используете "*", то Oracle получает список столбцов из метаданных, а если вы изменяете метаданные (то есть запускаете DDL для таблицы), то Oracle повторно анализирует запрос.

Таким образом, лучший вариант работы с «Динамическим поворотом» - это поворот и форматирование результата в пользовательском интерфейсе. Тем не менее, в базе данных все еще есть варианты, которые вы можете рассмотреть.

Генерация XML с поворотным результатом и его анализ.

Сделайте pivot для XML, а затем проанализируйте результаты. В этом случае, в конце концов, вам придется указывать поворотные столбцы тем или иным способом.

create table tablea(id, ccl, flag) as
(
  select 1, 'john', 'x' from dual
  union all select 1, 'adam', 'x' from dual
  union all select 1, 'terry', null from dual
  union all select 1, 'rob', 'x' from dual
  union all select 2, 'john', 'x' from dual
);

В приведенном ниже примере вам НЕ нужно указывать список значений для CCL, вы указываете только следующие литералы: выражение поворота (FLAG) и столбец, используемый для поворота (CCL).

SQL> select id, x.*
  2  from tablea t
  3  pivot xml (max(flag) flag for ccl in(any))
  4  -- parsing output
  5  , xmltable('/PivotSet' passing ccl_xml
  6             columns
  7               name1 varchar2(30) path '/PivotSet/item[1]/column[@name="CCL"]/text()',
  8               value1 varchar2(30) path '/PivotSet/item[1]/column[@name="FLAG"]/text()',
  9               name2 varchar2(30) path '/PivotSet/item[2]/column[@name="CCL"]/text()',
 10               value2 varchar2(30) path '/PivotSet/item[2]/column[@name="FLAG"]/text()',
 11               name3 varchar2(30) path '/PivotSet/item[3]/column[@name="CCL"]/text()',
 12               value3 varchar2(30) path '/PivotSet/item[3]/column[@name="FLAG"]/text()',
 13               name4 varchar2(30) path '/PivotSet/item[4]/column[@name="CCL"]/text()',
 14               value4 varchar2(30) path '/PivotSet/item[4]/column[@name="FLAG"]/text()') x;

        ID NAME1 VALUE NAME2 VALUE NAME3 VALUE NAME4 VALUE
---------- ----- ----- ----- ----- ----- ----- ----- -----
         1 adam  x     john  x     rob   x     terry
         2 john  x

Возможно, вы заметили 2 важные детали

  • Фактически каждый сводный столбец представлен двумя столбцами в результате - один для заголовка и один для значения

  • Имена упорядочены, поэтому вы не можете сохранить порядок, как в вашем примере («Джон», «Адам», «Терри», «Роб»), кроме того, один столбец может представлять разные имена, например, NAME1 представляет значения для 'adam' в первой строке и 'john' во второй строке.

Можно использовать только индексы для получения одинакового вывода.

select id, x.*
from tablea
pivot xml (max(flag) flag for ccl in(any))
-- parsing output
, xmltable('/PivotSet' passing ccl_xml
           columns
             name1 varchar2(30) path '/PivotSet/item[1]/column[1]',
             value1 varchar2(30) path '/PivotSet/item[1]/column[2]',
             name2 varchar2(30) path '/PivotSet/item[2]/column[1]',
             value2 varchar2(30) path '/PivotSet/item[2]/column[2]',
             name3 varchar2(30) path '/PivotSet/item[3]/column[1]',
             value3 varchar2(30) path '/PivotSet/item[3]/column[2]',
             name4 varchar2(30) path '/PivotSet/item[4]/column[1]',
             value4 varchar2(30) path '/PivotSet/item[4]/column[2]') x;

Но все же есть два столбца для каждого поворотного столбца в выводе.

Запрос ниже возвращает те же данные, что и в вашем примере

SQL> select id, x.*
  2  from tablea
  3  pivot xml (max(flag) flag for ccl in(any))
  4  -- parsing output
  5  , xmltable('/PivotSet' passing ccl_xml
  6             columns
  7               john varchar2(30) path '/PivotSet/item[column="john"]/column[2]',
  8               adam varchar2(30) path '/PivotSet/item[column="adam"]/column[2]',
  9               terry varchar2(30) path '/PivotSet/item[column="terry"]/column[2]',
 10               rob varchar2(30) path '/PivotSet/item[column="rob"]/column[2]') x;

        ID JOHN  ADAM  TERRY ROB
---------- ----- ----- ----- -----
         1 x     x           x
         2 x

Но подождите ... все значения для CCL указаны в запросе. Это связано с тем, что заголовок столбца не может зависеть от данных в таблице. Так какой смысл в повороте для XML, если бы вы могли просто закодировать все значения в предложении for с одинаковым успехом? Одна из идей заключается в том, что движок Oracle SQL транспонирует результат запроса, а инструмент, отображающий вывод, просто должен правильно анализировать XML. Таким образом, вы разделяете поворотную логику на два слоя. Синтаксический анализ XML может выполняться вне SQL, например, в вашем приложении.

Интерфейс таблицы ODCI

В другом ответе уже есть ссылка на Решение Антона . Вы также можете проверить пример здесь . И, конечно, это подробно объясняется в документации Oracle.

Полиморфные табличные функции

В Oracle 18 была введена еще одна передовая технология - Полиморфные табличные функции . Но опять же, вы не должны ожидать, что список столбцов вашего запроса изменится после добавления нового значения в CCL. Это может измениться только после повторного разбора. Существует способ принудительного разбора перед каждым извинением, но это уже другая тема.

Динамический SQL

Наконец, как уже отмечалось в комментариях, вы можете использовать старый добрый DSQL. Первый шаг - сгенерировать оператор SQL на основе содержимого таблицы. Второй шаг - выполнить его.

SQL> var rc refcursor
SQL> declare
  2    tmp clob;
  3    sql_str clob := 'select * from tablea pivot (max(flag) for ccl in ([dynamic_list]))';
  4  begin
  5    select listagg('''' || ccl || ''' as ' || ccl, ',') within group(order by max(ccl))
  6      into tmp
  7      from tablea
  8     group by ccl;
  9    open :rc for replace(sql_str, '[dynamic_list]', tmp);
 10  end;
 11  /

PL/SQL procedure successfully completed.

SQL> print rc

        ID ADAM  JOHN  ROB   TERRY
---------- ----- ----- ----- -----
         1 x     x     x
         2       x
0 голосов
/ 17 сентября 2018

Использование динамического sql для результата, когда столбцы неизвестны во время выполнения, является немного хлопотным в Oracle по сравнению с некоторыми другими RDMBS.

Поскольку тип записи для вывода еще неизвестен, его нельзя определить заранее.

В Oracle 11g одним из способов является использование безымянной процедуры, которая генерирует временную таблицу с поворотным результатом.

Затем выберите результаты из этой временной таблицы.

declare
  v_sqlqry clob;
  v_cols clob;
begin
  -- Generating a string with a list of the unique names
  select listagg(''''||CCL||''' as "'||CCL||'"', ', ') within group (order by CCL)
  into v_cols
  from 
  (
    select distinct CCL
    from tableA
  );

  -- drop the temporary table if it exists
  EXECUTE IMMEDIATE 'DROP TABLE tmpPivotTableA';
  EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF;

  -- A dynamic SQL to create a temporary table 
  -- based on the results of the pivot
  v_sqlqry := '
    CREATE GLOBAL TEMPORARY TABLE tmpPivotTableA
    ON COMMIT PRESERVE ROWS AS
    SELECT * 
    FROM (SELECT ID, CCL, Flag FROM TableA) src 
    PIVOT (MAX(Flag) FOR (CCL) IN ('||v_cols||')) pvt';

  -- dbms_output.Put_line(v_sqlqry); -- just to check how the sql looks like
  execute immediate v_sqlqry;

end;
/

select * from tmpPivotTableA;

Возвращает:

ID  adam john rob terry
--  ---- ---- --- -----
1   x    x    x
2        x      

Вы можете найти тест по db <> fiddle здесь

В Oracle 11g в этом блоге можно найти еще один интересный трюк (созданный Anton Scheffer ) для использования. Но вам придется добавить для него функцию поворота.
Исходный код можно найти в этом zip

После этого SQL может быть таким простым:

select * from 
table(pivot('SELECT ID, CCL, Flag FROM TableA'));

Вы найдете тест по db <> fiddle здесь

...