在 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
你得到那个错误是因为你在 INSERT
的 VALUES
子句中有一个文字。 FORALL
期望所有内容都绑定到一个数组。
从字面上看,您的程序存在大问题。 BULK COLLECT
子句上没有 LIMIT
,因此这将尝试将 TableB
中的所有五百万条记录加载到您的集合中。这会破坏您会话的内存限制。
使用 BULK COLLECT
和 FORALL
的要点是从更大的数据集中分块并分批处理。为此你需要一个循环。该循环没有 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 COLLECT
和 FORALL ... 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
/
我有大约 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
你得到那个错误是因为你在 INSERT
的 VALUES
子句中有一个文字。 FORALL
期望所有内容都绑定到一个数组。
从字面上看,您的程序存在大问题。 BULK COLLECT
子句上没有 LIMIT
,因此这将尝试将 TableB
中的所有五百万条记录加载到您的集合中。这会破坏您会话的内存限制。
使用 BULK COLLECT
和 FORALL
的要点是从更大的数据集中分块并分批处理。为此你需要一个循环。该循环没有 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 COLLECT
和 FORALL ... 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
/