PL/SQL遊標使用詳解
每當在PL/SQL中執行一個SQL語時,Oracle資料庫都會為這個語句分一個上下文區域(Context Area)來處理所必需的資訊,其中包括語句處理的行數,一個指向語句被分析以後的表示形式的指標以及查詢的結果集。遊標是指向上下文區域的控制代碼或指標,PL/SQL通過遊標可以控制或處理上下文區域。如果按照遊標是否繫結到一個專門的查詢語句來劃分,可以分為靜態遊標和動態遊標。其中靜態遊標又分為:隱式遊標和顯示遊標;動態遊標分為:強型別和弱型別。
1.隱式遊標
每當我們執行一個DML語句(包括INSERT,UPDATE,MERGE或者DELETE)或者SELECT INTO語句時,PL/SQL都會宣告一個隱式遊標並管理這個遊標。這種遊標之所以叫做隱式遊標,是因為資料庫自動執行遊標相關操作,比如,開啟,提取,關閉等。隱式遊標又叫做SQL遊標。
1.1隱式遊標屬性
隱式遊標屬性返回有關DDL語句和DML語句執行的資訊,該遊標屬性值總是返回最近執行的SQL語句。執行開啟的隱式遊標的屬性值是空的。隱式遊標可用的屬性如下:
隱式遊標屬性
名 字
說 明
SQL%FOUND
如果取到記錄就返回TRUE,否者返回FLASE
SQL%NOTFOUND
如果沒有取得記錄就返回TRUE,否者返回FALSE
SQL%ROWCOUNT
返回從遊標中取出的記錄數
SQL%ISOPEN
由於隱式遊標總是自動開啟和關閉,因此這個屬性中是FALSE
隱式遊標屬性總是返回最後一次執行的SQL語句的屬性值,而不管這個SQL語句時在哪個程式碼塊或者程式中執行的。
declare
v_empno emp.empno%type := 3792;
begin
update emp e set e.sal = 5000 where e.empno = v_empno;
if sql%notfound then
insert into emp
(empno, ename, hiredate, sal, deptno)
values
(v_empno, 'CHICLEWU', date '2011-07-08', 4000, 50);
end if;
end;
1.2使用隱式遊標屬性準則
當使用隱式遊標屬性是,需要考慮以下準則:
- 遊標屬性值總是返回最近執行的SQL語句,它可能是在不同的作用域(例如,在一個子塊)。
-
在SELECT INTO語句中,%NOTFOUND屬性是不生效的:
-
如果在SELECT INTO語句沒有返回行,PL/SQL立即丟擲一個預訂的NO_DATA_FOUND異常,在檢查%NOTFOUND之前中斷控制流。
-
SELECT INTO語句呼叫聚合函式總是返回個值或者空值。%NOTFOUNT屬性總是返回FALSE,因此檢查它是沒有必要的。
-
2.顯示遊標
當你需要精確地控制查詢處理時,可以在任何PL/SQL塊、子程式或者包的宣告部分顯示地宣告一個遊標。顯示遊標就是在程式碼的宣告部分明確定義的SELECT語句,並同時指定一個名字。然後你可以通過三個語句來控制遊標:OPEN、FETCH和CLOSE。首先,你需要使用OPEN語句初始化遊標,標識結果集。然後,可以反覆地執行FETCH語句直到所有行都被取出,或者使用BULK COLLECT語句一次性取出所有行。最後,只用CLOSE語句釋放遊標。
2.1宣告顯示遊標
要使用顯示遊標,必須先在PL/SQL塊或包的規範部分宣告它。
CURSOR cursor_name
[ ( cursor_parameter_declaration [, cursor_parameter_declaration ]... )]
[ RETURN rowtype] IS select_statement ;
其中,cusor_name是顯示遊標的名字;cursor_parameter_declartion是顯示遊標的引數,是可選項;select_statement是顯示遊標指定的SELECT語句。
2.2開啟顯示遊標
OPEN cursor_name
[ ( cursor_parameter_name [ [,] cursor_parameter_name ]... ) ] ;
其中,cusor_name是顯示遊標的名字;cursor_parameter_name是顯示遊標的引數,是可選項。如果遊標宣告時,指定了遊標引數,則開啟時一定要指定該引數。
2.3提取資料
每次只提取一行資料:
FETCH cursor_name
INTO [(variable_name [, variable_name ]... | record_name )];
一次提取多行資料:
FETCH cursor_name
BULK COLLECT INTO [(collection_name [,collection_name...])];
2.4關閉顯示遊標
CLOSE cursor_name;
2.5顯示遊標屬性
每個顯示遊標和遊標變數都有四個屬性:%FOUND、%NOTFOUND、%ROWCOUNT和%ISOPEN。可以下面的語法使用這些屬性:
cusor%attribute
其中,cursor就是我們宣告的遊標名字或者遊標變數。
顯示遊標屬性
名 字
說 明
cusor%FOUND
如果取到記錄就返回TRUE,否者返回FLASE
cusor%NOTFOUND
如果沒有取得記錄就返回TRUE,否者返回FALSE
cusor%ROWCOUNT
返回當值從指定遊標取得的記錄數量
cusor%ISOPEN
如果遊標時開啟的則返回TRUE,否則返回FALE
2.6顯示遊標例子
2.6.1不帶引數的顯示遊標
declare
cursor cur_emp is
select e.ename, e.sal from emp e where e.deptno = 10; --宣告遊標
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open cur_emp; --開啟遊標
--迴圈取出
loop
fetch cur_emp
into v_ename, v_sal; --提取記錄
exit when cur_emp%notfound;
dbms_output.put_line(v_ename || '的工資是' || v_sal);
end loop;
close cur_emp; --關閉遊標
end;
2.6.2帶引數引數的顯示遊標
declare
cursor cur_emp(p_empno number) is
select e.ename, e.sal from emp e where e.empno = p_empno; --宣告遊標
v_empno emp.empno%type := 7788;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open cur_emp(v_empno); --開啟遊標
--迴圈取出
loop
fetch cur_emp
into v_ename, v_sal; --提取記錄
exit when cur_emp%notfound;
dbms_output.put_line(v_ename || '的工資是' || v_sal);
end loop;
close cur_emp; --關閉遊標
end;
2.6.3一次取出所有記錄
declare
cursor cur_emp is
select e.ename, e.sal from emp e where e.deptno = 10; --宣告遊標
type it_ename is table of emp.ename%type index by binary_integer;
type it_sal is table of emp.sal%type index by binary_integer;
vit_ename it_ename;
vit_sal it_sal;
begin
open cur_emp; --開啟遊標
fetch cur_emp bulk collect
into vit_ename, vit_sal; --一次性取出所有記錄
close cur_emp; --關閉遊標
for i in vit_ename.first .. vit_ename.last loop
dbms_output.put_line(vit_ename(i) || '的工資是' || vit_sal(i));
end loop;
end;
3.遊標變數
遊標變數是指向或者引用底層遊標的變數。顯示遊標已經為結果集的工作區指定了名字,而遊標變數只是指向這個工作區的引用。顯示遊標和隱式遊標都繫結到一個專門的查詢語句,而遊標變數可以是任何一個查詢語句,也可以是查詢語句的字串變數或者字面量。因為遊標變數的查詢語句可以是多個不相同的查詢,因此這種遊標稱為動態遊標。顯示遊標和隱式遊標稱為靜態遊標。
3.1宣告REF CURSOR型別
TYPE cursor_type_name IS REF CURSOR [RETURN return_type];
其中,cursor_type_name是遊標型別的名字;return_type是該遊標型別返回的資料型別。
REF CURSOR型別可是強型別(帶有return)或者是弱型別(不帶return)。以下兩種宣告都是有效的宣告:
TYPE rc_emp is REF CURSOR RETURN emp%rowtype; --強型別
TYPE rc_emp is REF CURSOR;--弱型別
3.2宣告遊標變數
cursor_variable_name cursor_type_name;
其中,cursor_variable_name是遊標變數的名字;cursor_type_name是之前的REF CURSOR宣告的遊標型別的名字。
3.3開啟遊標變數
OPEN cursor_variable_name FOR select_statement;
其中,cursor_variable_name是遊標變數的名字; select_statement可以是一個直接的SQL語句,也可以一個SELECT語句的字串或者字面。如果select_statement是字串或者字面量,可以包含繫結變數的佔位符,並可以使用USING子句標識相應的佔位符值。
3.4提取資料
每次只提取一行資料:
FETCH cursor_name
INTO [(variable_name [, variable_name ]... | record_name )];
一次提取多行資料:
FETCH cursor_name
BULK COLLECT INTO [(collection_name [,collection_name...])];
3.5關閉顯示遊標
CLOSE cursor_name;
3.6遊標變數屬性
遊標變數屬性與顯示遊標屬性一樣,參見2.5
3.7遊標變數例子
3.7.1一次只取一行記錄
declare
type rc_emp is ref cursor; --宣告ref遊標型別
vrc_emp rc_emp; --宣告遊標變數
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open vrc_emp for
select e.ename, e.sal from emp e where e.deptno = 10; --開啟遊標變數,並賦值。
loop
fetch vrc_emp
into v_ename, v_sal; --提取記錄
exit when vrc_emp%notfound;
dbms_output.put_line(v_ename || '的工資是' || v_sal);
end loop;
close vrc_emp; ---關閉遊標變數
end;
3.7.2一次取出所有行記錄
declare
type it_ename is table of emp.ename%type index by binary_integer;
type it_sal is table of emp.sal%type index by binary_integer;
type rc_emp is ref cursor; --宣告ref遊標型別\
vrc_emp rc_emp; --宣告遊標變數
vit_ename it_ename;
vit_sal it_sal;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open vrc_emp for
select e.ename, e.sal from emp e where e.deptno = 10; --開啟遊標變數,並賦值。
fetch vrc_emp bulk collect
into vit_ename, vit_sal; --一次性取出所有記錄
close vrc_emp; --關閉遊標
for i in vit_ename.first .. vit_ename.last loop
dbms_output.put_line(vit_ename(i) || '的工資是' || vit_sal(i));
end loop;
end;
3.7.3查詢語句是字串變數
declare
type rc_emp is ref cursor; --宣告ref遊標型別
vrc_emp rc_emp; --宣告遊標變數
v_ename emp.ename%type;
v_sal emp.sal%type;
v_deptno emp.deptno%type := 10;
v_sql clob;
begin
v_sql := 'select e.ename, e.sal from emp e where e.deptno = :deptno';
open vrc_emp for v_sql
using v_deptno; --開啟遊標變數,並賦值。
loop
fetch vrc_emp
into v_ename, v_sal; --提取記錄
exit when vrc_emp%notfound;
dbms_output.put_line(v_ename || '的工資是' || v_sal);
end loop;
close vrc_emp; ---關閉遊標變數
end;
4.遊標表示式
遊標表示式返回一個巢狀遊標,使用CUSOR操作符表示。可以使用遊標表示式從一個或者多個表中提取龐大的複雜的記錄集。
可以在下面的場合使用遊標表示式:
- 顯示遊標宣告
- REF CURSOR宣告和REF CURSOR變數
- 動態SQL查詢
遊標表示式語法:
CURSOR (subquery)
當父遊標提取資料時,巢狀遊標就會隱式地開啟。巢狀遊標在以下這些時刻關閉:
- 使用者顯示地關閉巢狀遊標
- 父遊標再次執行
- 父遊標關閉
- 父遊標取消
- 從父遊標提取資料時丟擲了異常。巢狀遊標會和父遊標一起關閉
使用遊標表示式,查詢各個部門的名稱、地址以及該部門下的員工:
declare
cursor cur_dept_emp is
select d.dname,
d.loc,
cursor (select * from emp e where e.deptno = d.deptno) nc_emp --巢狀遊標
from dept d;
type rc_emp is ref cursor return emp%rowtype;
vrc_emp rc_emp;
vrt_emp emp%rowtype;
v_dname dept.dname%type;
v_loc dept.loc%type;
begin
open cur_dept_emp;
loop
fetch cur_dept_emp
into v_dname, v_loc, vrc_emp; --提取父遊標資料,並自動開啟巢狀遊標
exit when cur_dept_emp%notfound;
dbms_output.put_line(v_loc || '的' || v_dname || '部門的員工資訊:');
loop
fetch vrc_emp
into vrt_emp; --從巢狀遊標提取資料
exit when vrc_emp%notfound;
dbms_output.put_line(vrt_emp.ename || '的工資' || vrt_emp.sal);
end loop;
dbms_output.put_line('');
end loop;
close cur_dept_emp; --關閉父遊標,同時也關閉了巢狀遊標
end;
5.遊標FOR迴圈
遊標FOR迴圈的迭代變數不需要事先宣告。這是一個%ROWTYPE記錄,其欄位名稱匹配查詢的列名,而且只能存在於迴圈中。資料庫自動開啟、提取、關閉遊標FOR迴圈。即使在迴圈中使用EXIT語句、GO語句或者丟擲異常,資料庫都會自動關閉該遊標。遊標FOR迴圈可以分為:隱式遊標FOR迴圈和顯示遊標FOR迴圈。
5.1隱式遊標FOR迴圈
begin
--隱式遊標FOR迴圈
for vrc_emp in (select * from emp e where e.deptno = 10) loop
dbms_output.put_line(vrc_emp.ename || '的工資' || vrc_emp.sal);
end loop;
end;
5.2顯示遊標FOR迴圈
declare
cursor cur_emp is
select * from emp e where e.deptno = 10;
begin
--隱式遊標FOR迴圈
for vrc_emp in cur_emp loop
dbms_output.put_line(vrc_emp.ename || '的工資' || vrc_emp.sal);
end loop;
end;
6.WHERE CURRENT OF語句
PL/SQL為遊標的UPDATE和DELETE語句提供了WHERE CURRENT OF語句。當你宣告一個在UPDATE或者DELETE語句的CURRENT OF子句引用的遊標時,必須使用FOR UPDATE語句獲取獨立的行級鎖。
要修改最新取出來的記錄的列:
UPDATE table_name
SET set_clause
WHERE CURRENT OF cursor_name;
要刪除最新取出的記錄:
DELETE FORM table_name WHERE CURRENT OF cursor_name;
declare
cursor cur_emp is
select * from emp for update nowait;
vrt_emp emp%rowtype;
begin
open cur_emp;
loop
fetch cur_emp
into vrt_emp;
exit when cur_emp%notfound;
if vrt_emp.ename = 'SCOTT' then
update emp e set e.sal = 6000 where current of cur_emp; --注意current of 後面子游標,而不是記錄
end if;
if vrt_emp.ename = 'CHICLEWU' then
delete from emp where current of cur_emp;
end if;
end loop;
end;