如何在打开游标之前在存储过程中创建临时表?

时间:2017-01-17 04:33:10

标签: oracle stored-procedures plsql

我正在尝试在存储过程中创建临时表以对其执行多个操作,然后执行select语句。我没有成功使用全局临时表。当我执行下面的代码时,我得到ORA-00942 error(表或视图不存在)。我必须返回光标以在Spring中使用它。有什么想法吗?

PROCEDURE DELETME (
          O_CURSOR         OUT tCursor,
          COD_ERROR        OUT NUMBER,
          MSM_ERROR        OUT VARCHAR2
          )
     AS
BEGIN
    execute immediate 'create global temporary table my_temp_table(column1 number) on commit delete rows';
     insert into my_temp_table(column1) values (1);
    COD_ERROR := 1;
    OPEN o_cursor FOR
           select * from my_temp_table;
EXCEPTION 
    WHEN OTHERS THEN
              COD_ERROR:=0;
              MSM_ERROR:=dbms_utility.format_error_backtrace ||' '||SQLERRM;
END DELETME;

2 个答案:

答案 0 :(得分:3)

您的过程将无法编译,因为您对TEMP_TABLE具有依赖性,而该TEMP_TABLE不存在。因此ora-00942。当然,如果确实存在,那么您的过程在运行时失败:create global temporary table调用将失败,因为该表已经存在。

基本上你误解了Oracle中全局临时表的用途。它们是永久性结构,只是它们所持有的数据是瞬态的。这是一个常见问题,尤其适用于熟悉SQL Server并尝试将T-SQL转换为Oracle的人员。 MSSQL中的临时表更像是PL / SQL集合。

显然你发布的代码是一个玩具,所以你不清楚为什么你认为你需要一个临时表。你很可能不这样做,因为Oracle SQL非常强大。您可以只为复杂的SELECT语句打开引用游标。

但你碰巧有一些真正的需要,这就是如何做到这一点:

首先,作为一次性练习:

create global temporary table my_temp_table
   (column1 number) on commit delete rows   
    tablespace temporary_ts;

请注意tablespace子句:GTT写入磁盘而不是内存,这意味着它们填充速度慢且读取速度慢。如果您打算使用GTT,最好只为他们提供专用的临时表空间,因为与其他临时进程(如排序)相比,它们具有不同的使用情况。

无论如何,您的程序变为

PROCEDURE DELETME (
          O_CURSOR         OUT tCursor,
          COD_ERROR        OUT NUMBER,
          MSM_ERROR        OUT VARCHAR2
          )
     AS
BEGIN
    insert into my_temp_table(column1) values (1);
    COD_ERROR := 1;
    OPEN o_cursor FOR
           select * from my_temp_table;
EXCEPTION 
    WHEN OTHERS THEN
              COD_ERROR:=0;
              MSM_ERROR:=dbms_utility.format_error_backtrace ||' '||SQLERRM;
END DELETME;

请记住,您需要发出提交(或回滚)来清除表格;如果你没有保管它可能会在同一个会话重新使用它时产生问题。

或者,改为使用集合。集合更快,因为它们是内存结构。虽然内存来自会话分配,但如果总行数太大,这不是最佳解决方案。

像这样的东西。再次,作为一次性的练习:

create or replace object num_nt as table of number;

然后您的程序变为:

PROCEDURE DELETME (
          O_CURSOR         OUT tCursor,
          COD_ERROR        OUT NUMBER,
          MSM_ERROR        OUT VARCHAR2
          )
     AS
    local_nt num_nt;
BEGIN
    select 1 
    bulk collect into local_nt
    from dual;
    COD_ERROR := 1;
    OPEN o_cursor FOR
           select * from table(local_nt);
EXCEPTION 
    WHEN OTHERS THEN
              COD_ERROR:=0;
              MSM_ERROR:=dbms_utility.format_error_backtrace ||' '||SQLERRM;
END DELETME;

还有第三个“解决方案”,即对过程中的所有调用使用动态SQL。这是一个非常糟糕的方法(甚至除了误解全局临时表的使用之外)。动态代码比常规代码更复杂,只应在真正需要时使用。执行DDL是昂贵的,而不是作为标准业务处理的一部分要做的事情;这也使交易变得复杂。

答案 1 :(得分:0)

您无法编译过程,因为表尚不存在。 你可以:    

  • 在创建过程之前创建(在过程外创建表)    
  • 等过程中使用动态创建的SQL
    execute immediate 'insert into my_temp_table(column1) values (:x)' using 1. 
    

    关于EXECUTE IMMEDIATE http://docs.oracle.com/database/122/LNPLS/EXECUTE-IMMEDIATE-statement.htm#LNPLS01317

    的Oracle文档

    注意:从头部编写,未经测试