oracle sql中的动态数据透视

时间:2013-03-19 04:46:16

标签: sql oracle pivot

... pivot((X)中B的总和(A))

现在B的数据类型为varchar2,X是由逗号分隔的varchar2值的字符串 X的值是来自同一表的列(比如CL)的不同值。这种方式使用数据透视查询。

但问题是,只要列CL中有新值,我就必须手动将其添加到字符串X中。

我尝试用CL中的select distinct值替换X.但是查询没有运行 我之所以感觉到的原因是,为了替换X,我们需要用逗号分隔的值 然后我创建了一个函数来返回精确的输出以匹配字符串X.但查询仍然没有运行 显示的错误消息类似于“丢失较大的parantheses”,“文件结束通信通道”等 我尝试使用pivot xml而不仅仅是pivot,查询运行但是提供了类似oraxxx等的值,这些值根本没有值。

也许我没有正确使用它 你能告诉我一些用动态值创建枢轴的方法吗?

9 个答案:

答案 0 :(得分:21)

如果不使用PIVOT XML,则不能在PIVOT的IN语句中放置动态语句,而PIVOT XML会输出一些不太理想的输出。但是,您可以创建一个IN字符串并将其输入到您的语句中。

首先,这是我的样本表;

  myNumber    myValue myLetter
---------- ---------- --------
         1          2 A        
         1          4 B        
         2          6 C        
         2          8 A        
         2         10 B        
         3         12 C        
         3         14 A      

首先设置要在IN语句中使用的字符串。在这里,您将字符串放入“str_in_statement”。我们正在使用COLUMN NEW_VALUELISTAGG来设置字符串。

clear columns
COLUMN temp_in_statement new_value str_in_statement
SELECT DISTINCT 
    LISTAGG('''' || myLetter || ''' AS ' || myLetter,',')
        WITHIN GROUP (ORDER BY myLetter) AS temp_in_statement 
    FROM (SELECT DISTINCT myLetter FROM myTable);

您的字符串将如下所示:

'A' AS A,'B' AS B,'C' AS C

现在在PIVOT查询中使用String语句。

SELECT * FROM 
    (SELECT myNumber, myLetter, myValue FROM myTable)
    PIVOT (Sum(myValue) AS val FOR myLetter IN (&str_in_statement));

这是输出:

  MYNUMBER      A_VAL      B_VAL      C_VAL
---------- ---------- ---------- ----------
         1          2          4            
         2          8         10          6 
         3         14                    12 

但有一些限制。您只能连接最多4000个字节的字符串。

答案 1 :(得分:9)

您不能在pivot子句的IN子句中放置非常量字符串 您可以使用Pivot XML。

来自documentation

  

子查询子查询仅与XML关键字一起使用。   指定子查询时,将使用子查询找到的所有值   用于旋转

它应该是这样的:

select xmlserialize(content t.B_XML) from t_aa
pivot xml(
sum(A) for B in(any)
) t;

您还可以使用子查询而不是ANY关键字:

select xmlserialize(content t.B_XML) from t_aa
pivot xml(
sum(A) for B in (select cl from t_bb)
) t;

Here is a sqlfiddle demo

答案 2 :(得分:4)

对于后来的读者,这是另一种解决方案 https://technology.amis.nl/2006/05/24/dynamic-sql-pivoting-stealing-antons-thunder/

允许像

这样的查询
select * from table( pivot(  'select deptno,  job, count(*) c from scott.emp group by deptno,job' ) )

答案 3 :(得分:2)

使用DYNAMIC QUERY

测试代码在

下面
--  DDL for Table TMP_TEST
--------------------------------------------------------

  CREATE TABLE "TMP_TEST" 
   (    "NAME" VARCHAR2(20), 
    "APP" VARCHAR2(20)
   );
/
SET DEFINE OFF;
Insert into TMP_TEST (NAME,APP) values ('suhaib','2');
Insert into TMP_TEST (NAME,APP) values ('suhaib','1');
Insert into TMP_TEST (NAME,APP) values ('shahzad','3');
Insert into TMP_TEST (NAME,APP) values ('shahzad','2');
Insert into TMP_TEST (NAME,APP) values ('shahzad','5');
Insert into TMP_TEST (NAME,APP) values ('tariq','1');
Insert into TMP_TEST (NAME,APP) values ('tariq','2');
Insert into TMP_TEST (NAME,APP) values ('tariq','6');
Insert into TMP_TEST (NAME,APP) values ('tariq','4');
/
  CREATE TABLE "TMP_TESTAPP" 
   (    "APP" VARCHAR2(20)
   );

SET DEFINE OFF;
Insert into TMP_TESTAPP (APP) values ('1');
Insert into TMP_TESTAPP (APP) values ('2');
Insert into TMP_TESTAPP (APP) values ('3');
Insert into TMP_TESTAPP (APP) values ('4');
Insert into TMP_TESTAPP (APP) values ('5');
Insert into TMP_TESTAPP (APP) values ('6');
/
create or replace PROCEDURE temp_test(
  pcursor out sys_refcursor,
    PRESULT                   OUT VARCHAR2
    )
AS
V_VALUES VARCHAR2(4000);
V_QUERY VARCHAR2(4000);
BEGIN
 PRESULT := 'Nothing';

-- concating activities name using comma, replace "'" with "''" because we will use it in dynamic query so "'" can effect query.
  SELECT DISTINCT 
         LISTAGG('''' || REPLACE(APP,'''','''''') || '''',',')
         WITHIN GROUP (ORDER BY APP) AS temp_in_statement 
    INTO V_VALUES
    FROM (SELECT DISTINCT APP 
            FROM TMP_TESTAPP);

-- designing dynamic query  

  V_QUERY := 'select * 
                from (  select NAME,APP 
                          from TMP_TEST   )   
               pivot (count(*) for APP in 
                     (' ||V_VALUES|| '))  
           order  by NAME' ;

    OPEN PCURSOR
     FOR V_QUERY;


 PRESULT := 'Success';

Exception
WHEN OTHERS THEN
 PRESULT := SQLcode || ' - ' || SQLERRM;
END temp_test;

答案 4 :(得分:1)

我使用了上面的方法(Anton PL / SQL自定义函数pivot())并完成了这项工作!由于我不是专业的Oracle开发人员,所以这些都是我已经完成的简单步骤:

1)下载zip包以在那里找到pivotFun.sql。 2)运行pivotFun.sql以创建一个新函数 3)在普通SQL中使用该函数。

请注意动态列名称。在我的环境中,我发现列名限制为30个字符,并且不能包含单个引号。所以,我的查询现在是这样的:

SELECT 
  *
FROM   
  table( 
        pivot('
                SELECT DISTINCT
                    P.proj_id,
                    REPLACE(substr(T.UDF_TYPE_LABEL, 1, 30), '''''''','','') as Attribute,
                    CASE
                      WHEN V.udf_text is null     and V.udf_date is null and      V.udf_number is NOT null  THEN to_char(V.udf_number)
                      WHEN V.udf_text is null     and V.udf_date is NOT null and  V.udf_number is null      THEN to_char(V.udf_date)
                      WHEN V.udf_text is NOT null and V.udf_date is null and      V.udf_number is null      THEN V.udf_text
                      ELSE NULL END
                    AS VALUE
                FROM
                    project   P
                LEFT JOIN UDFVALUE V ON P.proj_id     = V.proj_id 
                LEFT JOIN UDFTYPE  T ON V.UDF_TYPE_ID = T.UDF_TYPE_ID
                WHERE 
                    P.delete_session_id  IS NULL AND
                    T.TABLE_NAME = ''PROJECT''
    ')
)

适用于最多100万条记录。

答案 5 :(得分:0)

我不会完全回答OP提出的问题,而是我将描述如何完成动态调整。

这里我们必须使用动态sql,最初将列值检索到变量中并将变量传递给动态sql。

示例

考虑我们有一个如下表格。

enter image description here

如果我们需要将YR列中的值显示为列名称,并将QTY中的值显示在那些列中,那么我们可以使用以下代码。

declare
  sqlqry clob;
  cols clob;
begin
  select listagg('''' || YR || ''' as "' || YR || '"', ',') within group (order by YR)
  into   cols
  from   (select distinct YR from EMPLOYEE);


  sqlqry :=
  '      
  select * from
  (
      select *
      from EMPLOYEE
  )
  pivot
  (
    MIN(QTY) for YR in (' || cols  || ')
  )';

  execute immediate sqlqry;
end;
/

结果

enter image description here

如果需要,您还可以创建临时表并在该临时表中执行选择查询以查看结果。它很简单,只需在上面的代码中添加CREATE TABLE TABLENAME AS

sqlqry :=
'    
  CREATE TABLE TABLENAME AS
  select * from

答案 6 :(得分:0)

如果不使用PIVOT XML,则不能在PIVOT的IN语句中放置动态语句,但是 您可以使用小型Technic 来使用动态语句在PIVOT中。在PL / SQL中,在一个字符串值内,两个撇号等于一个撇号。

declare
  sqlqry clob;   
  search_ids  varchar(256) := '''2016'',''2017'',''2018'',''2019''';
begin
  search_ids := concat( search_ids,'''2020''' ); -- you can append new search id dynamically as you wanted
  sqlqry :=
  '      
  select * from
  (
      select *
      from EMPLOYEE
  )
  pivot
  (
    MIN(QTY) for YR in (' || search_ids   || ')
  )';

  execute immediate sqlqry;
end;

答案 7 :(得分:0)

除非在SQL中返回XML类型结果,否则没有直接的方法可用于动态进行Oracle SQL数据透视。 对于非XML结果,可以通过创建SYS_REFCURSOR返回类型的函数来使用PL / SQL

  • 具有条件聚合

    CREATE OR REPLACE FUNCTION Get_Jobs_ByYear RETURN SYS_REFCURSOR IS
      v_recordset SYS_REFCURSOR;
      v_sql       VARCHAR2(32767);
      v_cols      VARCHAR2(32767); 
    BEGIN
      SELECT LISTAGG( 'SUM(  CASE  WHEN job_title =  '''||job_title||'''  THEN  1  ELSE  0  END )  AS  "'||job_title||'"' , ',' )
                     WITHIN GROUP ( ORDER BY job_title )
        INTO v_cols
        FROM ( SELECT DISTINCT job_title
                 FROM jobs j );
    
      v_sql :=
      'SELECT "HIRE YEAR",'|| v_cols ||
      '  FROM
         (
          SELECT TO_NUMBER(TO_CHAR(hire_date,''YYYY'')) AS "HIRE YEAR", job_title
            FROM employees e
            JOIN jobs j
              ON j.job_id = e.job_id
         )
        GROUP BY "HIRE YEAR"
        ORDER BY "HIRE YEAR"';
    
      OPEN v_recordset FOR v_sql;
      DBMS_OUTPUT.PUT_LINE(v_sql);
      RETURN v_recordset;
    END;
    /
    
  • 带有 PIVOT 条款

    CREATE OR REPLACE FUNCTION Get_Jobs_ByYear RETURN SYS_REFCURSOR IS
      v_recordset SYS_REFCURSOR;
      v_sql       VARCHAR2(32767);
      v_cols      VARCHAR2(32767);
    BEGIN
      SELECT LISTAGG( ''''||job_title||''' AS "'||job_title||'"' , ',' )
                     WITHIN GROUP ( ORDER BY job_title )
        INTO v_cols
        FROM ( SELECT DISTINCT job_title
                 FROM jobs j  );
    
      v_sql :=
      'SELECT *
         FROM
         (
          SELECT TO_NUMBER(TO_CHAR(hire_date,''YYYY'')) AS "HIRE YEAR", job_title
            FROM employees e
            JOIN jobs j
              ON j.job_id = e.job_id  
         )
        PIVOT
        (
         COUNT(*) FOR job_title IN ( '|| v_cols ||' )
        )
        ORDER BY "HIRE YEAR"';
    
      OPEN v_recordset FOR v_sql;
      DBMS_OUTPUT.PUT_LINE(v_sql);
      RETURN v_recordset;
    END;
    /
    

但是LISTAGG()缺点编码为 ORA-01489: 字符串连接的结果太长第一个参数中的并置字符串超过了 4000 个字符的长度。在这种情况下,返回v_cols变量值的查询可能被嵌套在XMLELEMENT()中的XMLAGG()函数替换,例如

CREATE OR REPLACE FUNCTION Get_Jobs_ByYear RETURN SYS_REFCURSOR IS
  v_recordset SYS_REFCURSOR;
  v_sql       VARCHAR2(32767);
  v_cols      VARCHAR2(32767); 
BEGIN
  SELECT RTRIM(DBMS_XMLGEN.CONVERT(
         XMLAGG(
                XMLELEMENT(e, 'SUM(  CASE  WHEN job_title =  '''||job_title||
                            '''  THEN  1  ELSE  0  END )  AS  "'||job_title||'",')
               ).EXTRACT('//text()').GETCLOBVAL() ,1),',') AS "v_cols"
    FROM ( SELECT DISTINCT job_title
               FROM jobs j);
           
  v_sql :=
  'SELECT "HIRE YEAR",'|| v_cols ||
  '  FROM
     (
      SELECT TO_NUMBER(TO_CHAR(hire_date,''YYYY'')) AS "HIRE YEAR", job_title
        FROM employees e
        JOIN jobs j
          ON j.job_id = e.job_id
     )
    GROUP BY "HIRE YEAR"
    ORDER BY "HIRE YEAR"';
  DBMS_OUTPUT.put_line(LENGTH(v_sql));
  OPEN v_recordset FOR v_sql;
  RETURN v_recordset;
END;
/

除非已超出 VARCHAR2 类型的上限 32767 。后一种方法也可能适用于版本 Oracle 11g第2版之前的数据库,因为它们不包含LISTAGG()函数。

顺便说一句,LISTAGG()函数可以在v_cols的结帐期间使用,即使生成的很长的串联字符串也不会在结尾部分出现 ORA-01489 错误的情况下使用如果数据库的版本是 12.2 + ,例如

,则使用 ON OVERFLOW TRUNCATE 子句将字符串截断
LISTAGG( <concatenated string>,',' ON OVERFLOW TRUNCATE 'THE REST IS TRUNCATED' WITHOUT COUNT )

该函数可以调用为

VAR rc REFCURSOR
EXEC :rc := Get_Jobs_ByYear;
PRINT rc

SQL Developer 的命令行

BEGIN
  :result := Get_Jobs_ByYear;
END;
PL / SQL Developer Test 窗口中

以获得结果 设置。

Demo for generated queries

答案 8 :(得分:0)

您可以使用开源程序 Method4.Pivot 在单个 SQL 语句中动态透视数据。

安装包后,调用函数并传入一个SQL语句作为字符串。 SQL 语句的最后一列定义值,倒数第二列定义列名称。默认的聚合函数是 MAX,它适用于像这样的常见实体-属性-值查询:

select * from table(method4.pivot(
    q'[
        select 'A' name, 1 value from dual union all
        select 'B' name, 2 value from dual union all
        select 'C' name, 3 value from dual
    ]'
));

A   B   C
-   -   -
1   2   3

该程序还通过参数 P_AGGREGATE_FUNCTION 支持不同的聚合函数,如果添加名为 PIVOT_COLUMN_ID 的列,则允许自定义列名顺序。

该包使用类似于 Anton 的数据透视表的 Oracle Data Cartridge 方法,但 Method4.Pivot 有几个重要优点:

  1. 带有存储库、安装说明、许可证、单元测试、文档和评论的常规开源程序 - 而不仅仅是博客上的 Zip 文件。
  2. 处理异常的列名。
  3. 处理不常见的数据类型,如浮点数。
  4. 最多可处理 1000 列。
  5. 为常见错误提供有意义的错误消息。
  6. 处理 NULL 列名。
  7. 处理 128 个字符的列名。
  8. 防止误导性的隐式转换。
  9. 每次都对语句进行硬解析以捕获基础表的更改。

但大多数用户仍然最好在应用程序层或使用数据透视 XML 选项创建动态数据透视。