Oracle Cursor 遊標

Oracle 遊標


一、遊標的概述
當在PL/SQL塊中執行select和dml語句時,Oracle會爲其分配上下文區,遊標是指向上下文去的指針。


二、遊標的分類
1、Oracle中的遊標分爲顯式遊標和隱式遊標:
顯式遊標:用cursor...is 命令定義的遊標,它可以對select語句返回的多條記錄進行處理。
隱式遊標:在執行insert、delete、update和返回單條記錄的查詢select into語句時由pl/sql自動定義,隱式遊標也叫sql遊標。


2、Oracle顯式遊標分爲普通遊標、參數遊標、遊標變量。
其中普通遊標和參數遊標也被稱爲靜態遊標,遊標變量被稱爲動態遊標。


一般所說的遊標是指顯式遊標,因爲隱式並不能被操作者控制,只能獲取他的屬性,本篇先講述最常用的顯式遊標,隱式遊標在最後簡述。


三、顯式遊標的一般使用步驟
定義遊標:cursor cursor_name is select_sql; (注意,遊標的定義只能用使關鍵字IS,它與AS不通用)
打開遊標:open cursor_name;
提取數據:
語法1 fetch cursor_name into variable1[,variable2,...];
其中,fetch into 每次只能提取一行數據,批量數據需使用循環;variable指定接收遊標數據的變量,select_sql的字段有幾個,就有幾個variable。
語法2 fetch cursor_name bulk collect into collect1,collect2,...[limit rows];
其中,collect指定接收遊標結果的集合變量,這種語法9i後支持,比較耗內存。


顯示遊標(遊標的四個屬性):
%isopen:檢測遊標是否已經打開,已經打開則返回true。
%found:檢測遊標結果集是否存在數據,存在返回true。如果打開遊標後沒有使用fetch推進,則返回null.
%notfound:檢測遊標結果集是否不存在數據,不存在返回true。
%rowcount:返回到當前行爲止已提取的實際行數。


關閉遊標:close cursor_name;


到底哪種類型可以把一行的數據都裝進來,使用ROWTYPE類型,此類型表示可以把一行的數據都裝進來。
打開一個已經打開的遊標也是合法的。當第二次打開時,plsql先自動關閉,再打開,一次打開遊標也是允許的。
關閉一個已經關閉的遊標回報ORA-10001錯誤。


四、顯式遊標的三種類型(普通遊標、參數遊標、遊標變量)
其中普通遊標和參數遊標被稱爲靜態遊標,遊標變量被被稱爲動態遊標。


1、普通遊標
說明:最普通的遊標。本例列出的是帶變量v_cnt的普通遊標


1.1、最普遍的方法fetch...into
說明:每次只能處理一行,爲了處理多行,必須使用循環語句。
declare
  v_cnt number :=20;
  cursor emp_cursor is select ename,job,sal from emp where deptno>=v_cnt;
  vname emp.ename%type;
  vsal emp.sal%type;
  vjob emp.job%type;
begin
  open emp_cursor;
  loop
    fetch emp_cursor into vname,vjob,vsal;
    exit when emp_cursor%notfound;
    dbms_output.put_line('姓名' || vname || '崗頂' || vjob || '工資' || vsal);
  end loop;
  close emp_cursor;
end;


declare
  v_cnt number :=20;
  cursor emp_cursor is select ename,job,sal from emp where deptno>=v_cnt;
  emp_record emp_cursor%rowtype;
begin
  open emp_cursor;
  loop
    fetch emp_cursor into emp_record;
    exit when emp_cursor%notfound;
    dbms_output.put_line('姓名' || emp_record.ename || '崗頂' || emp_record.job || '工資' || emp_record.sal);
  end loop;
  close emp_cursor;
end;


2)、9i後新增的方法fetch...bulk collect into
說明:一次就可以提取結果的所有數據,需要使用PL/SQL集合數據類型。PL/SQL集合數據類型分爲PL/SQL表(別名索引表)、嵌套表、VARRAY。
declare
  v_cnt number :=20;
  cursor emp_cursor is select ename from emp where deptno>=v_cnt;
  type ename_table_type is table of varchar2(10);  --定義 嵌套表
  ename_table ename_table_type;                    --自定義一個此數據類型的變量
begin
  open emp_cursor;
  fetch emp_cursor bulk collect into ename_table;
  for i in 1..ename_table.count loop
    dbms_output.put_line(ename_table(i));
  end loop;
close emp_cursor;
end;


3)、9i後新增的方法fetch...bulk collect into...limit
說明:提起部分數據
declare
  type name_array_type is varray(5) of varchar2(10);  --定義 VARRAY
  name_array name_array_type;
  cursor emp_cursor is select name from emp;
  rows int:=5;
  v_count int:=0;
begin
  open emp_cursor;
  loop 
    fetch emp_cursor bulk collect into name_array limit rows;
    dbms_output.put('僱員名');
    for i in 1..(emp_cursor%rowcount-v_count) loop
      dbms_output.put(name_array(i));
    end loop;
    dbms_output.new_line;
    v_count:=emp_cursor%rowcount;
    exit when emp_cursor%notfound;
  end loop;
  close emp_cursor;
end;


2、參數遊標
說明:參數只指定數據類型,不能指定長度,而且必須在where子句中引用參數,與帶變量的普通遊標相比,進一步縮小了變量的作用範圍。
在打開遊標前,應先爲綁定在遊標中的變量賦值,當使用不同參數值多次打開遊標時,可以生產不同的結果集,
但如果打開遊標後,再爲該變量賦值,查詢結果集不發生任何改變,這是因爲結果集在遊標打開時被確定,同時指向結果集的指針也被確認。


1)、普通參數遊標
declare
  v_cnt number :=2;
  cursor ec(p_dno number) is select ename,job from emp where deptno>=p_dno;
begin
  for erec in ec(v_cnt) loop
    dbms_output.put_line('姓名' || erec.ename || '崗位' || erec.job);
  end loop;
end;


2)、cursor表達式
說明:9i新增,用於返回嵌套遊標。
declare
  type myis_refcursor is ref cursor;
  empcur myis_refcursor;
  cursor dept_cursor(no number) is select a.dname,cursor(select ename,sal from emp where deptno=a.deptno) from dept a where a.deptno=no;
  v_dname dept.dname%type;
  v_ename emp.ename%type;
  v_sal emp.sal%type;
begin
  open dept_cursor(&no);
  loop
    fetch dept_cursor into v_dname,empcur;
    exit when dept_cursor%notfound;
    loop
      fetch empcur into v_ename,v_sal;
      exit when empcur%notfound;
    end loop;
  end loop;
  close dept_cursor;
end;


3、遊標變量
說明:遊標變量是基於ref cursor類型所定義的變量,它實際上是指向內存地址的指針。
使用顯式遊標只能定義靜態遊標,而通過使用遊標變量可以在打開遊標時指定其對應的select語句,從而實現動態遊標。
結構:TYPE type_name is ref cursor [return return_type];
其中type_name是遊標變量類型名,return_type是一個記錄類型,這個記錄類型指定了遊標變量最終返回的查詢結果集類型,
可以是用戶自定義的記錄類型或%rowtype定義的記錄類型。


1)使用無返回類型的遊標變量
說明:如果不指定return子句,那麼打開遊標時可以指定的select語句。
declare
  v_col1 varchar2(20):='empno';
  v_col2 varchar2(20):='ename';
  v_tablename varchar2(20):='emp';
  v_cond varchar2(20):='deptno=10 and cnt>=5000';
  type ref_cursor_type is ref cursor; --定義
  rc ref_cursor_tye; --聲明
  v1 number(6);
  v2 varchar2(10);
begin
  open rc for select v_col1,v_col2 from v_tablename where v_cond; --用open for來打開一個查詢,需要注意的是它可以多次使用,用來打開不同的查詢。
  loop
    fetch rc into v1,v2;
    exit when rc%notfound;
    dbms_output.put_line('col1=' || v1 || ',col2=' || v2);
  end loop;
  close rc;
end;


2)使用有返回類型的遊標變量
說明:對於有返回類型的遊標變量,打開遊標open查詢列表必須與遊標的返回類型匹配,且列也要一一對應。
注意:在打開遊標,open查詢語句時不能使用for ypdate,在dml語句中也不能使用where current of,這與靜態遊標不同。
我們可以爲不同的查詢多次打開一個遊標變量,在重新打開這個遊標變量之前不必先關閉它,這與靜態遊標不同。
當爲一個不同的查詢再次打開該遊標時,前面的查詢就丟失。


例一(用rowtype定義記錄類型):
declare
  dno number:=20;
  type emp_cursor_type is ref cursor return emp%rowtype;  --%rowtype定義的記錄類型
  ec emp_cursor_type;
  er emp%rowtype;
begin
  open ec for select * from emp where deptno=dno; --
  loop
    fetch ec into er;
    exit when ec%not found;
    dbms_output.put_line('姓名' || er.ename ||  '工資' || er.sal);
  end loop;
  close ec;
end;


例二(自定義記錄類型):
declare
type t_coderecord is record(au_code article.au_code%type,ar_code article.ar_code%type);--自定義一個記錄類型
v_code t_coderecord;--聲明一個變量
type t_coderef is ref cursor return t_coderecord;
type t_coderef2 is ref cursor return v_code%type;
v_code t_coderef;


五、顯式遊標的三種循環策略LOOP循環、WHILE循環、FOR循環。
一個遊標打開後,必須執行一次fetch語句,遊標的屬性纔會起作用,檢查它的%found或%notfound屬性,屬性纔有值(ture\false),否則得到的結果是null。 


1、LOOP循環遊標
說明:loop循環開始,先執行fetch語句,緊跟接着exit when語句的%notfound判斷,然後再進行數據處理動作。
declare
  v_cnt number :=20;
  cursor emp_cursor is select ename,job,sal from emp where deptno=v_cnt;  --遊標的定義
  vname emp.ename%type;
  vsal emp.sal%type;
  vjob emp.job%type;
begin
  open emp_cursor;  --打開遊標
  loop
    fetch emp_cursor into vname,vjob,vsal;  --檢索一條記錄
    exit when emp_cursor%notfound;  
--exit when語句在fetch語句之後,這樣在最後一行被檢索後,emp_cursor%notfound變爲true,循環結束。exit when語句同時放在數據處理語句前,這樣做能確保不會重複處理同一行(檢索到的最後一行)。
    dbms_output.put_line('姓名' || vname || '崗頂' || vjob || '工資' || vsal);
  end loop;
  close emp_cursor;  --關閉遊標
end;


2、while循環遊標
說明:使用while 循環時,需要在循環之前進行一次fetch動作,而且數據處理動作必須放在循環體內的fetch方法之前,循環體內的fetch方法要放在最後。否則就會多處理一次,這一點也要非常的小心。
declare
  v_cnt number :=20;
  cursor emp_cursor is select ename,job,sal from emp where deptno=v_cnt;
  vname emp.ename%type;
  vsal emp.sal%type;
  vjob emp.job%type;
begin
  open emp_cursor;
  fetch emp_cursor into vname,vjob,vsal;
  while emp_cursor%found loop
    dbms_output.put_line('姓名' || vname || '崗頂' || vjob || '工資' || vsal);
    fetch emp_cursor into vname,vjob,vsal;
  end loop;
  close emp_cursor;
end;


3、FOR循環遊標
說明:使用遊標FOR循環時,Oracle會隱含的自動打開遊標、關閉遊標,並自動定義了一個記錄類型及聲明該類型的變量,並自動fetch數據到這個變量中。


1)、使用遊標FOR循環
declare
  v_cnt number :=3;
  cursor c_ec is select ename,hiredate from emp order by hiredate desc;
begin                     --開始遊標for循環,隱含地打開c_ec遊標
  for v_erec in c_ec loop   --v_erec這個變量無需在循環外進行聲明,無需爲其指定數據類型,for循環隱含地聲明瞭。
                          -- 一個隱含的fetch語句在這裏被執行
    dbms_output.put_line('姓名' || v_erec.ename || '工作時間' || v_erec.hiredate);
    exit when c_ec%rowcount=v_cnt;
                               -- 循環繼續前,一個隱含的c_ec%notfound被檢查
  end loop;--循環結束,c_ec遊標的一個隱含close操作被執行。
end;


2)、在遊標for循環中直接使用子查詢
說明:如果在使用for循環時不需要使用任何遊標屬性,那麼可以直接在遊標for循環中使用子查詢。
begin
  for emp_record in (select ename,sal from emp) loop
    dbms_output.put_line(emp_record.ename);
  end loop;
end;


綜上,for循環遊標的方法最高效簡潔安全,while循環最複雜,而實際使用中最常用的是loop循環。
另外,如果在update語句中使用current of cursor子句時,必須先聲明遊標的select語句中有for update of語句。


六、接收顯式遊標數據的數據類型選擇


1.使用普通變量接收遊標數據(上面的例子中已經普遍引用)
declare
  v_cnt number :=20;
  cursor emp_cursor is select ename,job,sal from emp where deptno=v_cnt;
  vname emp.ename%type;
  vsal emp.sal%type;
  vjob emp.job%type;
begin
  open emp_cursor;
  loop
    fetch emp_cursor into vname,vjob,vsal;
    exit when emp_cursor%notfound;
    dbms_output.put_line('姓名' || vname || '崗頂' || vjob || '工資' || vsal);
  end loop;
  close emp_cursor;
end;


2.使用PL/SQL記錄變量接收遊標數據:簡化單行數據處理
declare
  v_cnt number :=5;
  cursor ecur is select ename,sal from emp order by sal desc; --遊標
  erec ecur%rowtype; --定義聲明一個行級的PL/SQL記錄變量erec
begin
  open ecur;
  loop
    fetch ecur into erec;
    exit when ecur%notfound or ecur%rowcount>v_cnt;
    dbms_output.put_line('姓名' || erec.ename ||  '工資' || erec.sal);
  end loop;
  close ecur;
end;


3.使用PL/SQL集合變量接收遊標數據,簡化多行多列數據處理
declare
  v_cnt number :=5;
  cursor ec is select ename,sal from emp where lower(job)=lower(v_cnt); --遊標
  type eetype is table of ec%rowtype index by binary_intefer; --定義一個複合數據類型PL/SQL表變量eetype
  et eetype; --聲明etype
  i int;
begin
  open ec;
  loop
    i:=ec%rowcount+1;
    fetch ec into et(i);
    exit when ec%notfound;
    dbms_output.put_line('姓名' || et(i).ename ||  '工資' || et(i).sal);
  end loop;
  close ec;
end;


七、通過遊標更新、刪除表的數據
說明:必須要帶for update子句。
語法:cursor cursor_name(parameter_name datatype) is select_statement for update [of column_reference] [nowait]
其中,for update用於在遊標結果集加共享鎖。
當select語句引用多張表示,使用of子句可以確定哪些表要加鎖,沒有則select語句所引用的全部表加上鎖。
nowait用於指定不等待鎖,當其他會話已經鎖表之後,默認是當前會話一直等待釋放,但指定了NOWAIT後,
如果表已經被其它會話加鎖,則拋出異常並退出當前塊。
注意,爲了更新或刪除當前遊標行數據,必須在update或delete語句中引用where current of子句。
例子:
cursor c_name is select emp.sal,dept.deptno from emp,dept where emp.deptno=dept.deptno for update;--全部加共享鎖
cursor c_name is select emp.sal,dept.deptno from emp,dept where emp.deptno=dept.deptno for update of emp.deptno;--只在emp表加共享鎖
cursor c_name is select sal from emp for update nowait;


declare
  cursor emp_cursor is select ename,sal,deptno from emp for update;--默認所有表均加上共享鎖。
  dno int:=1001;
begin
  for emp_record in emp_cursor loop
    if emp_record.deptno=dno then
      update emp set sal=sal*1.1 where current of emp_cursor; --更新操作
      delete from emp where current of emp_cursor; --刪除操作
    end if;
  end loop;
end;


八、顯式遊標在開發中不多見的應用


2.通過遊標更新、刪除表的數據對於多表的情況(使用for子句在特定表上加共享鎖)
declare
  cursor emp_cursor is select a.dname,b.ename from dept a join emp b on a.deprno=b.deptno;
  name varchar2(20):='sales';
begin
  for emp_record in emp_cursor loop
    if emp_record.dname=name then
      delete from emp where current of emp_cursor;
    end if;
  end loop;
end;


3.使用fetch...bulk collect批量提取的用法
說明:批量提起所有數據
declare
  v_name varchar2(20):='clerk';
  cursor ec is select * from emp where job=v_name;
  type etype is table of emp%rowtype; --定義一個複合數據類型嵌套表etype
  et etype;
begin
  open ec;
  fetch ec bulk collect into et;
  close ec;
  for i in 1..et.count loop
    dbms_output.put_line('姓名' || et(i).ename || '工資' || et(i).sal);
  end loop;
end;


4.使用fetch...bulk collect批量提取 + limit子句限制提取行數的應用
declare
  v_cnt number:=4;
  cursor ec is select * from emp;
  type emp_array_type is varray(5) of emp%rowtype; --定義一個複合數據類型varray變成數組
  ea emp_array_type;
begin
  open ec;
  loop
    fetch ec bulk collect into ea limit v_cnt;
    for i in 1..ea.count loop
      dbms_output.put_line('姓名' || et(i).ename || '工資' || et(i).sal); --結果只會輸出4行
    end loop;
    exit when ec%notfound;
  end loop;
  close ec;
end;


5.遊標中保存着遊標的應用
declare
  v_dno number:=5010;
  cursor dept_cursor(no number) is select a.dname,cursor(select * from emp where deptno=a.deptno) from dept a where a.deptno=no;
  type ref_cursor_type is ref cursor;
  ec ref_cursor_type;
  er emp%rowtype;
  vdname dept.dname%type;
begin
  open dept_cursor(v_dno);
  loop
    fetch dept_cursor into vdname,ec;
    exit when dept_cursor%notfound;
    dbms_output.put_line('部門' || vdname);
    loop
      fetch ec into er;
      exit when ec%notfound;
      dbms_output.put_line('僱員' || er.ename || '崗位' || er.job); --一個部門有多名員工
    end loop;
  end loop;
  close dept_cursor;
end;


九、隱式遊標
1、介紹
(1)、顯式遊標僅僅是用來控制返回多行的select語句。而隱式遊標是指向處理所有的sql語句的環境區域的指針,隱式遊標也叫sql遊標。
(2)、與顯式遊標不同的是,sql遊標不能通過專門的命令打開或關閉。pl/sql隱式地打開sql遊標,並在它內部處理sql,然後關閉它。
(3)、sql遊標用來處理insert、update、delete、以及返回一行的select...into語句。一個sql遊標不管是打開還是關閉,open、fetch和close命令都不能操作它。


2、隱式遊標的四個屬性
(1)、%found
說明:當使用insert,delete,update語句處理一行或多行,或執行select into 語句返回一行是,%found屬性返回true,否則爲false.
注意:當select into語句返回多行數據報too_meny_rows異常時,%found並不會返回true;select into語句返回0行數據報no_data_found異常時,%found也不會返回false,因爲已經轉到異常處理部分。
(2)、%notfound
說明:與%found屬性相反。
(3)、%isopen
說明:在執行dml語句之前後,Oracle會自動隱含式的打開和關閉sql遊標,所以%isopen總爲false.
(4)、%rowcount
說明:該屬性返回執行insert,delete,update語句返回的行數,或返回執行select into語句時查詢出的行數。如果insert,delete,update,select into語句返回行數爲0,則%rowcount屬性返回0。
注意:如果select into語句返回多行,則產生too_meny_rows異常,會轉掃異常處理部分,而不會去判斷%rowcount.


3、應用例子
(1)、%notfound
begin
  update auths set entry_date_time=sysdate where author_code='A00017';
--如果update語句中修改的行不存在(sql%notfound返回值爲true)
--則向auths表中插入一行
  if sql%notfound then
    insert into auths(author_code,name) values('A00017','照');
  end if;
end;


(1)、%rowcount
begin
  update auths set entry_date_time=sysdate where author_code='A00017';
--如果update語句中修改的行不存在(sql%rowcount=0)
--則向auths表中插入一行
  if sql%rowcount=0 then
    insert into auths(author_code,name) values('A00017','照');
  end if;
end;


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章