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

Posted

技术标签:

【中文标题】如何在打开游标之前在存储过程中创建一个临时表?【英文标题】:How to create a temporary table inside Stored Procedure before opening a cursor? 【发布时间】:2017-01-17 04:33:10 【问题描述】:

我正在尝试在存储过程中创建一个临时表以对其执行多项操作,然后执行一个 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;

【问题讨论】:

【参考方案1】:

您的程序无法编译,因为您对 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;

注意表空间子句: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 是昂贵的,不是标准业务处理的一部分;它也使交易复杂化。

【讨论】:

【参考方案2】:

你不能编译程序,因为表还不存在。 您可以:

在创建过程之前创建(在过程之外创建表) 在过程中使用动态创建的 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 文档

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

【讨论】:

以上是关于如何在打开游标之前在存储过程中创建一个临时表?的主要内容,如果未能解决你的问题,请参考以下文章

在存储过程中创建临时表

oracle 怎么在存储过程中创建一个临时表,在里面插入数据,再查找这个临时表的所有数据,最后drop这个表。

oracle 怎么在存储过程中创建一个临时表,在里面插入数据,再查找这个临时表的所有数据,最后drop这个表。

在 MySQL 存储过程中创建临时表

在SQL存储过程中创建临时表

怎样在pl/sql中创建,执行和删除存储过程