Oracle 绑定变量与批量处理 (BULK COLLECT & FORALL) 用法

149 篇文章 20 订阅
61 篇文章 7 订阅

一、 sql语句

var x number;
var 1 numner;
exec :x :=7369;
exec :x :=7369;

select name from emp where id=:x;
select name from emp where id=:1;


二、 plsql语句

1. select语句

execute immediate '带绑定变量的目标sql' into 目标变量 using 绑定变量具体值

declare
v_name varchar2(10);
begin
execute immediate 'select name from emp where id=:1' into v_name using 7369;
dbms_output.putline(v_name);
end;
/

2. dml语句

注意目标sql有几个绑定变量,using后就跟多少输入值。using后的输入值只与绑定变量位置有关,与名称无关,所以写insert into emp(id,name,job) values(:1,:1,:1)也是可以的

declare
v_sql_1 varchar2(4000);
v_sql_2 varchar2(4000);
tmp_1 number;
tmp_2 number;

begin
v_sql_1 := 'insert into emp(id,name,job) values(:1,:2,:3)'
execute immediate v_sql_1 using 7369,'xiaoming','dba';
tmp_1 := sql%rowcount;

v_sql_2 := 'insert into emp(id,name,job) values(:1,:1,:1)'
execute immediate v_sql_1 using 7370,'xiaoh','dba';
tmp_2 := sql%rowcount;

dbms_output.putline(to_char(tmp_1+tmp_2));
commit;
end;
/

3. 动态sql

returning关键字可以和带绑定变量的sql连用,取出受该sql影响的行的对应字段值

declare
v_sql_1 varchar2(4000);
v_column varchar2(10);
v_name varchar2(10);

begin
v_column := 'id';
v_sql_1 := 'delete from emp where ' || v_column || ' = :1 returning name into :2';
execute immediate v_sql_1 using 7369 returning into v_name;

dbms_output.putline(v_name);
commit;
end;
/

 

三、 plsql中的批量处理

 

1. 为什么批量处理更高效?

Oracle使用两个引擎来处理PL/SQL代码块——PL/SQL引擎处理过程化代码,SQL引擎处理SQL语句。两个引擎之间每次数据传递和交互称为一次上下文切换(context switch),每一次上下文切换都会带来额外的开销。

如果PL/SQL代码遍历一个集合,并且对该集合中的每行执行相同的DML操作,则可以通过一次操作将整个集合批量绑定到DML语句来减少上下文切换。

PL/SQL Architecture

 

2. 批量处理分类

  • BULK COLLECT:可以提高从查询加载数据集合(表变量)时的性能,将查询结果一次性地fetch到变量中,而不是通过cursor一条条处理,提高数据获取效率(减少SQL引擎->PL/SQL引擎的context switch)。语法如下:

fetch cursor_name bulk collect into [自定义数组] <limit 常量值>

<limit 常量值>表示一次只fetch 指定的记录数(通常为1000),避免一次fetch所有数据占用大量PGA
  • FORALL:用于 INSERT, UPDATE, and DELETE,一次修改多行数据,提高DML效率(减少PL/SQL引擎->SQL引擎的context switch)。

语法如下:

forall i in 1..[自定义数组长度]
  execute immediate '带绑定变量的目标sql' using 绑定变量具体值


下面是几个例子

3. 单独使用 bulk collect将表数据fetch至变量

可以在select into,fetch into,returning into语句使用bulk collect,注意所有的into变量都必须是collections. 

--在select into语句中使用bulk collect
DECLARE
TYPE sallist IS TABLE OF employees.salary%TYPE;
sals sallist;
BEGIN
SELECT salary BULK COLLECT INTO sals FROM employees where rownum<=50;
--接下来使用集合中的数据
END;
/
--在fetch into中使用bulk collect 
DECLARE
TYPE deptrectab IS TABLE OF departments%ROWTYPE;
dept_recs deptrectab;
CURSOR cur IS SELECT department_id,department_name FROM departments where department_id>10;
BEGIN
OPEN cur;
FETCH cur BULK COLLECT INTO dept_recs;
--接下来使用集合中的数据
END;
/
--使用limit限制每次fetch的量
SET SERVEROUTPUT ON
DECLARE
  TYPE t_bulk_collect_test_tab IS TABLE OF bulk_collect_test%ROWTYPE;
  l_tab t_bulk_collect_test_tab;
  CURSOR c_data IS SELECT * FROM bulk_collect_test;
BEGIN
  OPEN c_data;
  LOOP
    FETCH c_data
    BULK COLLECT INTO l_tab LIMIT 10000;
    EXIT WHEN l_tab.count = 0;
    -- Process contents of collection here.
    DBMS_OUTPUT.put_line(l_tab.count || ' rows');
  END LOOP;
  CLOSE c_data;
END;
/

--输出
10000 rows
10000 rows
10000 rows
10000 rows
2578 rows

PL/SQL procedure successfully completed.
-- 在returning into中使用bulk collect 
CREATE TABLE emp AS SELECT * FROM employees;

DECLARE
TYPE numlist IS TABLE OF employees.employee_id%TYPE;
enums numlist;
TYPE namelist IS TABLE OF employees.last_name%TYPE;
names namelist;
BEGIN
DELETE emp WHERE department_id=30
RETURNING employee_id,last_name BULK COLLECT INTO enums,names;
DBMS_OUTPUT.PUT_LINE('deleted'||SQL%ROWCOUNT||'rows:');
FOR i IN enums.FIRST .. enums.LAST
LOOP
DBMS_OUTPUT.PUT_LINE('employee#'||enums(i)||':'||names(i));
END LOOP;
END;
/

-- 结果
deleted6rows:
employee#114:Raphaely
employee#115:Khoo
employee#116:Baida
employee#117:Tobias
employee#118:Himuro
employee#119:Colmenares

 

4. 单独用forall进行批量DML

DECLARE
    TYPE NumList IS VARRAY(20) OF NUMBER;
    depts NumList := NumList(10, 30, 70);
BEGIN
    FORALL i IN depts.FIRST..depts.LAST 
        UPDATE emp SET sal = sal * 1.10 WHERE deptno = depts(i); 
END;
/
-- 对比常规FOR..LOOP绑定和批量绑定FORALL插入10000行花费的时间
SET SERVEROUTPUT ON
DECLARE
  TYPE t_forall_test_tab IS TABLE OF forall_test%ROWTYPE;
  l_tab    t_forall_test_tab := t_forall_test_tab();
  l_start  NUMBER;
  l_size   NUMBER            := 10000;

BEGIN
  -- 数据初始化
  FOR i IN 1 .. l_size LOOP
    l_tab.extend;
    l_tab(l_tab.last).id          := i;
    l_tab(l_tab.last).code        := TO_CHAR(i);
    l_tab(l_tab.last).description := 'Description: ' || TO_CHAR(i);
  END LOOP;

  EXECUTE IMMEDIATE 'TRUNCATE TABLE forall_test';

  -- Time regular inserts.
  l_start := DBMS_UTILITY.get_time;

  FOR i IN l_tab.first .. l_tab.last LOOP
    INSERT INTO forall_test (id, code, description)
    VALUES (l_tab(i).id, l_tab(i).code, l_tab(i).description);
  END LOOP;

  DBMS_OUTPUT.put_line('Normal Inserts: ' || 
                       (DBMS_UTILITY.get_time - l_start));
  
  EXECUTE IMMEDIATE 'TRUNCATE TABLE forall_test';

  -- Time bulk inserts.  
  l_start := DBMS_UTILITY.get_time;

  FORALL i IN l_tab.first .. l_tab.last
    INSERT INTO forall_test VALUES l_tab(i);

  DBMS_OUTPUT.put_line('Bulk Inserts  : ' || 
                       (DBMS_UTILITY.get_time - l_start));

  COMMIT;
END;
/

-- 输出结果
Normal Inserts: 305
Bulk Inserts  : 14

 

5. bulk collect + forall 批量操作

DECLARE
TYPE lv_emp_name_tbl IS TABLE OF VARCHAR2(50);
lv_emp_name lv_emp_name_tbl;
CURSOR guru99_det IS SELECT emp_name FROM emp;

BEGIN
OPEN guru99_det;
LOOP
-- 批量取出数据,每次5000条
FETCH guru99_det BULK COLLECT INTO lv_emp_name LIMIT 5000;
-- 批量更新数据
FORALL i IN lv_emp_name.FIRST .. lv emp_name.LAST
UPDATE emp SET salaiy=salary+5000 WHERE emp_name=lv_emp_name(i);
COMMIT;	

EXIT WHEN guru99_det%NOTFOUND;
    END LOOP;
CLOSE guru99_det;
END;
/
CREATE OR REPLACE PROCEDURE fast_proc (p_array_size IN PLS_INTEGER DEFAULT 100)
IS

TYPE ARRAY IS TABLE OF tmo0820%ROWTYPE;
l_data ARRAY;

CURSOR c IS
SELECT *
FROM tmo0820;

BEGIN
    OPEN c;
    LOOP
    FETCH c BULK COLLECT INTO l_data LIMIT p_array_size;

    FORALL i IN 1..l_data.COUNT
    INSERT INTO t2 VALUES l_data(i);
    DBMS_OUTPUT.put_line(l_data.count || ' rows');
    COMMIT;

    EXIT WHEN c%NOTFOUND;
    END LOOP;
    CLOSE c;
END fast_proc;
/

 

参考

https://www.cnblogs.com/ivanfu/archive/2012/04/06/2434983.html

https://www.guru99.com/pl-sql-bulk-collect.html
https://xuejiangtao.iteye.com/blog/1168570

https://oracle-base.com/articles/9i/bulk-binds-and-record-processing-9i

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值