plsql 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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章