1.概述
上文PLSQL學習中提到的知識,可以發現,基本都可以通過Java等語言實現,而爲了實現程序的可移植性,實際開發工作中我們也是如此做的。
那麼PLSQL的重點是什麼呢?接下來我們來介紹遊標cursor的概念。
之前提到,在不使用顯式遊標的情況下,PLSQL的中select語句只能返回一條記錄,那麼我們如果想要返回多條記錄,或者遍歷整個結果集該如何實現呢。
這裏我們可以通過cursor實現。
1.1 cursor是什麼
cursor是光標,遊標的意思。比如我們的鼠標的光標就是cursor。
那麼在數據庫中cursor是什麼呢?
當運行DML(select,update,insert,delete)語句時,ORACLE會在內存中爲其分配緩衝區(Context Area),PL/SQL打開一個內建遊標並處理結果,遊標是維護查詢結果的內存中的一個區域。
遊標在運行DML語句時打開,完成後關閉。
通俗的說:
我們知道,select語句會產生一個結果集,而遊標是指在這個結果集上的第一條記錄的一個指針。
而我們每次取出(fetch)一條記錄,cursor就會自動指向下一條記錄。
如果學過Java的話,可以這樣理解,cursor類似於Java中的迭代器iterator。
1.2 cursor作用
基於以上的理解,cursor自然就被用於:取出每一條記錄,遍歷結果集。
1.3 cursor的四個屬性
cursor有如下四個屬性:
- %isopen:布爾類型。判斷遊標是否打開,打開爲true。對於隱式遊標而言,這個值總是false,因爲隱式遊標在DML語句執行時打開,結束時就立即關閉。
%found:布爾類型。在執行任何DML語句前SQL%FOUND和SQL%NOTFOUND的值都是NULL。在執行DML語句後,SQL%FOUND的屬性值將是:
. TRUE :INSERT
. TRUE :DELETE和UPDATE,至少有一行被DELETE或UPDATE.
. TRUE :SELECT INTO至少返回一行%notfound:布爾類型。當SQL%FOUND爲TRUE時,SQL%NOTFOUND爲FALSE
- %rowcount:數值類型。在執行任何DML語句之前,SQL%ROWCOUNT的值都是NULL,對於SELECT INTO語句,如果執行成功,SQL%ROWCOUNT的值爲1,如果沒有成功,SQL%ROWCOUNT的值爲0,同時產生一個異常NO_DATA_FOUND。
注:PLSQL中的布爾類型的值爲null、true、false。
2.顯式遊標和隱式遊標
在遊標聲明之前,我們來看下顯式遊標和隱式遊標。
2.1 隱式遊標
事實上,當我們在PLSQL中進行非查詢(或者返回單條記錄的查詢)語句,如update、delete、insert等時,ORACLE 系統會自動地爲這些操作設置遊標並創建其工作區,並且隱式遊標的名字爲SQL,由ORACLE 系統定義。
對於隱式遊標的操作,如定義、打開、取值及關閉操作,都由ORACLE 系統自動地完成,無需用戶進行處理。
PL/SQL管理隱式遊標,當查詢開始時隱式遊標打開,查詢結束時隱式遊標自動關閉。
用戶只能通過隱式遊標的相關屬性,來完成相應的操作。在隱式遊標的工作區中,所存放的數據是與用戶自定義的顯示遊標無關的、最新處理的一條SQL 語句所包含的數據。
簡單實例
對於如下一張表ljb_test,更新每個人的薪水:
set serveroutput on;
begin
update ljb_test set salary = salary + 1;
dbms_output.put_line('更新了'||SQL%rowcount||'行數據'); --must before commit
commit;
end;
/
結果如下:
這裏需要指出的是:關於隱式遊標的屬性操作,必須在commit之前,可以嘗試把打印輸出放在commit之後,得到的結果是0。這裏之所以結果加了2,是因爲我實驗了兩次。
2.2 顯式遊標
當查詢返回結果超過一行時,就需要一個顯式遊標,此時用戶不能使用select into語句。
顯式遊標在PL/SQL塊的聲明部分聲明,在執行部分或異常處理部分打開,取數據,關閉。
這裏要做一個聲明,我們所說的遊標通常是指顯式遊標,而顯式遊標需要被聲明。
2.2.1 cursor的聲明、打開、關閉、從遊標提取數據
聲明遊標
CURSOR cursor_name IS select_statement;
打開遊標
OPEN cursor_name;
關閉遊標
CLOSE cursor_name;
從遊標提取數據
從遊標得到一行數據使用FETCH命令。每一次提取數據後,遊標都指向結果集的下一行。
語法如下:
FETCH cursor_name INTO variable[,variable,...]
如:
set serveroutput on;
declare
cursor c is select * from ljb_test; --1.聲明遊標的時候Oracle不會從數據庫中取數據
v_test c%rowtype;
begin
open c; --2.打開遊標,此時從數據庫中取數據,並把結果集放在內存中
fetch c into v_test; --3.獲取數據,fetch的時候遊標自動往下移動一格
dbms_output.put_line(v_test.name);
fetch c into v_test;
dbms_output.put_line(v_test.name);
close c; --4.關閉遊標,清掉內存。成對編程
end;
/
命令行運行,結果如下:
2.2.2 遍歷結果集
如果我們想要遍歷整個測試表,顯然需要通過循環來進行。
正常情況下如果遵循循環遍歷遊標應當遵從以下步驟:
1、打開遊標
2、開始循環
3、從遊標中取值
4、檢查那一行被返回
5、處理
6、關閉循環
7、關閉遊標
事實上,我們確實可以通過該方式實現,即通過while循環和do while循環。
但是,for循環卻不無需這麼複雜,這裏我們重點介紹for循環。
上文提到,PLSQL中有三種循環,這裏需要指出的是,使用遊標遍歷時,最簡單最穩定的就是for循環,但另外兩種循環仍然會做簡單介紹。如下:
2.2.2.1 for循環遍歷
FOR循環的遊標按照正常的聲明方式聲明,但是不需要顯式的打開、關閉、取數據,測試數據的存在、定義存放數據的變量等等。
for循環是最簡單也是最不容易出錯的方式,推薦採用for循環遍歷。
set serveroutput on;
declare
cursor c is select * from ljb_test;
--無需在此聲明變量v_test
begin
--無需顯式打開遊標
for v_test in c loop
--無需顯式fetch
dbms_output.put_line(c%rowcount||'--'||v_test.name);
end loop;
--無需顯式關閉遊標
end;
/
九條記錄全部被打印,運行結果如下:
2.2.2.2 while循環遍歷
declare
cursor c is select * from ljb_test; --聲明遊標的時候Oracle不會從數據庫中取數據
v_test c%rowtype;
begin
open c; --打開遊標,此時從數據庫中取數據,並把結果集放在內存中
fetch c into v_test; --獲取數據,fetch的時候遊標自動往下移動一格
while c%found loop
dbms_output.put_line(c%rowcount||'--'||v_test.name);
fetch c into v_test;
end loop;
close c; --關閉遊標,清掉內存。成對編程
end;
/
九條記錄被遍歷,結果如下:
如果我們把fetch語句和打印語句調換一下位置,結果會怎樣?
可以看到,第一個記錄被跳過取,最後一個打印兩邊。原因是,我們第一次打印前fetch了兩次,而最後一個雖然c無法fetch到數據,但是上一個的c%fetch仍然是true。
所以,採用while循環一定注意fetch和打印的順序。do while類似。
2.2.2.3 do while循環遍歷
declare
cursor c is select * from ljb_test; --聲明遊標的時候Oracle不會從數據庫中取數據
v_test c%rowtype;
begin
open c;
loop
fetch c into v_test;
exit when(c%notfound);
dbms_output.put_line(c%rowcount||'--'||v_test.name); --如果順序反了,就會最後一條記錄打印兩次
end loop;
close c; --關閉遊標,清掉內存。成對編程
end;
/
2.2.3 含參遊標
類似於函數,我們可以將參數傳遞給遊標並在查詢中使用。
CURSOR cursor_name[(parameter[,parameter],...)]
IS select_statement;
參數定義方式爲:
Parameter_name [IN] data_type[{:=|DEFAULT} value]
需要注意的是:遊標只能接受傳遞的值,而不能返回值。參數只定義數據類型,沒有大小。
declare
cursor c(v_dep ljb_test.dep%type, v_salary ljb_test.salary%type)
is select * from ljb_test where dep = v_dep and salary = v_salary;
begin
for v_temp in c(3,4000) loop
dbms_output.put_line(v_temp.name);
end loop;
end;
/
結果如下:
2.4 可更新遊標
declare
cursor c is select * from ljb_test for update; --添加for update即可
begin
for v_temp in c loop
if(v_temp.salary<3500) then
update ljb_test set salary = salary * 2 where current of c; --更新條件
elsif(v_temp = 5000) then
delete from ljb_test where current of c; --更新條件
end if;
end loop;
commit;
end;
/