【问题标题】:execute immediate not showing records of Dynamic Select statement立即执行不显示动态选择语句的记录
【发布时间】:2018-08-08 10:05:34
【问题描述】:

我创建了动态创建 Select 语句的匿名块。当我执行块时,它只显示匿名块已完成但不显示 SQL 输出。

declare
    sql_stmt clob; 
    pivot_clause clob; 
begin 
    select listagg('''' || TO_CHAR(PERIOD_NAME,'MON-YY') || ''' as "' || TO_CHAR(PERIOD_NAME,'MON-YY') || '"', ',') 
    within group (order by PERIOD_NAME) 
    into pivot_clause
    from   ( select TO_DATE(PERIOD_NAME,'MON-YYYY') PERIOD_NAME 
             from table1 
             where request_id=<id> 
             group by TO_DATE(PERIOD_NAME,'MON-YYYY') 
             order by TO_DATE(PERIOD_NAME,'MON-YYYY') ASC );

     sql_stmt := 'select * from (select PERIOD_NAME, depreciation 
                                 from table1) pivot (sum(depreciation) for PERIOD_NAME in (' || pivot_clause || '))';

     execute immediate sql_stmt; 
 end; 

【问题讨论】:

  • 立即执行不适用于 SELECT。
  • 你必须使用execute immediate sql_stmt INTO ...;

标签: sql oracle plsql


【解决方案1】:

由于您事先不知道结构,因为动态枢轴到结果集中未知数量的列,您可以使用引用游标来检索动态查询的结果。

这使用 SQL*Plus/SQL Developer/SQLcl 绑定变量;

variable rc refcursor;

declare
  sql_stmt clob; 
  pivot_clause clob; 
begin 
  select listagg('''' || TO_CHAR(PERIOD_NAME,'MON-YY') || ''' as "' || TO_CHAR(PERIOD_NAME,'MON-YY') || '"', ',') 
  within group (order by PERIOD_NAME) 
  into pivot_clause from (select TO_DATE(PERIOD_NAME,'MON-YYYY') PERIOD_NAME 
                          from table1 
                          where request_id=<id> 
                          GROUP BY TO_DATE(PERIOD_NAME,'MON-YYYY') 
                          order by TO_DATE(PERIOD_NAME,'MON-YYYY') ASC); 
  sql_stmt := 'select * from (select PERIOD_NAME, depreciation 
                              from table1) pivot (sum(depreciation) for PERIOD_NAME in (' || pivot_clause || '))';

  open :rc for sql_stmt; 
end;
/

print rc

client variable command

variable rc refcursor;

将客户端绑定变量的变量和数据类型声明为引用游标。然后,而不是使用 execute immediate 它在您的动态语句中使用 open for

  open :rc for sql_stmt; 

打开带有该查询结果的引用游标。 (注意:rc 开头的:,表示这是一个绑定变量引用而不是本地PL/SQL 变量)。

然后在块外你可以print结果集:

print rc

不同的客户端/IDE 需要不同的语法。你也可以通过 JDBC 做类似的事情。您还可以有一个返回sys_refcursor 的函数。但这取决于您的最终目标是什么。


顺便说一句,目前您将获得所有透视总数的 null ;您的最终查询需要以与 pivot 子句相同的格式获取 PERIOD_NAME,例如

  sql_stmt := 'select * from (select to_char(to_date(PERIOD_NAME, ''MON-YYYY''), ''MON-YY'') as PERIOD_NAME, depreciation 
                              from table1) pivot (sum(depreciation) for PERIOD_NAME in (' || pivot_clause || '))';

虽然将原始格式保留在枢轴子句中会稍微简单一些:

declare
  sql_stmt clob; 
  pivot_clause clob; 
begin 

  select listagg('''' || PERIOD_NAME || ''' as "' || TO_CHAR(PERIOD_DATE,'MON-YY') || '"', ',') 
  within group (order by PERIOD_DATE) 
  into pivot_clause from (select distinct PERIOD_NAME, TO_DATE(PERIOD_NAME,'MON-YYYY') PERIOD_DATE 
                          from table1 
                          where request_id=<id>); 

  sql_stmt := 'select * from (select PERIOD_NAME, depreciation 
                              from table1) pivot (sum(depreciation) for PERIOD_NAME in (' || pivot_clause || '))';

  open :rc for sql_stmt; 
end;
/

使用虚拟表和数据:

create table table1 (request_id, period_name, depreciation) as
select 1, 'JAN-2018', 42 from dual
union all select 1, 'FEB-2018', 11 from dual
union all select 1, 'MAR-2018', 22 from dual
union all select 1, 'MAR-2018', 33 from dual
union all select 2, 'MAR-2018', 44 from dual;

运行任一版本并执行print rc 显示:

    JAN-18     FEB-18     MAR-18
---------- ---------- ----------
        42         11         99

【讨论】:

  • 感谢您的解决方案。但是当我打印 rc 时,它没有显示行和列中的记录。请帮忙。
  • @PrashantKulkarni - 你看到我的编辑了吗,解释了如何解决这个问题?您的原始数据透视子句基于较短的 MON-YY 值,因此与表中较长的 MON-YYYY 值不匹配。我展示了两种解决方法。
  • 当我执行程序并打印 rc 时,我得到了预期的结果,即我得到的记录是正确匹配的。但唯一的问题是它没有按预期出现在行和列中。抱歉,如果我不能正确理解您给定的解决方案。
  • 你应该每个月得到一列,就像我的演示一样。您不会得到多行,因为您选择的两个列都被枢轴消耗。你为什么期望多行,它们会显示什么?
【解决方案2】:

你只能选择一个动态的sql变成一些变量。

例子:

declare
    v_sql VARCHAR2(2000);
    v_col1 varchar2(100);
    v_col2 varchar2(100);
    v_col3 varchar2(100);
begin
    v_sql := 'SELECT 1, 2, 3 FROM DUAL';

    EXECUTE IMMEDIATE v_sql INTO  v_col1, v_col2, v_col3;

    dbms_output.put_line('v_col1: ' || v_col1);
    dbms_output.put_line('v_col2: ' || v_col2);
    dbms_output.put_line('v_col3: ' || v_col3); 
end;

如果您有多行,则必须使用光标:

DECLARE
    TYPE c IS REF CURSOR;

    v_c    c;
    v_sql        VARCHAR2(2000);
    v_col1       VARCHAR2(100);
    v_col2       VARCHAR2(100);      
BEGIN
    v_sql := 'SELECT 1, 2 FROM DUAL UNION ALL SELECT 3, 4 FROM DUAL';

    OPEN v_c FOR v_sql;

    LOOP
        FETCH v_c INTO v_col1, v_col2;
        EXIT WHEN v_c%NOTFOUND;

        dbms_output.put_line('v_col1: ' || v_col1 || ', v_col2: ' || v_col2);

    END LOOP;

    CLOSE v_c;
END;

【讨论】:

  • 我正在使用 listagg 函数,该函数用于枢轴。由于 listagg o/p 本质上是动态的,因此我无法在此处选择特定的列。
  • 为多行循环添加了光标示例。
【解决方案3】:

您需要为 EXECUTE IMMEDIATE 提供一种将值返回给程序的方法,因此您需要一个 INTO 子句。

当您返回一组行时,您需要一个结构来存储它。

为了举例,下面创建了一个从 ALL_OBJECTS 视图中选择的动态查询,并将结果放入一个集合中。

DECLARE
   sql_stmt     CLOB;
   TYPE my_rec_rt IS RECORD
   ( owner         VARCHAR2(30),
     object_name   VARCHAR2(30) );
   TYPE my_rec_t IS TABLE OF my_rec_rt;
   obj_record   my_rec_t;
BEGIN
   sql_stmt :=
      q'[select owner, object_name from all_objects where owner = 'ODS']';

   EXECUTE IMMEDIATE sql_stmt BULK COLLECT INTO obj_record;
END;

您随后如何浏览收藏取决于您的要求。

(您可以在 https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/executeimmediate_statement.htm#LNPLS01317 的 Oracle 文档中找到此文档)

【讨论】:

  • 我正在使用 listagg 函数,它是 'in cluase' 的枢轴。由于 listagg o/p 本质上是动态的,因此我无法在此处选择特定的列。请指教。
  • 在这种情况下,您需要使用 Alex Poole 的答案中的技术。
【解决方案4】:

根据您的查询,可能是这样的:

declare
    sql_stmt clob; 
    pivot_clause clob; 

    v_PERIOD_NAME DATE;
    v_depreciation NUMBER;
begin 
    select listagg('''' || TO_CHAR(PERIOD_NAME,'MON-YY') || ''' as "' || TO_CHAR(PERIOD_NAME,'MON-YY') || '"', ',') 
    within group (order by PERIOD_NAME) 
    into pivot_clause
    from   ( select TO_DATE(PERIOD_NAME,'MON-YYYY') PERIOD_NAME 
             from table1 
             where request_id=<id> 
             group by TO_DATE(PERIOD_NAME,'MON-YYYY') 
             order by TO_DATE(PERIOD_NAME,'MON-YYYY') ASC );

     sql_stmt := 'select * from (select PERIOD_NAME, depreciation 
                                 from table1) pivot (sum(depreciation) for PERIOD_NAME in (' || pivot_clause || '))';

     execute immediate sql_stmt INTO v_PERIOD_NAME, v_depreciation;
     dbms_output.put_line('v_PERIOD_NAME: ' || v_PERIOD_NAME); 
     dbms_output.put_line('v_depreciation: ' || v_depreciation); 
 end; 

请注意,这假定您只会(并且总是)从查询中返回一行。否则你会得到异常NO_DATA_FOUND,resp。 TOO_MANY_ROWS.

【讨论】:

  • table1 中的两列被转置为未知数量的列...它们不是直接选择的。
  • @AlexPoole,是的,你是对的,我不经常使用 PIVOT,所以我不习惯它。无论如何,我会留下我的答案来证明基本原理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-20
  • 1970-01-01
  • 2016-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多