1. 程式人生 > >批量SQL之 BULK COLLECT 子句

批量SQL之 BULK COLLECT 子句

    BULK COLLECT 子句會批量檢索結果,即一次性將結果集繫結到一個集合變數中,並從SQL引擎傳送到PL/SQL引擎。通常可以在SELECT INTO、
FETCH INTO以及RETURNING INTO子句中使用BULK COLLECT。本文將逐一描述BULK COLLECT在這幾種情形下的用法。
    有關FORALL語句的用法請參考:批量SQL之 FORALL 語句

一、BULK COLLECT批量繫結的示例

--下面的示例中使用了BULK COLLECT將得到的結果集繫結到記錄變數中		
DECLARE
   TYPE emp_rec_type IS RECORD          --宣告記錄型別
   (
      empno      emp.empno%TYPE
     ,ename      emp.ename%TYPE
     ,hiredate   emp.hiredate%TYPE
   );

   TYPE nested_emp_type IS TABLE OF emp_rec_type;  --宣告記錄型別變數 

   emp_tab   nested_emp_type;
BEGIN
   SELECT empno, ename, hiredate
   BULK   COLLECT INTO emp_tab       --使用BULK COLLECT 將所得的結果集一次性繫結到記錄變數emp_tab中
   FROM   emp;

   FOR i IN emp_tab.FIRST .. emp_tab.LAST
   LOOP
      DBMS_OUTPUT.put_line('Current record is '||emp_tab(i).empno||chr(9)||emp_tab(i).ename||chr(9)||emp_tab(i).hiredate);
   END LOOP;
END;
--上面的例子可以通過FOR 迴圈和普通的SELECT INTO來實現,那兩者之間的差異呢?
--差異是FOR迴圈的SELECT INTO逐行提取並繫結到記錄變數,而BULK COLLECT則一次即可提取所有行並繫結到記錄變數。即謂批量繫結。

二、使用LIMIT限制FETCH資料量
    在使用BULK COLLECT 子句時,對於集合型別,如巢狀表,聯合陣列等會自動對其進行初始化以及擴充套件(如下示例)。因此如果使用BULK
COLLECT子句操作集合,則無需對集合進行初始化以及擴充套件。由於BULK COLLECT的批量特性,如果資料量較大,而集合在此時又自動擴充套件,為避
免過大的資料集造成效能下降,因此使用limit子句來限制一次提取的資料量。limit子句只允許出現在fetch操作語句的批量中。

    用法:
        FETCH ... BULK COLLECT INTO ... [LIMIT rows]

DECLARE
   CURSOR emp_cur IS
      SELECT empno, ename, hiredate FROM emp;

   TYPE emp_rec_type IS RECORD
   (
      empno      emp.empno%TYPE
     ,ename      emp.ename%TYPE
     ,hiredate   emp.hiredate%TYPE
   );

   TYPE nested_emp_type IS TABLE OF emp_rec_type;   -->定義了基於記錄的巢狀表

   emp_tab     nested_emp_type;           -->定義集合變數,此時未初始化
   v_limit     PLS_INTEGER := 5;          -->定義了一個變數來作為limit的值
   v_counter   PLS_INTEGER := 0;
BEGIN
   OPEN emp_cur;

   LOOP
      FETCH emp_cur
      BULK   COLLECT INTO emp_tab         -->fetch時使用了BULK COLLECT子句
      LIMIT v_limit;                      -->使用limit子句限制提取資料量

      EXIT WHEN emp_tab.COUNT = 0;        -->注意此時遊標退出使用了emp_tab.COUNT,而不是emp_cur%notfound
      v_counter   := v_counter + 1;       -->記錄使用LIMIT之後fetch的次數

      FOR i IN emp_tab.FIRST .. emp_tab.LAST
      LOOP
         DBMS_OUTPUT.put_line( 'Current record is '||emp_tab(i).empno||CHR(9)||emp_tab(i).ename||CHR(9)||emp_tab(i).hiredate);
      END LOOP;
   END LOOP;

   CLOSE emp_cur;

   DBMS_OUTPUT.put_line( 'The v_counter is ' || v_counter );
END;

三、RETURNING 子句的批量繫結
    BULK COLLECT除了與SELECT,FETCH進行批量繫結之外,還可以與INSERT,DELETE,UPDATE語句結合使用。當與這幾個DML語句結合時,我們
需要使用RETURNING子句來實現批量繫結。

--下面示例中從表emp中刪除所有deptno=20的記錄
DECLARE
   TYPE emp_rec_type IS RECORD
   (
      empno      emp.empno%TYPE
     ,ename      emp.ename%TYPE
     ,hiredate   emp.hiredate%TYPE
   );

   TYPE nested_emp_type IS TABLE OF emp_rec_type;

   emp_tab   nested_emp_type;
--   v_limit   PLS_INTEGER := 3;
--   v_counter   PLS_INTEGER := 0;
BEGIN
   DELETE FROM emp
   WHERE  deptno = 20
   RETURNING empno, ename, hiredate     -->使用returning 返回這幾個列
   BULK   COLLECT INTO emp_tab;         -->將前面返回的列的資料批量插入到集合變數  

   DBMS_OUTPUT.put_line( 'Deleted ' || SQL%ROWCOUNT || ' rows.' );
   COMMIT;
   
   IF emp_tab.COUNT > 0 THEN                 -->當集合變數不為空時,輸出所有被刪除的元素
      FOR i IN emp_tab.FIRST .. emp_tab.LAST     
      LOOP
         DBMS_OUTPUT.
          put_line(
                       'Current record  '
                    || emp_tab( i ).empno
                    || CHR( 9 )
                    || emp_tab( i ).ename
                    || CHR( 9 )
                    || emp_tab( i ).hiredate
                    || ' has been deleted' );
      END LOOP;
   END IF;
END;

四、FORALL與BULK COLLECT 綜合運用
    FORALL與BULK COLLECT是實現批量SQL的兩個重要方式,我們可以將其結合使用以提高效能。下面的示例即是兩者的總和運用。

DROP TABLE tb_emp;

CREATE TABLE tb_emp AS                        -->建立表tb_emp
   SELECT empno, ename, hiredate
   FROM   emp
   WHERE  1 = 2;

DECLARE                               
   CURSOR emp_cur IS                          -->宣告遊標 
      SELECT empno, ename, hiredate FROM emp;

   TYPE nested_emp_type IS TABLE OF emp_cur%ROWTYPE;  -->基於遊標的巢狀表型別

   emp_tab   nested_emp_type;                         -->宣告巢狀變數
BEGIN
   SELECT empno, ename, hiredate
   BULK   COLLECT INTO emp_tab                        -->BULK  COLLECT批量提取資料
   FROM   emp
   WHERE  sal > 1000;

   FORALL i IN 1 .. emp_tab.COUNT                     -->使用FORALL語句將變數中的資料插入到表tb_emp
      INSERT INTO (SELECT empno, ename, hiredate FROM tb_emp)
      VALUES emp_tab( i );

   COMMIT;
   DBMS_OUTPUT.put_line( 'The total ' || emp_tab.COUNT || ' rows has been inserted to tb_emp' );
END;

五、BULK COLLECT的限制
1、不能對使用字串型別作鍵的關聯陣列使用BULK COLLECT 子句。
2、只能在伺服器端的程式中使用BULK COLLECT,如果在客戶端使用,就會產生一個不支援這個特性的錯誤。
3、BULK COLLECT INTO 的目標物件必須是集合型別。
4、複合目標(如物件型別)不能在RETURNING INTO 子句中使用。
5、如果有多個隱式的資料型別轉換的情況存在,多重複合目標就不能在BULK COLLECT INTO 子句中使用。
6、如果有一個隱式的資料型別轉換,複合目標的集合(如物件型別集合)就不能用於BULK COLLECTINTO 子句中。

六、更多參考