PL / SQL错误:应该重构包含DML语句的循环以使用BULK COLLECT和FORALL

时间:2016-07-06 09:06:59

标签: oracle bulkinsert dml

我在整个互联网上搜索了一些例子,但我仍然无法理解为什么我不能在这个游标中使用DML语句。我有点想念它背后的理论,但我不会否认一个如何正确地写这个的例子会让我的生活变得更轻松。这是我正在处理的查询(注意:我在未找到结果时删除了退出,如果光标已经打开则关闭,这样的事情只关注这里的主要点):

DECLARE
    // lots of vars

    // the cursor below gets all datasources connected to Node XXYZ123
    CURSOR DataSourceCheck
    IS
        SELECT NODENAAM, NAAM, URL, DBNODE1, DBNODE2, DBUSERNAAM, DBNAAM
        FROM SCHEMA.TABLENAME
        WHERE NODENAAM = 'XXYZ123';

    // this cursor will execute row-by-row based on the result set of above cursor
    CURSOR CheckIfOnlyDataSource
    IS
        SELECT NODENAAM, NAAM, URL, DBNODE1, DBNODE2, DBUSERNAAM, DBNAAM
        FROM SCHEMA.TABLENAME
        WHERE DBUSERNAAM = var_dbusernaam AND (DBNode1 = var_dbnode1 OR DBNode2 = var_dbnode2);

BEGIN  
    OPEN DataSourceCheck;   
    LOOP
        FETCH DataSourceCheck into var_nodenaam, var_naam, var_URL, var_dbnode1, var_dbnode2, var_dbusernaam, var_dbnaam;

        var_rowcount:= 0;
        OPEN CheckIfOnlyDataSource;     
            LOOP
                FETCH CheckIfOnlyDataSource into var_nodenaam2, var_naam2, var_URL2, var_dbnode12, var_dbnode22, var_dbusernaam2, var_dbnaam2;
                var_rowcount:= var_rowcount + 1;
            END LOOP;  

            // only save result in a temp table when var_rowcount is 1 and not higher.
            IF var_rowcount = 1
                THEN
                    INSERT INTO global_temp_table
                    (t_dbusernaam, t_nodenaam, t_dbnode1, t_dbnode2, t_distinctcount)
                    VALUES
                    (var_dbusernaam2, var_nodenaam2, var_dbnode12, var_dbnode22, var_rowcount)
            END IF;
        CLOSE CheckIfOnlyDataSource;
    END LOOP;    
END;

失败的关键在于这一部分,消息应该将DML重新配置为FORALL或BULK INTO语句:

IF var_rowcount = 1
    THEN
        INSERT INTO global_temp_table
        (t_dbusernaam, t_nodenaam, t_dbnode1, t_dbnode2, t_distinctcount)
        VALUES
        (var_dbusernaam2, var_nodenaam2, var_dbnode12, var_dbnode22, var_rowcount)
END IF;

我不明白为什么DML不是以逐行的方式工作?输出显然存储在变量var_dbusernaam2var_nodenaam2var_dbnode12var_dbnode22中,因此我可以执行dbms_output.put_line来显示它们。但是如果它已经存储到变量中,那么为什么我不能将它简单地存储到一个表中(这不是数十亿的批量数据,甚至不是1000个记录!)。

没有简单的解决方法吗?我给了BULK COLLECT和FORALL一试,但我需要花更多的时间去理解它并使查询正确 - 光标中的光标肯定不会让它变得更容易。

2 个答案:

答案 0 :(得分:5)

除了Mottor的回答中的建议之外,Toad之所以标记你的代码是因为逐行处理很慢。您已经在PL / SQL和SQL引擎之间进行了大量的上下文切换。

把它想象成在你家附近建造新墙 - 如果砖被送到驱动器的底部,你会这样做吗?

  1. 去一堆砖头
  2. 拿起一块砖
  3. 回到墙上
  4. 将砖块添加到墙上
  5. 返回步骤1并重复直到墙完成
  6. (这相当于逐行处理)

    或者:

    1. 把你的独轮车带到一堆砖头
    2. 用适合的砖块装载手推车和/或随身携带
    3. 把手推车拉回到墙上
    4. 将每块砖添加到墙上
    5. 返回步骤1并重复直到墙完成。
    6. (这相当于批量处理。)

      当然,如果你是精明的,你可以避免在上述场景中所需的所有行走和携带,首先将砖块放在墙壁旁边。 (这相当于基于集合的处理。)

      将您的程序转变为基于集合的方法(结合Mottor的答案)将简单地说明:

      declare
          -- lots of vars
      begin
        insert into global_temp_table (t_dbusernaam,
                                       t_nodenaam,
                                       t_dbnode1,
                                       t_dbnode2,
                                       t_distinctcount)
        select dbusernaam,
               nodenaam,
               dbnode1,
               dbnode2,
               cnt
        from   (select nodenaam,
                       naam,
                       url,
                       dbnode1,
                       dbnode2,
                       dbusernaam,
                       dbnaam,
                       count(*) over (partition by dbnode1, dbnode2, dbusernaam) cnt
                from   schema.tablename
                where  nodenaam = 'XXYZ123')
        where  cnt = 1;
      end;
      /
      

      这样做的优点是比原始代码更紧凑,使其更易于阅读,理解和调试。另外,您可以在程序之外单独运行select语句 - 更容易看到它正在这样做。

      它也会比你原来的循环遍历两个游标的方法更快(顺便说一句,它重新发明了嵌套循环连接 - 数据库在纯SQL中优化的东西......可能不是无论如何,如果你一直坚持加入吧,最快的方式就是加入!)。

      我也有兴趣知道你为什么需要将行插入GLOBAL_TEMP_TABLE(我怀疑它是GTT - 全局临时表 - 而不是普通的堆表) - 你能否在后续处理单个SQL语句,使用上面的select语句而不是将数据插入GTT?

答案 1 :(得分:2)

这不是错误,而是带有编号规则4809的TOAD建议。

P.S。如果两个查询中的表格相同,则可以使用

..., COUNT(*) OVER (PARTITION BY DBNODE1, DBNODE2, DBUSERNAAM) c 

在第一个查询中获取每个DBNODE1,DBNODE2,DBUSERNAAM的行数,而不需要第二个。