【Oracle】淺析遊標使用

1,什麼是遊標?

遊標可以理解爲在內存中的臨時表,通過 sql從數據庫中提取數據,形成一個臨時表並存於內存中,這就形成遊標。當需要遍歷遊標中的數據時,可以使用Fetch … into …的方式,Fetch就相當與於指向遊標的指針,可以從頭遍歷遊標。由於數據都被存於內存中,這樣可以大大提高處理效率,用空間換時間。

2,遊標的屬性

我們利用遊標的屬性值來獲取遊標所處的狀態,然後對應做相應的處理,常用的屬性有四個:
1、%NOTFOUND。表示遊標獲取數據的時候是否有數據提取出來,沒有數據返回TRUE,有數據返回false。經常用來判斷遊標是否全部循環完畢,如%NOTFOUND爲true的時候,說明循環完畢,跳出LOOP循環。
2、%FOUND。正好和%NOTFOUND相反,當遊標提取數據值時有值,返回TRUE,否則返回FALSE。
3、%ISOPEN。用來判斷遊標是否打開。
4、%ROWCOUNT。表示當前遊標FETCH INTO獲取了多少行的記錄值,用來做計數用的。

3,遊標的分類

3.1 顯式遊標

在DECLEAR部分按以下格式聲明遊標:
CURSOR 遊標名[(參數1 數據類型[,參數2 數據類型…])]
IS SELECT語句;

參數是可選部分,如果定義了參數,則必須在打開遊標時傳遞相應的實際參數。 SELECT語句是對錶或視圖的查詢語句,甚至也可以是聯合查詢。可以帶WHERE條件、ORDER BY或GROUP BY等子句.

3.1.1 語法定義
--一般總共 4 個步驟,缺一不可:(參數可選)
DECLARE
   CURSOR cur_emp(參數值 參數類型) IS SELECT * FROM emp t [WHERE t.empno = 參數值]; -- 步驟1: 聲明遊標
   v_emp  cur_emp%ROWTYPE;
BEGIN
   OPEN cur_emp(參數值); -- 步驟2: 打開遊標  
   
   LOOP 
     FETCH cur_emp INTO v_emp; -- 步驟3: 提取數據
     EXIT WHEN cur_emp%NOTFOUND;    --退出遍歷的條件
       dbms_output.put_line(v_emp.empno ||' : '||v_emp.ename);
   END LOOP;
   
   CLOSE cur_emp; -- 步驟4: 關閉遊標
END;
3.1.2 顯式遊標遍歷

第一種: loop方式,注意OPEN、CLOSE遊標,以及退出LOOP的條件

DECLARE
  CURSOR CUR_EMP(P_DEPT NUMBER) IS                  -- 步驟1: 聲明帶參數遊標
    SELECT * FROM EMP E WHERE E.DEPTNO = P_DEPT;
  V_EMP CUR_EMP%ROWTYPE;
  DEPT  NUMBER := 30;
BEGIN
  OPEN CUR_EMP(DEPT);-- 步驟2: 打開遊標,並傳入參數

  LOOP
    FETCH CUR_EMP INTO V_EMP; -- 步驟3: 提取數據
    EXIT WHEN CUR_EMP%NOTFOUND; --退出遍歷的條件
    IF CUR_EMP%FOUND THEN
      DBMS_OUTPUT.PUT_LINE(V_EMP.EMPNO || ',' || V_EMP.ENAME || ',' ||
                           V_EMP.DEPTNO);
    END IF;
  END LOOP;

  CLOSE CUR_EMP;

第二種: While方式,注意OPEN、CLOSE遊標,以及While的條件

DECLARE
  CURSOR CUR_EMP(P_DEPT NUMBER) IS
    SELECT * FROM EMP E WHERE E.DEPTNO = P_DEPT;   -- 步驟1: 聲明帶參數遊標
  V_EMP CUR_EMP%ROWTYPE;
  DEPT  NUMBER := 30;
BEGIN
  OPEN CUR_EMP(DEPT);  -- 步驟2: 打開遊標,並傳入參數
  FETCH CUR_EMP INTO V_EMP;   --先移動指針到第一條記錄

  WHILE CUR_EMP%FOUND LOOP     -- 獲取到數據才進入 while
    DBMS_OUTPUT.PUT_LINE(V_EMP.EMPNO || ',' || V_EMP.ENAME || ',' ||
                         V_EMP.DEPTNO);
    FETCH CUR_EMP INTO V_EMP;    --繼續移動指針取下一條記錄
  END LOOP;
  CLOSE CUR_EMP;

第三種:for…in…方式,這裏不需顯式打開和關閉遊標

DECLARE
  CURSOR CUR_EMP(P_DEPT NUMBER) IS
    SELECT * FROM EMP E WHERE E.DEPTNO = P_DEPT;
  -- V_EMP CUR_EMP%ROWTYPE; -- 可以不聲明遊標量
  DEPT  NUMBER := 30;
BEGIN
  FOR V_EMP IN CUR_EMP(DEPT) LOOP   -- 傳入參數
    IF CUR_EMP%FOUND THEN
      DBMS_OUTPUT.PUT_LINE(V_EMP.EMPNO || ',' || V_EMP.ENAME || ',' ||
                           V_EMP.DEPTNO);
    END IF;
  END LOOP;
END;

三種方式的比較:
1)loop循環:首先要將遊標指向第一行,再判斷%NOTFOUND來設置循環退出條件。
2)while循環:只有第一行%FOUND返回true時纔會執行體,在循環體中執行完用戶操作後,需要將遊標指向下一行。
3)最簡單是是for…in…循環:首先是:在DECLARE部分不需要聲明變量,其次,在BEGIN部分不需要打開遊標以及關閉遊標。

3.2 隱式遊標

我們常用到的SELECT…INTO…查詢語句,一次只能從數據庫中提取一行數據,對於這種形式的查詢和DML操作(insert、update、delete),系統都會使用一個隱式遊標。隱式遊標自動聲明、打開和關閉(無法手動查看),其名爲 SQL。通過檢查隱式遊標的屬性可以獲得最近執行的 DML 和SELECT…INTO…語句的信息。

爲什麼是SELECT…INTO…查詢語句,而不是SELECT?
因爲在begin … end塊中只能添加insert、update、delete之類的DML,不能添加純粹的select語句。PL/SQL語法要求,如果要select,可以使用顯式遊標。
SELECT…INTO…查詢語句,當執行的時候會有三種可能:
1,結果集只含有一行,且select是成功,;
2,沒有查詢到任何結果集,引發NO_DATA_FOUND異常;
3,結果集中含有兩行或者更多行,引發TOO_MANY_ROWS異常。

3.2.1 語法定義
DECLARE
  EMP_ROW EMP%ROWTYPE;
BEGIN
  DBMS_OUTPUT.PUT_LINE('ROWCOUNT初始值:' || SQL%ROWCOUNT);  --ROWCOUNT初始值:    
  SELECT * INTO EMP_ROW FROM EMP WHERE EMPNO = 7979;
  IF SQL%FOUND THEN
    DBMS_OUTPUT.PUT_LINE('SELECT..INTO語句影響的行數爲:' || SQL%ROWCOUNT);  --SELECT..INTO語句影響的行數爲:1
    DBMS_OUTPUT.PUT_LINE(EMP_ROW.ENAME);                 --young
  END IF;

  UPDATE EMP SET SAL = 7000 WHERE DEPTNO = 10;
  IF SQL%FOUND THEN
    DBMS_OUTPUT.PUT_LINE('UPDATE語句影響的行數爲:' || SQL%ROWCOUNT);   --UPDATE語句影響的行數爲:6
  END IF;

  INSERT INTO EMP(EMPNO, ENAME, DEPTNO, SAL) VALUES (7803, 'young', 30, 6000);
  IF SQL%FOUND THEN
    DBMS_OUTPUT.PUT_LINE('INSERT語句影響的行數爲:' || SQL%ROWCOUNT);   --INSERT語句影響的行數爲:1
  END IF;
 
  DELETE FROM emp WHERE ename = 'young';
  IF SQL%FOUND THEN
    DBMS_OUTPUT.PUT_LINE('DELETE語句影響的行數爲:' || SQL%ROWCOUNT);  --DELETE語句影響的行數爲:2
  END IF;
   COMMIT;
END;

4,遊標屬性的tips

通過前面的例子,我們知道遊標的屬性可以獲得前面最近執行的 DML 和SELECT…INTO…語句的信息,有三個tips可以注意下:

1,%ISOPEN

顯式遊標因爲有open,close所有可以用%ISOPEN來做些判斷,而隱式遊標自動聲明、打開和關閉,所以隱式遊標的%ISOPEN無法使用到,

2,%NOTFOUND

在顯式遊標的loop中,使用%NOTFOUND作爲退出循環的判斷條件,只有當%NOTFOUND 返回True才能退出loop,需要注意的是%NOTFOUND除了True、False,還可以是null。
Oracle 官方文檔:Before the first fetch%NOTFOUND returns NULL,也就是在進行第一次fetch之前%NOTFOUND 返回 NULL
舉個例子
表中數據
在這裏插入圖片描述

DECLARE
   CURSOR cur_stu IS SELECT * FROM stu;
   v_stu cur_stu%ROWTYPE;
BEGIN
   OPEN cur_stu;
   LOOP
      EXIT WHEN cur_stu%NOTFOUND;      
      FETCH cur_stu INTO v_stu; -- before the first fetch... 
         dbms_output.put_line(v_stu.s_id || ' : ' || v_stu.s_xm);
   END LOOP;
   CLOSE cur_stu; 
END;

上面的執行結果:
在這裏插入圖片描述
對結果進行分析:

第一次loop 第二次loop 第三次loop 第四次loop
cur_stu%NOTFOUND null False False True
EXIT?
FETCH 結果 1 科比 2 詹姆斯 null
v_stu 1 科比 2 詹姆斯 2 詹姆斯
輸出 1 科比 2 詹姆斯 2 詹姆斯

所以在使用EXIT WHEN cur_stu%NOTFOUND;時,需要緊跟在fetch之後,避免null。

3,%ROWCOUNT

在執行任何DML語句之前,SQL%ROWCOUNT的值都是NULL,對於SELECT INTO語句,如果執行成功,SQL%ROWCOUNT的值爲1。如果沒有成功或者沒有操作(如update、insert、delete爲0條),SQL%ROWCOUNT的值爲0,而對於update和delete來說SQL%ROWCOUNT表示遊標所檢索數據庫行的個數即更新或者刪除的行數。當執行DML語句,都需要commit,此時需注意SQL%ROWCOUNT要在DML語句和commit之間,否則無法得到正確結果,SQL%ROWCOUNT是最近的DMLDML語句的結果。
舉個例子

DECLARE
BEGIN
  UPDATE EMP SET SAL = 7000 WHERE DEPTNO = 10;
  COMMIT;
  DBMS_OUTPUT.PUT_LINE('UPDATE語句影響的行數爲:' || SQL%ROWCOUNT);   --輸出:UPDATE語句影響的行數爲:0
END;
DECLARE
BEGIN
  UPDATE EMP SET SAL = 7000 WHERE DEPTNO = 10;
  DBMS_OUTPUT.PUT_LINE('UPDATE語句影響的行數爲:' || SQL%ROWCOUNT);   --輸出:UPDATE語句影響的行數爲:5
   COMMIT;
END;

兩個結果不同,其實不僅僅%ROWCOUNT,四個屬性必須要在一個DML語句和commit之間放置,否則你就得不到正確的修改行數,一旦commit,DML事務結束,屬性也就不存在了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章