ORACLE PL/SQL开发之游标(Cursor)使用
[p=25, null, left][color=rgb(62, 62, 62)]Oracle plsq中游标主要包括以下几种类型:[p=25, null, left][color=rgb(62, 62, 62)]1显示Cursor[p=25, null, left][color=rgb(62, 62, 62)]2隐示Cursor[p=25, null, left][color=rgb(62, 62, 62)]3动态Ref Cursor区别[p=25, null, left][color=rgb(62, 62, 62)]相信不少做开发或维护的DBA在面试的时候,遇到过类似的面视问题:请描述一下光标的类型,说一下普通光标和REF光标之间的区别,以及什么时候该正确应用which one?[p=25, null, left][color=rgb(62, 62, 62)]下面我就给大家介绍详细介绍一下,如下图:[p=25, null, left][color=rgb(62, 62, 62)][img]http://mmbiz.qpic.cn/mmbiz/VkC3lgPs6cbK0eHXic5c0XNqGY5uYYYiaq9XE6NfmYBfVrDZ9oYiakrHe70IZhymsfqdpcj3q3jrpbc0hshpOP7jg/0?wx_fmt=png[/img][p=25, null, left][color=rgb(62, 62, 62)] [list]
[*]显式cursor
[p=25, null, left][color=rgb(62, 62, 62)]显式是相对与隐式cursor而言的,就是有一个明确的声明的cursor。显式游标的声明类似如下(详细的语法参加plsql ref doc ):[p=25, null, left][color=rgb(62, 62, 62)]cursor cursor_name (parameter list)is select ...[p=25, null, left][color=rgb(62, 62, 62)]游标从declare、open、fetch、close是一个完整的生命旅程。当然了一个这样的游标是可以被多次open进行使用的,显式cursor是静态cursor,她的作用域是全局的,但也必须明白,静态cursor也只有pl/sql代码才可以使用她。下面看一个简单的静态显式cursor的示例:[p=25, null, left][color=rgb(62, 62, 62)]declare [p=25, null, left][color=rgb(62, 62, 62)]cursor get_gsmno_cur (p_nettype in varchar2) [p=25, null, left][color=rgb(62, 62, 62)]is select gsmno from t_shzg_resource [p=25, null, left][color=rgb(62, 62, 62)]where nettype=p_nettype and status='0';[p=25, null, left][color=rgb(62, 62, 62)]v_gsmno t_shzg_resource.gsmno%type; [p=25, null, left][color=rgb(62, 62, 62)]begin open get_gsmno_cur('139'); [p=25, null, left][color=rgb(62, 62, 62)]loop fetch get_gsmno_cur into v_gsmno; [p=25, null, left][color=rgb(62, 62, 62)]exit when get_gsmno_cur%notfound;[p=25, null, left][color=rgb(62, 62, 62)]dbms_output.put_line(v_gsmno); [p=25, null, left][color=rgb(62, 62, 62)]end loop; [p=25, null, left][color=rgb(62, 62, 62)]close emp_cur; [p=25, null, left][color=rgb(62, 62, 62)]open get_gsmno_cur('138');[p=25, null, left][color=rgb(62, 62, 62)]loop [p=25, null, left][color=rgb(62, 62, 62)]fetch get_gsmno_cur into v_gsmno; [p=25, null, left][color=rgb(62, 62, 62)]exit when get_gsmno_cur%notfound;[p=25, null, left][color=rgb(62, 62, 62)] dbms_output.put_line(v_gsmno); [p=25, null, left][color=rgb(62, 62, 62)]end loop; [p=25, null, left][color=rgb(62, 62, 62)]close get_gsmno_cur; [p=25, null, left][color=rgb(62, 62, 62)]end;[p=25, null, left][color=rgb(62, 62, 62)] /
[p=25, null, left][color=rgb(62, 62, 62)]上面这段匿名块用来实现选号的功能,我们显式的定义了一个get_gsmno_cur,然后根据不同的号段输出当前系统中该号短对应的可用手机号码。当然了,实际应用中没人这么用的,我只是用来说应一个显式cursor的用法。[list]
[*]隐式cursor
[p=25, null, left][color=rgb(62, 62, 62)]隐式cursor当然是相对于显式而言的,就是没有明确的cursor的declare。在Oracle的PL/SQL中,所有的DML操作都被Oracle内部解析为一个cursor名为SQL的隐式游标,只是对我们透明罢了.[p=25, null, left][color=rgb(62, 62, 62)]另外,我们前面提到的一些循环操作中的指针for 循环,都是隐式cursor。[p=25, null, left][color=rgb(62, 62, 62)]隐式cursor示例一:[p=25, null, left][color=rgb(62, 62, 62)]CREATE TABLE t_shzg_test (str VARCHAR2(10));[p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_test values('ABCDEFG'); [p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_test values('ABCXEFG'); [p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_test values('ABCYEFG'); [p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_test values('ABCDEFG'); [p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_test values('ABCZEFG'); [p=25, null, left][color=rgb(62, 62, 62)]COMMIT;[p=25, null, left][color=rgb(62, 62, 62)]SQL> begin [p=25, null, left][color=rgb(62, 62, 62)]2 update t_shzg_test SET str ='updateD' where str like '%D%'; [p=25, null, left][color=rgb(62, 62, 62)]3 ifSQL%ROWCOUNT= 0 then [p=25, null, left][color=rgb(62, 62, 62)]4 insert into t_shzg_test values('1111111'); [p=25, null, left][color=rgb(62, 62, 62)]5 end if;[p=25, null, left][color=rgb(62, 62, 62)]6 end; [p=25, null, left][color=rgb(62, 62, 62)]7 /[p=25, null, left][color=rgb(62, 62, 62)]PL/SQL procedure successfullycompleted [p=25, null, left][color=rgb(62, 62, 62)]SQL> select * from t_shzg_test;[p=25, null, left][color=rgb(62, 62, 62)]STR ----------[p=25, null, left][color=rgb(62, 62, 62)]updateD [p=25, null, left][color=rgb(62, 62, 62)]ABCXEFG [p=25, null, left][color=rgb(62, 62, 62)]ABCYEFG [p=25, null, left][color=rgb(62, 62, 62)]updateD [p=25, null, left][color=rgb(62, 62, 62)]ABCZEFG[p=25, null, left][color=rgb(62, 62, 62)]SQL> begin [p=25, null, left][color=rgb(62, 62, 62)]2 update t_shzg_testSET str = 'updateD' where str like '%S%'; [p=25, null, left][color=rgb(62, 62, 62)]3 ifSQL%ROWCOUNT=0 THEN [p=25, null, left][color=rgb(62, 62, 62)]4 insert into t_shzg_testvalues ('0000000');[p=25, null, left][color=rgb(62, 62, 62)]5 end if; [p=25, null, left][color=rgb(62, 62, 62)]6 end; [p=25, null, left][color=rgb(62, 62, 62)]7 /[p=25, null, left][color=rgb(62, 62, 62)]PL/SQL proceduresuccessfully completed [p=25, null, left][color=rgb(62, 62, 62)]SQL> select *from t_shzg_test; [p=25, null, left][color=rgb(62, 62, 62)]STR[p=25, null, left][color=rgb(62, 62, 62)]---------- [p=25, null, left][color=rgb(62, 62, 62)]updated[p=25, null, left][color=rgb(62, 62, 62)]ABCXEFG [p=25, null, left][color=rgb(62, 62, 62)]ABCYEFG [p=25, null, left][color=rgb(62, 62, 62)]updateD [p=25, null, left][color=rgb(62, 62, 62)]ABCZEFG [p=25, null, left][color=rgb(62, 62, 62)]0000000 [p=25, null, left][color=rgb(62, 62, 62)]6 rows selected[p=25, null, left][color=rgb(62, 62, 62)]SQL>[p=25, null, left][color=rgb(62, 62, 62)]隐式cursor示例二:[p=25, null, left][color=rgb(62, 62, 62)]begin [p=25, null, left][color=rgb(62, 62, 62)]for rec in (select gsmno,statusfrom t_shzg_resource) loop [p=25, null, left][color=rgb(62, 62, 62)]dbms_output.put_line(rec.gsmno||'--'||rec.status);[p=25, null, left][color=rgb(62, 62, 62)]end loop;[p=25, null, left][color=rgb(62, 62, 62)] end; [p=25, null, left][color=rgb(62, 62, 62)]/[list]
[*]REFcursor
[p=25, null, left][color=rgb(62, 62, 62)]Ref cursor属于动态cursor(直到运行时才知道这条查询)。[p=25, null, left][color=rgb(62, 62, 62)]从技术上讲,在最基本的层次静态cursor和ref cursor是相同的。一个典型的PL/SQL光标按定义是静态的。Ref光标正好相反,可以动态地打开,或者利用一组SQL静态语句来打开,选择哪种方法由逻辑确定(一个IF/THEN/ELSE代码块将打开一个或其它的查询)。例如,下面的代码块显示一个典型的静态SQL光标,光标C。此外,还显示了如何通过使用动态SQL或静态SQL来用ref光标(在本例中为L_CURSOR)来打开一个查询:[p=25, null, left][color=rgb(62, 62, 62)]Declare [p=25, null, left][color=rgb(62, 62, 62)]type rc is ref cursor; [p=25, null, left][color=rgb(62, 62, 62)]cursor c is select * from dual;[p=25, null, left][color=rgb(62, 62, 62)]l_cursor rc; [p=25, null, left][color=rgb(62, 62, 62)]begin [p=25, null, left][color=rgb(62, 62, 62)]if (to_char(sysdate,'dd') = 30)then [p=25, null, left][color=rgb(62, 62, 62)]-- ref cursor with dynamic sql [p=25, null, left][color=rgb(62, 62, 62)]open l_cursor for 'select * fromemp';[p=25, null, left][color=rgb(62, 62, 62)]elsif (to_char(sysdate,'dd') = 29)then [p=25, null, left][color=rgb(62, 62, 62)]-- ref cursor with static sql [p=25, null, left][color=rgb(62, 62, 62)]open l_cursor for select * fromdept; else[p=25, null, left][color=rgb(62, 62, 62)]-- with ref cursor with static sql[p=25, null, left][color=rgb(62, 62, 62)]open l_cursor for select * fromdual; [p=25, null, left][color=rgb(62, 62, 62)]end if; [p=25, null, left][color=rgb(62, 62, 62)]-- the "normal" staticcursor open c;[p=25, null, left][color=rgb(62, 62, 62)] end;[p=25, null, left][color=rgb(62, 62, 62)] /[p=25, null, left][color=rgb(62, 62, 62)]在这段代码块中,可以看到了最显而易见的区别:无论运行多少次该代码块,光标C总是select *from dual。相反,ref光标可以是任何结果集,因为"select * from emp"字符串可以用实际上包含任何查询的变量来代替。[p=25, null, left][color=rgb(62, 62, 62)]在上面的代码中,声明了一个弱类型的REF cursor,下面再看一个强类型(受限)的REF cursor,这种类型的REF cursor在实际的应用系统中用的也是比较多的。[p=25, null, left][color=rgb(62, 62, 62)]create table t_shzg_resource ([p=25, null, left][color=rgb(62, 62, 62)]gsmno varchar2(11), [p=25, null, left][color=rgb(62, 62, 62)]status varchar2(1),[p=25, null, left][color=rgb(62, 62, 62)]price number(8,2),[p=25, null, left][color=rgb(62, 62, 62)]store_id varchar2(32)[p=25, null, left][color=rgb(62, 62, 62)]); [p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_resource values('G777778770001','0',200.00,'SD.JN.01');[p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_resourcevalues('G777778772002','0',800.00,'SD.JN.02'); [p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_resourcevalues('G777778775005','1',500.00,'SD.JN.01'); [p=25, null, left][color=rgb(62, 62, 62)]insert into t_shzg_resourcevalues('G777778776006','0',900.00,'SD.JN.03');[p=25, null, left][color=rgb(62, 62, 62)]commit; [p=25, null, left][color=rgb(62, 62, 62)]SQL> declare[p=25, null, left][color=rgb(62, 62, 62)]2 type t_shzg_rec is record( [p=25, null, left][color=rgb(62, 62, 62)]3 gsmno varchar2(11), [p=25, null, left][color=rgb(62, 62, 62)]4 status varchar2(1), [p=25, null, left][color=rgb(62, 62, 62)]5 price number(8,2)); [p=25, null, left][color=rgb(62, 62, 62)]6 [p=25, null, left][color=rgb(62, 62, 62)]7 type app_ref_cur_type is refcursor return t_shzg_rec; [p=25, null, left][color=rgb(62, 62, 62)]8 my_cur app_ref_cur_type; [p=25, null, left][color=rgb(62, 62, 62)]9 my_rec t_shzg_rec;[p=25, null, left][color=rgb(62, 62, 62)]10 [p=25, null, left][color=rgb(62, 62, 62)]11 begin [p=25, null, left][color=rgb(62, 62, 62)]12 open my_cur for select gsmno,status,price[p=25, null, left][color=rgb(62, 62, 62)]13 from t_shzg_resource [p=25, null, left][color=rgb(62, 62, 62)]14 where store_id='SD.JN.01';[p=25, null, left][color=rgb(62, 62, 62)]15 fetch my_cur into my_rec; [p=25, null, left][color=rgb(62, 62, 62)]16 while my_cur%found loop [p=25, null, left][color=rgb(62, 62, 62)]17dbms_output.put_line(my_rec.gsmno||'#'||my_rec.status||'#'||my_rec.price); [p=25, null, left][color=rgb(62, 62, 62)]18 fetch my_cur into my_rec; [p=25, null, left][color=rgb(62, 62, 62)]19 end loop; [p=25, null, left][color=rgb(62, 62, 62)]20 close my_cur; [p=25, null, left][color=rgb(62, 62, 62)]21 end; [p=25, null, left][color=rgb(62, 62, 62)]22 / [p=25, null, left][color=rgb(62, 62, 62)]G777778770001#0#200 [p=25, null, left][color=rgb(62, 62, 62)]G777778775005#1#500[p=25, null, left][color=rgb(62, 62, 62)]PL/SQL procedure successfullycompleted[p=25, null, left][color=rgb(62, 62, 62)]SQL>[p=25, null, left][color=rgb(62, 62, 62)]普通cursor与REF cursor还有一些大家应该都熟悉的区别,我再浪费点唾沫。[p=25, null, left][color=rgb(62, 62, 62)]1) PL/SQL静态光标不能返回到客户端,只有PL/SQL才能利用它。ref光标能够被返回到客户端,这就是从Oracle的存储过程返回结果集的方式。[p=25, null, left][color=rgb(62, 62, 62)]2) PL/SQL静态光标可以是全局的,而ref光标则不是。 也就是说,不能在包说明或包体中的过程或函数之外定义ref光标。 只能在定义ref光标的过程中处理它,或返回到客户端应用程序。[p=25, null, left][color=rgb(62, 62, 62)]3) ref光标可以从子例程传递到子例程,而光标则不能。 为了共享静态光标,必须在包说明或包体中把它定义为全局光标。 因为使用全局变量通常不是一种很好的编码习惯,因此可以用ref光标来共享PL/SQL中的光标,无需混合使用全局变量。[p=25, null, left][color=rgb(62, 62, 62)]最后,使用静态光标--通过静态SQL(但不用ref光标)--比使用ref光标效率高,而ref光标的使用仅限于以下几种情况:[p=25, null, left][color=rgb(62, 62, 62)]把结果集返回给客户端;在多个子例程之间共享光标(实际上与上面提到的一点非常类似);没有其他有效的方法来达到你的目标时,则使用ref光标,正如必须用动态SQL时那样;[p=25, null, left][color=rgb(62, 62, 62)]简言之,首先考虑使用静态SQL,只有绝对必须使用ref光标时才使用ref光标,也有人建议尽量使用隐式游标,避免编写附加的游标控制代码(声明,打开,获取,关闭),也不需要声明变量来保存从游标中获取的数据。这个就因人因具体的case大家去酌定吧。[list]
[*]游标属性
[p=25, null, left][color=rgb(62, 62, 62)]标中获取的数据。这个就因人因具体的case大家去酌定吧。四、游标属性%FOUND:bool - TRUE if >1 row returned%NOTFOUND:[p=25, null, left][color=rgb(62, 62, 62)]bool - TRUE if 0rows returned %ISOPEN: [p=25, null, left][color=rgb(62, 62, 62)]bool - TRUE ifcursor still open %ROWCOUNT:int - number of rows affected by last SQLstatement[p=25, null, left][color=rgb(62, 62, 62)]注:NO_DATA_FOUND和%NOTFOUND的用法是有区别的,小结如下: [p=25, null, left][color=rgb(62, 62, 62)]1)SELECT . . . INTO 语句触发NO_DATA_FOUND; [p=25, null, left][color=rgb(62, 62, 62)]2)当一个显式光标的where 子句未找到时触发%NOTFOUND; [p=25, null, left][color=rgb(62, 62, 62)]3)当UPDATE或DELETE 语句的where 子句未找到时触发SQL%NOTFOUND; [p=25, null, left][color=rgb(62, 62, 62)]4)在光标的提取(Fetch)循环中要用%NOTFOUND 或%FOUND 来确定循环的退出条件,不要用NO_DATA_FOUND。