19

...枢轴(总和(A)为(X)中的B)

现在 B 是数据类型 varchar2 而 X 是一个用逗号分隔的 varchar2 值字符串。
X 的值是从同一表的列(例如 CL)中选择不同的值。这种方式枢轴查询工作。

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

我尝试用从 CL 中选择的不同值替换 X。但是查询没有运行。
我觉得的原因是因为要替换 X 我们需要用逗号分隔的值。
然后我创建了一个函数来返回与字符串 X 匹配的精确输出。但是查询仍然没有运行。
显示的错误消息类似于“缺少正确的括号”、“文件通信通道结束”等。
我尝试了枢轴 xml 而不是枢轴,查询运行但给出了像 oraxxx 等根本没有值的值。

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

4

10 回答 10

23

您不能在不使用 PIVOT XML 的情况下将动态语句放入 PIVOT 的 IN 语句中,它会输出一些不太理想的输出。但是,您可以创建一个 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 个字节的字符串。

于 2014-05-16T15:16:49.817 回答
17

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

文档

子查询 子查询仅与 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;

这是一个 sqlfiddle 演示

于 2013-03-19T05:49:01.217 回答
6

对于以后的读者,这里是另一个解决方案 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' ) )
于 2015-07-15T02:27:54.000 回答
2

使用动态查询

测试代码如下


--  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;
于 2018-01-24T13:35:29.907 回答
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''
    ')
)

适用于多达 1m 条记录。

于 2016-11-11T02:39:21.880 回答
1

对于 OP 提出的问题,我不会完全给出答案,相反,我将仅描述如何完成动态枢轴。

在这里,我们必须使用动态 sql,首先将列值检索到变量中,然后在动态 sql 中传递变量。

例子

考虑我们有一个如下表。

在此处输入图像描述

如果我们需要将列中的值显示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;
/

结果

在此处输入图像描述

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

sqlqry :=
'    
  CREATE TABLE TABLENAME AS
  select * from
于 2017-04-23T09:45:14.897 回答
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;
于 2020-05-12T18:13:49.830 回答
0

Oracle 的 SQL 中没有直接的动态透视方法,除非它返回 XML 类型的结果。对于非 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;
    /
    

但是使用 ORA-01489 编码有一个缺点一个参数中的连接字符串超过4000 个字符的长度时,字符串连接的结果太长就会引发。在这种情况下,返回变量值的查询可能会替换为嵌套在其中的函数,例如LISTAGG() v_colsXMLELEMENT()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()

顺便说一句,如果数据库的版本是 12.2+,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的测试窗口中获取结果集。

Demo for generated queries

于 2020-11-28T22:39:28.243 回答
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 的 pivot 的 Oracle Data Cartridge 方法,但 Method4.Pivot 具有几个重要优点:

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

但大多数用户最好还是在应用层创建一个动态透视或使用透视 XML 选项。

于 2021-05-22T22:13:01.033 回答
0

SQL_MACRO自从 Oracle 19c 引入(可能还有我还没有使用的多态表函数)以来,看起来无需额外的开发工作就可以实现。

create table t as
select
  trunc(level/5) as id
  , chr(65+mod(level, 5)) as code
  , level as val
from dual
connect by level < 10
create function f_pivot
return varchar2 SQL_MACRO(TABLE)
is
  l_codes varchar2(1000);
begin
  select listagg(
    distinct '''' || code
    || ''' as ' || code, ',')
    into l_codes
  from t;
  
  return
    'select *
    from t
    pivot (
      max(val) for code in (
      ' || l_codes || '))';
end;
/
select *
from f_pivot()
身份证 | 乙| C | D | E | 一种
-: | -: | -: | -: | -: | ---:
 0 | 1 | 2 | 3 | 4 | 空值
 1 | 6 | 7 | 8 | 9 | 5

唯一的问题(在SQL_MACRO方法的情况下)是结果集在一个会话期间不会改变其结构:

insert into t
values(1, 'Q', 100);

commit;

select *
from f_pivot()
身份证 | 乙| C | D | E | 一种
-: | -: | -: | -: | -: | ---:
 0 | 1 | 2 | 3 | 4 | 空值
 1 | 6 | 7 | 8 | 9 | 5

但在单独的会话中它工作正常:

select dbms_xmlgen.getxml('select * from f_pivot()') as v
from dual
<?xml version="1.0"?> 
<ROWSET>
<ROW>
<ID>0</ID>
<B>1</B>
<C>2</C>
<D>3</D>
<E >4</E>
</ROW>
<ROW>
<ID>1</ID>
<B>6</B>
<C>7</C>
<D>8</D>
<E>9< /E>
<A>5</A>
<Q>100</Q>
</ROW>
</ROWSET>

使用with function特征动态枢轴可以在没有预定义函数的情况下就地使用:

with function f_pivot1
return varchar2 SQL_MACRO(TABLE)
is
  l_codes varchar2(1000);
begin
  select listagg(distinct '''' || code || ''' as ' || code, ',')
    into l_codes
  from t;
  
  return
    'select *
    from t
    pivot (
      max(val) for code in (
      ' || l_codes || '))';
end;

select *
from f_pivot1()
身份证 | 乙| C | D | E | 一个 | 问
-: | -: | -: | -: | -: | ---: | ---:
 0 | 1 | 2 | 3 | 4 | | 空值
 1 | 6 | 7 | 8 | 9 | 5 | 100

db<>在这里摆弄

于 2022-01-26T13:25:15.910 回答