BULK COLLECT的奇怪行为

时间:2017-12-06 16:19:24

标签: oracle plsql bulkupdate bulk-collect

我将以下PL / SQL BULK-COLLECT拼凑在一起,这对于巨大的表(> 50.000.000)的更新非常快速地工作。唯一的问题是,它不执行剩余的<更新。每个表5000行。 5000是FETCH指令的给定限制:

DECLARE
-- source table cursor (only columns to be updated)
  CURSOR base_table_cur IS
  select a.rowid, TARGET_COLUMN from TARGET_TABLE a
                        where TARGET_COLUMN is null;

    TYPE base_type IS
        TABLE OF base_table_cur%rowtype INDEX BY PLS_INTEGER;
    base_tab  base_type;    

-- new data
    CURSOR new_data_cur IS
      select a.rowid,
        coalesce(b.SOURCE_COLUMN, 'FILL_VALUE'||a.JOIN_COLUMN) TARGET_COLUMN from TARGET_TABLE a 
        left outer join SOURCE_TABLE b
        on a.JOIN_COLUMN=b.JOIN_COLUMN
        where a.TARGET_COLUMN is null;

    TYPE new_data_type IS TABLE OF new_data_cur%rowtype INDEX BY PLS_INTEGER;
    new_data_tab       new_data_type;
    TYPE row_id_type IS TABLE OF ROWID INDEX BY PLS_INTEGER;
    row_id_tab         row_id_type;
    TYPE rt_update_cols IS RECORD (
        TARGET_COLUMN   TARGET_TABLE.TARGET_COLUMN%TYPE
    );
    TYPE update_cols_type IS
        TABLE OF rt_update_cols INDEX BY PLS_INTEGER;
    update_cols_tab    update_cols_type;
    dml_errors EXCEPTION;
    PRAGMA exception_init ( dml_errors,-24381 );

BEGIN
    OPEN base_table_cur;
    OPEN new_data_cur;
    LOOP
        FETCH base_table_cur BULK COLLECT INTO base_tab LIMIT 5000;
        IF base_table_cur%notfound THEN
           DBMS_OUTPUT.PUT_LINE('Nothing to update. Exiting.');
           EXIT; 
        END IF;
        FETCH new_data_cur BULK COLLECT INTO new_data_tab LIMIT 5000;
        FOR i IN base_tab.first..base_tab.last LOOP
            row_id_tab(i) := new_data_tab(i).rowid;
            update_cols_tab(i).TARGET_COLUMN := new_data_tab(i).TARGET_COLUMN;
        END LOOP;

        FORALL i IN base_tab.first..base_tab.last SAVE EXCEPTIONS
            UPDATE (SELECT TARGET_COLUMN FROM TARGET_TABLE)
            SET row = update_cols_tab(i)
            WHERE ROWID = row_id_tab(i);

        COMMIT;
        EXIT WHEN base_tab.count < 5000; -- changing to 1 didn't help!
    END LOOP;

    COMMIT;
    CLOSE base_table_cur;
    CLOSE new_data_cur;
EXCEPTION
    WHEN dml_errors THEN
        FOR i IN 1..SQL%bulk_exceptions.count LOOP
            dbms_output.put_line('Some error occured');
        END LOOP;
END;

我的错误在哪里?不过,这对我来说是正确的。

1 个答案:

答案 0 :(得分:5)

问题在于这一行:

IF base_table_cur%notfound THEN

当找到的记录数小于%NOTFOUND时,光标符合LIMIT。因此,如果最后一次获取不完全是5000,那么这些记录将无法处理。

对于第一次使用BULK COLLECT ... LIMIT的人来说,这是一个常见的问题。解决方案是将退出条件更改为

EXIT when base_tab.count() = 0;
  

&#34;我需要确保base_table_cur不为空,如果是,则退出。如果它是空的,我会收到错误&#34;

new_data_cur游标包含在base_table_cur游标中选择的表。所以我认为你不需要这两个循环。您需要一个简单的测试来查看第一个游标是否返回某个东西,然后循环第二个游标。

我对你的逻辑并不完全清楚,所以我尽可能少地改变,以展示我认为你需要的那种结构。但是,UPDATE语句看起来有点奇怪,因此您可能仍会遇到问题。

OPEN base_table_cur;
FETCH base_table_cur BULK COLLECT INTO base_tab LIMIT 1;
if base_table_tab.count = 0 then 
    DBMS_OUTPUT.PUT_LINE('Nothing to update. Exiting.');
else
    OPEN new_data_cur;
    LOOP
        FETCH new_data_cur BULK COLLECT INTO new_data_tab LIMIT 5000;
        exit when new_data_tab.count() = 0;

        FOR i IN base_tab.first..base_tab.last LOOP
            row_id_tab(i) := new_data_tab(i).rowid;
            update_cols_tab(i).TARGET_COLUMN := new_data_tab(i).TARGET_COLUMN;
        END LOOP;

        FORALL i IN base_tab.first..base_tab.last SAVE EXCEPTIONS
            UPDATE (SELECT TARGET_COLUMN FROM TARGET_TABLE)
            SET row = update_cols_tab(i)
            WHERE ROWID = row_id_tab(i);

    END LOOP;
    CLOSE new_data_cur;
end if;
COMMIT;
CLOSE base_table_cur;