Oracle Dynamic Pivoting

时间:2018-05-09 18:16:02

标签: sql oracle pivot

我有下表。我需要根据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 :(得分:1)

与其他某些RDMBS相比,在Oracle中,使用动态sql生成结果时执行时列未知的结果有点麻烦。

因为输出的记录类型仍然未知,所以无法预先定义。

在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 <>小提琴here

上找到测试

在Oracle 11g中,可以在此Anton Scheffer中找到要使用的另一个很酷的技巧(由blog创建)。但是您必须为此添加数据透视功能。
可以找到源代码in this zip

此后,SQL可以像这样简单:

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

您会在 db <>小提琴here

上找到一个测试

答案 1 :(得分:1)

Oracle必须在PARSING阶段知道选择列表中的所有列。

这有很多后果

  1. Oracle无法在不重新解析的情况下更改查询的列列表。无论是什么影响这一点-无论是某些列中的值列表还是其他内容。换句话说,如果您在示例中将新值添加到CCL列中,则不能期望Oracle添加新列来输出。

  2. 在每个查询中,必须明确指定选择列表中的所有列,除非您将"*"与表别名一起使用。如果您使用"*",则Oracle从元数据中获取列列表,并且如果您修改元数据(即在表上运行DDL),则Oracle将重新解析查询。

因此,处理“动态透视”的最佳选择是在用户界面中旋转结果并设置其格式。但是,您可能还需要考虑数据库中的一些选项。

生成具有透视结果的XML并对其进行解析。

对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个重要细节

  • 实际上,每个枢轴列都使用两列结果表示-一列用于标题,一列用于值

  • 名称是有序的,因此您不能像示例中那样保留名称(“ john”,“ adam”,“ terry”,“ rob”), 而且,一列可能代表不同的名称,例如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的所有值。这是因为列标题不能依赖于表中的数据。那么,如果您能够以相同的成功对for子句中的所有值进行硬编码,那么转向XML的意义何在呢?想法之一是Oracle SQL引擎会转置查询结果,而显示输出的工具只需要正确地解析XML。因此,您将枢轴逻辑分为两层。可以在SQL外部(例如,在您的应用程序中)进行XML解析。

ODCI表界面

另一个Anton's solution答案中已经有一个链接。 您也可以查看示例here。 而且,当然,在Oracle文档中对其进行了详细说明。

多态表函数

Oracle 18-Polymorphic Table Functions中引入了一种更高级的技术。 但是同样,您不应该期望在将新值添加到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