在 Oracle 中批量插入 PL/SQL

Bulk inserting in Oracle PL/SQL

我有大约 500 万条记录需要从一个模式的 table 复制到另一个模式的 table(在同一数据库中)。我已经准备了一个脚本,但它给了我以下错误。

ORA-06502: PL/SQL: numeric or value error: Bulk bind: Error in define

以下是我的脚本

DECLARE    
    TYPE tA IS TABLE OF varchar2(10) INDEX BY PLS_INTEGER;
    TYPE tB IS TABLE OF SchemaA.TableA.band%TYPE INDEX BY PLS_INTEGER;
    TYPE tD IS TABLE OF SchemaA.TableA.start_date%TYPE INDEX BY PLS_INTEGER;
    TYPE tE IS TABLE OF SchemaA.TableA.end_date%TYPE INDEX BY PLS_INTEGER;        
    rA tA;
    rB tB;
    rD tD;
    rE tE;
    f number :=0;    
BEGIN

    SELECT col1||col2||col3 as main_col, band, effective_start_date as start_date, effective_end_date as end_date 
    BULK COLLECT INTO rA, rB, rD, rE 
    FROM schemab.tableb;

    FORALL i IN rA.FIRST..rE.LAST
        insert into SchemaA.TableA(main_col, BAND, user_type, START_DATE, END_DATE, roll_no) 
        values(rA(i), rB(i), 'C', rD(i), rE(i), 71);

    f:=f+1;

    if (f=10000) then
        commit;
    end if;

end;

能否请您帮我找出错误所在?

请在将代码的前两行更改为以下内容后尝试:

DECLARE

TYPE tA IS TABLE OF SchemaA.TableA.main_col%TYPE INDEX BY PLS_INTEGER;
...
...

这可能是因为数据 type/length 不匹配。在声明部分,您错过了声明一个继承自 table 的类型。

另外如前所述,提交的 f 逻辑不会为您施展魔法。最好将 LIMIT 与 BULL COLLECT

一起使用

为什么不简单

insert into SchemaA.TableA (main_col, BAND, user_type, START_DATE, END_DATE, roll_no) 
SELECT col1||col2||col3 as main_col, band, 'C', effective_start_date, effective_end_date, 71 
FROM schemab.tableb;

这个

f:=f+1;
if (f=10000) then
   commit;
end if;

没有任何意义。 f 变成 1 - 就是这样。 f=10000 永远不会为真,因此您不会做出 COMMIT。

ORA-06502: PL/SQL: numeric or value error: Bulk bind: Error in define

你得到那个错误是因为你在 INSERTVALUES 子句中有一个文字。 FORALL 期望所有内容都绑定到一个数组。

从字面上看,您的程序存在大问题。 BULK COLLECT 子句上没有 LIMIT,因此这将尝试将 TableB 中的所有五百万条记录加载到您的集合中。这会破坏您会话的内存限制。

使用 BULK COLLECTFORALL 的要点是从更大的数据集中分块并分批处理。为此你需要一个循环。该循环没有 FOR 条件:而是测试提取是否返回任何内容并在数组有零条记录时退出。

DECLARE    
    TYPE recA IS RECORD (
        main_col SchemaA.TableA.main_col%TYPE
        , band SchemaA.TableA.band%TYPE
        , start_date date
        , end_date date
        , roll_ni number);
    TYPE recsA is table of recA
    nt_a recsA;
    f number :=0;    
    CURSOR cur_b is
        SELECT col1||col2||col3 as main_col, 
               band, 
               effective_start_date as start_date, 
               effective_end_date as end_date ,
               71 as roll_no
    FROM schemab.tableb;
BEGIN
    open cur_b;
    loop
        fetch curb_b bulk collect into nt_a limit 1000;
        exit when nt_a.count() = 0;

        FORALL i IN rA.FIRST..rE.LAST
            insert into SchemaA.TableA(main_col, BAND, user_type, START_DATE, END_DATE, roll_no) 
            values nt_a(i);

        f := f + sql%rowcount;       
        if (f > = 10000) then
            commit;
            f := 0;
        end if;
    end loop;
    commit;
    close cur_b;
end;

请注意,在循环内发出提交是禁忌的。您让自己面临诸如 ORA-01002 和 ORA-01555 之类的运行时错误。如果你的程序确实在中途崩溃了,你将很难毫无问题地恢复它。如果您对 UNDO 表空间有问题,一定要坚持,但正确的答案是让 DBA 扩大 UNDO 表空间而不是削弱您的代码。

"i am using bulk insert because it gives better performance"

BULK COLLECTFORALL ... INSERT 确实比具有逐行单插入的 CURSOR FOR 循环更具性能。它并不比纯 SQL INSERT INTO ... SELECT 更有效。该构造的价值在于它允许我们在插入数组之前操作数组的内容。如果我们有只能以编程方式应用的复杂业务规则,这就是句柄。

以下脚本对我有用,我能够在 15 分钟内加载大约 500 万条数据。

 ALTER SESSION ENABLE PARALLEL DML
 /

 DECLARE


 cursor c_p1 is 
    SELECT col1||col2||col3 as main_col, band, effective_start_date as start_date, effective_end_date as end_date 
    FROM schemab.tableb;

    TYPE TY_P1_FULL is table of c_p1%rowtype
    index by pls_integer;

   v_P1_FULL TY_P1_FULL;

   v_seq_num number;

BEGIN

open c_p1;

loop

fetch c_p1 BULK COLLECT INTO v_P1_FULL LIMIT 10000;
exit when v_P1_FULL.count = 0;
FOR i IN 1..v_P1_FULL.COUNT loop


INSERT /*+ APPEND */ INTO schemaA.tableA VALUES (v_P1_FULL(i));

end loop;
commit;
end loop;
close c_P1;
dbms_output.put_line('Load completed');


end;

-- Disable parallel mode for this session
ALTER SESSION DISABLE PARALLEL DML
/