Oracle顯式遊標和隱式遊標
遊標的概念:
遊標是SQL的一個記憶體工作區,由系統或使用者以變數的形式定義。遊標的作用就是用於臨時儲存從資料庫中提取的資料塊。在某些情況下,需要把資料從存放在磁碟的表中調到計算機記憶體中進行處理,最後將處理結果顯示出來或最終寫回資料庫。這樣資料處理的速度才會提高,否則頻繁的磁碟資料交換會降低效率。
遊標有兩種型別:顯式遊標和隱式遊標。我們常用到的SELECT...INTO...查詢語句,一次只能從資料庫中提取一行資料,對於這種形式的查詢和DML操作,系統都會使用一個隱式遊標。但是如果要提取多行資料,就要由程式設計師定義一個顯式遊標,並通過與遊標有關的語句進行處理。顯式遊標對應一個返回結果為多行多列的SELECT語句。
遊標一旦開啟,資料就從資料庫中傳送到遊標變數中,然後應用程式再從遊標變數中分解出需要的資料,並進行處理。
隱式遊標
如前所述,DML操作和單行SELECT語句會使用隱式遊標,它們是:
* 插入操作:INSERT。
* 更新操作:UPDATE。
* 刪除操作:DELETE。
* 單行查詢操作:SELECT ... INTO ...。
當系統使用一個隱式遊標時,可以通過隱式遊標的屬性來了解操作的狀態和結果,進而控制程式的流程。隱式遊標可以使用名字SQL來訪問,但要注意,通過SQL遊標名總是隻能訪問前一個DML操作或單行SELECT操作的遊標屬性。所以通常在剛剛執行完操作之後,立即使用SQL遊標名來訪問屬性。遊標的屬性有四種,如下所示:
sql%found (布林型別,預設值為null)
sql%notfound(布林型別,預設值為null)
sql%rowcount(數值型別預設值為0)
sql%isopen(布林型別)
當執行一條DML語句後,DML語句的結果儲存在四個遊標屬性中,這些屬性用於控制程式流程或者瞭解程式的狀態。當執行DML語句時,PL/SQL開啟一個內建遊標並處理結果,遊標是維護查詢結果的記憶體中的一個區域,遊標在執行DML語句時開啟,完成後關閉。隱式遊標只使用SQL%FOUND,SQL%NOTFOUND,SQL%ROWCOUNT三個屬性.SQL%FOUND,SQL%NOTFOUND是布林值,SQL%ROWCOUNT是整數值。
SQL%FOUND和SQL%NOTFOUND
在執行任何DML語句前SQL%FOUND和SQL%NOTFOUND的值都是NULL,在執行DML語句後,SQL%FOUND的屬性值將是:
. TRUE :INSERT
. TRUE :DELETE和UPDATE,至少有一行被DELETE或UPDATE.
. TRUE :SELECT INTO至少返回一行
當SQL%FOUND為TRUE時,SQL%NOTFOUND為FALSE。
SQL%ROWCOUNT
在執行任何DML語句之前,SQL%ROWCOUNT的值都是NULL,對於SELECT INTO語句,如果執行成功,SQL%ROWCOUNT的值為1,如果沒有成功或者沒有操作(如update、insert、delete為0條),SQL%ROWCOUNT的值為0,而對於update和delete來說表示遊標所檢索資料庫行的個數即更新或者刪除的行數。
SQL%ISOPEN
SQL%ISOPEN是一個布林值,如果遊標開啟,則為TRUE, 如果遊標關閉,則為FALSE.對於隱式遊標而言SQL%ISOPEN總是FALSE,這是因為隱式遊標在DML語句執行時開啟,結束時就立即關閉。
最後我們來說一下隱式遊標中SELECT..INTO 語句,當執行的時候會有三種可能:
(1).結果集只含有一行,且select是成功的
(2).沒有查詢到任何結果集,引發NO_DATA_FOUND異常
(3).結果集中含有兩行或者更多行,引發TOO_MANY_ROWS異常。
例子:
BEGIN UPDATE exchangerate SET rate=7 where quarter='2011Q1';
DBMS_output.put_line('遊標所影響的行數:'||SQL%rowcount); if SQL%NotFound thenDBMS_output.put_line(</span><span style="color: #ff0000;">'</span><span style="color: #ff0000;">NotFound為真</span><span style="color: #ff0000;">'</span><span style="color: #000000;">); DBMS_output.put_line(</span><span style="color: #ff0000;">'</span><span style="color: #ff0000;">NofFound為假</span><span style="color: #ff0000;">'</span><span style="color: #000000;">); </span><span style="color: #0000ff;">end</span> <span style="color: #0000ff;">if</span><span style="color: #000000;">; </span><span style="color: #0000ff;">if</span> SQL<span style="color: #808080;">%</span>Found <span style="color: #0000ff;">then</span><span style="color: #000000;"> DBMS_output.put_line(</span><span style="color: #ff0000;">'</span><span style="color: #ff0000;">Found為真</span><span style="color: #ff0000;">'</span><span style="color: #000000;">); </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> DBMS_output.put_line(</span><span style="color: #ff0000;">'</span><span style="color: #ff0000;">Found為假</span><span style="color: #ff0000;">'</span><span style="color: #000000;">); </span><span style="color: #0000ff;">end</span> <span style="color: #0000ff;">if</span><span style="color: #000000;">; </span><span style="color: #0000ff;">if</span> SQL<span style="color: #808080;">%</span>isopen <span style="color: #0000ff;">then</span><span style="color: #000000;"> DBMS_output.put_line(</span><span style="color: #ff0000;">'</span><span style="color: #ff0000;">isOpen為真</span><span style="color: #ff0000;">'</span><span style="color: #000000;">); </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> DBMS_output.put_line(</span><span style="color: #ff0000;">'</span><span style="color: #ff0000;">isOpen為假</span><span style="color: #ff0000;">'</span><span style="color: #000000;">); </span><span style="color: #0000ff;">end</span> <span style="color: #0000ff;">if</span><span style="color: #000000;">;
END;
顯式遊標:
遊標的定義和操作
遊標的使用分成以下4個步驟。
1.宣告遊標
在DECLEAR部分按以下格式宣告遊標:
CURSOR 遊標名[(引數1 資料型別[,引數2 資料型別...])]
IS SELECT語句;
引數是可選部分,所定義的引數可以出現在SELECT語句的WHERE子句中。如果定義了引數,則必須在開啟遊標時傳遞相應的實際引數。
SELECT語句是對錶或檢視的查詢語句,甚至也可以是聯合查詢。可以帶WHERE條件、ORDER BY或GROUP BY等子句,但不能使用INTO子句。在SELECT語句中可以使用在定義遊標之前定義的變數。
2.開啟遊標
在可執行部分,按以下格式開啟遊標:
OPEN 遊標名[(實際引數1[,實際引數2...])];
開啟遊標時,SELECT語句的查詢結果就被傳送到了遊標工作區。
3.提取資料
在可執行部分,按以下格式將遊標工作區中的資料取到變數中。提取操作必須在開啟遊標之後進行。
FETCH 遊標名 INTO 變數名1[,變數名2...];
或
FETCH 遊標名 INTO 記錄變數;
遊標開啟後有一個指標指向資料區,FETCH語句一次返回指標所指的一行資料,要返回多行需重複執行,可以使用迴圈語句來實現。控制迴圈可以通過判斷遊標的屬性來進行。
下面對這兩種格式進行說明:
第一種格式中的變數名是用來從遊標中接收資料的變數,需要事先定義。變數的個數和型別應與SELECT語句中的欄位變數的個數和型別一致。
第二種格式一次將一行資料取到記錄變數中,需要使用%ROWTYPE事先定義記錄變數,這種形式使用起來比較方便,不必分別定義和使用多個變數。
定義記錄變數的方法如下:
變數名 表名|遊標名%ROWTYPE;
其中的表必須存在,遊標名也必須先定義。
4.關閉遊標
CLOSE 遊標名;
顯式遊標開啟後,必須顯式地關閉。遊標一旦關閉,遊標佔用的資源就被釋放,遊標變成無效,必須重新開啟才能使用。
現在通過一個例子來學習一下顯示遊標的使用方法:
有一個表原來結構是如下的
create table EXCHANGERATE ( QUARTER VARCHAR2(20), RATE NUMBER(10,4), DESCRIPTION VARCHAR2(900), ID VARCHAR2(10) not null, CURRENCY VARCHAR2(100) )
這是一個匯率表裡面維護著的是季度 幣種和匯率的關係,現在有一個新的需求是在原來表的基礎上增加一列名字為currentmonth,變為季度、季度中月份、 幣種和匯率的關係,
並且使原來每個季度對應的幣種和匯率變成每個季度 對應該季度月份 幣種和匯率,每個月的預設值為原來季度對應的值。
例如 原來 2013Q2 CNY 6.2
現在我們要變為2013Q2 2013-04 CNY 6.2 2013Q2 2013-05 CNY 6.2
2013Q2 2013-06 CNY 6.2 三條記錄。
通過分析以上需求,我們首先要增加一列:
alter table exchangerate add currentmonth varchar2(20);
然後我們通過在匿名塊中通過顯示遊標來實現以上需求:
declare
v_year varchar2(20);
v_month number;
p_rate exchangerate%rowtype;
cursor c_rate is select from exchangerate t where t.currentmonth is null;
begin
open c_rate;
loop
fetch c_rate into p_rate;
v_year:=substr(p_rate.quarter, 0, 4);
v_month:=(to_number(substr(p_rate.quarter,6,1)) - 1) 3;
for i in 1 … 3 loop
</span><span style="color: #0000ff;">insert</span> <span style="color: #0000ff;">into</span> exchangerate(id,quarter,currentmonth,rate,currency,Description)<br> <span style="color: #0000ff;">values</span>(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter,<br> to_char(to_date(v_year<span style="color: #808080;">||</span>(v_month<span style="color: #808080;">+</span>i),<span style="color: #ff0000;">'</span><span style="color: #ff0000;">yyyyMM</span><span style="color: #ff0000;">'</span>),<span style="color: #ff0000;">'</span><span style="color: #ff0000;">yyyy-MM</span><span style="color: #ff0000;">'</span><span style="color: #000000;">),p_rate.rate,p_rate.currency,p_rate.description);
</span><span style="color: #0000ff;">end</span><span style="color: #000000;"> loop; <br>
</span><span style="color: #0000ff;">exit</span> <span style="color: #0000ff;">when</span> c_rate<span style="color: #808080;">%</span><span style="color: #000000;">notfound;<br>
end loop;
close c_rate;
end;
/
我們把上面的例子有遊標的for迴圈來改寫一下。
顯式遊標的for迴圈
declare
v_year varchar2(20);
v_month number;
cursor c_rate is select * from exchangerate t where t.currentmonth is null;
begin
for p_rate in c_rate loop
v_year:=substr(p_rate.quarter, 0, 4);
v_month:=(to_number(substr(p_rate.quarter,6,1)) - 1) * 3;
</span><span style="color: #0000ff;">for</span> i <span style="color: #808080;">in</span> <span style="color: #800000; font-weight: bold;">1</span> .. <span style="color: #800000; font-weight: bold;">3</span><span style="color: #000000;"> loop
</span><span style="color: #0000ff;">insert</span> <span style="color: #0000ff;">into</span> exchangerate(id,quarter,currentmonth,rate,currency,Description)<br> <span style="color: #0000ff;">values</span>(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter,<br> to_char(to_date(v_year<span style="color: #808080;">||</span>(v_month<span style="color: #808080;">+</span>i),<span style="color: #ff0000;">'</span><span style="color: #ff0000;">yyyyMM</span><span style="color: #ff0000;">'</span>),<span style="color: #ff0000;">'</span><span style="color: #ff0000;">yyyy-MM</span><span style="color: #ff0000;">'</span><span style="color: #000000;">),p_rate.rate,p_rate.currency,p_rate.description);
</span><span style="color: #0000ff;">end</span><span style="color: #000000;"> loop;
end loop;
end;
/
我們可以看到遊標FOR迴圈確實很好的簡化了遊標的開發,我們不在需要open、fetch和close語句,不在需要用%FOUND屬性檢測是否到最後一條記錄,這一切Oracle隱式的幫我們完成了。
隱式遊標的for迴圈
declare
v_year varchar2(20);
v_month number;
begin
for p_rate in (select * from exchangerate t where t.currentmonth is null) loop
v_year:=substr(p_rate.quarter, 0, 4);
v_month:=(to_number(substr(p_rate.quarter,6,1)) - 1) * 3;
for i in 1 … 3 loop
</span><span style="color: #0000ff;">insert</span> <span style="color: #0000ff;">into</span> exchangerate(id,quarter,currentmonth,rate,currency,Description) <br><span style="color: #0000ff;"> values</span>(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter,<br> to_char(to_date(v_year<span style="color: #808080;">||</span>(v_month<span style="color: #808080;">+</span>i),<span style="color: #ff0000;">'</span><span style="color: #ff0000;">yyyyMM</span><span style="color: #ff0000;">'</span>),<span style="color: #ff0000;">'</span><span style="color: #ff0000;">yyyy-MM</span><span style="color: #ff0000;">'</span><span style="color: #000000;">),p_rate.rate,p_rate.currency,p_rate.description);
</span><span style="color: #0000ff;">end</span><span style="color: #000000;"> loop;
end loop;
end;
/
顯示遊標中游標引數的傳遞
例子:就以上面的表來說 加入我們在定義遊標時不確定查詢條件中的值,這時我們可以通過遊標引數來解決
declare
v_year varchar2(20);
v_month number;
p_rate exchangerate%rowtype;
cursor c_rate(p_quarter varchar2) --宣告遊標帶引數
is
select * from exchangerate t where t.quarter<=p_quarter;
begin
open c_rate(p_quarter=>‘2011Q3’);–開啟遊標,傳遞引數值
loop
</span><span style="color: #0000ff;">fetch</span> c_rate <span style="color: #0000ff;">into</span><span style="color: #000000;"> p_rate;
</span><span style="color: #0000ff;">update</span> exchangerate <span style="color: #0000ff;">set</span> rate<span style="color: #808080;">=</span>p_rate.rate<span style="color: #808080;">+</span><span style="color: #800000; font-weight: bold;">1</span> <span style="color: #0000ff;">where</span> id<span style="color: #808080;">=</span><span style="color: #000000;">p_rate.id;
</span><span style="color: #0000ff;">exit</span> <span style="color: #0000ff;">when</span> c_rate<span style="color: #808080;">%</span><span style="color: #000000;">notfound;
end loop;
close c_rate;
end;
遊標變數
遊標是資料庫中一個命名的工作區,當遊標被聲明後,他就與一個固定的SQL想關聯,在編譯時刻是已知的,是靜態的.它永遠指向一個相同的查詢工作區.
遊標變數是動態的可以在執行時刻與不同的SQL語句關聯,在執行時可以取不同的SQL語句.它可以引用不同的工作區.
如何定義遊標型別
TYPE ref_type_name IS REF CURSOR
[RETURN return_type];
宣告遊標變數
cursor_name ref_type_name;
ref_type_name 是後面宣告遊標變數時要用到的我們的遊標型別(自定義遊標型別,即CURSOR是系統預設的,ref_type_name是我們定義的 );
return_type代表資料庫表中的一行,或一個記錄型別
TYPE ref_type_name IS REF CURSOR RETURN EMP%TYPE
RETURN 是可選的,如果有是強型別,可以減少錯誤,如果沒有return是弱引用,有較好的靈活性.
遊標變數的操作
例子:
declare
v_year varchar2(20);
v_month number;
p_rate exchangerate%rowtype;
type rate is ref cursor;–定義遊標變數
c_rate rate; –宣告遊標變數
begin
open c_rate for select * from exchangerate t where t.quarter=‘2011Q3’;–開啟遊標變數
loop
</span><span style="color: #0000ff;">fetch</span> c_rate <span style="color: #0000ff;">into</span> p_rate;<span style="color: #008080;">--</span><span style="color: #008080;">提取遊標變數</span>
<span style="color: #0000ff;">update</span> exchangerate <span style="color: #0000ff;">set</span> rate<span style="color: #808080;">=</span>p_rate.rate<span style="color: #808080;">+</span><span style="color: #800000; font-weight: bold;">1</span> <span style="color: #0000ff;">where</span> id<span style="color: #808080;">=</span><span style="color: #000000;">p_rate.id;
</span><span style="color: #0000ff;">exit</span> <span style="color: #0000ff;">when</span> c_rate<span style="color: #808080;">%</span><span style="color: #000000;">notfound;
end loop;
–將同一個遊標變數對應到另一個SELECT語句
open c_rate for select * from exchangerate t where t.quarter=‘2011Q2’;–開啟遊標變數
loop
</span><span style="color: #0000ff;">fetch</span> c_rate <span style="color: #0000ff;">into</span> p_rate;<span style="color: #008080;">--</span><span style="color: #008080;">提取遊標變數</span>
<span style="color: #0000ff;">update</span> exchangerate <span style="color: #0000ff;">set</span> rate<span style="color: #808080;">=</span>p_rate.rate<span style="color: #808080;">-</span><span style="color: #800000; font-weight: bold;">1</span> <span style="color: #0000ff;">where</span> id<span style="color: #808080;">=</span><span style="color: #000000;">p_rate.id;
</span><span style="color: #0000ff;">exit</span> <span style="color: #0000ff;">when</span> c_rate<span style="color: #808080;">%</span><span style="color: #000000;">notfound;
end loop;
close c_rate;–關閉遊標變數
end;
遊標表示式
Oracle在SQL語言中提供了一個強有力的工具:遊標表示式。一個遊標表示式從一個查詢中返回一個內嵌的遊標。在這個內嵌遊標的結果集中,每一行資料包含了在SQL查詢中的可允許的數值範圍;它也能包含被其他子查詢所產生的遊標。
因此,你能夠使用遊標表示式來返回一個大的和複雜的,從一張或多張表獲取的資料集合。遊標表示式的複雜程度,取決於查詢和結果集。然而,瞭解所有從Oracle RDBMS提取資料的可能途徑,還有大有好處的。
你能夠在以下任何一種情況使用遊標表示式:
(1)、 顯式遊標宣告
(2)、動態SQL查詢。
(3)、REF CURSOR 宣告和變數。
你不能在一個隱式查詢中使用遊標表示式。
遊標表示式的語法是相當簡單的:
CURSOR (查詢語句)
當Oracle從父遊標或外圍遊標那裡檢取包含遊標表示式的資料行時,Oracle就會隱式地開啟一個內嵌的遊標,這個遊標就是被上述的遊標表示式所定義。在以下情況發生時,這個內遷遊標將會被關閉:
(1)、你顯式地關閉這個遊標。
(2)、外圍或父遊標被重新執行,關閉或撤銷。
(3)、當從父遊標檢取資料時,發生異常。內嵌遊標就會與父遊標一起被關閉。
使用遊標表示式
你可以通過兩種不同的,但是非常有用的方法來使用遊標表示式:
1. 在一個外圍查詢中把字查詢作為一列來檢取資料。
2. 把一個查詢轉換成一個結果集,而這個結果集就可以被當成一個引數傳遞給一個流型或變換函式。
例子:
CREATE OR REPLACE PROCEDURE emp_report(p_locid NUMBER) IS TYPE refcursor IS REF CURSOR; CURSOR all_in_one IS SELECT l.city, CURSOR( SELECT d.department_name, CURSOR ( SELECT e.last_name FROM employees e WHERE e.DEPARTMENT_ID = d.DEPARTMENT_ID ) as ename FROM departments d WHERE d.LOCATION_ID = l.LOCATION_ID ) as dname FROM locations l WHERE l.location_id = p_locid; departments_cur refcursor; employees_cur refcursor; v_city locations.city%type; v_dname departments.department_name%type; v_ename employees.last_name%type; i integer :=1; j integer :=1; k integer :=1; BEGIN OPEN all_in_one; LOOP FETCH all_in_one INTO v_city, departments_cur; EXIT WHEN all_in_one%NOTFOUND; LOOP FETCH departments_cur INTO v_dname, employees_cur; EXIT WHEN departments_cur%NOTFOUND; LOOP FETCH employees_cur INTO v_ename; EXIT WHEN employees_cur%NOTFOUND; dbms_output.put_line(i || ' , ' || j || ' , ' || k || '----' || v_city || ' ,' || v_dname || ' ,' || v_ename ); k := k + 1; END LOOP; j := j + 1; END LOOP; i := i + 1; END LOOP; END; /