null;
end;
 
OUT 参数:作为输出参数,需要注意,当一个参数被指定为OUT类型时,就算在调用存储过程之前对该参数进行了赋值,在存储过程中该参数的值仍然是null.?
INOUT 是真正的按引用传递参数。即可作为传入参数也可以作为传出参数。
对于IN参数,其宽度是由外部决定。?  
对于OUT 和IN OUT 参数,其宽度是由存储过程内部决定。?
1.3 参数的默认值?  
存储过程的参数可以设置默认值?
   复制代码
 
create or replace procedure procdefault(p1 varchar2,
                                     p2 varchar2 default 'mark')   
   复制代码
 
as
begin
dbms_output.put_line(p2);
end;
 
?可以通过default 关键字为存储过程的参数指定默认值。在对存储过程调用时,就可以省略默认值。?  
需要注意的是:默认值仅仅支持IN传输类型的参数。OUT 和 IN OUT不能指定默认值?
2\. 存储过程内部块?  
2.1 内部块?  
我们知道了存储过程的结构,语句块由begin开始,以end结束。这些块是可以嵌套。在语句块中可以嵌套任何以下的块。?
   复制代码
 
Declare … begin … exception … end;
create or replace procedure innerBlock(p1 varchar2)
as
o1 varchar2(10) := 'out1';
begin
dbms_output.put_line(o1);
declare
begin
 inner1 :='inner1';   
dbms_output.put_line(inner1);   
declare    
  inner2 varchar2(20);   
begin   
  inner2 := 'inner2';   
  dbms_output.put_line(inner2);   
end;   
   复制代码
 
exception
end;
end;
 
需要注意变量的作用域。?
3.存储过程的常用技巧?  
3.1 哪种集合??  
我们在使用存储过程的时候经常需要处理记录集,也就是多条数据记录。分为单列多行和多列多行,这些类型都可以称为集合类型。我们在这里进行比较这些集合类型,以便于在编程时做出正确的选择。?  
索引表,也称为pl/sql表,不能存储于数据库中,元素的个数没有限制,下标可以为负值。?
   复制代码
 
type t_table is table of varchar2(20) index by binary_integer;
v_student t_table;
 
varchar2(20)表示存放元素的数据类型,binary\_integer表示元素下标的数据类型。?  
嵌套表,索引表没有 index by子句就是嵌套表,它可以存放于数据中,元素个数无限,下标从1开始,并且需要初始化?
   复制代码
 
type t_nestTable is table of varchar2(20);
v_class t_nestTable ;
 
仅是这样声明是不能使用的,必须对嵌套表进行初始化,对嵌套表进行初始化可以使用它的构造函数?
   复制代码
 
v_class :=t_nestTable('a','b','c');
 
变长数组,变长数组与高级语言的数组类型非常相似,下标以1开始,元素个数有限。?
   复制代码
 
type t_array is varray (20) of varchar2(20);
 
varray(20)就定义了变长数组的最大元素个数是20个?  
变长数组与嵌套表一样,也可以是数据表列的数据类型。?  
同时,变长数组的使用也需要事先初始化
由此可见,如果仅仅是在存储过程中当作集合变量使用,索引表是最好的选择。?
3.2 选用何种游标??
显示游标分为:普通游标,参数化游标和游标变量三种。?  
下面以一个过程来进行说明?
   复制代码
 
create or replace procedure proccursor(p varchar2)
as
v_rownum number(10) := 1;
cursor c_postype is select pos_type from pos_type_tbl where rownum =1;
cursor c_postype1 is select pos_type from pos_type_tbl where rownum = v_rownum;
cursor c_postype2(p_rownum number) is select pos_type from pos_type_tbl where rownum = p_rownum;
type t_postype is ref cursor ;
c_postype3 t_postype;
v_postype varchar2(20);
begin
open c_postype;
fetch c_postype into v_postype;
dbms_output.put_line(v_postype);
close c_postype;
open c_postype1;
fetch c_postype1 into v_postype;
dbms_output.put_line(v_postype);
close c_postype1;
open c_postype2(1);
fetch c_postype2 into v_postype;
dbms_output.put_line(v_postype);
close c_postype2;
open c_postype3 for select pos_type from pos_type_tbl where rownum =1;
fetch c_postype3 into v_postype;
dbms_output.put_line(v_postype);
close c_postype3;
end;
 
cursor c\_postype is select pos\_type from pos\_type\_tbl where rownum =1?  
这一句是定义了一个最普通的游标,把整个查询已经写死,调用时不可以作任何改变。
cursor c\_postype1 is select pos\_type from pos\_type\_tbl where rownum = v\_rownum;?  
这一句并没有写死,查询参数由变量v\_rownum来决定。需要注意的是v\_rownum必须在这个游标定义之前声明。?
cursor c\_postype2(p\_rownum number) is select pos\_type from pos\_type\_tbl where rownum = p\_rownum;?  
这一条语句与第二条作用相似,都是可以为游标实现动态的查询。但是它进一步的缩小了参数的作用域范围。但是可读性降低了不少。?
type t\_postype is ref cursor ;?  
c\_postype3 t\_postype;?  
先定义了一个引用游标类型,然后再声明了一个游标变量。?  
open c\_postype3 for select pos\_type from pos\_type\_tbl where rownum =1;?  
然后再用open for 来打开一个查询。需要注意的是它可以多次使用,用来打开不同的查询。?  
从动态性来说,游标变量是最好用的,但是阅读性也是最差的。?  
注意,游标的定义只能用使关键字IS,它与AS不通用。?
3.3 游标循环最佳策略?  
我们在进行PL/SQL编程时,经常需要循环读取结果集的数据。进行逐行处理,这个过程就需要对游标进行循环。对游标进行循环的方法有多种,我们在此一一分析。?
   复制代码
 
create or replace procedure proccycle(p varchar2)
as
cursor c_postype is select pos_type, description from pos_type_tbl where rownum < 6;
v_postype varchar2(20);
v_description varchar2(50);
begin
open c_postype;
if c_postype%found then
 dbms_output.put_line('found true');   
   复制代码
 
elsif c_postype%found = false then
 dbms_output.put_line('found false');   
   复制代码
 
else
 dbms_output.put_line('found null');   
   复制代码
 
end if;
loop
fetch c_postype into v_postype,v_description ;
exit when c_postype%notfound;
dbms_output.put_line('postype:'||v_postype||',description:'||v_description);
end loop;
close c_postype;
dbms_output.put_line('---loop end---');
open c_postype;
 fetch c_postype into v_postype,v_description;   
while c_postype%found loop   
  dbms_output.put_line('postype:'||v_postype||',description:'||v_description);   
  fetch c_postype into v_postype,v_description ;   
end loop;   
   复制代码
 
close c_postype;
dbms_output.put_line('---while end---');
for v_pos in c_postype loop
 v_postype := v_pos.pos_type;   
v_description := v_pos.description;   
dbms_output.put_line('postype:'||v_postype||',description:'||v_description);   
   复制代码
 
end loop;
dbms_output.put_line('---for end---');
end;
 
使用游标之前需要开打游标,open cursor,循环完后再关闭游标close cursor.?  
这是使用游标应该慎记于心的法则。?  
上面的过程演示了游标循环的三种方法。?  
在讨论循环方法之前,我们先看看%found和%notfound这些游标的属性。?
   复制代码
 
open c_postype;
if c_postype%found then
dbms_output.put_line('found true');
elsif c_postype%found = false then
dbms_output.put_line('found false');
else
dbms_output.put_line('found null');
end if;
 
在打开一个游标之后,马上检查它的%found或%notfound属性,它得到的结果即不是true也不是false.而是null.必须执行一条fetch语句后,这些属性才有值。?
第一种使用loop 循环?
   复制代码
 
loop
fetch c_postype into v_postype,v_description ;
exit when c_postype%notfound;
……
end loop
 
这里需要注意,exit when语句一定要紧跟在fetch之后。必避免多余的数据处理。?  
处理逻辑需要跟在exit when之后。这一点需要多加小心。?  
循环结束后要记得关闭游标。
第二种使用while循环。?
   复制代码
 
fetch c_postype into v_postype,v_description;
while c_postype%found loop
……
   fetch c_postype into v_postype,v_description ;   
   复制代码
 
end loop;
 
我们知道了一个游标打开后,必须执行一次fetch语句,游标的属性才会起作用。所以使用while 循环时,就需要在循环之前进行一次fetch动作。?  
而且数据处理动作必须放在循环体内的fetch方法之前。循环体内的fetch方法要放在最后。否则就会多处理一次。这一点也要非常的小心。?  
总之,使用while来循环处理游标是最复杂的方法。?
第三种 for循环
   复制代码
 
for v_pos in c_postype loop
v_postype := v_pos.pos_type;
v_description := v_pos.description;
…
end loop;
 
可见for循环是比较简单实用的方法。?  
首先,它会自动open和close游标。解决了你忘记打开或关闭游标的烦恼。?  
其它,自动定义了一个记录类型及声明该类型的变量,并自动fetch数据到这个变量中。?  
我们需要注意v\_pos 这个变量无需要在循环外进行声明,无需要为其指定数据类型。?  
它应该是一个记录类型,具体的结构是由游标决定的。?  
这个变量的作用域仅仅是在循环体内。?  
把v\_pos看作一个记录变量就可以了,如果要获得某一个值就像调用记录一样就可以了。?  
如v\_pos.pos\_type?  
由此可见,for循环是用来循环游标的最好方法。高效,简洁,安全。?  
但遗憾的是,常常见到的却是第一种方法。所以从今之后得改变这个习惯了。?  
3.4 select into不可乎视的问题?
我们知道在pl/sql中要想从数据表中向变量赋值,需要使用select into 子句。?  
但是它会带动来一些问题,如果查询没有记录时,会抛出no\_data\_found异常。?  
如果有多条记录时,会抛出too\_many\_rows异常。?  
这个是比较糟糕的。一旦抛出了异常,就会让过程中断。特别是no\_data\_found这种异常,没有严重到要让程序中断的地步,可以完全交给由程序进行处理。?
   复制代码
 
create or replace procedure procexception(p varchar2)
as
v_postype varchar2(20);
begin
select pos_type into v_postype from pos_type_tbl where 1=0;
 dbms_output.put_line(v_postype);   
   复制代码
 
end;
SQL> exec procexception('a');
报错
ORA-01403: no data found
ORA-06512: at "LIFEMAN.PROCEXCEPTION", line 6
ORA-06512: at line 1
create or replace procedure procexception(p varchar2)
as
v_postype varchar2(20);
begin
select pos_type into v_postype from pos_type_tbl where 1=0;
 dbms_output.put_line(v_postype);   
   复制代码
 
exception
when no_data_found then
 dbms_output.put_line('没找到数据');   
   复制代码
 
end;
 
这样做换汤不换药,程序仍然被中断。可能这样不是我们所想要的。?  
2\. select into做为一个独立的块,在这个块中进行异常处理?
   复制代码
 
create or replace procedure procexception(p varchar2)
as
v_postype varchar2(20);
begin
begin
select pos_type into v_postype from pos_type_tbl where 1=0;
 dbms_output.put_line(v_postype);   
   复制代码
 
exception
when no_data_found then
end;
dbms_output.put_line(v_postype);
end;
 
这是一种比较好的处理方式了。不会因为这个异常而引起程序中断。?
3.使用游标?
   复制代码
 
create or replace procedure procexception(p varchar2)
as
v_postype varchar2(20);
cursor c_postype is select pos_type  from pos_type_tbl where 1=0;
begin
open c_postype;
 fetch c_postype into v_postype;   
   复制代码
 
close c_postype;
dbms_output.put_line(v_postype);
end;
 
这样就完全的避免了no\_data\_found异常。完全交由程序员来进行控制了。?
第二种情况是too\_many\_rows 异常的问题。?  
Too\_many\_rows 这个问题比起no\_data\_found要复杂一些。?  
给一个变量赋值时,但是查询结果有多个记录。?  
处理这种问题也有两种情况:?
1. 多条数据是可以接受的,也就是说从结果集中随便取一个值就行。这种情况应该很极端了吧,如果出现这种情况,也说明了程序的严谨性存在问题。?  
2. 多条数据是不可以被接受的,在这种情况肯定是程序的逻辑出了问题,也说是说原来根本就不会想到它会产生多条记录。?  
对于第一种情况,就必须采用游标来处理,而对于第二种情况就必须使用内部块来处理,重新抛出异常。?  
多条数据可以接受,随便取一条,这个跟no\_data\_found的处理方式一样,使用游标。?  
我这里仅说第二种情况,不可接受多条数据,但是不要忘了处理no\_data\_found哦。这就不能使用游标了,必须使用内部块。?
   复制代码
 
create or replace procedure procexception2(p varchar2)
as
v_postype varchar2(20);
begin
begin
 select pos_type into v_postype from pos_type_tbl where rownum < 5;   
   复制代码
 
exception
 when no_data_found then   
  v_postype :=null;   
when too_many_rows then   
  raise_application_error(-20000,'对v_postype赋值时,找到多条数据');   
   复制代码
 
end;
dbms_output.put_line(v_postype);
end;
 
需要注意的是一定要加上对no\_data\_found的处理,对出现多条记录的情况则继续抛出异常,让上一层来处理。?  
总之对于select into的语句需要注意这两种情况了。需要妥当处理啊。
3.5 在存储过程中返回结果集?  
我们使用存储过程都是返回值都是单一的,有时我们需要从过程中返回一个集合。即多条数据。这有几种解决方案。比较简单的做法是写临时表,但是这种做法不灵活。而且维护麻烦。我们可以使用嵌套表来实现.没有一个集合类型能够与java的jdbc类型匹配。这就是对象与关系数据库的阻抗吧。数据库的对象并不能够完全转换为编程语言的对象,还必须使用关系数据库的处理方式。
   复制代码
 
create or replace package procpkg is
type refcursor is ref cursor;
procedure procrefcursor(p varchar2, p_ref_postypeList  out refcursor);
end procpkg;
create or replace package body procpkg is
procedure procrefcursor(p varchar2, p_ref_postypeList out  refcursor)
is
 v_posTypeList PosTypeTable;   
   复制代码
 
begin
 v_posTypeList :=PosTypeTable();--初始化嵌套表   
v_posTypeList.extend;   
v_posTypeList(1) := PosType('A001','客户资料变更');   
v_posTypeList.extend;   
v_posTypeList(2) := PosType('A002','团体资料变更');   
v_posTypeList.extend;   
v_posTypeList(3) := PosType('A003','受益人变更');   
v_posTypeList.extend;   
v_posTypeList(4) := PosType('A004','续期交费方式变更');   
open p_ref_postypeList for  select * from table(cast (v_posTypeList as PosTypeTable));   
   复制代码
 
end;
end procpkg;
 
在包头中定义了一个游标变量,并把它作为存储过程的参数类型。?  
在存储过程中定义了一个嵌套表变量,对数据写进嵌套表中,然后把嵌套表进行类型转换为table,游标变量从这个嵌套表中进行查询。外部程序调用这个游标。?  
所以这个过程需要定义两个类型。?
   复制代码
 最后
在面试前我整理归纳了一些面试学习资料,文中结合我的朋友同学面试美团滴滴这类大厂的资料及案例感兴趣的朋友可以点击Java学习免费获取。
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
大家看完有什么不懂的可以在下方留言讨论也可以关注。
觉得文章对你有帮助的话记得关注我点个赞支持一下!
评论