今天簡單的總結一下PL/SQL中cursor(光標/游標)的用法。
cursor分類:
--顯式cursor
--靜態cursor |
| --隱式cursor
cursor | |
--強類型(限制),規定返回類型
--動態cursor --ref cursor |
--弱類型(非限制),不規定返回類型,可以獲取任何結果集
一、顯式cursor
顯式是相對與隱式cursor而言的,就是有一個明確的聲明的cursor。顯式游標的聲明類似如下:
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 number;
begin
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;
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 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;
begin
update zrp SET str = 'updateD' where str like '%D%';
ifSQL%ROWCOUNT= 0 then
insert into zrp values ('1111111');
end if;
end;
/
PL/SQL procedure successfully completed
SQL> select * from zrp;
STR
----------
updateD
ABCXEFG
ABCYEFG
updateD
ABCZEFG
begin
update zrp SET str = 'updateD' where str like '%S%';
ifSQL%ROWCOUNT= 0 THEN
insert into zrp values ('0000000');
end if;
end;
/
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;
set serveroutput on
declare
type gsm_rec is record(
gsmno varchar2(11),
status varchar2(1),
price number(8,2));
my_rec gsm_rec;
type app_ref_cur_type is ref cursor /*return gsm_rec可加可不加,不影響執行結果*/;
my_cur app_ref_cur_type;
begin
open my_cur for select gsmno,status,price from gsm_resource where store_id='SD.JN.01';
fetch my_cur into my_rec;
while my_cur%found loop
dbms_output.put_line(my_rec.gsmno||'#'||my_rec.status||'#'||my_rec.price);
fetch my_cur into my_rec;
end loop;
close my_cur;
end;
/
13905310001#0#200
13905315005#1#500
PL/SQL procedure successfully completed
static cursor與ref cursor還存在下面一些區別:
1)PL/SQL靜態光標不能返回到客戶端,只有PL/SQL才能利用它。ref光標能夠被返回到客戶端,這就是從Oracle的存儲過程返回結果集的方式。
2)PL/SQL靜態光標可以是全局的,而ref光標則不是。 也就是說,不能在包說明或包體中的過程或函數之外定義ref光標。 只能在定義ref光標的過程中處理它,或返回到客戶端應用程序。
3)ref光標可以從子例程傳遞到子例程,而光標則不能。 為了共享靜態光標,必須在包說明或包體中把它定義為全局光標。 因為使用全局變量通常不是一種很好的編碼習慣,因此可以用ref光標來共享PL/SQL中的光標,無需混合使用全局變量。
4)使用靜態光標--通過靜態SQL(但不用ref光標)--比使用ref光標效率高,而ref光標的使用僅限于這幾種情況:把結果集返回給客戶端;在多個子例程之間共享光標;沒有其他有效的方法來達到你的目標時,則使用ref光標,正如必須用動態SQL時那樣;
注:首先考慮使用靜態SQL,只有絕對必須使用ref光標時才使用ref光標,也有人建議盡量使用隱式游標,避免編寫附加的游標控制代碼(聲明,打開,獲取,關閉),也不需要聲明變量來保存從游標中獲取的數據。
四、游標屬性
%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 來確定循環的退出條件。
********************************轉自:http://hi.baidu.com/edeed **********************************
Oracle動態游標中,游標變量在定義時不指定固定的SQL語句,在Open時才指定SQL語句。下面是自己的一些實踐筆記:
【1】動態游標的2中不同寫法
create or replace procedure pro_set_loop
(
i_id varchar2,
o_result_code out number,
o_result_msg out varchar2
)
as
v_bookname varchar2(100);
v_id number;
type ref_cursor_type is REF CURSOR;
cursor_select ref_cursor_type;
select_cname varchar2(1000);
begin
select_cname:='select bookname from book where id =:1'; --1
Open cursor_select For select_cname using i_id; --2
loop
Fetch cursor_select into v_bookname;
exit when cursor_select%notfound;
update book set price = '25' where bookname = v_bookname;
end loop;
Close cursor_select;
end;
備注:上面1,2兩句也可以寫成:
select_cname:='select bookname from book where id = '||i_id;
Open cursor_select For select_cname;
【2】動態游標返回結果集給客戶端
返回結果集給客戶端,可以通過2中方式來實現,一是oracle存儲過程,另外一個是oracle函數。由于oracle存儲過程沒有返回值,它的所有返回值都是通過out參數來替代的,列表同樣也不例外,對于集合的返回,能用一般的參數,必須要用pagkage來實現,oracle函數也是這樣。
建包:
create or replace package types
as
type ref_cursor is ref cursor;
end;

oracle存儲過程:
create or replace procedure get_book_pro
(
i_id number,
o_bookname out types.ref_cursor
)
as
begin
open o_bookname for select * from book where id = i_id;
end get_book_pro;
oracle 函數:
create or replace function get_book_func
(
i_id in number
)
return types.ref_cursor
as
o_bookname types.ref_cursor;
begin
open o_bookname for select * from book where id = i_id;
return o_bookname;
end get_book_func;
測試SQL:
create table book
(
id number,
bookname varchar2(100),
price varchar2(100)
);
insert into book(1,'dephi','100');
insert into book(2,'c','200');
insert into book(3,'c++','300');
insert into book(4,'java','400');
insert into book(5,'c#','500');
insert into book(6,'shell','600');
insert into book(7,'vb','700');
insert into book(8,'plsql','800');

PL/SQL procedure successfully completed

SQL>select * from book;
1 1 dephi 100
2 2 c 200
3 3 c++ 300
4 4 java 400
5 5 c# 500
6 6 shell 600
7 7 vb 700
8 8 vj 800
9 9 plsql 900
下面代碼就是調用oracle存儲過程或函數并返回結果集:
package J4;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;


public class TestOracle
{
public static final String URL = "jdbc:oracle:thin:@10.40.152.186:1521:zxin";

public static final String DRIVER = "oracle.jdbc.driver.OracleDriver";

public static final String USER_NAME = "wap";

public static final String PASSWORD = "wap";

private boolean useOracleQuery = false;
private String oracleQuery_func = "{?= call get_book_func(?)}";--oracle函數聲明,一個入參一個出參。
private String oracleQuery_pro = "{call get_book_pro(?,?) }";--oracle存儲過程聲明,一個入參一個出參。


public void findStored_ref(int id)
{

try
{
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USER_NAME,PASSWORD);
String query = useOracleQuery ? oracleQuery_pro : oracleQuery_func;

if(useOracleQuery)
{
//oracle動態游標在存儲過程中的運用
System.out.println("--------ref cursor in proc--------");
CallableStatement stmt = conn.prepareCall(query);
stmt.setInt(1,id);--存儲過程入參
stmt.registerOutParameter(2,oracle.jdbc.OracleTypes.CURSOR);--存儲過程出參
stmt.execute();
ResultSet rs = (ResultSet) stmt.getObject(2);--注意是getObject(2)

while (rs.next())
{
System.out.print(rs.getString(1)+" ");
System.out.print(rs.getString(2)+" ");
System.out.println(rs.getString(3)+" ");
}
stmt.close();
conn.close();
}

else
{
//oracle動態游標在函數中的運用
System.out.println("--------ref cursor in func--------");
CallableStatement stmt = conn.prepareCall(query);
stmt.registerOutParameter(1,oracle.jdbc.OracleTypes.CURSOR);--函數出參
stmt.setInt(2,id);--函數入參
stmt.execute();
ResultSet rs = (ResultSet) stmt.getObject(1); --注意是getObject(1)

while (rs.next())
{
System.out.print(rs.getString(1)+" ");
System.out.print(rs.getString(2)+" ");
System.out.println(rs.getString(3));
}
stmt.close();
conn.close();
}

} catch (ClassNotFoundException e)
{
e.printStackTrace();

} catch (SQLException e)
{
e.printStackTrace();
}
}

public static void main(String[] args)
{
TestOracle test = new TestOracle();
test.findStored_ref(1);--實參為id = 1
}

}

結果:
當private boolean useOracleQuery = true;時,程序打?。?br />

--------ref cursor in proc--------
1 dephi 100

當private boolean useOracleQuery = false;時,程序打?。?br />
------ref cursor in func--------
1 dephi 100

請選擇國家...
中國China |
+86 |
|
中國香港China Hong Kong |
+852 |
|
中國澳門China Macao |
+853 |
|
中國臺灣China Taiwan |
+886 |
|
阿爾巴尼亞Albania |
+355 |
|
阿爾及利亞Algeria |
+213 |
|
阿富汗Afghanistan |
+93 |
|
阿根廷Argentina |
+54 |
|
阿拉伯聯合酋長國United Arab Emirates |
+971 |
|
阿塞拜疆Azerbaijan |
+994 |
|
埃及Egypt |
+20 |
|
埃塞俄比亞Ethiopia |
+251 |
|
愛爾蘭Ireland |
+353 |
|
愛沙尼亞Estonia |
+372 |
|
安道爾共和國Andorra |
+376 |
|
安哥拉Angola |
+244 |
|
安圭拉島Anguilla |
+1264 |
|
安提瓜和巴布達Antigua and Barbuda |
+1268 |
|
奧地利Austria |
+43 |
|
澳大利亞Australia |
+61 |
|
巴巴多斯Barbados |
+1246 |
|
巴布亞新幾內亞Papua New Cuinea |
+675 |
|
巴哈馬Bahamas |
+1242 |
|
巴基斯坦Pakistan |
+92 |
|
巴拉圭Paraguay |
+595 |
|
巴林Bahrain |
+973 |
|
巴拿馬Panama |
+507 |
|
巴西Brazil |
+55 |
|
白俄羅斯Belarus |
+375 |
|
百慕大群島Bermuda Is. |
+1441 |
|
保加利亞Bulgaria |
+359 |
|
貝寧Benin |
+229 |
|
比利時Belgium |
+32 |
|
冰島Iceland |
+354 |
|
波多黎各Puerto Rico |
+1787 |
|
波蘭Poland |
+48 |
|
玻利維亞Bolivia |
+591 |
|
伯利茲Belize |
+501 |
|
布基納法索Burkina-faso |
+226 |
|
布隆迪Burundi |
+257 |
|
朝鮮North Korea |
+850 |
|
丹麥Denmark |
+45 |
|
德國Germany |
+49 |
|
多哥Togo |
+228 |
|
多米尼加共和國Dominica Rep. |
+1890 |
|
俄羅斯Russia |
+7 |
|
厄瓜多爾Ecuador |
+593 |
|
法國France |
+33 |
|
法屬玻利尼西亞French Polynesia |
+689 |
|
法屬圭亞那French Guiana |
+594 |
|
菲律賓Philippines |
+63 |
|
斐濟Fiji |
+679 |
|
芬蘭Finland |
+358 |
|
岡比亞Gambia |
+220 |
|
剛果Congo |
+242 |
|
哥倫比亞Colombia |
+57 |
|
哥斯達黎加Costa Rica |
+506 |
|
格林納達Grenada |
+1473 |
|
古巴Cuba |
+53 |
|
關島Guam |
+1671 |
|
圭亞那Guyana |
+592 |
|
海地Haiti |
+509 |
|
韓國Korea |
+82 |
|
荷蘭Netherlands |
+31 |
|
荷屬安的列斯Netheriands Antilles |
+599 |
|
洪都拉斯Honduras |
+504 |
|
吉布提Djibouti |
+253 |
|
幾內亞Guinea |
+224 |
|
加拿大Canada |
+1 |
|
加納Ghana |
+233 |
|
加蓬Gabon |
+241 |
|
柬埔寨Kampuchea (Cambodia ) |
+855 |
|
捷克共和國Czech Republic |
+420 |
|
津巴布韋Zimbabwe |
+263 |
|
喀麥隆Cameroon |
+237 |
|
卡塔爾Qatar |
+974 |
|
開曼群島Cayman Is. |
+1345 |
|
科威特Kuwait |
+965 |
|
肯尼亞Kenya |
+254 |
|
庫克群島Cook Is. |
+682 |
|
拉脫維亞Latvia |
+371 |
|
老撾Laos |
+856 |
|
黎巴嫩Lebanon |
+961 |
|
立陶宛Lithuania |
+370 |
|
利比里亞Liberia |
+231 |
|
利比亞Libya |
+218 |
|
留尼旺Reunion |
+262 |
|
盧森堡Luxembourg |
+352 |
|
羅馬尼亞Romania |
+40 |
|
馬達加斯加Madagascar |
+261 |
|
馬爾代夫Maldives |
+960 |
|
馬耳他Malta |
+356 |
|
馬拉維Malawi |
+265 |
|
馬來西亞Malaysia |
+60 |
|
馬里Mali |
+223 |
|
馬提尼克Martinique |
+596 |
|
毛里求斯Mauritius |
+230 |
|
美國United States of America |
+1 |
|
蒙古Mongolia |
+976 |
|
蒙特塞拉特島Montserrat Is |
+1664 |
|
孟加拉Bangladesh |
+880 |
|
秘魯Peru |
+51 |
|
緬甸Burma |
+95 |
|
摩爾多瓦Moldova |
+373 |
|
摩洛哥Morocco |
+212 |
|
摩納哥Monaco |
+377 |
|
莫桑比克Mozambique |
+258 |
|
墨西哥Mexico |
+52 |
|
納米比亞Namibia |
+264 |
|
南非South Africa |
+27 |
|
瑙魯Nauru |
+674 |
|
尼泊爾Nepal |
+977 |
|
尼加拉瓜Nicaragua |
+505 |
|
尼日爾Niger |
+227 |
|
尼日利亞Nigeria |
+234 |
|
挪威Norway |
+47 |
|
葡萄牙Portugal |
+351 |
|
喬治亞Georgia |
+995 |
|
日本Japan |
+81 |
|
瑞典Sweden |
+46 |
|
瑞士Switzerland |
+41 |
|
薩爾瓦多EI Salvador |
+503 |
|
薩摩亞Samoa Western |
+685 |
|
塞拉利昂Sierra Leone |
+232 |
|
塞內加爾Senegal |
+221 |
|
塞浦路斯Cyprus |
+357 |
|
塞舌爾Seychelles |
+248 |
|
沙特阿拉伯Saudi Arabia |
+966 |
|
圣多美和普林西比Sao Tome and Principe |
+239 |
|
圣盧西亞St.Lucia |
+1758 |
|
圣馬力諾San Marino |
+378 |
|
圣文森特St.Vincent |
+1784 |
|
斯里蘭卡Sri Lanka |
+94 |
|
斯洛伐克Slovakia |
+421 |
|
斯洛文尼亞Slovenia |
+386 |
|
斯威士蘭Swaziland |
+268 |
|
蘇丹Sudan |
+249 |
|
蘇里南Suriname |
+597 |
|
所羅門群島Solomon Is |
+677 |
|
索馬里Somali |
+252 |
|
塔吉克斯坦Tajikstan |
+992 |
|
泰國Thailand |
+66 |
|
坦桑尼亞Tanzania |
+255 |
|
湯加Tonga |
+676 |
|
特立尼達和多巴哥Trinidad and Tobago |
+1868 |
|
土耳其Turkey |
+90 |
|
土庫曼斯坦Turkmenistan |
+993 |
|
危地馬拉Guatemala |
+502 |
|
委內瑞拉Venezuela |
+58 |
|
文萊Brunei |
+673 |
|
烏干達Uganda |
+256 |
|
烏克蘭Ukraine |
+380 |
|
烏拉圭Uruguay |
+598 |
|
烏茲別克斯坦Uzbekistan |
+998 |
|
西班牙Spain |
+34 |
|
希臘Greece |
+30 |
|
新加坡Singapore |
+65 |
|
新西蘭New Zealand |
+64 |
|
匈牙利Hungary |
+36 |
|
敘利亞Syria |
+963 |
|
也門Yemen |
+967 |
|
伊拉克Iraq |
+964 |
|
伊朗Iran |
+98 |
|
以色列Israel |
+972 |
|
意大利Italy |
+39 |
|
印度India |
+91 |
|
印度尼西亞Indonesia |
+62 |
|
英國United Kiongdom |
+44 |
|
約旦Jordan |
+962 |
|
越南Vietnam |
+84 |
|
贊比亞Zambia |
+260 |
|
乍得Chad |
+235 |
|
直布羅陀Gibraltar |
+350 |
|
智利Chile |
+56 |
|
中非共和國Central African Republic |
+236 |
|
posted on 2008-07-31 19:04
cheng 閱讀(7858)
評論(1) 編輯 收藏 所屬分類:
Oracle