oracle 中 cursor 與refcursor及sys_refcursor的區別

來源:互聯網
上載者:User
引用一、顯式cursor

顯式是相對與隱式cursor而言的,就是有一個明確的聲明的cursor。顯式遊標的聲明類似如下(詳細的文法參加plsql ref doc ):

cursor cursor_name (parameter list) is select ...

遊標從declare、open、fetch、close是一個完整的生命旅程。當然了一個這樣的遊標是可以被多次open進行使用的,顯式cursor
是靜態cursor,她的範圍是全域的,但也必須明白,靜態cursor也只有pl/sql代碼才可以使用她。下面看一個簡單的靜態顯式cursor的樣本:

declare
          cursor get_gsmno_cur (p_nettype in varchar2) is

             select gsmno
                from gsm_resource

                where nettype=p_nettype and status='0';
          
v_gsmno gsm_resource.gsmno%type;
        begin
           open
get_gsmno_cur('139');
           loop
                fetch
get_gsmno_cur into v_gsmno;
                exit when
get_gsmno_cur%notfound;       
               
dbms_output.put_line(v_gsmno);
           end loop;
           close
emp_cur;
          
           open get_gsmno_cur('138');
          
loop
                fetch get_gsmno_cur into v_gsmno;
               
exit when get_gsmno_cur%notfound;       
               
dbms_output.put_line(v_gsmno);
           end loop;
           close
get_gsmno_cur;
        end;
        /

上面這段匿名塊用來實現選號的功能,我們顯式的定義了一個get_gsmno_cur,然後根據不同的號段輸出當前系統中該號短對應的可用手機號碼。當然了,實際應用中沒人這麼用的,我只是用來說應一個顯式cursor的用法。

引用二、隱式cursor

隱式cursor當然是相對於顯式而言的,就是沒有明確的cursor的declare。在Oracle的PL/SQL中,所有的DML操作都被Oracle內部解析為一個cursor名為SQL的隱式遊標,只是對我們透明罷了。

另外,我們前面提到的一些迴圈操作中的指標for 迴圈,都是隱式cursor。 

隱式cursor樣本一:

CREATE TABLE zrp (str VARCHAR2(10));
        insert into zrp values
('ABCDEFG');
        insert into zrp values ('ABCXEFG');
        insert
into zrp values ('ABCYEFG');
        insert into zrp values ('ABCDEFG');

        insert into zrp values ('ABCZEFG');
        COMMIT;
       

        SQL> begin
          2    update zrp SET str = 'updateD'
where str like '%D%';
          3    ifSQL%ROWCOUNT= 0 then
         
4      insert into zrp values ('1111111');
          5    end if;

          6 end;
          7 /
       
        PL/SQL procedure
successfully completed
       
        SQL> select * from zrp;

       
        STR
        ----------
        updateD

        ABCXEFG
        ABCYEFG
        updateD
        ABCZEFG

       
        SQL>
        SQL> begin
          2   
update zrp SET str = 'updateD' where str like '%S%';
          3   
ifSQL%ROWCOUNT= 0 THEN
          4      insert into zrp values ('0000000');

          5    end if;
          6 end;
          7 /
       

        PL/SQL procedure successfully completed
       
       
SQL> select * from zrp;
       
        STR
        ----------

        updateD
        ABCXEFG
        ABCYEFG
        updateD

        ABCZEFG
        0000000
       6 rows selected
       

        SQL>

隱式cursor樣本二:

begin
          for rec in
(select gsmno,status from gsm_resource) loop
             
dbms_output.put_line(rec.gsmno||'--'||rec.status);
          end loop;

        end;
        /

引用三、REFcursor

Ref
cursor屬於動態cursor(直到運行時才知道這條查詢)。

從技術上講,在最基本的層次靜態cursor和ref
cursor是相同的。一個典型的PL/SQL游標按定義是靜態。Ref游標正好相反,可以動態地開啟,或者利用一組SQL靜態語句來開啟,選擇哪種方法由邏輯確定(一個IF/THEN/ELSE代碼塊將開啟一個或其它的查詢)。例如,下面的代碼塊顯示一個典型的靜態SQL游標,游標C。此外,還顯示了如何通過使用動態SQL或靜態SQL來用ref游標(在本例中為L_CURSOR)來開啟一個查詢:

Declare
      type rc is ref cursor;
      cursor c is select *
from dual;
     
      l_cursor rc;
    begin
      if
(to_char(sysdate,'dd') = 30) then
          -- ref cursor with dynamic sql

          open l_cursor for 'select * from emp';
      elsif
(to_char(sysdate,'dd') = 29) then
          -- ref cursor with static sql

          open l_cursor for select * from dept;
      else

           -- with ref cursor with static sql
           open l_cursor
for select * from dual;
      end if;
      -- the "normal" static
cursor
      open c;
    end;
    /

在這段代碼塊中,可以看到了最顯而易見的區別:無論運行多少次該代碼塊,游標C總是select * from
dual。相反,ref游標可以是任何結果集,因為"select * from emp"字串可以用實際上包含任何查詢的變數來代替。

在上面的代碼中,聲明了一個弱類型的REF cursor,下面再看一個強型別(受限)的REF cursor,這種類型的REF
cursor在實際的應用系統中用的也是比較多的。

create table gsm_resource
    (
     
gsmno varchar2(11),
      status varchar2(1),
      price number(8,2),

      store_id varchar2(32)
    );
    insert into gsm_resource
values('13905310001','0',200.00,'SD.JN.01');
    insert into gsm_resource
values('13905312002','0',800.00,'SD.JN.02');
    insert into gsm_resource
values('13905315005','1',500.00,'SD.JN.01');
    insert into gsm_resource
values('13905316006','0',900.00,'SD.JN.03');
    commit;
   
   
SQL> declare
      2     type gsm_rec is record(
      3         
gsmno varchar2(11),
      4          status varchar2(1),
     
5          price number(8,2));
      6
      7     type app_ref_cur_type
is ref cursor return gsm_rec;
      8     my_cur app_ref_cur_type;
     
9     my_rec gsm_rec;
     10
     11 begin
     12     open my_cur
for select gsmno,status,price
     13          from gsm_resource
    
14          where store_id='SD.JN.01';
     15     fetch my_cur into my_rec;

     16     while my_cur%found loop
     17          
dbms_output.put_line(my_rec.gsmno||'#'||my_rec.status||'#'||my_rec.price);

     18           fetch my_cur into my_rec;
     19     end loop;

     20     close my_cur;
     21 end;
     22 /
   
   
13905310001#0#200
    13905315005#1#500
   
    PL/SQL procedure
successfully completed
   
    SQL>

普通cursor與REF
cursor還有一些大家應該都熟悉的區別,我再浪費點唾沫。

1)PL/SQL靜態游標不能返回到用戶端,只有PL/SQL才能利用它。ref游標能夠被返回到用戶端,這就是從Oracle的預存程序返回結果集的方式。

2)PL/SQL靜態游標可以是全域的,而ref游標則不是。 也就是說,不能在包說明或包體中的過程或函數之外定義ref游標。
只能在定義ref游標的過程中處理它,或返回到用戶端應用程式。

3)ref游標可以從子常式傳遞到子常式,而游標則不能。
為了共用靜態游標,必須在包說明或包體中把它定義為全域游標。
因為使用全域變數通常不是一種很好的編碼習慣,因此可以用ref游標來共用PL/SQL中的游標,無需混合使用全域變數。

最後,使用靜態游標--通過靜態SQL(但不用ref游標)--比使用ref游標效率高,而ref游標的使用僅限於以下幾種情況:

把結果集返回給用戶端;
在多個子常式之間共用游標(實際上與上面提到的一點非常類似);

沒有其他有效方法來達到你的目標時,則使用ref游標,正如必須用動態SQL時那樣;

簡言之,首先考慮使用靜態SQL,只有絕對必須使用ref游標時才使用ref游標,也有人建議盡量使用隱式遊標,避免編寫附加的遊標控制碼(聲明,開啟,擷取,關閉),也不需要聲明變數來儲存從遊標中擷取的資料。這個就因人因具體的case大家去酌定吧。

四、遊標屬性

%FOUND: bool - TRUE if >1 row
returned
%NOTFOUND:bool - TRUE if 0 rows returned
%ISOPEN: bool - TRUE
if cursor still open
%ROWCOUNT:int - number of rows affected by last SQL
statement

註:NO_DATA_FOUND和%NOTFOUND的用法是有區別的,小結如下:
1)SELECT . . .
INTO 語句觸發 NO_DATA_FOUND;
2)當一個顯式游標的 where 子句未找到時觸發 %NOTFOUND;

3)當UPDATE或DELETE 語句的where 子句未找到時觸發 SQL%NOTFOUND;
4)在游標的提取(Fetch)迴圈中要用
%NOTFOUND 或%FOUND 來確定迴圈的允出準則,不要用NO_DATA_FOUND

引用五、sys_refcursor

sys_refcursor是oracle9i以後系統定義的一個refcursor,主要用在過程中返回結果集。

例:

SQL> conn scott/tiger@vongates
connected.

SQL> create or replace procedure getEmpByDept(in_deptNo in
emp.deptno%type,
2 out_curEmp out SYS_REFCURSOR) as
3
4 begin
5
open out_curEmp for
6 SELECT * FROM emp WHERE deptno = in_deptNo ;
7
EXCEPTION
8 WHEN OTHERS THEN
9 RAISE_APPLICATION_ERROR(-20101,
10
'Error in getEmpByDept' || SQLCODE );
12 end getEmpByDept;
13 /

已建立程式.

SQL> var rset refcursor;
SQL> exec
getEmpByDept(10,:rset);

PL/SQL 程式順利完成.

SQL> print rset

引用二、隱式cursor

隱式cursor當然是相對於顯式而言的,就是沒有明確的cursor的declare。在Oracle的PL/SQL中,所有的DML操作都被Oracle內部解析為一個cursor名為SQL的隱式遊標,只是對我們透明罷了。

另外,我們前面提到的一些迴圈操作中的指標for 迴圈,都是隱式cursor。 

隱式cursor樣本一:

CREATE TABLE zrp (str VARCHAR2(10));
        insert into zrp values
('ABCDEFG');
        insert into zrp values ('ABCXEFG');
        insert
into zrp values ('ABCYEFG');
        insert into zrp values ('ABCDEFG');

        insert into zrp values ('ABCZEFG');
        COMMIT;
       

        SQL> begin
          2    update zrp SET str = 'updateD'
where str like '%D%';
          3    ifSQL%ROWCOUNT= 0 then
         
4      insert into zrp values ('1111111');
          5    end if;

          6 end;
          7 /
       
        PL/SQL procedure
successfully completed
       
        SQL> select * from zrp;

       
        STR
        ----------
        updateD

        ABCXEFG
        ABCYEFG
        updateD
        ABCZEFG

       
        SQL>
        SQL> begin
          2   
update zrp SET str = 'updateD' where str like '%S%';
          3   
ifSQL%ROWCOUNT= 0 THEN
          4      insert into zrp values ('0000000');

          5    end if;
          6 end;
          7 /
       

        PL/SQL procedure successfully completed
       
       
SQL> select * from zrp;
       
        STR
        ----------

        updateD
        ABCXEFG
        ABCYEFG
        updateD

        ABCZEFG
        0000000
       6 rows selected
       

        SQL>

隱式cursor樣本二:

begin
          for rec in
(select gsmno,status from gsm_resource) loop
             
dbms_output.put_line(rec.gsmno||'--'||rec.status);
          end loop;

        end;
        /

引用三、REFcursor

Ref
cursor屬於動態cursor(直到運行時才知道這條查詢)。

從技術上講,在最基本的層次靜態cursor和ref
cursor是相同的。一個典型的PL/SQL游標按定義是靜態。Ref游標正好相反,可以動態地開啟,或者利用一組SQL靜態語句來開啟,選擇哪種方法由邏輯確定(一個IF/THEN/ELSE代碼塊將開啟一個或其它的查詢)。例如,下面的代碼塊顯示一個典型的靜態SQL游標,游標C。此外,還顯示了如何通過使用動態SQL或靜態SQL來用ref游標(在本例中為L_CURSOR)來開啟一個查詢:

Declare
      type rc is ref cursor;
      cursor c is select *
from dual;
     
      l_cursor rc;
    begin
      if
(to_char(sysdate,'dd') = 30) then
          -- ref cursor with dynamic sql

          open l_cursor for 'select * from emp';
      elsif
(to_char(sysdate,'dd') = 29) then
          -- ref cursor with static sql

          open l_cursor for select * from dept;
      else

           -- with ref cursor with static sql
           open l_cursor
for select * from dual;
      end if;
      -- the "normal" static
cursor
      open c;
    end;
    /

在這段代碼塊中,可以看到了最顯而易見的區別:無論運行多少次該代碼塊,游標C總是select * from
dual。相反,ref游標可以是任何結果集,因為"select * from emp"字串可以用實際上包含任何查詢的變數來代替。

在上面的代碼中,聲明了一個弱類型的REF cursor,下面再看一個強型別(受限)的REF cursor,這種類型的REF
cursor在實際的應用系統中用的也是比較多的。

create table gsm_resource
    (
     
gsmno varchar2(11),
      status varchar2(1),
      price number(8,2),

      store_id varchar2(32)
    );
    insert into gsm_resource
values('13905310001','0',200.00,'SD.JN.01');
    insert into gsm_resource
values('13905312002','0',800.00,'SD.JN.02');
    insert into gsm_resource
values('13905315005','1',500.00,'SD.JN.01');
    insert into gsm_resource
values('13905316006','0',900.00,'SD.JN.03');
    commit;
   
   
SQL> declare
      2     type gsm_rec is record(
      3         
gsmno varchar2(11),
      4          status varchar2(1),
     
5          price number(8,2));
      6
      7     type app_ref_cur_type
is ref cursor return gsm_rec;
      8     my_cur app_ref_cur_type;
     
9     my_rec gsm_rec;
     10
     11 begin
     12     open my_cur
for select gsmno,status,price
     13          from gsm_resource
    
14          where store_id='SD.JN.01';
     15     fetch my_cur into my_rec;

     16     while my_cur%found loop
     17          
dbms_output.put_line(my_rec.gsmno||'#'||my_rec.status||'#'||my_rec.price);

     18           fetch my_cur into my_rec;
     19     end loop;

     20     close my_cur;
     21 end;
     22 /
   
   
13905310001#0#200
    13905315005#1#500
   
    PL/SQL procedure
successfully completed
   
    SQL>

普通cursor與REF
cursor還有一些大家應該都熟悉的區別,我再浪費點唾沫。

1)PL/SQL靜態游標不能返回到用戶端,只有PL/SQL才能利用它。ref游標能夠被返回到用戶端,這就是從Oracle的預存程序返回結果集的方式。

2)PL/SQL靜態游標可以是全域的,而ref游標則不是。 也就是說,不能在包說明或包體中的過程或函數之外定義ref游標。
只能在定義ref游標的過程中處理它,或返回到用戶端應用程式。

3)ref游標可以從子常式傳遞到子常式,而游標則不能。
為了共用靜態游標,必須在包說明或包體中把它定義為全域游標。
因為使用全域變數通常不是一種很好的編碼習慣,因此可以用ref游標來共用PL/SQL中的游標,無需混合使用全域變數。

最後,使用靜態游標--通過靜態SQL(但不用ref游標)--比使用ref游標效率高,而ref游標的使用僅限於以下幾種情況:

把結果集返回給用戶端;
在多個子常式之間共用游標(實際上與上面提到的一點非常類似);

沒有其他有效方法來達到你的目標時,則使用ref游標,正如必須用動態SQL時那樣;

簡言之,首先考慮使用靜態SQL,只有絕對必須使用ref游標時才使用ref游標,也有人建議盡量使用隱式遊標,避免編寫附加的遊標控制碼(聲明,開啟,擷取,關閉),也不需要聲明變數來儲存從遊標中擷取的資料。這個就因人因具體的case大家去酌定吧。

四、遊標屬性

%FOUND: bool - TRUE if >1 row
returned
%NOTFOUND:bool - TRUE if 0 rows returned
%ISOPEN: bool - TRUE
if cursor still open
%ROWCOUNT:int - number of rows affected by last SQL
statement

註:NO_DATA_FOUND和%NOTFOUND的用法是有區別的,小結如下:
1)SELECT . . .
INTO 語句觸發 NO_DATA_FOUND;
2)當一個顯式游標的 where 子句未找到時觸發 %NOTFOUND;

3)當UPDATE或DELETE 語句的where 子句未找到時觸發 SQL%NOTFOUND;
4)在游標的提取(Fetch)迴圈中要用
%NOTFOUND 或%FOUND 來確定迴圈的允出準則,不要用NO_DATA_FOUND

引用五、sys_refcursor

sys_refcursor是oracle9i以後系統定義的一個refcursor,主要用在過程中返回結果集。

例:

SQL> conn scott/tiger@vongates
connected.

SQL> create or replace procedure getEmpByDept(in_deptNo in
emp.deptno%type,
2 out_curEmp out SYS_REFCURSOR) as
3
4 begin
5
open out_curEmp for
6 SELECT * FROM emp WHERE deptno = in_deptNo ;
7
EXCEPTION
8 WHEN OTHERS THEN
9 RAISE_APPLICATION_ERROR(-20101,
10
'Error in getEmpByDept' || SQLCODE );
12 end getEmpByDept;
13 /

已建立程式.

SQL> var rset refcursor;
SQL> exec
getEmpByDept(10,:rset);

PL/SQL 程式順利完成.

SQL> print rset

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.