3

我对 Oracle 11g 上的 BULK COLLECT 逻辑有疑问。

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

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;

但我想使用BULK COLLECT功能。

我写了这样的东西:

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 怎么办?


行。我的问题更复杂,但我想简化它并用漂亮的示例代码丰富它。错误OTHERS处理不是这个问题的一部分。也许这会更清楚:

这是怎么回事:

  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;

更改为BULK COLLECTFORALL语句?

4

3 回答 3

2

某事是否是“好方法”是非常主观的——这取决于您要比较的对象。

如果我们假设您的查询some_table没有谓词,那么在集合中工作而不是进行任何类型的循环几乎肯定会更有效(除了更少的代码)

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;

通常,WHEN OTHERS异常处理程序是一个坏主意。捕获异常只是为了尝试将其写入DBMS_OUTPUT调用者不知道发生错误的位置,错误堆栈丢失的位置,以及无法保证调用应用程序甚至为要写入的数据分配缓冲区的位置to 是一个等待发生的错误。如果你的系统中有这样的代码,你将不可避免地最终努力重现错误,因为某处的某些代码遇到并吞下了异常,导致后面的代码以意想不到的方式失败。

于 2013-01-29T15:34:52.267 回答
2

你原来的错误管理过程有问题,这使得很难将逻辑转换为批量处理。

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

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

您可能想要做的是:在循环中运行这两个语句;如果遇到错误,记录信息并撤消更改,如果没有成功退出。在 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_OUTPUT,不是最好的日志记录工具,您可能希望创建一个日志记录表和一个自治事务过程来插入相关的错误消息和标识符。

如果您想通过使用批量逻辑来转换上述过程,您最好的做法是使用Justin Cave描述的方法(单个 DML 语句)。使用批量数组时,SAVE EXCEPTIONS如果要记录单个异常,则需要使用该子句。不要忘记重新提出错误。这应该有效:

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;
于 2013-01-29T15:56:45.470 回答
1

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

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;

在这个流程中:

  1. 发生的所有异常INSERT都将存储在SQL%BULK_EXCEPTIONS集合中
  2. 每个异常都将由DBMS_OUTPUT.PUT_LINE(在现实生活中按AUTONOMOUS TRANSACTION过程记录在日志表中)
  3. 的每个错误索引LT_CUT将通过DELETE集合上的方法删除。
  4. 只使用“好”行,UPDATE因为INDICES OF子句允许通过删除对特定元素的引用对稀疏集合进行批量操作
于 2013-02-19T16:27:25.413 回答