我在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 COLLECT
和FORALL
语句?
答案 0 :(得分: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
,其中调用者不知道发生错误,错误堆栈丢失,并且无法保证调用应用程序甚至已为其分配缓冲区要写入的数据是等待发生的错误。如果你的系统中有这种代码,那么你将不可避免地最终追逐错误,因为某些代码遇到了某些代码并吞噬了一个异常,导致后来的代码以意想不到的方式失败。
答案 1 :(得分:1)
您的原始程序中存在关于错误管理的错误,这使得很难将逻辑转换为批量处理。
基本上,第一个过程的逻辑是:在循环中运行两个语句,在第一次遇到错误时或在游标结束时成功退出,以先发生者为准。
这不是一个正确的事务逻辑。如果你的两个语句协同工作而第二个语句失败,那么第一个语句就不会回滚!
您可能想要做的是:在循环中运行两个语句;如果遇到错误,请记录信息并撤消更改,如果未成功退出。在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;
答案 2 :(得分: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;
在此流程中:
INSERT
中出现的所有异常都将存储在SQL%BULK_EXCEPTIONS
集合DBMS_OUTPUT.PUT_LINE
将记录每个异常(在AUTONOMOUS TRANSACTION
过程的日志表中的现实生活中)LT_CUT
的每个错误索引都将在收集时通过DELETE
方法删除。UPDATE
中仅使用“好”行,因为INDICES OF
子句允许通过删除对特定元素的引用对稀疏集合进行批量操作