如何在 PLSQL 中存储立即执行的结果?

Posted

技术标签:

【中文标题】如何在 PLSQL 中存储立即执行的结果?【英文标题】:How to store the result of an execute immediate in PLSQL? 【发布时间】:2017-01-03 05:32:41 【问题描述】:

我有一个表,其中包含存储为 varchars 的选择查询和插入查询。我必须使用过程执行选择查询并使用插入查询插入选择查询的结果。现在我正在执行立即和批量将选择查询收集到 varchars 表中。

在此之后,我转到 vartable 的每一行并获取值并将其放入插入查询中。我有许多对许多表的选择和插入查询。所以这必须动态完成。 我的问题是,有没有更好的方法来存储选择查询的结果?除了使用 varchar 表?因为选择查询的结果集可能有数百万条记录,这可能会导致问题。 使用嵌套表类型并在其上使用 EXTEND 会解决问题吗?

PROCEDURE SEL_INS
AS
  CURSOR C
  IS
    SELECT 
      SELEQRY SELQRY,
      INSQUERY INSERTQRY,
      cols COLS
      FROM TAB1;
      selqry  VARCHAR2(1000);
      insqry VARCHAR2(1000);
      tab1 vartable:=vartable();
      cols   NUMBER;

 BEGIN
   tab1:=vartable(NULL);
     FOR X    IN C
      LOOP
          selqry:= X.SELQRY;
          cols:=X.COLS;
  EXECUTE immediate selqry bulk collect INTO tab1;
 -- select statement is concatenated before executing. so that each index has one record    
 --with values separated by commas 
   --- a sample column in tab1 will have values like (abc,abc1,abc2)
     FOR i IN 1..tab1.count
     LOOP
       insqry :=X.INSERTQRY; 
       --- insert query will have values like insert into tab2   values('abc,'abc1','abc2')

         EXECUTE immediate insqry;

     END LOOP;
   END LOOP;


  END SEL_INS;

vartable 是 varchars2(4000) 类型的表

【问题讨论】:

我很困惑SELECTINSERT 如何协同工作。这些“插入查询”是什么?它们只是要插入数据的表吗?你能重新写这个吗:execute immediate 'insert into '||var_insert_table||' '||var_selquery; @cumberdame 你能举一些简单的例子来说明这些语句的样子吗?我想知道是否有一种方法可以直接插入表而不将数据存储在 PL/SQL 数组中。 您可以选择使用open ... for ...打开动态sql游标,而不是将结果存储在大数组中,参见docs.oracle.com/cd/B28359_01/appdev.111/b28370/…,而不是execute immediate 为什么要分开插入和选择语句?为什么不在你的表中存储一个insert into ... select .... 语句呢?然后运行execute immediate v_sql_statement; 就变得很简单了。或者,如果您的插入和选择语句位于不同的列中,而不是将插入语句存储为insert into .... values (...),为什么不将其设为insert into ....,这样您就可以执行execute immediate v_ins_statement||chr(10)||v_sel_statement; 插入语句长什么样子?也许可以收集 Insert into ... Select ... 语句并动态执行它。或者作为其他方式将 Select query result 插入临时表并在 Insert statement 中使用 【参考方案1】:

如 cmets 中所述,您应该尝试将您的语句重写为 INSERT INTO ... SELECT ...。让我们假设无论出于何种原因这是不可能的。在这种情况下,您可以使用以下程序:

PROCEDURE SEL_INS AS

    CURSOR C IS
    SELECT SELEQRY, INSQUERY, COLS
    FROM TAB1;

    selqry  VARCHAR2(1000);
    insqry VARCHAR2(1000);
    tab1 vartable;
    cols   NUMBER;

    cur INTEGER;
    res INTEGER;
    col_cnt INTEGER;
    desctab DBMS_SQL.DESC_TAB;
    i INTEGER;

BEGIN

    FOR aQuery IN C LOOP
        tab1 := vartable();

        cur := DBMS_SQL.OPEN_CURSOR;
        DBMS_SQL.PARSE(cur, aQuery.SELEQRY, DBMS_SQL.NATIVE);
        DBMS_SQL.DESCRIBE_COLUMNS(cur, col_cnt, desctab)
        FOR i IN 1..col_cnt LOOP
            DBMS_SQL.DEFINE_COLUMN(cur, i, desctab(i).COL_NAME, 2000);
        END LOOP;
        res := DBMS_SQL.EXECUTE_AND_FETCH(cur, TRUE);
        FOR i IN 1..col_cnt LOOP
            tab1.EXTEND;
            DBMS_SQL.COLUMN_VALUE(cur, i, tab1(tab1.LAST));
        END LOOP;
        DBMS_SQL.CLOSE_CURSOR(cur);

        -- ... do whatever with tab1(xyz) -> otherwise this procedure would be an overkill

        cur := DBMS_SQL.OPEN_CURSOR;
        DBMS_SQL.PARSE(cur, aQuery.INSQUERY, DBMS_SQL.NATIVE);
        i := tab1.FIRST;
        WHILE i IS NOT NULL LOOP
            DBMS_SQL.BIND_VARIABLE(cur, ':b'||i, tab1(i));
            i := tab1.NEXT(i);
        END LOOP;
        res := DBMS_SQL.EXECUTE(cur);
        DBMS_SQL.CLOSE_CURSOR(cur);

    END LOOP;   

END;

注意,此过程假定所有列都是 VARCHAR2 数据类型(最大长度为 2000 个字符)。如果你有其他数据类型,那么DBMS_SQL.DEFINE_COLUMN(cur, i, desctab(i).COL_NAME, 2000); 行必须像IF desctab(c).col_type = 1 THEN ... 一样扩展

另外,请注意 DBMS_SQL.EXECUTE_AND_FETCH 将失败,除非您的选择恰好返回一行。如果您的查询可能返回不止一行,您必须使用

DBMS_SQL.EXECUTE(cur);
WHILE (DBMS_SQL.FETCH_ROWS(cur) > 0) LOOP
   ...
END LOOP;

查看Oracle Built-in Data Types获取每种数据类型的代码。

【讨论】:

【参考方案2】:

假设您能够更改存储在存储要运行的 sql 语句的表中的数据,这是一个示例,我将获得存储数据的外观:

INSERT INTO sel_ins (selqry, insquery)
SELECT 'select col1, col2, col3 from table1' selqry, 'insert into other_table1 (col1, col2, col3)') insqry FROM dual
UNION ALL
SELECT 'select col1, col2 from table2' selqry, 'insert into other_table2 (col1, col2)') insqry FROM dual
UNION ALL
SELECT 'select col1, col2, col3 from table3 where col4 = ''fred''' selqry, 'insert into other_table3 (col1, col2, col3)') insqry FROM dual;

通过这样做,您进行插入的过程现在要简单得多:

PROCEDURE sel_ins IS
  CURSOR ins_sel_cur IS
    SELECT seleqry selqry, insquery insqry, cols cols
    FROM   tab1;
  v_selqry tab1.seleqry%TYPE;
  v_insqry tab1.insquery%TYPE;
BEGIN
  FOR ins_sel_rec IN ins_sel_cur
  LOOP
    EXECUTE IMMEDIATE ins_sel_rec.v_insqry || CHR(10) || ins_sel_rec.v_selqry;
  END LOOP;
END;
/

这样,您无需从表中检索大量数据并将其存储在内存中,而只是获取该数据并将其逐行添加回另一个表中 - 您正在完成所有工作一个单一的 DML 语句(相当于建筑商将装有她需要的所有砖块的卡车运送到她正在建造墙壁的地方,而不是在驱动器的底部,然后一个接一个地去取每块砖) .这应该会让事情变得更快,更不用说更容易阅读、维护等了。

如果您喜欢单独存储列,例如:

INSERT INTO sel_ins (selqry, insquery, cols)
SELECT 'select <COLS> from table1' selqry, 'insert into other_table1 (<COLS>)') insqry, 'col1, col2, col3' cols FROM dual
UNION ALL
SELECT 'select <COLS> from table2' selqry, 'insert into other_table2 (<COLS>)') insqry, 'col1, col2' cols FROM dual
UNION ALL
SELECT 'select <COLS> from table3 where col4 = ''fred''' selqry, 'insert into other_table3 (<COLS>)') insqry, 'col1, col2, col3' cols FROM dual;

那么你的执行立即变成:

execute immediate replace(ins_sel_rec.v_insqry, '<COLS>', ins_sel_rec.cols) ||
                  chr(10) ||
                  replace(ins_sel_rec.v_selqry, '<COLS>', ins_sel_rec.cols);

【讨论】:

以上是关于如何在 PLSQL 中存储立即执行的结果?的主要内容,如果未能解决你的问题,请参考以下文章

如何在plsql中执行存储过程

plsql中如何执行存储过程?

将所有查询结果转储到 plsql 块内的文件中

执行立即变量中的单引号 - Oracle PLSQL

sql如何批量进行执行存储过程并传参数?

plsql 中如何使用dbms_output输出结果