1. 游標概念
字面意思是游動的光標,是指向上下文區域的句柄或指針。
在PL/SQL塊中執行CRUD操作時,ORACLE會在內存中為其分配上下文區。用數據庫語言來描述游標就是:映射在上下文區結果集中一行數據上的位置實體。
用戶可以使用游標訪問結果集中的任意一行數據,將游標指向某行后,即可對該行數據進行操作。游標為應用提供了一種對具有多行數據查詢結果集中的每一行數據分別進行單獨處理的方法,是設計嵌入式SQL語句的應用程序的常用編程方式。
在每個用戶會話中,可以同時打開多個游標,其最大數量由數據庫初始化參數文件中的OPEN_CURSORS參數定義。
游標可分為顯式游標和隱式游標兩類。
2. 顯式游標
顯式游標使用主要有四個步驟:
- 聲明/定義游標
- 打開游標
- 讀取數據
- 關閉游標
2.1 聲明/定義游標
語法:
CURSOR cursor_name [(parameter_dec [, parameter_dec ]…)] [RETURN datatype] IS select_statement;
示例:
DECLARE CURSOR c1 RETURN departments%ROWTYPE; -- 聲明C1游標 CURSOR c2 IS -- 聲明C2游標並定義 SELECT employee_id, job_id, salary FROM employees WHERE salary > 2000; CURSOR c1 RETURN departments%ROWTYPE IS -- 定義C1游標 SELECT * FROM departments WHERE department_id = 110; CURSOR c3 RETURN locations%ROWTYPE; -- 聲明C3游標 CURSOR c3 IS -- 定義C3游標 SELECT * FROM locations WHERE country_id = 'JP'; CURSOR c4(sal number) IS -- 聲明C4游標並定義 SELECT employee_id, job_id, salary FROM employees WHERE salary > sal; BEGIN NULL; END;
說明:
在指定參數數據類型時,不能使用長度約束,如C4游標的參數,不能寫為number(10,4)這種結構。
[RETURN datatype]是可選的,表示游標返回數據的數據。如果選擇,則應該嚴格與select_statement中的選擇列表在次序和數據類型上匹配。一般是記錄數據類型(RECORD)或帶“%ROWTYPE”的數據。
2.2 打開游標
執行游標所對應的SELECT語句,將其查詢結果放入工作區,並且指針指向工作區的首部,標識游標結果集。
語法:
OPEN cursor_name [ ( cursor_parameter [ [,] actual_cursor_parameter ]... ) ]
示例:
OPEN c4 (1300);
2.3 讀取數據
檢索結果集合中的數據行,放入指定的輸出變量中。
語法:
FETCH { cursor | cursor_variable | :host_cursor_variable } { into_clause | bulk_collect_into_clause [ LIMIT numeric_expression ] } ;
執行FETCH語句時,每次返回一個數據行,然后自動將游標移動指向下一個數據行。當檢索到最后一行數據時,如果再次執行FETCH語句,將操作失敗,並將游標屬性%NOTFOUND置為TRUE。所以每次執行完FETCH語句后,檢查游標屬性%NOTFOUND就可以判斷FETCH語句是否執行成功並返回一個數據行,以便確定是否給對應的變量賦了值。
示例:
fetch c4 into eid, jid, sal;
2.4 關閉游標
當處理完游標結果集合數據后,應及時關閉游標,以釋放該游標所占用的系統資源。
關閉游標后不能再使用FETCH語句獲取其中數據。關閉后的游標可以使用OPEN語句重新打開。
語法:
CLOSE cursor_name;
完整示例1:
DECLARE -- 定義游標 CURSOR c_cursor IS SELECT first_name || last_name, Salary FROM EMPLOYEES WHERE rownum<11; -- 聲明變量 v_ename EMPLOYEES.first_name%TYPE; v_sal EMPLOYEES.Salary%TYPE; BEGIN -- 打開游標 OPEN c_cursor; -- 獲取數據 FETCH c_cursor INTO v_ename, v_sal; -- 處理數據 WHILE c_cursor%FOUND LOOP DBMS_OUTPUT.PUT_LINE(v_ename||'---'||to_char(v_sal) ); FETCH c_cursor INTO v_ename, v_sal; END LOOP; -- 關閉游標 CLOSE c_cursor; END;
完整示例2:
DECLARE -- 定義RECORD記錄類型 TYPE emp_record_type IS RECORD( f_name employees.first_name%TYPE, h_date employees.hire_date%TYPE); -- 聲明記錄變量 v_emp_record EMP_RECORD_TYPE; -- 定義游標,有參數與返回值 CURSOR c3(dept_id NUMBER, j_id VARCHAR2) RETURN EMP_RECORD_TYPE IS SELECT first_name, hire_date FROM employees WHERE department_id = dept_id AND job_id = j_id; BEGIN -- 打開游標,傳遞參數值 OPEN c3(j_id => 'AD_VP', dept_id => 90); LOOP FETCH c3 INTO v_emp_record; -- 獲取數據 IF c3%FOUND THEN DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name||'的雇佣日期是'||v_emp_record.h_date); ELSE DBMS_OUTPUT.PUT_LINE('已經處理完結果集了'); EXIT; -- 處理完則退出循環 END IF; END LOOP; CLOSE c3; --關閉游標 END;
3. 顯式游標屬性
游標的狀態(如是否打開,獲取了多少行數據等)可以使用游標屬性來獲取。
游標屬性以“%屬性名”的形式加在游標名之后。顯式游標屬性有:
| 屬性名 | 說明 |
|---|---|
| %FOUND | 如果記錄成功獲取,返回TRUE,否則返回FALSE |
| %NOTFOUND | 如果記錄獲取失敗,返回TRUE,否則返回FALSE |
| %ROWCOUNT | 返回已經從游標中獲取的記錄數 |
| %ISOPEN | 如果游標是打開的,返回TRUE,否則返回FALSE |
示例:
DECLARE v_empno EMPLOYEES.EMPLOYEE_ID%TYPE; v_sal EMPLOYEES.Salary%TYPE; -- 定義游標 CURSOR c_cursor IS SELECT EMPLOYEE_ID, Salary FROM EMPLOYEES; BEGIN -- 打開游標 OPEN c_cursor; LOOP -- 獲取數據 FETCH c_cursor INTO v_empno, v_sal; EXIT WHEN c_cursor%NOTFOUND; -- 未讀取到記錄,則退出循環 IF v_sal<=1200 THEN UPDATE EMPLOYEES SET Salary=Salary+50 WHERE EMPLOYEE_ID=v_empno; DBMS_OUTPUT.PUT_LINE('編碼為'||v_empno||'工資已更新!'); END IF; DBMS_OUTPUT.PUT_LINE('記錄數:'|| c_cursor %ROWCOUNT); END LOOP; -- 關閉游標 CLOSE c_cursor; END;
4. 基於游標定義記錄變量
使用%ROWTYPE屬性不僅可以基於表和視圖定義記錄變量,也可以基於游標定義記錄變量。當基於游標定義記錄變量時,記錄成員名實際就是SELECT語句的列名和列別名。
為了簡化顯式游標的數據處理,建議使用基於游標的記錄變量存放游標數據。基於游標定義記錄變量,比聲明記錄類型變量要方便,不容易出錯。
示例:
DECLARE -- 定義游標 CURSOR emp_cursor IS SELECT ename,sal FROM emp; emp_reocrd emp_cursor%ROWTYPE;-- 游標變量 BEGIN -- 打開游標 OPEN emp_cursor; LOOP -- 獲取記錄 FETCH emp_cursor INTO emp_record; EXIT WHEN emp_record%NOTFOUND; dbms_ouput.put_line('雇員名:'||emp_record.ename||',雇員工資:'||emp_record.sal); END LOOP; -- 關閉游標 CLOSE emp_cursor; END;
5. 隱式游標
如果在PL/SQL塊中使用了SELECT語句進行操作,PL/SQL會隱含處理游標定義,而對於非查詢語句,如修改、刪除操作,則由ORACLE系統自動地為這些操作設置游標並創建其工作區。由系統隱含創建的游標稱為隱式游標,隱式游標的名字為SQL。
對於隱式游標的操作,如定義、打開、取值及關閉操作,都由ORACLE 系統自動地完成,無需用戶進行處理。用戶只能通過隱式游標的相關屬性,來完成相應的操作。在隱式游標的工作區中,所存放的數據是與用戶自定義的顯示游標無關的、最新處理的一條SQL語句所包含的數據。
隱式游標的屬性:
| 屬性名 | 說明 |
|---|---|
| SQL%FOUND | 如果記錄成功獲取,返回TRUE,否則返回FALSE |
| SQL%NOTFOUND | 如果記錄獲取失敗,返回TRUE,否則返回FALSE |
| SQL%ROWCOUNT | 返回已經從游標中獲取的記錄數 |
| SQL%ISOPEN | 如果游標是打開的,返回TRUE,否則返回FALSE |
隱式游標在INSERT,UPDATE,DELETE,SELECT語句中不必明確定義游標。
示例:
DECLARE v_rows NUMBER; BEGIN -- 更新表數據 UPDATE employees SET salary = 5000 WHERE department_id = 90 AND job_id = 'AD_VP'; -- 獲取受影響行數 v_rows := SQL%ROWCOUNT; DBMS_OUTPUT.PUT_LINE('更新了'||v_rows||'個員工的工資'); END;
6. 游標FOR循環
游標FOR循環和顯示游標的一種快捷使用方式,它使用FOR循環依次讀取結果集中的行數據,當FOR循環開始時,游標自動打開(不需要OPEN),每循環一次系統自動讀取游標當前行的數據(不需要FETCH),當退出FOR循環時,游標被自動關閉(不需要使用CLOSE)使用游標FOR循環的時候不能使用OPEN語句,FETCH語句和CLOSE語句,否則會產生錯誤。
語法:
FOR index_variable IN cursor_name[(value[, value]…)] LOOP -- 游標處理語句 END LOOP;
示例:
DECLARE CURSOR emp_cur(vartype number) IS SELECT emp_no,emp_zc FROM cus_emp_basic WHERE com_no=vartype; BEGIN FOR person IN emp_cur(123) LOOP DBMS_OUTPUT.PUT_LINE('編號:'||person.emp_no||',地址:'||person.emp_zc); END LOOP; END;
7. 使用顯示游標修改數據
在PL/SQL中依然可以使用UPDATE和DELETE語句更新或刪除數據行。顯式游標只有在需要獲得多行數據的情況下使用。PL/SQL提供了僅僅使用游標就可以執行刪除或更新記錄的方法。
UPDATE或DELETE語句中的WHERE CURRENT OF子句專門處理要執行UPDATE或DELETE操作的表中取出的最近的數據。要使用這個方法,在聲明游標時必須使用FOR UPDATE子句,當使用FOR UPDATE子句打開一個游標時,所有返回集中的數據行都將處於行級(ROW-LEVEL)獨占式鎖定,其他對象只能查詢這些數據行,不能進行UPDATE、DELETE或SELECT…FOR UPDATE操作。
語法:
FOR UPDATE [OF [schema.]table.column[,[schema.]table.column].. [NOWAIT]
在多表查詢中,使用OF子句來鎖定特定的表,如果忽略了OF子句,那么所有表中選擇的數據行都將被鎖定。如果這些數據行已經被其他會話鎖定,那么正常情況下ORACLE將等待,直到數據行解鎖。當加上NOWAIT子句時,如果這些行真的被另一個會話鎖定,則OPEN立即返回並給出:
ORA-00054 :resource busy and acquire with nowait specified.
在UPDATE和DELETE中使用WHERE CURRENT OF子串的語法如下:
WHERE{CURRENT OF cursor_name|search_condition}
示例:
DELCARE CURSOR c1 IS SELECT empno,salary FROM emp WHERE comm IS NULL FOR UPDATE OF comm; v_comm NUMBER(10,2); BEGIN FOR r1 IN c1 LOOP IF r1.salary<500 THEN v_comm:=r1.salary*0.25; ELSEIF r1.salary<1000 THEN v_comm:=r1.salary*0.20; ELSEIF r1.salary<3000 THEN v_comm:=r1.salary*0.15; ELSE v_comm:=r1.salary*0.12; END IF; UPDATE emp SET comm=v_comm WHERE CURRENT OF c1; END LOOP; END
8. 游標變量
與游標類似,游標變量指向多行查詢的結果集的當前行。但是,游標與游標變量是不同的,就像常量和變量的關系一樣。游標是靜態的,游標變量是動態的,因為它不與特定的查詢綁定在一起。
8.1 聲明游標變量
語法:
TYPE ref_type_name IS REF CURSOR [ RETURN return_type];
說明:
游標變量類型有強類型定義和弱類型定義兩種。強類型定義必須指定游標變量的返回值類型,而弱類型定義則不說明返回值類型。
return_type為游標變量的返回值類型,它必須為記錄變量。
示例:
-- 定義一個REF CURSOU類型 TYPE ref_cursor_type IS REF CURSOR; -- 聲明一個游標變量 cv_ref REF_CURSOR_TYPE;
8.2 游標變量的使用
與游標一樣,游標變量操作也包括打開、提取和關閉三個步驟。
8.2.1 打開游標變量
語法:
OPEN {cursor_variable_name | :host_cursor_variable_name} FOR select_statement;
說明:
host_cursor_variable_name為PL/SQL主機環境(如OCI: ORACLE Call Interface,Pro*c 程序等)中聲明的游標變量。
OPEN…FOR 語句可以在關閉當前的游標變量之前重新打開游標變量,而不會導致CURSOR_ALREAD_OPEN異常錯誤。新打開游標變量時,前一個查詢的內存處理區將被釋放。
8.2.2 提取數據
語法:
FETCH {cursor_variable_name | :host_cursor_variable_name} INTO {variable [, variable]…| record_variable};
說明:
將提取到的數據放入普通變量和記錄變量中存放。
8.2.3 關閉游標
語法:
CLOSE {cursor_variable_name | :host_cursor_variable_name}
說明:
如果應用程序試圖關閉一個未打開的游標變量,則將導致INVALID_CURSOR異常錯誤。
示例1:
DECLARE TYPE ref_type_table IS REF CURSOR; v_cursor ref_type_table; emp_record emp%rowtype; BEGIN OPEN v_cursor FOR select * from emp where deptno=&no; LOOP FETCH v_cursor INTO emp_record; EXIT WHEN v_cursor%NOTFOUND; dbms_output.put_line('員工號:'||emp_record.ename||'部門號:'||emp_record.deptno); END LOOP; CLOSE v_cursor; END;
示例2:
DECLARE emp_record emp%rowtype; TYPE ref_type_table IS REF CURSOR RETURN emp%rowtype; v_cursor ref_type_table; BEGIN OPEN v_cursor FOR select * from emp where deptno=&no; LOOP FETCH v_cursor INTO emp_record; EXIT WHEN v_cursor%NOTFOUND; dbms_output.put_line('員工號:'||emp_record.ename||'部門號:'||emp_record.deptno); END LOOP; CLOSE v_cursor; END; DECLARE Type emp_record_type IS RECORD( ename emp.ename%TYPE, salary emp.sal%TYPE, deptno emp.deptno%TYPE); emp_record emp_record_type; TYPE ref_type_table IS REF CURSOR RETURN emp_record_type; v_cursor ref_type_table; BEGIN OPEN v_cursor FOR select ename,sal,deptno from emp where deptno=&no; LOOP FETCH v_cursor INTO emp_record; EXIT WHEN v_cursor%NOTFOUND; dbms_output.put_line('員工號:'||emp_record.ename||',部門號:'||emp_record.deptno||',工資:'||emp_record.salary); END LOOP; CLOSE v_cursor; END;
9. 使用游標批量獲取
語法:
FETCH ... BULK COLLECT INTO ...[LIMIT row_number];
說明:
使用BULK COLLECT,我們可以用對數據庫的一個來回,返回多行數據。BULK COLLECT減少了PL/SQL和SQL引擎之間的上下文開關數目,因而加速了數據獲取的速度。
示例:
DECLARE CURSOR emp_cursor(v_deptno number) IS SELECT * FROM EMP WHERE deptno = v_deptno; TYPE type_emp_table IS TABLE OF emp%ROWTYPE INDEX BY BINARY_INTEGER; emp_table type_emp_table; v_dno emp.deptno%TYPE; BEGIN v_dno := &no; OPEN emp_cursor(v_dno); FETCH emp_cursor BULK COLLECT INTO emp_table; CLOSE emp_cursor; FOR i IN 1..emp_table.COUNT LOOP dbms_output.put_line('員工號:'||emp_table(i).ename||'工資:'||emp_table(i).sal); END LOOP; CLOSE emp_cursor; END;
10. 游標表達式
游標表達式作用是用於返回嵌套游標。語法:
CURSOR(sub_query)
示例:
DECLARE CURSOR dept_emp_cursor(v_deptno number) IS SELECT dname,cursor(SELECT * FROM emp e WHERE e.deptno = d.deptno) FROM dept d WHERE deptno = v_deptno; TYPE emp_cursor_type IS REF CURSOR; emp_cursor emp_cursor_type; emp_record emp%ROWTYPE; v_name dept.dname%TYPE; v_dno emp.deptno%TYPE; BEGIN v_dno := &no; OPEN dept_emp_cursor(v_dno); loop FETCH dept_emp_cursor INTO v_name,emp_cursor; EXIT WHEN dept_emp_cursor%NOTFOUND; dbms_output.put_line('部門名稱:'||v_name); LOOP FETCH emp_cursor INTO emp_record; EXIT WHEN emp_cursor%NOTFOUND; dbms_output.put_line('員工名稱:'||emp_record.ename||',工資:'||emp_record.sal); END LOOP; end loop; CLOSE dept_emp_cursor; END;
