plsql bulk collect 详解

文章目录

  • 1 概述
  • 2 使用
    • 2.1 在 select into 中
    • 2.2 在 fetch into 中
    • 2.3 在 returning into 中
  • 3 扩展:forall + bulk collect 综合运用

1 概述

  • bulk collect 子句会批量检索结果,即一次性将结果集绑定到一个集合变量中,并从 SQL引擎发送到 PL/SQL引擎。
  • bulk collect into 的目标对象必须是 集合类型
关键字 解释 链接
forall 用于增强 PL/SQL 引擎到 SQL 引擎的交换 PL/SQL foall 详解
bulk collect 用于增强 SQL 引擎到 PL/SQL 引擎的交换

2 使用

基础数据准备:

CREATE TABLE stu_info AS (
  id    number(3),
  name  varchar2(30),
  sex   varchar(2)
);

INSERT INTO stu_info(ID, NAME, sex) VALUES(1, '瑶瑶', '女');
INSERT INTO stu_info(ID, NAME, sex) VALUES(2, '优优', '男');
INSERT INTO stu_info(ID, NAME, sex) VALUES(3, '倩倩', '女');
COMMIT;

2.1 在 select into 中

DECLARE
   -- 定义记录类型
   TYPE stu_info_record IS RECORD(
      id   stu_info.id%TYPE,
      NAME stu_info.name%TYPE,
      sex  stu_info.sex%TYPE);
   -- 定义基于记录类型的嵌套表
   TYPE stu_info_table IS TABLE OF stu_info_record;
   -- 声明变量
   v_stu_info_table stu_info_table;
BEGIN
   -- 使用 bulk collect 将所得的结果集一次绑定到记录记录变量中
   SELECT si.id, si.name, si.sex
     BULK COLLECT
     INTO v_stu_info_table
     FROM stu_info si;

   -- 输出验证
   FOR i IN v_stu_info_table.first .. v_stu_info_table.last LOOP
      dbms_output.put_line('id: ' || v_stu_info_table(i).id || ', name: ' || v_stu_info_table(i).name ||
                           ', sex: ' || v_stu_info_table(i).sex);
   END LOOP;
EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(dbms_utility.format_error_backtrace);
      dbms_output.put_line(SQLCODE || ', ' || SQLERRM);
END;

输出结果:

id: 1, name: 瑶瑶, sex: 女
id: 2, name: 优优, sex: 男
id: 3, name: 倩倩, sex: 女

2.2 在 fetch into 中

  • 在游标中可以使用 bulk collect 一次取出一个数据集合,比单条取数据效率高。

语法:

fetch ... bulk collect into ... [limit row_number]
  • 在使用 bulk collect 子句时,对于集合类型会 自动 对其进行初始化以及扩展。
  • 为避免过大的数据集造成性能下降,使用 limit 子句来限制一次提取的数据量。
DECLARE
   -- 定义记录类型
   TYPE stu_info_record IS RECORD(
      id   stu_info.id%TYPE,
      NAME stu_info.name%TYPE,
      sex  stu_info.sex%TYPE);
   -- 定义基于记录类型的嵌套表
   TYPE stu_info_table IS TABLE OF stu_info_record;
   -- 声明变量
   v_stu_info_table stu_info_table;

   -- 声明游标
   CURSOR cur_stu_info IS
      SELECT si.id, si.name, si.sex FROM stu_info si;
   -- 定义变量来记录 fetch 的次数
   v_fetch_count PLS_INTEGER := 0;
BEGIN
   -- 开启游标
   OPEN cur_stu_info;

   LOOP
      -- fetch 时使用 bulk collect 子句
      FETCH cur_stu_info BULK COLLECT
         INTO v_stu_info_table LIMIT 2; -- 数据有限,仅做测试,一般限制 500 左右
   
      EXIT WHEN v_stu_info_table.count = 0; -- 注意此时游标退出使用了 xx.count,而不是 xx%notfound  
   
      v_fetch_count := v_fetch_count + 1;
   
      -- 输出验证
      FOR i IN v_stu_info_table.first .. v_stu_info_table.last LOOP
         dbms_output.put_line('id: ' || v_stu_info_table(i).id ||
                              ', name: ' || v_stu_info_table(i).name ||
                              ', sex: ' || v_stu_info_table(i).sex);
      END LOOP;
   END LOOP;

   -- 关闭游标
   CLOSE cur_stu_info;
   dbms_output.put_line('总共获取的次数:' || v_fetch_count);
EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(dbms_utility.format_error_backtrace);
      dbms_output.put_line(SQLCODE || ', ' || SQLERRM);
      -- 异常时,也要关闭游标
      IF cur_stu_info%ISOPEN THEN
         CLOSE cur_stu_info;
      END IF;
END;

输出结果:

id: 1, name: 瑶瑶, sex: 女
id: 2, name: 优优, sex: 男
id: 3, name: 倩倩, sex: 女
总共获取的次数:2 -- 总共 3 条记录,第一次获取 2 条(limit 2), 第二次获取 1 条

2.3 在 returning into 中

  • 当执行 insert、update、delete 时,可使用 returning 子句来批量绑定。
DECLARE
   -- 定义记录类型
   TYPE stu_info_record IS RECORD(
      id   stu_info.id%TYPE,
      NAME stu_info.name%TYPE,
      sex  stu_info.sex%TYPE);
   -- 定义基于记录类型的嵌套表
   TYPE stu_info_table IS TABLE OF stu_info_record;
   -- 声明变量
   v_stu_info_table stu_info_table;
BEGIN
   -- delete 语句,insert,update 同理
   DELETE FROM stu_info si
    WHERE si.sex = '女'
   RETURNING si.id, si.name, si.sex BULK COLLECT INTO v_stu_info_table;

   dbms_output.put_line('删除了 ' || SQL%ROWCOUNT || ' 行记录');
   COMMIT;

   -- 输出验证
   IF v_stu_info_table.count > 0 THEN
      FOR i IN v_stu_info_table.first .. v_stu_info_table.last LOOP
         dbms_output.put_line('id: ' || v_stu_info_table(i).id ||
                              ', name: ' || v_stu_info_table(i).name ||
                              ', sex: ' || v_stu_info_table(i).sex);
      END LOOP;
   END IF;
EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(dbms_utility.format_error_backtrace);
      dbms_output.put_line(SQLCODE || ', ' || SQLERRM);
END;

输出结果:

删除了 2 行记录
id: 1, name: 瑶瑶, sex: 女
id: 3, name: 倩倩, sex: 女

3 扩展:forall + bulk collect 综合运用

需求:将 stu_info 中的数据同步至 stu_info_temp

基础数据准备:

TRUNCATE TABLE stu_info ;

INSERT INTO stu_info(ID, NAME, sex) VALUES(1, '瑶瑶', '女');
INSERT INTO stu_info(ID, NAME, sex) VALUES(2, '优优', '男');
INSERT INTO stu_info(ID, NAME, sex) VALUES(3, '倩倩', '女');
COMMIT;

-- 测试表
CREATE TABLE stu_info_temp AS SELECT * from stu_info WHERE 1 = 2;

实战:

DECLARE
   -- 声明游标
   CURSOR cur_stu_info IS
      SELECT si.id, si.name, si.sex FROM stu_info si;
   -- 定义基于游标类型的嵌套表 (也可用 RECORD 替换,只是写法会稍微麻烦点)
   TYPE stu_info_table IS TABLE OF cur_stu_info%ROWTYPE;
   -- 声明变量
   v_stu_info_table stu_info_table;
BEGIN
   -- 开启游标
   OPEN cur_stu_info;

   LOOP
      -- fetch 时使用 bulk collect 子句
      FETCH cur_stu_info BULK COLLECT
         INTO v_stu_info_table LIMIT 2; -- 数据有限,仅做测试,一般限制 500 左右
   
      EXIT WHEN v_stu_info_table.count = 0; -- 注意此时游标退出使用了 xx.count,而不是 xx%notfound  
   
      -- 批量插入
      FORALL i IN 1 .. v_stu_info_table.count
      -- INSERT INTO stu_info_temp (id, NAME, sex) VALUES v_stu_info (i); -- ORA-00947: 没有足够的值
         INSERT INTO
            (SELECT sit.id, sit.name, sit.sex FROM stu_info_temp sit)
         VALUES v_stu_info_table
            (i);
   END LOOP;

   -- 关闭游标
   CLOSE cur_stu_info;

   -- 提交数据
   COMMIT;
EXCEPTION
   WHEN OTHERS THEN
      dbms_output.put_line(dbms_utility.format_error_backtrace);
      dbms_output.put_line(SQLCODE || ', ' || SQLERRM);
      -- 异常时,也要关闭游标
      IF cur_stu_info%ISOPEN THEN
         CLOSE cur_stu_info;
      END IF;
END;

你可能感兴趣的:(数据库,plsql)