在PL/SQL程序中,對於處理多行記錄的事務常常使用遊標來實現
sql
爲了處理SQL語句,Oracle必須分配一片叫上下文(Context area)的區域來處理所必須的信息,其中包括要處理的行的數目,一個指向語句被分析之後的表示形式和指針以及查詢的活動集(active set)。
數據庫
遊標是一個指向上下文的句柄(handle)或指針。經過遊標,PL/SQL能夠控制上下文區和處理語句時上下文區會發生些什麼事情。
express
對於不一樣的SQL語句,遊標的使用狀況不一樣函數
SQL語句 | 遊標 |
非查詢語句 | 隱式的 |
結果是單行的查詢語句 | 隱式的或顯示的 |
結果是多行的查詢語句 | 顯示的 |
顯示遊標處理須要四個PL/SQL步驟:
oop
定義遊標: 就是定義一個遊標名,以及與其相對應的SELECT語句。學習
格式: cursor cursor_name[(parameter[,parameter]...)] is select_statement;fetch
遊標參數只能爲輸入參數,其格式爲spa
parameter_name in datatype[{:=|DEFAULT} expression]指針
注意:
code
在指定數據類型時,不能使用長度約束。如NUMBER(4)、CHAR(10)等都是錯誤的。
定義遊標時,不能有into自居。
打開遊標: 就是執行遊標所對應的SELECT 語句,將其查詢結果放入工做區,而且指針指向工做區的首部,表示遊標結果集合。若是遊標查詢語句中帶有FOR UPDATE選項,open語句還將鎖定數據庫表中游標結果集合對應的數據行。
OPEN cursor_name[([parameter=>]value[,[parameter=>]value]...)]
在向遊標傳遞參數時,可使用與函數參數相同的傳值方法,即位置表示法和名稱表示法。PL/SQL程序不能用OPEN語句重複打開一個遊標。
提取遊標數據: 就是檢索結果集中的數據行,放入指定的輸出變量中。
格式:
FETCH cursor_name into {variable_list|record_variable};
對該記錄進行處理;
繼續處理,直到活動集合中沒有記錄;
關閉遊標: 當提取和處理完遊標結果集合數據後,應及時關閉遊標,以釋放該遊標所佔用的系統資源,並使該遊標的工做區變成無效,不能再使用FETCH語句取其中數據。關閉後的遊標可使用OPEN語句從新打開。
格式:
close cursor;
實例:
declare v_sal emp.sal%type; v_empno emp.empno%type; cursor emp_cursor is select sal,empno from emp where deptno=30; begin open emp_cursor;--打開遊標 fetch emp_cursor into v_sal,v_empno;--提取遊標 while emp_cursor%found loop dbms_output.put_line('僱員編號:'||v_empno||',salary:'||v_sal); fetch emp_cursor into v_sal,v_empno;--提取遊標 end loop; close emp_cursor;--關閉遊標 end;
頭腦風暴:
假如咱們的遊標查詢不少個字段的值,那麼fetch emp_cursor into v_1,v_2,....是否是很麻煩呢?想象咱們的複合數據類型。
顯示遊標的屬性
遊標屬性 | 值類型 | 說明 |
%FOUND | 布爾型 | 當最近一次讀取記錄時成功返回,則值爲true |
%NOTFOUND | 布爾型 | 與%FOUND相反 |
%ISOPEN | 布爾型 | 當遊標已打開時返回true |
%ROWCOUNT | 數字型 | 返回已從遊標中讀取的記錄數 |
隱式遊標和顯示遊標有所差別,它雖然沒有顯示遊標同樣的可操做性,可是在實際的工做當中也常常用到。
每當容許SELECT或DML語句時,PL/SQL會打開一個隱式的遊標。隱式遊標不受用戶的控制,這一點和顯示遊標有明顯的不一樣。下面列出隱式遊標和顯示遊標的不一樣處。
隱式遊標有PL/SQL自動管理;
隱式遊標的默認名稱是SQL;
SELECT 或DML操做產生隱式遊標;
隱式遊標的屬性值始終是最新執行的SQL語句的;
實例:
--隱式遊標begin --------------- --業務說明: 跟新指定員工編號員工的工資+11;若是存在該員工更新數據 -- 若是不存在,打印查無此人。 begin update emp set sal=sal+11 where empno=7788; if sql%notfound then dbms_output.put_line('查無此人'); end if; --------------- --隱式遊標end
隱式遊標的屬性
屬性名 | 值類型 | 說明 |
%ISOPEN | true/false | 該屬性返回false,由Oracle本身控制 |
%FOUND | true/false | 此屬性反應DML操做是否影響到了數據,SELECT INTO語句返回數據爲true |
%NOTFOUND | true/false | 與%FOUND相反 |
%ROWCOUNT | number | 該屬性返回的是DML操做影響的行數。 |
案例:把僱員工資低於3000的調整到3000(loop)
----------- --業務說明: 把僱員工資低於3000的調整到3000 ----------- declare v_sal emp.sal%type;--僱員工資,用於判斷工資是否大於3000 v_empno emp.empno%type;--僱員編號,用於更新工資 cursor emp_cursor is select sal,empno from emp;--定義遊標 begin --打開遊標 open emp_cursor; loop --獲取數據 fetch emp_cursor into v_sal,v_empno; exit when emp_cursor%notfound; if v_sal<3000 then update emp set sal=3000 where empno=v_empno; dbms_output.put_line('僱員編號爲:'||v_empno||'執行了更新操做'); commit; end if; end loop; close emp_cursor;--關閉遊標 end;
咱們能夠對以上案例進行修改,通常狀況下使用cursor時是與for循環配置實用的:(for)
declare type emp_sal_empbno_record is record( sal emp.sal%type,--僱員工資,這兒須要注意,多個字段,須要用,隔開 empno emp.empno%type--僱員編號 ); cursor emp_cursor is select sal,empno from emp;--定義遊標 begin --for循環自動打開遊標,獲取每行的數據,關閉遊標 for emp_sal_empbno_record in emp_cursor loop if emp_sal_empbno_record.sal<3000 then update emp set sal=3000 where empno=emp_sal_empbno_record.empno; dbms_output.put_line('僱員編號爲:'||emp_sal_empbno_record.empno||'執行了更新操做'); commit; end if; end loop; end;
分析: 從上面的代碼中能夠看到,for循環中,沒有open cursor,fetch cursor,close cursor 可是這些都確切的發生了。
for循環幫咱們在其內部處理了這些操做,簡化了用戶的操做,通常狀況下for循環+cursor 堪稱完美。(特殊狀況除外),
這兒還有一個須要注意,emp_sal_empbno_record 是記錄類型,不是記錄類型的變量。
案例: 工資在0-3000 的加薪5%, 3000-4000 加薪3%,4000-5000 加薪2% 5000-n 加薪1%(while)
declare type emp_record is record( sal emp.sal%type, empno emp.empno%type );--定義一個記錄類型 v_emp_record emp_record;--定義記錄類型的變量 v_temp number(4,2);--加薪比例 cursor emp_cursor is select sal,empno from emp;--定義遊標 begin open emp_cursor;--打開遊標 fetch emp_cursor into v_emp_record;--提取數據 while emp_cursor%found loop if v_emp_record.sal<3000 then v_temp:=0.05;--須要注意賦值操做使用:= elsif v_emp_record.sal<4000 then v_temp:=0.03; elsif v_emp_record.sal<5000 then v_temp:=0.02; else v_temp:=0.01; end if; --記住 if語句結束後必定要有end if update emp set sal=sal*(1+v_temp) where empno=v_emp_record.empno; commit; --提取數據,必須在while循環內部提取數據,否則就是死循環了 fetch emp_cursor into v_emp_record; end loop; end;
注意: 兩個案例,對應三種不一樣的循環處理遊標,你們作下對比。
對上面的案例進行改進:
declare v_temp number(4,2);--加薪比例 cursor emp_cursor is select sal,empno from emp;--定義遊標 begin --從遊標中取數據,自動打開,關閉,判讀是否還有數據 for v_emp in emp_cursor loop if v_emp.sal<3000 then v_temp:=0.005; elsif v_emp.sal<4000 then v_temp:=0.03; elsif v_emp.sal<5000 then v_temp:=0.02; else v_temp:=0.01; end if; update emp set sal=sal*(1+v_temp) where empno=v_emp.empno; commit; end loop; end;
遊標中一般使用fetch.... into ... 語句取數據,這種方式是單條數據提取,在數據量很大的狀況下執行效率不是很理想。而FETCH ... BULK COLLECT INTO 語句能夠批量提取數據,
在數據量大的狀況下它的執行效率比單條提取數據高。
declare cursor emp_cursor is select * from emp; type emp_tab is table of emp%rowtype; v_emp_tab emp_tab; begin open emp_cursor;--打開遊標 loop fetch emp_cursor bulk collect into v_emp_tab limit 5;--使用limit顯示一次取得記錄數 for i in 1 .. v_emp_tab.count loop dbms_output.put_line('僱員編號:'||v_emp_tab(i).empno||',僱員姓名:'||v_emp_tab(i).ename); end loop; exit when emp_cursor%notfound; end loop; end;
fetch emp_cursor bulk collect into v_emp_tab limit 5; 一次取5條記錄到集合中,減小了取的次數。
---------帶參數的遊標begin------------- --業務說明: 查詢出某部門中僱員名中包含C的員工的姓名 declare c_ename emp.ename%type:='%C%'; v_ename emp.ename%type;--僱員姓名 cursor emp_cursor( name varchar2,--僱員名,模糊查詢 dtno number--部門編號 ) is select ename from emp where ename like name and deptno=dtno; begin open emp_cursor(c_ename,10); loop fetch emp_cursor into v_ename; exit when emp_cursor%NOTFOUND; dbms_output.put_line(v_ename||'的名字包含C'); end loop; close emp_cursor; end; ---------帶參數的遊標end---------------
注意: 申明遊標變量是,參數的類型,不能使用長度限制:
錯誤的寫法: varchar2(50),number(7,2)