文章目录
1、概念
1.1 思维导图
1.2 数据准备
DROP TABLE stu; -- if exists
CREATE TABLE stu(
s_id NUMBER,
s_xm VARCHAR2(10)
);
2、forall 的三种语法
2.1 forall i in 下限 … 上限
-- 语法1:
FORALL 下标变量(只能用作下标) IN 下限 .. 上限
SQL 语句。 -- 只允许一条 sql 语句
2.1.1 批量插入
DECLARE
TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;
v_stu_array stu_table;
BEGIN
FOR i IN 1 .. 10 LOOP
v_stu_array(i).s_id := i;
v_stu_array(i).s_xm := 'a' || i;
END LOOP;
FORALL i IN v_stu_array.first .. v_stu_array.last
INSERT INTO stu VALUES v_stu_array(i);
-- 等同于
-- INSERT INTO stu(s_id, s_xm) VALUES(v_stu_array(i).s_id, v_stu_array(i).s_xm);
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
2.1.2 批量修改
DECLARE
TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;
v_stu_array stu_table;
BEGIN
FOR i IN 1 .. 10 LOOP
v_stu_array(i).s_id := i;
v_stu_array(i).s_xm := 'a' || i;
END LOOP;
FORALL i IN v_stu_array.first .. v_stu_array.last
UPDATE stu t SET t.s_xm = 'b' WHERE t.s_id = v_stu_array(i).s_id;
-- 下句会报错!(该上下文中不允许使用 forall 循环变量!)
-- UPDATE stu t SET t.s_xm = 'b'|| i WHERE t.s_id = v_stu_array(i).s_id;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
2.1.3 批量删除
DECLARE
TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;
v_stu_array stu_table;
BEGIN
FOR i IN 1 .. 10 LOOP
v_stu_array(i).s_id := i;
v_stu_array(i).s_xm := 'a' || i;
END LOOP;
FORALL i IN v_stu_array.first .. v_stu_array.last
DELETE FROM stu t WHERE t.s_id = v_stu_array(i).s_id;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
2.2 forall i in indices of 集合
--语法2:
FORALL 下标变量 IN INDICES OF(跳过没有赋值的元素,例如被 DELETE 的元素,NULL 也算值) 集合
sql 语句;
DECLARE
TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;
v_stu_array stu_table;
BEGIN
FOR i IN 1 .. 10 LOOP
v_stu_array(i).s_id := i;
v_stu_array(i).s_xm := 'a' || i;
END LOOP;
v_stu_array.delete(3);
v_stu_array(6) := NULL;
v_stu_array.delete(9);
FORALL i IN INDICES OF v_stu_array
INSERT INTO stu VALUES v_stu_array(i);
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
测试结果:
2.3 forall i in values of pls_ 、binary_
--语法3:
FORALL 下标变量 IN VALUES OF 集合(把该集合中的值当作下标,且该集合值的类型只能是 PLS_INTEGER、BINARY_INTEGER)
sql 语句;
DECLARE
TYPE index_table IS TABLE OF PLS_INTEGER;
TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;
v_index_array index_table;
v_stu_array stu_table;
BEGIN
FOR i IN 1 .. 10 LOOP
v_stu_array(i).s_id := i;
v_stu_array(i).s_xm := 'a' || i;
END LOOP;
v_index_array := index_table(1, 3, 5, 7, 9);
FORALL i IN VALUES OF v_index_array
INSERT INTO stu VALUES v_stu_array(i);
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
测试结果:
3、for loop、forall、绑定变量 效率对比
TRUNCATE TABLE stu; -- 清空数据,方便测试
CREATE TABLE stu1 AS SELECT * FROM stu WHERE 1 = 2;
CREATE TABLE stu2 AS SELECT * FROM stu WHERE 1 = 2;
CREATE TABLE stu3 AS SELECT * FROM stu WHERE 1 = 2;
--------- 以上为基础数据准备 ---------
DECLARE
TYPE stu_table IS TABLE OF system.stu%ROWTYPE INDEX BY PLS_INTEGER;
v_stu_array stu_table;
t NUMBER;
t1 NUMBER;
t2 NUMBER;
t3 NUMBER;
BEGIN
FOR i IN 1 .. 500000 LOOP
v_stu_array(i).s_id := i;
v_stu_array(i).s_xm := 'a' || i;
END LOOP;
t := dbms_utility.get_time;
FOR i IN 1 .. 500000 LOOP
INSERT INTO stu1 VALUES v_stu_array(i);
END LOOP;
t1 := dbms_utility.get_time;
FORALL i IN 1 .. 500000
INSERT INTO stu2 VALUES v_stu_array(i);
t2 := dbms_utility.get_time;
FORALL i IN 1 .. 500000
EXECUTE IMMEDIATE 'INSERT INTO stu3(s_id, s_xm) VALUES(:b1, :b2)' USING v_stu_array(i).s_id, v_stu_array(i).s_xm;
t3 := dbms_utility.get_time;
dbms_output.put_line('一般 for 循环用时:'|| to_char(t1 - t));
dbms_output.put_line('一般 forall 循环用时:'|| to_char(t2 - t1));
dbms_output.put_line('一般 forall + 绑定变量 循环用时:'|| to_char(t3 - t2));
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE || ' : ' || SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
测试结果:
4、forall 回滚及异常
4.1 forall 如何影响回滚
TRUNCATE TABLE stu;
INSERT INTO stu(s_id, s_xm) VALUES(1, '小游子');
INSERT INTO stu(s_id, s_xm) VALUES(2, '小倩子');
INSERT INTO stu(s_id, s_xm) VALUES(3, '小王子aaaa');
INSERT INTO stu(s_id, s_xm) VALUES(4, '小优子');
COMMIT;
--- SELECT LENGTHB('小王子aaaa') from dual; 长度为 10, 为限定长度
DECLARE
TYPE num_list_table IS TABLE OF NUMBER;
v_num_list num_list_table := num_list_table(1, 2, 3, 4);
BEGIN
FORALL j IN v_num_list.first .. v_num_list.last
UPDATE stu t
SET t.s_xm = t.s_xm || 'a'
WHERE t.s_id = v_num_list(j);
EXCEPTION
WHEN OTHERS THEN
COMMIT; -- 如果此处不加 commit,则回滚所有记录
END;
运行结果:(第三个执行报错)
4.2 %bulk_rowcount 属性计算 forall 影响行数
在进行 SQL 数据操作语句时,SQL 引擎开发一个
隐式游标(命名 SQL)
,该游标的标量属性(Scalar Attribute)有%found、%isopen、%notfound and %rowcount
,forall
语句除具有上边的标量属性外,还有个复合属性(Composite attribute):%bulk_rowcount
,该属性具有索引表(index by table)
语法
它的第"i"
个元素储存 SQL 语句(insert、update、delete
)第"i"
个执行的处理行数。如果第“i”
个执行 未影响 行,那么%bulk_rowcount(i) 返回 0
%rowcount
: 返回 SQL 语句所有执行处理的总行数%found
、%notfound
仅与 SQL 语句的最后执行有关,但是,可以使用%bulk_rowcount
推断单个执行的值。如%bulk_rowcount(i) = 0 时
,%found 和 %notfound 分别是 false 和 true
TRUNCATE TABLE stu;
INSERT INTO stu(s_id, s_xm) VALUES(1, '小游子');
INSERT INTO stu(s_id, s_xm) VALUES(2, '小倩子');
INSERT INTO stu(s_id, s_xm) VALUES(3, '小王子');
INSERT INTO stu(s_id, s_xm) VALUES(4, '小优子');
COMMIT;
------------- 基础数据准备 -------------
DECLARE
TYPE num_list_table IS TABLE OF NUMBER;
v_num_list num_list_table := num_list_table(1, 2, 3, 4);
BEGIN
FORALL j IN v_num_list.first .. v_num_list.last
UPDATE stu t
SET t.s_xm = t.s_xm || 'a'
WHERE t.s_id = v_num_list(j);
IF SQL%FOUND THEN
dbms_output.put_line('sql%found is true');
END IF;
IF SQL%BULK_ROWCOUNT(4) = 1 THEN
dbms_output.put_line('sql%bulk_rowcount(4) is true');
END IF;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE ||' : '||SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
运行结果:
4.3 %bulk_exceptions 属性处理 forall 异常
在执行 forall 语句期间,PL/SQL 提供了一个处理异常的机制,该机制使批绑定(bulk-bind)操作能保存储存异常信息并继续执行。方法是:在 forall 语句中增加
sava exceptions
关键字
%bulk_exceptions(i).error_index
: 存储在引发异常期间 forall 语句迭代(重复:iteration)%bulk_exceptions(i).error_code
: 存储相应的 Oracle 错误代码%bulk_exceptions(i).count
: 存储异常的数量(该属性来自 index-by)
TRUNCATE TABLE stu;
INSERT INTO stu(s_id, s_xm) VALUES(1, '小游子');
INSERT INTO stu(s_id, s_xm) VALUES(2, '小倩子aaaa');
INSERT INTO stu(s_id, s_xm) VALUES(3, '小王子bbbb');
INSERT INTO stu(s_id, s_xm) VALUES(4, '小优子cccc');
COMMIT;
------------- 基础数据准备 -------------
DECLARE
TYPE num_list_table IS TABLE OF NUMBER;
v_num_list num_list_table := num_list_table(1, 2, 3, 4);
errors_count NUMBER;
BEGIN
FORALL j IN v_num_list.first .. v_num_list.last SAVE EXCEPTIONS
UPDATE stu t
SET t.s_xm = t.s_xm || 'a'
WHERE t.s_id = v_num_list(j);
EXCEPTION
WHEN OTHERS THEN
errors_count := SQL%bulk_exceptions.count;
dbms_output.put_line('Number of errors is ' || errors_count);
FOR i IN 1 .. errors_count LOOP
dbms_output.put_line('Error i: ' || i || ' ,error_index: ' || SQL%BULK_EXCEPTIONS(i)
.error_index);
dbms_output.put_line('Error' || i || ' ,error_code: ' || SQL%BULK_EXCEPTIONS(i)
.error_code);
END LOOP;
END;
运行结果:
4.4 同时使用 forall + bulk collect
TRUNCATE TABLE stu;
INSERT INTO stu(s_id, s_xm) VALUES(1, '小游子');
INSERT INTO stu(s_id, s_xm) VALUES(2, '小倩子');
INSERT INTO stu(s_id, s_xm) VALUES(3, '小王子');
INSERT INTO stu(s_id, s_xm) VALUES(4, '小优子');
COMMIT;
------------- 基础数据准备 -------------
DECLARE
TYPE num_list_table IS TABLE OF NUMBER;
TYPE stu_id_table IS TABLE OF system.stu.s_id%TYPE;
TYPE stu_xm_table IS TABLE OF system.stu.s_xm%TYPE;
v_num_list num_list_table := num_list_table(1, 2, 3, 4);
v_id_table stu_id_table;
v_xm_table stu_xm_table;
BEGIN
FORALL i IN v_num_list.first .. v_num_list.last
DELETE FROM stu t
WHERE t.s_id = v_num_list(i)
RETURNING t.s_id, t.s_xm BULK COLLECT INTO v_id_table, v_xm_table;
FOR i IN v_xm_table.first .. v_xm_table.last LOOP
dbms_output.put_line('删除的姓名:'||v_xm_table(i));
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE ||' : '||SQLERRM);
dbms_output.put_line(dbms_utility.format_error_backtrace);
END;
运行结果: