Plsql 一个批量收集操作循环内的两个(或多个)DML

Plsql 一个批量收集操作循环内的两个(或多个)DML,plsql,oracle11g,bulkinsert,paradigms,bulk-collect,Plsql,Oracle11g,Bulkinsert,Paradigms,Bulk Collect,我对Oracle 11g上的批量收集逻辑有问题 存储过程中的原始逻辑是: PROCEDURE FOO(IN_FOO IN VARCHAR2) IS BEGIN FOR CUR IN (SELECT COL1,COL2,COL3 FROM SOME_TABLE) LOOP INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (CUR.COL1,CUR.COL2,CUR.COL3); UPDATE THIRD_TABLE T SET T.C_SUM

我对Oracle 11g上的批量收集逻辑有问题

存储过程中的原始逻辑是:

PROCEDURE FOO(IN_FOO IN VARCHAR2) IS
BEGIN
  FOR CUR IN (SELECT COL1,COL2,COL3 FROM SOME_TABLE) LOOP
    INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (CUR.COL1,CUR.COL2,CUR.COL3);
    UPDATE THIRD_TABLE T SET T.C_SUM = CUR.COL2 + CUR.COL3 WHERE T.C_ID = CUR.COL1);
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERROR || ': ' || SQLERRM);
END FOO;
但我想使用
批量收集
功能

我写了这样的东西:

PROCEDURE FOO_FAST(IN_FOO IN VARCHAR2) IS
  CURSOR CUR IS SELECT COL1,COL2,COL3 FROM SOME_TABLE;
  TYPE RT_CUR IS TABLE OF CUR%ROWTYPE;
  LT_CUR RT_CUR;
  DML_EXCEPTION EXCEPTION;
  PRAGMA EXCEPTION_INIT(DML_EXCEPTION, -24381);
BEGIN
  OPEN CUR;
  LOOP
    FETCH CUR BULK COLLECT INTO LT_CUR LIMIT 1000;
    EXIT WHEN LT_CUR.COUNT = 0;
    BEGIN
      FORALL I IN 1 .. LT_CUR.COUNT 
        INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (LT_CUR(I).COL1,LT_CUR(I).COL2,LT_CUR(I).COL3);
      FORALL I IN 1 .. LT_CUR.COUNT 
        UPDATE THIRD_TABLE T SET T.C_SUM = LT_CUR(I).COL2 + LT_CUR(I).COL3 WHERE T.C_ID = LT_CUR(I).COL1);
    EXCEPTION
      WHEN DML_EXCEPTION THEN
        FORALL I IN 1 .. SQL%BULK_EXCEPTIONS(1).ERROR_INDEX-1
          UPDATE THIRD_TABLE T SET T.C_SUM = LT_CUR(I).COL2 + LT_CUR(I).COL3 WHERE T.C_ID = LT_CUR(I).COL1);
        DBMS_OUTPUT.PUT_LINE(SQLERRM(-SQL%BULK_EXCEPTIONS(1).ERROR_CODE));
        RETURN;
    END;
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERROR || ': ' || SQLERRM);
END FOO_FAST;
这是解决这个问题的好方法吗

如果我有更多的DML要执行呢


嗯。我的问题更复杂,但我想用很好的示例代码简化它并丰富它。错误
其他
处理不是此问题的一部分。也许这会更清楚:

如何:

  FOR CUR IN (SELECT COL1,COL2,COL3 FROM SOME_TABLE) LOOP
    INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (CUR.COL1,CUR.COL2,CUR.COL3);
    UPDATE THIRD_TABLE T SET T.C_SUM = CUR.COL2 + CUR.COL3 WHERE T.C_ID = CUR.COL1);
  END LOOP;
对于所有的语句,更改为
批量收集

某件事情是否是“好方法”是非常主观的,这取决于您试图对比的内容

如果我们假设您对
某个\u表的查询没有谓词,那么几乎可以肯定,在集合中工作比执行任何类型的循环更有效率(除了更少的代码)

PROCEDURE FOO(IN_FOO IN VARCHAR2) IS
BEGIN
  INSERT INTO other_table( c1, c2, c3 )
    SELECT col1, col2, col3
      FROM some_table;

  UPDATE third_table tt
     SET tt.c_sum = (SELECT st.col2 + st.col3
                       FROM some_table st
                      WHERE tt.c_id = st.col1)
   WHERE EXISTS( SELECT 1
                   FROM some_table st
                  WHERE tt.c_id = st.col1);
END;

通常,当其他人
异常处理程序时,使用
异常处理程序是个坏主意。捕获异常仅尝试将其写入
DBMS\u输出
,调用者不知道发生了错误,错误堆栈丢失,并且无法保证调用应用程序已为要写入的数据分配了缓冲区,这是一个等待发生的错误。如果您的系统中有此类代码,您将不可避免地追踪难以重现的错误,因为某些代码在某个地方遇到并吞下了异常,导致后续代码以意外方式失败。

关于错误管理,您的原始过程中有一些错误,这使得很难将逻辑转换为批量处理

基本上,第一个过程的逻辑是:在循环中运行这两条语句,在第一次遇到错误时或在光标结束时成功退出,以先发生的为准

这不是正确的事务逻辑。如果您的两条语句同时工作,而第二条语句失败,则第一条语句不会回滚

您可能想做的是:在循环中运行这两条语句;如果遇到错误,请记录信息并撤消更改(如果未成功退出)。在PL/SQL中撤消更改非常容易,只需让错误传播:

PROCEDURE FOO(IN_FOO IN VARCHAR2) IS
BEGIN
  FOR CUR IN (SELECT COL1,COL2,COL3 FROM SOME_TABLE) LOOP
    BEGIN
       INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (CUR.COL1,CUR.COL2,CUR.COL3);
       UPDATE THIRD_TABLE T SET T.C_SUM = CUR.COL2 + CUR.COL3 
        WHERE T.C_ID = CUR.COL1;
    EXCEPTION
       WHEN OTHERS THEN
          dbms_output.put_line(cur.col1/*...*/); -- log **useful** debug info
          RAISE;-- very important for transactional logic
    END;
  END LOOP;
END;
顺便说一下,
DBMS\u OUTPUT
不是最好的日志记录工具,您可能需要创建一个日志记录表和一个自治事务过程来插入相关的错误消息和标识符

如果您想使用批量逻辑来转换上述过程,那么最好使用(单个DML语句)描述的方法。使用大容量数组时,如果要记录单个异常,则需要使用子句。别忘了重新提出错误。这应该起作用:

PROCEDURE foo_fast(in_foo IN VARCHAR2) IS
   CURSOR cur IS
      SELECT col1, col2, col3 FROM some_table;
   TYPE rt_cur IS TABLE OF cur%ROWTYPE;
   lt_cur rt_cur;
   dml_exception EXCEPTION;
   PRAGMA EXCEPTION_INIT(dml_exception, -24381);
BEGIN
   OPEN cur;
   LOOP
      FETCH cur BULK COLLECT
         INTO lt_cur LIMIT 1000;
      EXIT WHEN lt_cur.COUNT = 0;
      BEGIN
         FORALL i IN 1 .. lt_cur.COUNT SAVE EXCEPTIONS -- important clause
            INSERT INTO other_table (c1, c2, c3) 
               VALUES (lt_cur(i).col1, lt_cur(i).col2, lt_cur(i).col3);
         FORALL i IN 1 .. lt_cur.COUNT SAVE EXCEPTIONS -- 
            UPDATE third_table t SET t.c_sum = lt_cur(i).col2 + lt_cur(i).col3 
             WHERE t.c_id = lt_cur(i).col1;
      EXCEPTION
         WHEN dml_exception THEN
            FOR i IN 1 .. SQL%BULK_EXCEPTIONS.COUNT LOOP
               dbms_output.put_line('error '||i||':'||
                      SQL%BULK_EXCEPTIONS(i).error_code);
               dbms_output.put_line('col1='|| 
                      lt_cur(SQL%BULK_EXCEPTIONS(i).error_index).col1);-- 11g+
            END LOOP;
         raise_application_error(-20001, 'error in bulk processing');
      END;
   END LOOP;
END foo_fast;

我通过使用这种流找到了解决方案:

PROCEDURE FOO_FAST(IN_FOO IN VARCHAR2) IS
  CURSOR CUR IS SELECT COL1,COL2,COL3 FROM SOME_TABLE;
  TYPE RT_CUR IS TABLE OF CUR%ROWTYPE;
  LT_CUR RT_CUR;
  DML_EXCEPTION EXCEPTION;
  PRAGMA EXCEPTION_INIT(DML_EXCEPTION, -24381);
BEGIN
  OPEN CUR;
  LOOP
    FETCH CUR BULK COLLECT INTO LT_CUR LIMIT 1000;
    EXIT WHEN LT_CUR.COUNT = 0;
    BEGIN
      FORALL I IN 1 .. LT_CUR.COUNT SAVE EXCEPTIONS
        INSERT INTO OTHER_TABLE (C1,C2,C3) VALUES (LT_CUR(I).COL1,LT_CUR(I).COL2,LT_CUR(I).COL3);
    EXCEPTION
      WHEN DML_EXCEPTION THEN
        FOR I IN 1 .. SQL%BULK_EXCEPTIONS.COUNT
          DBMS_OUTPUT.PUT_LINE(SQLERRM(-SQL%BULK_EXCEPTIONS(1).ERROR_CODE));
          LT_CUR.DELETE(SQL%BULK_EXCEPTIONS(1).ERROR_INDEX);
    END;
    FORALL I IN INDICES OF LT_CUR 
        UPDATE THIRD_TABLE T SET T.C_SUM = LT_CUR(I).COL2 + LT_CUR(I).COL3 WHERE T.C_ID = LT_CUR(I).COL1);
  END LOOP;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERROR || ': ' || SQLERRM);
END FOO_FAST;
在此流程中:

  • INSERT
    中记录的所有异常将存储在
    SQL%BULK\u异常
    集合中
  • 每个异常都将通过
    DBMS\u OUTPUT.PUT\u LINE
    记录(在现实生活中,通过
    自主事务
    过程记录在日志表中)
  • 收集时,
    DELETE
    方法将删除
    LT\u CUT
    的每个错误索引
  • UPDATE
    中只使用“good”行,因为
    index OF
    子句允许通过删除对特定元素的引用对稀疏集合进行批量操作

  • 您的
    commit
    在哪里?此示例过程是一个大程序包的一部分。当其他人提交
    时,您不应该捕获
    ,而不重新引发过程中的错误。调用应用程序可能会捕获错误并显示一条漂亮的错误消息,但您正在运行的过程不应忽略该错误。记录并忽略是一种错误的理念。如果
    commit
    rollback
    位于此过程之外,那么为什么只使用
    return
    而不重新出现异常?如何告知来电者发生了什么?为什么要优化光标?在10g以上的情况下,它们得到了内部优化,以类似于批量采集的速度运行。请看:我非常尊重您的知识,但处理
    其他
    异常是不可能的。我只是想知道如何使用FORALL语句处理两个(或更多)DML操作。您的解决方案有缺陷。在原始语句中,当
    INSERT
    抛出异常(例如,unique约束)时,将不会处理更新,并在循环中执行任何进一步的INSERT&update操作。循环中的处理将停止。在您的解决方案中,先生,当您通过
    SAVE exceptions
    收集异常时,将不会处理任何更新,并且除了错误的插入之外,所有插入都将被处理。@WBAR:第二种情况中的提升似乎没有达到我的预期(回滚挂起的更改),我测试了一个
    RAISE\u APPLICATION\u ERROR
    ,现在它的行为正常了:任何错误都会使过程撤销其DML。这些过程现在是原子化的:它们要么完全失败而不更改数据库的状态,要么成功完成。但是,对于收集批量异常的其他用户,您的努力得到了+1,并且获得了不错的提示。如果更新中发生错误,则该过程将成功退出,只完成一半的工作,太好了:)我的
    UPDATE
    用于设置已处理的行,因此在我的情况下工作得非常完美。:)