游標的概念:
游標是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 then DBMS_output.put_line('NotFound為真'); DBMS_output.put_line('NofFound為假'); end if; if SQL%Found then DBMS_output.put_line('Found為真'); else DBMS_output.put_line('Found為假'); end if; if SQL%isopen then DBMS_output.put_line('isOpen為真'); else DBMS_output.put_line('isOpen為假'); end if; 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 insert into exchangerate(id,quarter,currentmonth,rate,currency,Description)
values(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter,
to_char(to_date(v_year||(v_month+i),'yyyyMM'),'yyyy-MM'),p_rate.rate,p_rate.currency,p_rate.description); end loop;
exit when c_rate%notfound;
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; for i in 1 .. 3 loop insert into exchangerate(id,quarter,currentmonth,rate,currency,Description)
values(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter,
to_char(to_date(v_year||(v_month+i),'yyyyMM'),'yyyy-MM'),p_rate.rate,p_rate.currency,p_rate.description); end 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 insert into exchangerate(id,quarter,currentmonth,rate,currency,Description)
values(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter,
to_char(to_date(v_year||(v_month+i),'yyyyMM'),'yyyy-MM'),p_rate.rate,p_rate.currency,p_rate.description); end 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 fetch c_rate into p_rate; update exchangerate set rate=p_rate.rate+1 where id=p_rate.id; exit when c_rate%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 fetch c_rate into p_rate;--提取游標變量 update exchangerate set rate=p_rate.rate+1 where id=p_rate.id; exit when c_rate%notfound; end loop; --將同一個游標變量對應到另一個SELECT語句 open c_rate for select * from exchangerate t where t.quarter='2011Q2';--打開游標變量 loop fetch c_rate into p_rate;--提取游標變量 update exchangerate set rate=p_rate.rate-1 where id=p_rate.id; exit when c_rate%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; /