1. 程式人生 > >Oracle顯式遊標和隱式遊標

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 then
     DBMS_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 13 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 13 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;

複製程式碼

 

遊標表示式

OracleSQL語言中提供了一個強有力的工具:遊標表示式。一個遊標表示式從一個查詢中返回一個內嵌的遊標。在這個內嵌遊標的結果集中,每一行資料包含了在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;
/
複製程式碼