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事務結束,屬性也就不存在了。