Oracle 动态透视

Posted

技术标签:

【中文标题】Oracle 动态透视【英文标题】:Oracle Dynamic Pivoting 【发布时间】:2018-05-09 18:16:02 【问题描述】:

我有下表。我需要根据列 CCL 创建列。 CCL 列中的值未知。我不知道从哪里开始。任何帮助,将不胜感激。

表格

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       

【问题讨论】:

请看这篇博文——它应该会有所帮助thesqlserverdeveloper.blogspot.com/2018/02/… 这里基本上有三种选择。 (1) 不要这样做。 (2) 如果您需要这样做,并且仅用于最终报告,则使用可以进行数据透视的报告前端。在大多数情况下,这就是“动态旋转”(这就是所谓的)所属的地方。 (3) 如果您需要这样做是因为您需要进一步计算结果,那么请注意这是一个高级主题。它与动态 SQL 有关,只能作为最后的手段使用,而且绝对不适合初学者。所以,如果你是初学者,千万不要尝试! @John 你能提供一些预期的结果吗? @D-Shih 添加了预期输出 关于这个主题的非常好的文章,带有现成的 PL/SQL API:technology.amis.nl/2006/05/24/…。我用过它,它适用于大多数的情况。例如,不要尝试在动态 SQL 中使用它。 【参考方案1】:

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

由于输出的记录类型未知,因此无法预先定义。

在 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      

您可以在 dbfiddle here

上找到测试

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

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

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

你会在 dbfiddle here

上找到一个测试

【讨论】:

旁注。如果您使用的是 Oracle 12c,请查看 this article。他们为 12c 中的 DBMS_SQL 包添加了一些功能,以促进向 Oracle 的迁移。我找不到 Oracle 12c 的在线测试仪,所以无法验证。但我认为您可以使用它运行动态 PIVOT,而无需通过临时表。 您可以使用任何可以获取引用光标的客户端应用程序来执行此操作,而无需创建任何对象,如我的答案末尾所示。从 26 年前的 1992 年发布的 Oracle 7 开始,这是可能的。隐式结果与动态 SQL 无关,以防万一。【参考方案2】:

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

这有几个后果

    Oracle 无法在不重新解析的情况下更改查询的列列表。不管应该有什么影响——无论是某些列中的不同值列表还是其他东西。换句话说,如果您在示例中向 CCL 列添加了新值,您不能期望 Oracle 会在输出中添加新列。

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

因此,处理“动态旋转”的最佳选择是在 UI 中旋转和格式化结果。但是,您可能需要考虑数据库中的一些选项。

生成带有旋转结果的 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。因此,您将旋转逻辑分为两层。 XML 解析可以在 SQL 之外完成,例如在您的应用程序中。

ODCI 表接口

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

多态表函数

Oracle 18 中引入了一项更先进的技术 - Polymorphic Table Functions。 但同样,您不应期望在向 CCL 添加新值后查询的列列表会发生变化。只有重新解析后才能改变。有一种方法可以在每次执行之前强制进行硬解析,但这是另一个话题。

动态 SQL

最后,正如 cmets 中已经指出的那样,您可以使用良好的旧 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

【讨论】:

以上是关于Oracle 动态透视的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle 中使用动态列进行透视

用于动态 sql 透视的 Oracle 函数

在oracle数据库中动态透视行

SQL - Oracle - 具有动态数据的数据透视表

使用 Sql Developer Oracle 的动态数据透视查询

oracle sql中的动态数据透视