Oracle - 使用 CURSOR LOOP 进行批量更新

Posted

技术标签:

【中文标题】Oracle - 使用 CURSOR LOOP 进行批量更新【英文标题】:Oracle - BULK UPDATE with CURSOR LOOP 【发布时间】:2018-10-05 13:12:54 【问题描述】:

我想在这样的表上对一个巨大的表执行更新(我现在不是最佳做法):

TARGET_TABLE (
TICKET_ID number,
product_id number,
NET number(15,2),
VAT number(15,2));

http://sqlfiddle.com/#!4/d39ed/3

目标:UPDATE TARGET_TABLE set NET=VAT, VAT=NET

我想出了一个 BULK UPDATE,但我在第 43 行得到一个 ORA-00913: "To many values",我无法解释。另外,我不知道如何在该变体中一次更新两行。 有人可以帮忙吗?

DECLARE

-- new data
    CURSOR new_data_cur IS
      select 
                     a.rowid, 
                     a.TICKET_ID,
                     a.product_id,
                     b.NET,
                     b.VAT
 from TARGET_TABLE a
                     join TARGET_TABLE_COPY b
                     on  ( a.TICKET_ID=b.TICKET_ID AND  a.product_id =b.product_id ) ;

    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 (
        NET   TARGET_TABLE.NET%TYPE
      --  VAT   TARGET_TABLE.VAT%TYPE
    );
    TYPE update_cols_type IS
        TABLE OF rt_update_cols INDEX BY PLS_INTEGER;
    update_cols_tab1    update_cols_type;
    --update_cols_tab2    update_cols_type;
    dml_errors EXCEPTION;
    PRAGMA exception_init ( dml_errors,-24381 );

BEGIN

    OPEN new_data_cur;
    LOOP
        FETCH new_data_cur BULK COLLECT INTO new_data_tab LIMIT 50000;
        EXIT WHEN new_data_tab.count=0;
        FOR i IN new_data_tab.first..new_data_tab.last LOOP
            row_id_tab(i) := new_data_tab(i).rowid;
            update_cols_tab1(i).NET := new_data_tab(i).VAT;
           -- update_cols_tab2(i).VAT := new_data_tab(i).NET;
        END LOOP;

        FORALL i IN new_data_tab.first..new_data_tab.last SAVE EXCEPTIONS # ORA-00913: To many values
            UPDATE TARGET_TABLE
           -- SET row = update_cols_tab(i)
            SET row = update_cols_tab1(i) 
           --         row = update_cols_tab2(i) 
            WHERE ROWID = row_id_tab(i);

        COMMIT;
        EXIT WHEN new_data_tab.count=0;
    END LOOP;
    COMMIT;
    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;

【问题讨论】:

你为什么要这样做,而你可以这样做UPDATE TARGET_TABLE set NET=VAT, VAT=NET; >500.000.000 条记录 所以?在单个更新语句中更新表仍然比批量更新更快。 或者,您可以尝试重命名列(net -> net_vat,vat -> net,net_vat -> vat)。不过,这假设 select * 或在没有指定在生产代码中插入的列列表的情况下插入 target_table 没有任何作用。但是,如果您绝对需要,您可以使用 dbms_redefinition 对列重新排序。 或者您可以暂时添加一列UP2DATE INTEGER DEFAULT 0,然后执行UPDATE TARGET_TABLE set NET=VAT, VAT=NET, UP2DATE = 1 WHERE UP2DATE = 0 AND ROWNUM < 10000 以明智地更新数据块... 【参考方案1】:

我相信您在交换值时不需要额外的光标

FOR i IN new_data_tab.first..new_data_tab.last LOOP
        row_id_tab(i) := new_data_tab(i).rowid;
        update_cols_tab1(i).NET := new_data_tab(i).VAT;
       -- update_cols_tab2(i).VAT := new_data_tab(i).NET;
    END LOOP;

因此您的代码将在批量更新中使用这些值

DECLARE

-- new data
CURSOR new_data_cur IS
  select 
                 a.rowid, 
                 a.TICKET_ID,
                 a.product_id,
                 b.NET,
                 b.VAT
   from TARGET_TABLE a
                 join TARGET_TABLE_COPY b
                 on  ( a.TICKET_ID=b.TICKET_ID AND  a.product_id =b.product_id ) ;

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 (
    NET   TARGET_TABLE.NET%TYPE
  --  VAT   TARGET_TABLE.VAT%TYPE
);
TYPE update_cols_type IS
    TABLE OF rt_update_cols INDEX BY PLS_INTEGER;
update_cols_tab1    update_cols_type;
--update_cols_tab2    update_cols_type;
dml_errors EXCEPTION;
PRAGMA exception_init ( dml_errors,-24381 );

BEGIN

OPEN new_data_cur;
LOOP
    FETCH new_data_cur BULK COLLECT INTO new_data_tab LIMIT 50000;
    EXIT WHEN new_data_tab.count=0;


    FORALL i IN new_data_tab.first..new_data_tab.last SAVE EXCEPTIONS # ORA-00913: To many values
        UPDATE TARGET_TABLE
       -- SET row = update_cols_tab(i)
       -- SET row = update_cols_tab1(i) 
       --         row = update_cols_tab2(i) 
        NET =  update_cols_tab1(i).VAT
        VAT = update_cols_tab1(i).NET
        WHERE ROWID = row_id_tab(i);

    COMMIT;
    EXIT WHEN new_data_tab.count=0;
END LOOP;
COMMIT;
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;

【讨论】:

以上是关于Oracle - 使用 CURSOR LOOP 进行批量更新的主要内容,如果未能解决你的问题,请参考以下文章

CURSOR 和 LOOP 在 Oracle 数据库上无法正常工作

Oracle 学习----游标(使用无参光标cursor)

oracle for loop循环以及游标循环

如何实现Oracle数据库中的动态游标

在 CURSOR FOR LOOP 中使用游标属性

Oracle/PLSQL 在 BULK COLLECT 之后处理 for LOOP 中的所有数据