一:前言:
一:存储过程
1:创建储存过程
View Code1
2 create procedure pro_insertDept is
3
4 begin
5 insert into dept values(77,'市场拓展部','家啊的发放');/*插入记录数据*/
6 commit;
7 dbms_output.put_line('插入新记录成功');
8 end pro_insertDept;
9 /
10View Code1 create or replace procedure pro_insertDept is
2
3 begin
4 insert into dept values(77,'市场拓展部','家啊的发放');/*插入记录数据*/
5 commit;
6 dbms_output.put_line('插入新记录成功');
7 end pro_insertDept;
8 /从运行结果中可以看出,执行存储过程是成功的;另外,代码中的“execute”命令也可以简写为 “exec”;但有时候需要在一个PL/SQL
程序快中调用某个储存过程
2:存储过程的参数
2-1:in 模式参数
View Code1
2 create or replace procedure insert_dept(
3 num_deptno in number,/*定义in模式的变量,它储存部门编号*/
4 var_ename in varchar2,/*定义in模式的变量,它储存部门名称*/
5 var_loc in varchar2
6 ) is
7 begin
8 insert into dept values(num_deptno,var_ename,var_loc); /*向dept表中插入记录*/
9 commit; /*提交数据库*/
10 end insert_dept;
11 /
12
13View Code1 SQL> exec insert_dept(38,var_loc=>'adfasd',var_ename=>'adfasd');
2
3 PL/SQL procedure successfully completed
2-2: out 模式参数
View Code1 create or replace procedure select_dept(
2 num_deptno in number,/*定义 in 模式变量 要求输入部门编号*/
3 var_dname out dept.dname%type,/*定义out模式变量,可以储存部门名称并输出*/
4 var_loc out dept.loc%type
5 )
6 is
7
8 begin
9 select dname,loc into var_dname,var_loc from dept where deptno=num_deptno;
10 exception
11 when no_data_found then
12 dbms_output.put_line('该部门编号不存在');
13
14 end select_dept;
15 /View Code1 set serveroutput on
2 declare
3 var_dname dept.dname%type;/*声明变量,对应过程中的out模式的var_dname */
4 var_loc dept.loc%type;/*声明变量,对应过程这哦功能的out模式的var_loc*/
5 begin
6 select_dept(99,var_dname ,var_loc );/*传入部门编号然后输出部门名称和位置信息*/
7 dbms_output.put_line(var_dname||'位于:'||var_loc); /*输出部门信息*/
8 end;
9 /在上面代码中,把声明的两个变量传入熬存储过程中,当存储过程执行时,其中的out参数会被赋值,当存储过程执行完毕,out参数的值会在调用处返回,这样定义的两个变量就可以得到out参数被赋予的值,最后这两个值就可以在储存过程外任意使用了。
View Code1 variable var_dname varchar2(50);
2 variable var_loc varchar2(50);
3 exec select_dept(15,:var_dname,:var_loc );
4
2-3:in out 模式参数
1 create or replace procedure pro_square( 2 num in out number ,/*计算它的平方或者是平方根,这是一个‘int out ’参数*/
3 flag in boolean
4 )is
5 i int :=2;
6 begin
7 if flag then /*true*/
8 num:=power(num,i);
9 else
10 num :=sqrt(num);
11
12 end if;
13
14 end pro_square;
15 /1 2 declare 3 var_number number;/*储存要进行运算的值和运算后的结果*/ 4 var_temp number;/*储存要进行运算的值*/ 5 boo_flag boolean;/*平方或者 平方根的逻辑标记*/ 6 begin 7 var_temp:=3;/*变量赋值*/ 8 var_number:=var_temp; 9 boo_flag:=false;/*false 表示计算平方根 true 表示计算平方*/ 10 pro_square(var_number,boo_flag); 11 if boo_flag then 12 dbms_output.put_line(var_temp ||'的平方是:'||var_number);/*输出计算结果*/ 13 else 14 dbms_output.put_line(var_temp ||'的平方根是:'||var_number);/*输出计算结果*/ 15 end if; 16 end; 17 / 18
2-4:in 参数的默认值
1 create or replace procedure insert_dept1( 2 num_deptno in number,/*定义储存部门编号的in参数*/ 3 var_dname in varchar2 default '综合部',/*定义储存部门名称的in 参数 并初始化默认值*/ 4 var_loc in varchar2 default '北京' 5 ) is 6 7 begin 8 insert into dept values (num_deptno,var_dname,var_loc) ; 9 10 end insert_dept1; 11 / 121 declare 2 row_dept dept%rowtype ;/*定义行变量,与dept表的一行类型相同*/ 3 begin 4 insert_dept1(57,var_loc => '太原');/*调用 insert_dept存储过程,插入参数*/ 5 commit; 6 select * into row_dept from dept where deptno =57 ;/*查询新插入的记录*/ 7 dbms_output.put_line('部门名称是:'||row_dept.dname||',位置是:'||row_dept.loc); 8 end; 9 /
3:存储过程示例:
3-1:中规中矩的 loop while 循环。常态是拼接游标的方式循环数据
1 create or replace procedure proc_yszxtz_aduit ( 2 yszxtz_ids in varchar2 /* 预算执行调整表id,其值为: '1,2,3,4,5,6,7,8' */ 3 ) is 4 5 rowvar_yszxtz ys_zxtz%rowtype ; /*声明了:预算执行调整主表 单条数据对象 */ 6 --- 预算执行调整主表的id的 游标 7 var_sql varchar2(4000); 8 cursor yszxtz_cursor is select * into rowvar_yszxtz from ys_zxtz z where 1=1 ; 9 begin 10 dbms_output.put_line('----===========预算执行调整审批:审结后业务处理开始-----====== '); 11 /*进行:入参*/ 12 if yszxtz_ids is null then 13 dbms_output.put_line(' 预算执行调整主表的id值不允许 null'); /*则输出异常提示信息*/ 14 rollback; /*回滚插入的数据记录*/ 15 end if; 16 17 --循环开始 18 19 /*判断 split_cursor 是否已经打开*/ 20 if not yszxtz_cursor%isopen then 21 open yszxtz_cursor ; /*打开游标 */ 22 end if; 23 fetch yszxtz_cursor into rowvar_yszxtz;/*先让指针指向结果集中的第一行,并将值保存到emp_row 中*/ 24 while yszxtz_cursor%found loop 25 dbms_output.put_line('预算执行调整的id:' || rowvar_yszxtz.id ); 26 27 28 29 fetch yszxtz_cursor into rowvar_yszxtz;/*先让指针指向结果集中的第一行,并将值保存到emp_row 中*/ 30 /*退出循环的条件*/ 31 exit when yszxtz_cursor%notfound or yszxtz_cursor%notfound is null; 32 33 dbms_output.put_line('---------------------------------------------预算执行调整的 业务逻辑处理结束---------------------------------------------' ); 34 end loop; 35 close yszxtz_cursor;/*关闭游标*/ 36 dbms_output.put_line('---------------------------------------------预算执行调整的 业务逻辑处理结束:关闭游标---------------------------------------------' ); 37 38 end proc_yszxtz_aduit; 39
3-2:动态拼接 游标的sql语句和参数条件的方式 进行 loop循环
1 create or replace procedure proc_yszxtz_aduit_1 ( 2 yszxtz_ids in varchar2 /* 预算执行调整表id,其值为: 1,2,3,4,5,6,7,8 */ 3 ) 4 is 5 type ref_cursor_type is ref cursor; --定义一个动态游标 6 yszxtz_cursor ref_cursor_type; 7 rowvar_yszxtz ys_zxtz%rowtype ; /*声明:预算执行调整主表 单条数据对象 */ 8 rowvar_adjustMx view_yszxtzms_adjustmx%rowtype;/*声明:view_yszxtzms_adjustmx 视图 行数据对象*/ 9 var_gkadjustid number(20);/*声明:获取 gk_adjust 表主键 对象*/ 10 11 begin 12 dbms_output.put_line('============================================预算执行调整审批:审结后业务处理开始============================================ '); 13 /*进行:入参*/ 14 if yszxtz_ids is null then 15 dbms_output.put_line(' 预算执行调整主表的id值不允许 null'); /*则输出异常提示信息*/ 16 rollback; /*回滚插入的数据记录*/ 17 end if; 18 19 --循环开始 20 --打开游标 21 open yszxtz_cursor for 'select * from ys_zxtz z where 1=1 and z.id in ('|| yszxtz_ids ||' )' ; 22 dbms_output.put_line('============================================ 预算执行调整的 业务逻辑处理 开始:执行的sql为: '|| 'select * from ys_zxtz z where 1=1 and z.id in ('|| yszxtz_ids ||' )============================================' ); /*则输出异常提示信息*/ 23 fetch yszxtz_cursor into rowvar_yszxtz;/*先让指针指向结果集中的第一行,并将值保存到emp_row 中*/ 24 while yszxtz_cursor%found loop 25 var_gkadjustid := nextid('GK_ADJUST_SEQ');/*获取预算执行调整主表的id值*/ 26 dbms_output.put_line('============================================预算执行调整的 业务逻辑处理 开始: 其id:' || rowvar_yszxtz.id ||';gk_adjust 表主键值:var_gkadjustid='||var_gkadjustid||'===========================================' ); 27 28 29 fetch yszxtz_cursor into rowvar_yszxtz;/*先让指针指向结果集中的第一行,并将值保存到emp_row 中*/ 30 /*退出循环的条件*/ 31 exit when yszxtz_cursor%notfound or yszxtz_cursor%notfound is null; 32 dbms_output.put_line('============================================预算执行调整的 业务逻辑处理 结束:其id:' || rowvar_yszxtz.id ||'=====================================================================================' ); 33 end loop; 34 close yszxtz_cursor;/*关闭游标*/ 35 dbms_output.put_line('============================================预算执行调整的 业务逻辑处理结束:关闭游标============================================' ); 36 37 end proc_yszxtz_aduit_1; 38
3-3:使用包头、包体的形式
1 --包头 2 create or replace package mypackage as 3 type empcursor is ref cursor; --声明一个光标类型 4 procedure queryEmpList(dno in number,empList out empcursor); 5 end; 6 7 --创建包体 8 create or replace package body mypackage as 9 procedure queryEmpList(dno in number,empList out empcursor) as 10 begin 11 --打开光标 12 open empList for select * from emp where deptno=dno; 13 end; 14 end; 15
3-4: 使用存储过程,返回游标的形式
1 --定义一个返回程序集的引用游标 2 CREATE OR REPLACE PACKAGE BAF_QUERY_TABLE AS 3 TYPE P_CURSOR IS ref CURSOR; 4 END BAF_QUERY_TABLE; 5 6 --创建存储过程,并返回游标的形式返回程序集 7 create or replace procedure getList(p_eno number, p_out_cursor out BAF_QUERY_TABLE.P_CURSOR) is 8 begin 9 --没有给定员工ID则返回所有员工信息 10 if p_eno is null then 11 open p_out_cursor for select * from emp; 12 else 13 --返回指定ID的员工信息 14 open p_out_cursor for select * from emp where empno = p_eno; 15 end if; 16 17 end getList; 18 19 20 --以上创建的包还可以给存储函数使用 21 create or replace function sp_ListEmp return BAF_QUERY_TABLE.P_CURSOR 22 as 23 l_cursor BAF_QUERY_TABLE.P_CURSOR; 24 begin 25 open l_cursor for select ename, empno from emp order by ename; 26 return l_cursor; 27 end;
3-5:使用sys_refcursor类型
1 create or replace procedure rsCursor(p_eno number,rs_cursor out SYS_REFCURSOR) 2 AS 3 BEGIN 4 --没有给定员工ID则返回所有员工信息 5 if p_eno is null then 6 OPEN rs_cursor for select * from emp; 7 else 8 --返回指定ID的员工信息 9 OPEN rs_cursor for select * from emp where deptno = p_eno ; 10 end if; 11 12 END;
java中调用
1 Connection conn = null; 2 //sql语句 (一定要写上包名) 3 String sql = "{call mypackage.queryEmpList(?,?)}"; 4 5 try { 6 //获取数据库的连接 7 conn = JDBCUtil.getConnection(); 8 //通过连接创建statment 9 CallableStatement call = conn.prepareCall(sql); 10 11 //对于IN参数需要赋值 12 call.setInt(1,10); 13 14 //对于OUT参数需要先申明 15 call.registerOutParameter(2,OracleTypes.CURSOR); 16 17 //执行调用 18 call.execute(); 19 20 //取出该部门中所有员工信息(注意这里) 21 ResultSet rs = ((OracleCallableStatement)call).getCursor(2); 22 23 while(rs.next()){ 24 //可以取出sql语句中查询的所有字段(这里只取几个演示下) 25 int empno = rs.getInt("empno"); 26 String ename = rs.getString("ename"); 27 double sal = rs.getDouble("sal"); 28 System.out.println("=================================================="); 29 System.out.println("empno:"+empno+"\t ename:"+ename+"\t sal:"+sal); 30 System.out.println("=================================================="); 31 }
3-6:存储过程的常用技巧
注意:本段内容来源:《oracle 存储过程,游标、异常处理,集合使用入门详解》
在进行pl/sql编程时打交道最多的就是存储过程了。存储过程的结构是非常的简单的,我们在这里除了学习存储过程的基本结构外,还会学习编写存储过程时相关的一些实用的知识。如:游标的处理,异常的处理,集合的选择等等
1.存储过程结构
1.1 第一个存储过程
1 create or replace procedure proc1( 2 p_para1 varchar2, 3 p_para2 out varchar2, 4 p_para3 in out varchar2 5 )as 6 v_name varchar2(20); 7 begin 8 v_name := '张三丰'; 9 p_para3 := v_name; 10 dbms_output.put_line('p_para3:'||p_para3); 11 end;上面就是一个最简单的存储过程。一个存储过程大体分为这么几个部分:
创建语句:create or replace procedure 存储过程名
如果没有or replace语句,则仅仅是新建一个存储过程。如果系统存在该存储过程,则会报错。Create or replace procedure 如果系统中没有此存储过程就新建一个,如果系统中有此存储过程则把原来删除掉,重新创建一个存储过程。
存储过程名定义:包括存储过程名和参数列表。参数名和参数类型。参数名不能重复, 参数传递方式:IN, OUT, IN OUT
IN 表示输入参数,按值传递方式。
OUT 表示输出参数,可以理解为按引用传递方式。可以作为存储过程的输出结果,供外部调用者使用。
IN OUT 即可作输入参数,也可作输出参数。
参数的数据类型只需要指明类型名即可,不需要指定宽度。
参数的宽度由外部调用者决定。
过程可以有参数,也可以没有参数
变量声明块:紧跟着的as (is )关键字,可以理解为pl/sql的declare关键字,用于声明变量。
变量声明块用于声明该存储过程需要用到的变量,它的作用域为该存储过程。另外这里声明的变量必须指定宽度。遵循PL/SQL的变量声明规范。
过程语句块:从begin 关键字开始为过程的语句块。存储过程的具体逻辑在这里来实现。
异常处理块:关键字为exception ,为处理语句产生的异常。该部分为可选
结束块:由end关键字结果。1.2 存储过程的参数传递方式
存储过程的参数传递有三种方式:IN,OUT,IN OUT .
IN 按值传递,并且它不允许在存储过程中被重新赋值。如果存储过程的参数没有指定存参数传递类型,默认为IN1 create or replace procedure proc1( 2 p_para1 varchar2, 3 p_para2 out varchar2, 4 p_para3 in out varchar2 5 )as 6 v_name varchar2(20); 7 begin 8 p_para1 :='aaa'; 9 p_para2 :='bbb'; 10 v_name := '张三丰'; 11 p_para3 := v_name; 12 dbms_output.put_line('p_para3:'||p_para3); 13 null; 14 end; 15 16 Warning: Procedure created with compilation errors 17 18 SQL> show error; 19 Errors for PROCEDURE LIFEMAN.PROC1: 20 21 LINE/COL ERROR 22 -------- ---------------------------------------------------------------------- 23 8/3 PLS-00363: expression 'P_PARA1' cannot be used as an assignment target 24 8/3 PL/SQL: Statement ignored这一点与其它高级语言都不同。它相当于java在参数前面加上final关键字。
OUT 参数:作为输出参数,需要注意,当一个参数被指定为OUT类型时,就算在调用存储过程之前对该参数进行了赋值,在存储过程中该参数的值仍然是null.1 create or replace procedure proc1( 2 p_para1 varchar2, 3 p_para2 out varchar2, 4 p_para3 in out varchar2 5 )as 6 v_name varchar2(20); 7 begin 8 v_name := '张三丰'; 9 p_para3 := v_name; 10 dbms_output.put_line('p_para1:'||p_para1); 11 dbms_output.put_line('p_para2:'||p_para2); 12 dbms_output.put_line('p_para3:'||p_para3); 13 end; 14 15 SQL> var p1 varchar2(10); 16 SQL> var p2 varchar2(10); 17 SQL> var p3 varchar2(10); 18 SQL> exec :p1 :='aaaa'; 19 SQL> exec :p2 :='bbbb'; 20 SQL> exec :p3 :='cccc'; 21 SQL> exec proc1(:p1,:p2,:p3); 22 p_para1:aaaa 23 p_para2: 24 p_para3:张三丰 25 SQL> exec dbms_output.put_line(:p2); 26 27 28 PL/SQL procedure successfully completed 29 p2 30 --------- 31INOUT 是真正的按引用传递参数。即可作为传入参数也可以作为传出参数。
1 1.3 存储过程参数宽度 2 create or replace procedure proc1( 3 p_para1 varchar2, 4 p_para2 out varchar2, 5 p_para3 in out varchar2 6 )as 7 v_name varchar2(2); 8 begin 9 v_name := p_para1; 10 end; 11 12 SQL> var p1 varchar2(10); 13 SQL> var p2 varchar2(20); 14 SQL> var p3 varchar2(30); 15 SQL> exec :p1 :='aaaaaa'; 16 SQL> exec proc1(:p1,:p2,:p3); 17 18 19 ORA-06502: PL/SQL: numeric or value error: character string buffer too small 20 ORA-06512: at "LIFEMAN.PROC1", line 8 21 ORA-06512: at line 1首先,我们要明白,我们无法在存储过程的定义中指定存储参数的宽度,也就导致了我们无法在存储过程中控制传入变量的宽度。这个宽度是完全由外部传入时决定的。
我们再来看看OUT类型的参数的宽度1 create or replace procedure proc1( 2 p_para1 varchar2, 3 p_para2 out varchar2, 4 p_para3 in out varchar2 5 )as 6 v_name varchar2(2); 7 begin 8 p_para2 :='aaaaaaaaaaaaaaaaaaaa'; 9 end; 10 SQL> var p1 varchar2(1); 11 SQL> var p2 varchar2(1); 12 SQL> var p3 varchar2(1); 13 SQL> exec :p2 :='a'; 14 SQL> exec proc1(:p1,:p2,:p3);
在该过程中,p_para2被赋予了20个字符a.
而在外部的调用过程中,p2这个参数仅仅被定义为varchar2(1).
而把p2作为参数调用这个过程,却并没有报错。而且它的真实值就是20个a1 SQL> select dump(:p2) from dual; 2 DUMP(:P2) 3 --------------------------------------------------------------------------- 4 Typ=1 Len=20: 97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97 5 p2 6 --------- 7 aaaaaaaaaaaaaaaaaaaa 8 9 再来看看IN OUT参数的宽度 10 create or replace procedure proc1( 11 p_para1 varchar2, 12 p_para2 out varchar2, 13 p_para3 in out varchar2 14 )as 15 v_name varchar2(2); 16 begin 17 p_para3 :='aaaaaaaaaaaaaaaaaaaa'; 18 end; 19 20 SQL> var p1 varchar2(1); 21 SQL> var p2 varchar2(1); 22 SQL> var p3 varchar2(1); 23 SQL> exec proc1(:p1,:p2,:p3);
执行这个过程,仍然正确执行。
可见,对于IN参数,其宽度是由外部决定。
对于OUT 和IN OUT 参数,其宽度是由存储过程内部决定。
因此,在写存储过程时,对参数的宽度进行说明是非常有必要的,最明智的方法就是参数的数据类型使用%type。这样双方就达成了一致。1.3 参数的默认值
存储过程的参数可以设置默认值1 create or replace procedure procdefault(p1 varchar2, 2 p2 varchar2 default 'mark') 3 as 4 begin 5 dbms_output.put_line(p2); 6 end; 7 8 SQL> set serveroutput on; 9 SQL> exec procdefault('a');mark
可以通过default 关键字为存储过程的参数指定默认值。在对存储过程调用时,就可以省略默认值。
需要注意的是:默认值仅仅支持IN传输类型的参数。OUT 和 IN OUT不能指定默认值
对于有默认值的参数不是排在最后的情况。1 create or replace procedure procdefault2(p1 varchar2 default 'remark', 2 p2 varchar2 ) 3 as 4 begin 5 dbms_output.put_line(p1); 6 end;第一个参数有默认值,第二个参数没有。如果我们想使用第一个参数的默认值时
exec procdefault2('aa');
这样是会报错的。
那怎么变呢?可以指定参数的值。
emark
这样就OK了,指定aa传给参数p22. 存储过程内部块
2.1 内部块
我们知道了存储过程的结构,语句块由begin开始,以end结束。这些块是可以嵌套。在语句块中可以嵌套任何以下的块。1 Declare … begin … exception … end; 2 create or replace procedure innerBlock(p1 varchar2) 3 as 4 o1 varchar2(10) := 'out1'; 5 begin 6 dbms_output.put_line(o1); 7 declare 8 inner1 varchar2(20); 9 begin 10 inner1 :='inner1'; 11 dbms_output.put_line(inner1); 12 13 declare 14 inner2 varchar2(20); 15 begin 16 inner2 := 'inner2'; 17 dbms_output.put_line(inner2); 18 end; 19 exception 20 when others then 21 null; 22 end; 23 end;
需要注意变量的作用域。
3.存储过程的常用技巧
3.1 哪种集合?
我们在使用存储过程的时候经常需要处理记录集,也就是多条数据记录。分为单列多行和多列多行,这些类型都可以称为集合类型。我们在这里进行比较这些集合类型,以便于在编程时做出正确的选择。
索引表,也称为pl/sql表,不能存储于数据库中,元素的个数没有限制,下标可以为负值。varchar2(20)表示存放元素的数据类型,binary_integer表示元素下标的数据类型。
嵌套表,索引表没有 index by子句就是嵌套表,它可以存放于数据中,元素个数无限,下标从1开始,并且需要初始化
仅是这样声明是不能使用的,必须对嵌套表进行初始化,对嵌套表进行初始化可以使用它的构造函数
1 v_class :=t_nestTable('a','b','c');变
长数组,变长数组与高级语言的数组类型非常相似,下标以1开始,元素个数有限。
varray(20)就定义了变长数组的最大元素个数是20个
变长数组与嵌套表一样,也可以是数据表列的数据类型。
同时,变长数组的使用也需要事先初始化。
类型 可存储于数据库 元素个数 是否需初始化 初始下标值
索引表 否 无限 不需
嵌套表 可 无限 需 1
可变数组 可 有限(自定义) 需 1
由此可见,如果仅仅是在存储过程中当作集合变量使用,索引表是最好的选择。3.2 选用何种游标?
显示游标分为:普通游标,参数化游标和游标变量三种。
下面以一个过程来进行说明1 create or replace procedure proccursor(p varchar2) 2 as 3 v_rownum number(10) := 1; 4 cursor c_postype is select pos_type from pos_type_tbl where rownum =1; 5 cursor c_postype1 is select pos_type from pos_type_tbl where rownum = v_rownum; 6 cursor c_postype2(p_rownum number) is select pos_type from pos_type_tbl where rownum = p_rownum; 7 type t_postype is ref cursor ; 8 c_postype3 t_postype; 9 v_postype varchar2(20); 10 begin 11 open c_postype; 12 fetch c_postype into v_postype; 13 dbms_output.put_line(v_postype); 14 close c_postype; 15 open c_postype1; 16 fetch c_postype1 into v_postype; 17 dbms_output.put_line(v_postype); 18 close c_postype1; 19 open c_postype2(1); 20 fetch c_postype2 into v_postype; 21 dbms_output.put_line(v_postype); 22 close c_postype2; 23 open c_postype3 for select pos_type from pos_type_tbl where rownum =1; 24 fetch c_postype3 into v_postype; 25 dbms_output.put_line(v_postype); 26 close c_postype3; 27 end; 28
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编程时,经常需要循环读取结果集的数据。进行逐行处理,这个过程就需要对游标进行循环。对游标进行循环的方法有多种,我们在此一一分析。
1 create or replace procedure proccycle(p varchar2) 2 as 3 cursor c_postype is select pos_type, description from pos_type_tbl where rownum < 6; 4 v_postype varchar2(20); 5 v_description varchar2(50); 6 begin 7 open c_postype; 8 if c_postype%found then 9 dbms_output.put_line('found true'); 10 elsif c_postype%found = false then 11 dbms_output.put_line('found false'); 12 else 13 dbms_output.put_line('found null'); 14 end if; 15 loop 16 fetch c_postype into v_postype,v_description ; 17 exit when c_postype%notfound; 18 dbms_output.put_line('postype:'||v_postype||',description:'||v_description); 19 end loop; 20 close c_postype; 21 dbms_output.put_line('---loop end---'); 22 open c_postype; 23 fetch c_postype into v_postype,v_description; 24 while c_postype%found loop 25 dbms_output.put_line('postype:'||v_postype||',description:'||v_description); 26 fetch c_postype into v_postype,v_description ; 27 end loop; 28 29 close c_postype; 30 dbms_output.put_line('---while end---'); 31 for v_pos in c_postype loop 32 v_postype := v_pos.pos_type; 33 v_description := v_pos.description; 34 dbms_output.put_line('postype:'||v_postype||',description:'||v_description); 35 end loop; 36 dbms_output.put_line('---for end---'); 37 end;
使用游标之前需要开打游标,open cursor,循环完后再关闭游标close cursor.
这是使用游标应该慎记于心的法则。
上面的过程演示了游标循环的三种方法。
在讨论循环方法之前,我们先看看%found和%notfound这些游标的属性。1 open c_postype; 2 if c_postype%found then 3 dbms_output.put_line('found true'); 4 elsif c_postype%found = false then 5 dbms_output.put_line('found false'); 6 else 7 dbms_output.put_line('found null'); 8 end if;在打开一个游标之后,马上检查它的%found或%notfound属性,它得到的结果即不是true也不是false.而是null.必须执行一条fetch语句后,这些属性才有值。
第一种使用loop 循环
1 loop 2 fetch c_postype into v_postype,v_description ; 3 exit when c_postype%notfound; 4 …… 5 end loop
这里需要注意,exit when语句一定要紧跟在fetch之后。必避免多余的数据处理。
处理逻辑需要跟在exit when之后。这一点需要多加小心。
循环结束后要记得关闭游标。第二种使用while循环。
1 fetch c_postype into v_postype,v_description; 2 while c_postype%found loop 3 …… 4 fetch c_postype into v_postype,v_description ; 5 end loop;我们知道了一个游标打开后,必须执行一次fetch语句,游标的属性才会起作用。所以使用while 循环时,就需要在循环之前进行一次fetch动作。
而且数据处理动作必须放在循环体内的fetch方法之前。循环体内的fetch方法要放在最后。否则就会多处理一次。这一点也要非常的小心。
总之,使用while来循环处理游标是最复杂的方法。第三种 for循环
.1 for v_pos in c_postype loop 2 v_postype := v_pos.pos_type; 3 v_description := v_pos.description; 4 … 5 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这种异常,没有严重到要让程序中断的地步,可以完全交给由程序进行处理。1 create or replace procedure procexception(p varchar2) 2 as 3 v_postype varchar2(20); 4 begin 5 select pos_type into v_postype from pos_type_tbl where 1=0; 6 dbms_output.put_line(v_postype); 7 end;执行这个过程
1 SQL> exec procexception('a'); 2 报错 3 ORA-01403: no data found 4 ORA-06512: at "LIFEMAN.PROCEXCEPTION", line 6 5 ORA-06512: at line 1 6处理这个有三个办法
1. 直接加上异常处理。1 create or replace procedure procexception(p varchar2) 2 as 3 v_postype varchar2(20); 4 5 begin 6 select pos_type into v_postype from pos_type_tbl where 1=0; 7 dbms_output.put_line(v_postype); 8 exception 9 when no_data_found then 10 dbms_output.put_line('没找到数据'); 11 end;这样做换汤不换药,程序仍然被中断。可能这样不是我们所想要的。
2. select into做为一个独立的块,在这个块中进行异常处理1 create or replace procedure procexception(p varchar2) 2 as 3 v_postype varchar2(20); 4 5 begin 6 begin 7 select pos_type into v_postype from pos_type_tbl where 1=0; 8 dbms_output.put_line(v_postype); 9 exception 10 when no_data_found then 11 v_postype := ''; 12 end; 13 dbms_output.put_line(v_postype); 14 end;这是一种比较好的处理方式了。不会因为这个异常而引起程序中断。
3.使用游标1 create or replace procedure procexception(p varchar2) 2 as 3 v_postype varchar2(20); 4 cursor c_postype is select pos_type from pos_type_tbl where 1=0; 5 begin 6 open c_postype; 7 fetch c_postype into v_postype; 8 close c_postype; 9 dbms_output.put_line(v_postype); 10 end;
这样就完全的避免了no_data_found异常。完全交由程序员来进行控制了。
第二种情况是too_many_rows 异常的问题。
Too_many_rows 这个问题比起no_data_found要复杂一些。
给一个变量赋值时,但是查询结果有多个记录。
处理这种问题也有两种情况:
1. 多条数据是可以接受的,也就是说从结果集中随便取一个值就行。这种情况应该很极端了吧,如果出现这种情况,也说明了程序的严谨性存在问题。
2. 多条数据是不可以被接受的,在这种情况肯定是程序的逻辑出了问题,也说是说原来根本就不会想到它会产生多条记录。
对于第一种情况,就必须采用游标来处理,而对于第二种情况就必须使用内部块来处理,重新抛出异常。
多条数据可以接受,随便取一条,这个跟no_data_found的处理方式一样,使用游标。
我这里仅说第二种情况,不可接受多条数据,但是不要忘了处理no_data_found哦。这就不能使用游标了,必须使用内部块。1 create or replace procedure procexception2(p varchar2) 2 as 3 v_postype varchar2(20); 4 5 begin 6 begin 7 select pos_type into v_postype from pos_type_tbl where rownum < 5; 8 exception 9 when no_data_found then 10 v_postype :=null; 11 when too_many_rows then 12 raise_application_error(-20000,'对v_postype赋值时,找到多条数据'); 13 end; 14 dbms_output.put_line(v_postype); 15 end;
需要注意的是一定要加上对no_data_found的处理,对出现多条记录的情况则继续抛出异常,让上一层来处理。
总之对于select into的语句需要注意这两种情况了。需要妥当处理啊。
3.5 在存储过程中返回结果集
我们使用存储过程都是返回值都是单一的,有时我们需要从过程中返回一个集合。即多条数据。这有几种解决方案。比较简单的做法是写临时表,但是这种做法不灵活。而且维护麻烦。我们可以使用嵌套表来实现.没有一个集合类型能够与java的jdbc类型匹配。这就是对象与关系数据库的阻抗吧。数据库的对象并不能够完全转换为编程语言的对象,还必须使用关系数据库的处理方式。
1 create or replace package procpkg is 2 type refcursor is ref cursor; 3 procedure procrefcursor(p varchar2, p_ref_postypeList out refcursor); 4 end procpkg; 5 6 create or replace package body procpkg is 7 procedure procrefcursor(p varchar2, p_ref_postypeList out refcursor) 8 is 9 v_posTypeList PosTypeTable; 10 begin 11 v_posTypeList :=PosTypeTable();--初始化嵌套表 12 v_posTypeList.extend; 13 v_posTypeList(1) := PosType('A001','客户资料变更'); 14 v_posTypeList.extend; 15 v_posTypeList(2) := PosType('A002','团体资料变更'); 16 v_posTypeList.extend; 17 v_posTypeList(3) := PosType('A003','受益人变更'); 18 v_posTypeList.extend; 19 v_posTypeList(4) := PosType('A004','续期交费方式变更'); 20 open p_ref_postypeList for select * from table(cast (v_posTypeList as PosTypeTable)); 21 end; 22 end procpkg;
在包头中定义了一个游标变量,并把它作为存储过程的参数类型。
在存储过程中定义了一个嵌套表变量,对数据写进嵌套表中,然后把嵌套表进行类型转换为table,游标变量从这个嵌套表中进行查询。外部程序调用这个游标。
所以这个过程需要定义两个类型。1 create or replace type PosType as Object ( 2 posType varchar2(20), 3 description varchar2(50) 4 );create or replace type PosTypeTable is table of PosType;
需要注意,这两个类型不能定义在包头中,必须单独定义,这样java层才能使用。
在外部通过pl/sql来调用这个过程非常简单。1 set serveroutput on; 2 declare 3 type refcursor is ref cursor; 4 v_ref_postype refcursor; 5 v_postype varchar2(20); 6 v_desc varchar2(50); 7 begin 8 procpkg.procrefcursor('a',v_ref_postype); 9 loop 10 fetch v_ref_postype into v_postype,v_desc; 11 exit when v_ref_postype%notfound; 12 dbms_output.put_line('posType:'|| v_postype || ';description:' || v_desc); 13 end loop; 14 end;
注意:对于游标变量,不能使用for循环来处理。因为for循环会隐式的执行open动作。而通过open for来打开的游标%isopen是为true的。也就是默认打开的。Open一个已经open的游标是错误的。所以不能使用for循环来处理游标变量。
我们主要讨论的是如何通过jdbc调用来处理这个输出参数。1 conn = this.getDataSource().getConnection(); 2 CallableStatement call = conn.prepareCall("{call procpkg.procrefcursor(?,?)}"); 3 call.setString(1, null); 4 call.registerOutParameter(2, OracleTypes.CURSOR); 5 call.execute(); 6 ResultSet rsResult = (ResultSet) call.getObject(2); 7 while (rsResult.next()) { 8 String posType = rsResult.getString("posType"); 9 String description = rsResult.getString("description"); 10 ...... 11 }
这就是jdbc的处理方法。
Ibatis处理方法:
1.参数配置
1 <parameterMap id="PosTypeMAP" class="java.util.Map"> 2 <parameter property="p" jdbcType="VARCHAR" javaType="java.lang.String" /> 3 <parameter property="p_ref_postypeList" jdbcType="ORACLECURSOR" javaType="java.sql.ResultSet" mode="OUT" typeHandler="com.palic.elis.pos.dayprocset.integration.dao.impl.CursorHandlerCallBack" /> 4 </parameterMap> 5 6 2.调用过程 7 <procedure id ="procrefcursor" parameterMap ="PosTypeMAP"> 8 {call procpkg.procrefcursor(?,?)} 9 </procedure> 10 11 3.定义自己的处理器 12 public class CursorHandlerCallBack implements TypeHandler{ 13 public Object getResult(CallableStatement cs, int index) throws SQLException { 14 ResultSet rs = (ResultSet)cs.getObject(index); 15 List result = new ArrayList(); 16 while(rs.next()) { 17 String postype =rs.getString(1); 18 String description = rs.getString(2); 19 CodeTableItemDTO posTypeItem = new CodeTableItemDTO(); 20 posTypeItem.setCode(postype); 21 posTypeItem.setDescription(description); 22 result.add(posTypeItem); 23 } 24 return result; 25 } 26 27 28 29 4. dao方法 30 public List procPostype() { 31 String p = ""; 32 Map para = new HashMap(); 33 para.put("p",p); 34 para.put("p_ref_postypeList",null); 35 this.getSqlMapClientTemplate().queryForList("pos_dayprocset.procrefcursor", para); 36 return (List)para.get("p_ref_postypeList"); 37 }
这个跟jdbc的方式非常的相似.
我们使用的是ibatis的2.0版本,比较麻烦。
如果是使用2.2以上版本就非常简单的。
因为可以在parameterMap中定义一个resultMap.这样就无需要自己定义处理器了。
可以从分析2.0和2.0的dtd文件知道。
上面的两种方式都是非常的复杂,如果仅仅是需要返回一个结果集,那就完全可以使用函数来实现了。 .1 create or replace package procpkg is 2 type refcursor is ref cursor; 3 procedure procrefcursor(p varchar2, p_ref_postypeList out refcursor); 4 function procpostype(p varchar2) return PosTypeTable; 5 end procpkg; 6 7 create or replace package body procpkg is 8 procedure procrefcursor(p varchar2, p_ref_postypeList out refcursor) 9 is 10 v_posTypeList PosTypeTable; 11 begin 12 v_posTypeList :=PosTypeTable();--初始化嵌套表 13 v_posTypeList.extend; 14 v_posTypeList(1) := PosType('A001','客户资料变更'); 15 v_posTypeList.extend; 16 v_posTypeList(2) := PosType('A002','团体资料变更'); 17 v_posTypeList.extend; 18 v_posTypeList(3) := PosType('A003','受益人变更'); 19 v_posTypeList.extend; 20 v_posTypeList(4) := PosType('A004','续期交费方式变更'); 21 open p_ref_postypeList for select * from table(cast (v_posTypeList as PosTypeTable)); 22 end; 23 24 function procpostype(p varchar2) return PosTypeTable 25 as 26 v_posTypeList PosTypeTable; 27 begin 28 v_posTypeList :=PosTypeTable();--初始化嵌套表 29 v_posTypeList.extend; 30 v_posTypeList(1) := PosType('A001','客户资料变更'); 31 v_posTypeList.extend; 32 v_posTypeList(2) := PosType('A002','团体资料变更'); 33 v_posTypeList.extend; 34 v_posTypeList(3) := PosType('A003','受益人变更'); 35 v_posTypeList.extend; 36 v_posTypeList(4) := PosType('A004','续期交费方式变更'); 37 return v_posTypeList; 38 end; 39 end procpkg;
ibatis配置
1 <resultMap id="posTypeResultMap" class="com.palic.elis.pos.common.dto.CodeTableItemDTO"> 2 <result property="code" column="posType"/> 3 <result property="description" column="description"/> 4 </resultMap> 5 6 <select id="procPostype" resultMap="posTypeResultMap"> 7 select * from table(cast (procpkg.procpostype(#value#) as PosTypeTable)) 8 </select>
Dao的写法跟普通查询一样
1 public List queryPostype() { 2 return this.getSqlMapClientTemplate().queryForList("pos_dayprocset.procPostype", null); 3 }
有几点需要注意,这里不能使用索引表,而是嵌套表。
另外就是把嵌套表强制转换为普通表。
4:Oracle动态游标实现动态SQL循环遍历,和静态游标的比较。
注: 本文 来源与:《 Oracle动态游标实现动态SQL循环遍历,和静态游标的比较。 》
动态游标可以遍历动态的表,
格式:
如果查询的表的数据不同的,动态变化的,这时候可以用动态游标。
需要注意的是,动态游标的定义,
在普通存储过程中:需要放在 is 后面的第一行。
动态游标通过:open 游标 for 字符串,形式使用,遍历。
1 create or replace procedure P_TEST_SQL 2 is 3 TYPE ref_cursor_type IS REF CURSOR; --定义一个动态游标 4 tablename varchar2(200) default 'ess_client'; 5 v_sql varchar2(1000); 6 mobile varchar2(15); 7 usrs ref_cursor_type; 8 begin 9 --使用连接符拼接成一条完整SQL 10 v_sql := 'select usrmsisdn from '||tablename||' where rownum < 11'; 11 --打开游标 12 open usrs for v_sql ; 13 loop 14 fetch usrs into mobile; 15 exit when usrs%notfound; 16 insert into tmp(usrmsisdn) values(mobile); 17 end loop; 18 close usrs; 19 commit; 20 end P_TEST_SQL;下面是一个自己做过的一个实例,代码只保留了可参考部分。
1 create or replace procedure DDGZ1--订单跟踪 2 ( 3 P_flag varchar,--订单类型 4 P_operate varchar,--操作类型 5 P_BH varchar --传入的编号ID 6 ) 7 is 8 TYPE ref_cursor_type IS REF CURSOR; 9 v_cur_CKD ref_cursor_type; 10 11 begin 12 open v_cur_CKD for 'select v_ERP,v_DRP,v_jxsmc,v_jxs,v_DWMC,v_czr,v_jlsj from tmp_DDGZ1'; 13 fetch v_cur_CKD into v_ERP,v_DRP,v_jxsmc,v_jxs,v_DWMC,v_czr,v_jzsj; 14 while v_cur_CKD%found 15 loop 16 insert into DRPDDGZJL2 (JLID,DRP,ERP,JXS,JXSMC,JLSJ,GZJL,CZR,BHID) values(SYS_GUID(),v_drp,v_erp,v_jxs,v_jxsmc,v_jzsj,v_DWMC||'受托代销成品库接收订单,组织发货',v_CZR,'出库单'||P_BH); 17 fetch v_cur_CKD into v_ERP,v_DRP,v_jxsmc,v_jxs,v_DWMC,v_czr,v_jzsj; 18 end loop; 19 close v_cur_CKD; 20 end;
而对于包头包体的存储过程。经常是如下形式:
在包头中:
1 CREATE OR REPLACE PACKAGE BIA_FYJSDCX 2 3 is --发运结算单查询 4 type T_CURSOR is ref cursor; 5 procedure ERP_HY_FYJSDCX 6 ( 7 Re_CURSOR out T_CURSOR 8 ); 9 end BIA_FYJSDCX;包体中:
1 procedure ERP_HY_FYJSDCX 2 ( 3 Re_CURSOR out T_CURSOR 4 ) 5 begin 6 v_sql:='select * from T_FYJSDCX4'; 7 Open Re_CURSOR For v_sql; 8 9 end;
3-7:oracle存储过程异常捕获
注: 本段内容来自于:《 oracle存储过程异常捕获 》
oracle存储过程异常捕获学习,执行及演示过程:
存储过程:
1 CREATE OR REPLACE PROCEDURE sp_test_2 2 ( 3 param1 in int, --输入参数 4 param2 in int, 5 out_return out varchar2 --返回结果 6 ) 7 is 8 --全局变量 9 val int; 10 errorException exception; --申明异常 11 errorCode number; --异常代号 12 errorMsg varchar2(1000); --异常信息 13 flag varchar2(10); 14 begin 15 flag := 'true'; 16 out_return := 'flag=' || flag || ',errorCode=' || errorCode || ',errorMsg=' || errorMsg; 17 val := param1/param2; 18 --/* 19 exception 20 when errorException then 21 errorCode := SQLCODE; 22 errorMsg := SUBSTR(SQLERRM, 1, 200); 23 flag := 'false'; 24 out_return := 'flag=' || flag || ',errorCode=' || errorCode || ',errorMsg=' || errorMsg; 25 when others then 26 errorCode := SQLCODE; 27 errorMsg := SUBSTR(SQLERRM, 1, 200); 28 flag := 'false'; 29 out_return := 'flag=' || flag || ',errorCode=' || errorCode || ',errorMsg=' || errorMsg; 30 --dbms_output.put_line(errorCode || ',' || errorMsg); 31 --*/ 32 end sp_test_2;演示存储过程:
1 DECLARE 2 out_return varchar2(1000); 3 val int; --全局变量 4 errorException exception; --申明异常 5 errorCode number; --异常编码 6 errorMsg varchar2(1000); --异常信息 7 flag varchar2(10); 8 begin 9 flag := 'true'; 10 out_return := 'flag=' || flag || ',errorCode=' || errorCode || ',errorMsg=' || errorMsg; 11 val := 1/0; 12 exception --异常捕捉,不要把有需要的代码放在异常捕捉后面,有异常才会执行异常代码下所有代码,没有异常不会执行 13 when errorException then 14 errorCode := SQLCODE; 15 errorMsg := SUBSTR(SQLERRM, 1, 200); 16 flag := 'false'; 17 out_return := 'flag=' || flag || ',errorCode=' || errorCode || ',errorMsg=' || errorMsg; 18 when others then 19 errorCode := SQLCODE; 20 errorMsg := SUBSTR(SQLERRM, 1, 200); 21 flag := 'false'; 22 out_return := 'flag=' || flag || ',errorCode=' || errorCode || ',errorMsg=' || errorMsg; 23 24 dbms_output.put_line(out_return); 25 end;sqlplus中执行存储过程:
1 DECLARE 2 out_return varchar2(1000); 3 begin 4 sp_test_2(1,0,out_return); 5 dbms_output.put_line(out_return); --打印结果 6 end;
执行存储过程 1除以0 结果:
oracle 之定义数组类型
注:本文来源:《oracle 之定义数组类型 》
oracle 数组类型,没有现成的类型,但是可以自己随意定义,很方便。
Oracle 数组可以分为定长数组和可变长的数组两类。以下主要是一维数组介绍:
1:定长数组:
1 /*定长字符数组,数组大小为10*/ 2 declare 3 type v_arr is varray(10) of varchar2(30); 4 my_arr v_arr; 5 my_arr:=v_arr('1','2','3'); 6 begin 7 for i in 1..my_arr.count 8 loop 9 dbms_output_line(my_arr(i)); 10 end loop; 11 end; 12
2:变长数组:
1 /*可变长字符数组,元素大小30,索引标号integer类型自增长*/ 2 declare 3 type v_table is table of varchar2(30) index by binary_integer; 4 --类型可以是前面的类型定义,index by binary_integer子句代表以符号整数为索引, 5 --这样访问表类型变量中的数据方法就是“表变量名(索引符号整数)”。 6 my_table v_table; 7 begin 8 for i in 1..20 9 loop 10 my_table(i):=i; 11 dbms_output.put_line(my_table(i)); 12 end loop; 13 end; 14
Oracle存储过程 数组集合的使用
注:本文来源《 Oracle存储过程 数组集合的使用 》
1 说明
1.1 RECORD
定义记录数据类型。它类似于C语言中的结构数据类型(STRUCTURE),PL/SQL提供了将几个相关的、分离的、基本数据类型的变量组成一个整体的方法,即RECORD复合数据类型。在使用记录数据类型变量时,需要在声明部分先定义记录的组成、记录的变量,然后在执行部分引用该记录变量本身或其中的成员。
定义记录数据类型的语法如下:
1 TYPE RECORD_NAME IS RECORD( 2 3 V1 DATA_TYPE1 [NOT NULL][:=DEFAULT_VALUE], 4 5 V2 DATA_TYPE2 [NOT NULL][:=DEFAULT_VALUE], 6 7 VN DATA_TYPEN [NOT NULL][:=DEFAULT_VALUE]); 81.2 VARRAY
数组是具有相同数据类型的一组成员的集合。每个成员都有一个唯一的下标,它取决于成员在数组中的位置。在PL/SQL中,数组数据类型是VARRAY(variable array,即可变数组)。
定义VARRAY数据类型的语法如下:
其中,varray_name是VARRAY数据类型的名称,size是正整数,表示可以容纳的成员的最大数量,每个成员的数据类型是element_typeo默认时,成员可以取空值,否则需要使用NOT NULL加以限制。
1.3 TABLE
定义记录表(或索引表)数据类型。它与记录类型相似,但它是对记录类型的扩展。它可以处理多行记录,类似于C语言中的二维数组,使得可以在PL/SQL中模仿数据库中的表。
定义记录表类型的语法如下:
1 TYPE TABLE NAME IS TABLE OF ELEMENT_TYPE [NOT NULL] 2 3 INDEX BY [BINARY_INTEGER|PLS_INTEGER|VARRAY2]; 4关键字INDEX BY表示创建一个主键索引,以便引用记录表变量中的特定行。
BINARY_INTEGER的说明
如语句:TYPE NUMBERS IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;其作用是,加了”INDEX BYBINARY_INTEGER ”后,NUMBERS类型的下标就是自增长,NUMBERS类型在插入元素时,不需要初始化,不需要每次EXTEND增加一个空间。
而如果没有这句话“INDEXBY BINARY_INTEGER”,那就得要显示对初始化,且每插入一个元素到NUMBERS类型的TABLE中时,都需要先EXTEND。
2 举例
2.1 创建表结构以及数据准备
1 --组织机构结构表 2 CREATE TABLE SF_ORG 3 ( 4 ORG_ID INT NOT NULL, --组织机构主键ID 5 ORG_NAME VARCHAR2(50),--组织机构名称 6 PARENT_ID INT--组织机构的父级 7 ) 8 9 --一级组织机构 10 INSERT INTO SF_ORG(ORG_ID, ORG_NAME, PARENT_ID) VALUES(1, '一级部门1',0); 11 12 --二级部门 13 14 INSERT INTO SF_ORG(ORG_ID, ORG_NAME, PARENT_ID) VALUES(2, '二级部门2',1); 15 INSERT INTO SF_ORG(ORG_ID, ORG_NAME, PARENT_ID) VALUES(3, '二级部门3',1); 16 INSERT INTO SF_ORG(ORG_ID, ORG_NAME, PARENT_ID) VALUES(4, '二级部门4',1);
2.2 RECORD的使用举例
先定义一个只与SF_ORG表中某几个列的数据类型相同的记录数据类型TYPE_ORG_RECORD,然后声明一个该数据类型的记录变量V_ORG_RECORD,最后用替换变量&ORG_ID接受输入的雇员编码,查询并显示该雇员的这几列中的信息。注意,在使用RECORD数据类型的变量时要用“.”运算符指定记录变量名限定词。
一个记录类型的变量只能保存从数据库中查询出的一行记录,如果查询出了多行记录,就会出现错误。
1 DECLARE 2 TYPE TYPE_ORG_RECORD IS RECORD( 3 V_ORG_NAME SF_ORG.ORG_NAME%TYPE, 4 V_PARENT_ID SF_ORG.PARENT_ID%TYPE); 5 V_ORG_RECORD TYPE_ORG_RECORD; 6 BEGIN 7 SELECT ORG_NAME,PARENT_ID INTO V_ORG_RECORD 8 FROM SF_ORG SO 9 WHERE SO.ORG_ID=&ORG_ID; 10 DBMS_OUTPUT.PUT_LINE('部门名称:' || V_ORG_RECORD.V_ORG_NAME); 11 DBMS_OUTPUT.PUT_LINE('上级部门编码:' || TO_CHAR(V_ORG_RECORD.V_PARENT_ID)); 12 END;
2.3 VARRAY的使用举例
先定义一个能保存5个VARCHAR2(25)数据类型的成员的VARRAY数据类型ORG_VARRAY_TYPE,然后声明一个该数据类型的VARRAY变量V_ORG_VARRAY,最后用与ORG_VARRAY_TYPE数据类型同名的构造函数语法给V_ORG_VARRAY变量赋予初值并显示赋值结果。
注意,在引用数组中的成员时.需要在一对括号中使用顺序下标,下标从1开始而不是从0开始。
1 DECLARE 2 TYPE ORG_VARRAY_TYPE IS VARRAY(5) OF VARCHAR2(25); 3 V_ORG_VARRAY ORG_VARRAY_TYPE; 4 BEGIN 5 V_ORG_VARRAY := ORG_VARRAY_TYPE('1','2','3','4','5'); 6 DBMS_OUTPUT.PUT_LINE('输出1:' || V_ORG_VARRAY(1) || '、'|| V_ORG_VARRAY(2) || '、'|| V_ORG_VARRAY(3) || '、'|| V_ORG_VARRAY(4)); 7 DBMS_OUTPUT.PUT_LINE('输出2:' || V_ORG_VARRAY(5)); 8 V_ORG_VARRAY(5) := '5001'; 9 DBMS_OUTPUT.PUT_LINE('输出3:' || V_ORG_VARRAY(5)); 10 END;
2.4 TABLE使用举例
2.4.1 存储单列多行
这个和VARRAY类似。但是赋值方式稍微有点不同,不能使用同名的构造函数进行赋值。具体的如下:
1 DECLARE 2 TYPE ORG_TABLE_TYPE IS TABLE OF VARCHAR2(25) 3 INDEX BY BINARY_INTEGER; 4 V_ORG_TABLE ORG_TABLE_TYPE; 5 BEGIN 6 V_ORG_TABLE(1) := '1'; 7 V_ORG_TABLE(2) := '2'; 8 V_ORG_TABLE(3) := '3'; 9 V_ORG_TABLE(4) := '4'; 10 V_ORG_TABLE(5) := '5'; 11 DBMS_OUTPUT.PUT_LINE('输出1:' || V_ORG_TABLE(1) || '、'|| V_ORG_TABLE(2) || '、'|| V_ORG_TABLE(3) || '、'|| V_ORG_TABLE(4)); 12 DBMS_OUTPUT.PUT_LINE('输出2:' || V_ORG_TABLE(5)); 13 END;
2.4.2 存储多列多行和ROWTYPE结合使用
采用bulkcollect可以将查询结果一次性地加载到collections中。而不是通过cursor一条一条地处理。
1 DECLARE 2 TYPE T_TYPE IS TABLE OF SF_ORG%ROWTYPE; 3 V_TYPE T_TYPE; 4 BEGIN 5 SELECT ORG_ID,ORG_NAME,PARENT_ID BULK COLLECT INTO V_TYPE 6 FROM SF_ORG 7 WHERE SF_ORG.ORG_ID <= 3; 8 9 FOR V_INDEX IN V_TYPE.FIRST .. V_TYPE.LAST LOOP 10 DBMS_OUTPUT.PUT_LINE(V_TYPE(V_INDEX).C1 || ' ' || V_TYPE(V_INDEX).C2); 11 END LOOP; 12 END;
2.4.3 存储多列多行和RECORD结合使用
采用bulkcollect可以将查询结果一次性地加载到collections中。而不是通过cursor一条一条地处理。
1 DECLARE 2 TYPE TEST_EMP IS RECORD 3 ( 4 C1 SF_ORG.ORG_NAME%TYPE, 5 C2 SF_ORG.PARENT_ID%TYPE 6 ); 7 TYPE T_TYPE IS TABLE OF TEST_EMP; 8 V_TYPE T_TYPE; 9 BEGIN 10 SELECT ORG_NAME, PARENT_ID BULK COLLECT INTO V_TYPE 11 FROM SF_ORG 12 WHERE SF_ORG.ORG_ID <= 3; 13 14 FOR V_INDEX IN V_TYPE.FIRST .. V_TYPE.LAST LOOP 15 DBMS_OUTPUT.PUT_LINE(V_TYPE(V_INDEX).C1 || ' ' || V_TYPE(V_INDEX).C2); 16 END LOOP; 17 END;
Oracle存储过程自定义数组定义与使用
最近为公司项目数据库通过存储过程做归档,需要用到自定义数组
百度结果中很多写的都不是很清晰,可变长数组定义好后,如何使用。
在此做个记录:
定义:
type id_array is table of number(15) index by binary_integer;
acb_ids id_array;
这个定义方式适用在package,如果是纯粹在存储过程中自定义类型请百度 。
使用:
acb_ids(acb_ids.count+1) := c_account_books_cbs_rec.acb_id;
网上很多实例是这样的:acb_ids(acb_ids.count) := c_account_books_cbs_rec.acb_id;这样写是没有用的,因为这个时候acb_ids是空的,那么acb_ids.count也是无效的,acb_ids在使用时回会报"未找到任何数据",因此需要acb_ids.count+1。
c_account_books_cbs_rec为游标遍历中的一个对象,上述代码意思是将游标数据中每行记录的ID放到自定义数组中,方便存储过程的值返回或者游标遍历外程序体中使用。
for x in 1 .. acb_ids.count loop
do something;
end loop;
存储过程中定义参数类型为数组
注意:本文来源:《存储过程中定义参数类型为数组》
1:存储过程
1 Procedure Update_Batch_Id(p_entity_id in Number, 2 p_vdr_id In fnd_table_of_number) is 3 begin 4 5 For i In 1 .. p_vdr_id.COUNT Loop 6 update cux_table_header cvs 7 set cvs.attribute10 = p_entity_id 8 where cvs.header_id = p_vdr_id(i); 9 End Loop; 10 end;
2:JAVA中调用
1 List list = new ArrayList(); 2 ... 3 list.add(row.getHeaderId()); 4 ... 5 6 7 OracleCallableStatement statement = null; 8 OracleConnection oracleConnection = (OracleConnection)tsn.getJdbcConnection(); 9 int size = list.size(); 10 if (size>0) 11 { 12 Number[] vdrIdArray = (Number[])list.toArray(new Number[size]); 13 ARRAY vdrArray=null; 14 try { 15 ArrayDescriptor tableOfNumber = 16 oracle.sql.ArrayDescriptor.createDescriptor("FND_TABLE_OF_NUMBER", 17 oracleConnection); 18 vdrArray = new ARRAY(tableOfNumber, oracleConnection, vdrIdArray); 19 String sql = 20 "BEGIN cux_XXXXXXX_pkg.Update_Batch_Id(:1,:2);end;"; 21 statement = (OracleCallableStatement)oracleConnection.prepareCall(sql); 22 23 statement.setObject(1, batchid); 24 statement.setARRAY(2, vdrArray); 25 statement.execute(); 26 }catch (Exception ex) { 27 String[][] stra2 = { { "123456wewee", ex.getMessage() }, }; 28 LogUtil.of(stra2, this).print(pageContext); 29 ex.printStackTrace(); 30 System.out.println(ex.getMessage()); 31 } 32 }
——————————————————————————————————————————————————————————————————————————————————————————
二:函数
1:创建函数
1 create or replace function get_avg_pay(num_deptno number )return number is /*创建一个函数,该函数实现计算某个部门的平均工资,传入部门编号参数*/ 2 num_avg_pay number; /*保存平均工资的内部变量*/ 3 begin 4 select avg(sal) into num_avg_pay from emp where deptno=num_deptno;/*某个部门的平均工资*/ 5 return (round(num_avg_pay)); 6 exception 7 when no_data_found then /*如果此部门编号不存在*/ 8 dbms_output.put_line('该部门编号不存在'); 9 return(0); /*返回平均工资0*/ 10 end; 11 /
2:调用函数
3:删除函数
三:触发器
1:触发器钙素
2:语句级触发器
1 2 /*在scott模式下 创建dept_log数据表,并在其中定义连个字段,分别用来 3 储存操作种类信息和操作日期*/ 4 create table dept_log 5 ( 6 operate_tag varchar2(10),/*定义字段,储存操作种类信息*/ 7 operate_time date /*定义字段,储存操作日期*/ 8 ); 9 10 111 2 /*创建一个触发器 tri_dept,该触发器在 insert、update、delete 事件下 3 都可以被触发,并且操作的数据对象是dept,要求在触发器执行时输出对dept表所做的具体操作*/ 4 create or replace trigger tri_dept 5 before insert or update or delete 6 on dept /*创建触发器,当dept表发生插入、修改、删除等操作时引起的触发器执行*/ 7 declare 8 var_tag varchar2(10); 9 begin 10 if inserting then /*当触发器事件是 insert 时*/ 11 var_tag:='插入'; 12 elsif updating then /*当触发器事件是 update 时*/ 13 var_tag:='修改'; 14 elsif deleting then /*当触发事件是delete时*/ 15 var_tag:='删除'; 16 end if; 17 insert into dept_log values(var_tag,sysdate); 18 end tri_dept; 19 /1 insert into dept values(66,'adfasdf','fsdafd'); 2 update dept set loc='w235f' where deptno=66; 3 delete from dept where deptno=66; 4
3:行级别触发器
1 /*在scott模式下,创建一个用于储存商品种类的数据表,其中包括商品序号和商品名称*/ 2 create table goods( 3 id int primary key, 4 good_name varchar2(50) 5 );1 2 /*使用create sequence语句创建一个序列,命名为seq_id */ 3 create sequence seq_id; 41 /*创建一个行级别触发器,该触发器在数据表goods插入数据时被触发,并且在该触发器的主体中实现设置 2 goods表的id列的值。*/ 3 create or replace trigger tri_insert_good 4 before insert 5 on goods /*关于goods 数据表,在向其插入新记录之前,引发该触发器的运行*/ 6 for each row /*创建行触发器*/ 7 begin 8 select seq_id.nextval into :new.id from dual; /*从序列号中生成 一个新的数值,赋值给当前插入的行的id*/ 9 end; 10 / 11
4:替换触发器
1 With the Partitioning, OLAP, Data Mining and Real Application Testing options 2 [oracle@localhost ~]$ sqlplus / as sysdba; 3 4 SQL*Plus: Release 11.2.0.3.0 Production on Sun Jan 7 16:38:32 2018 5 6 Copyright (c) 1982, 2011, Oracle. All rights reserved. 7 8 9 Connected to: 10 Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - Production 11 With the Partitioning, OLAP, Data Mining and Real Application Testing options 12 13 SYS@orcl> alter user system identified by oracle; 14 15 User altered. 16 17 SYS@orcl> conn system/oracle; 18 Connected. 19 SYSTEM@orcl> grant create view to scott; 20 21 Grant succeeded. 22 23 SYSTEM@orcl> conn scott/scott; 24 Connected. 25 SQL> create view view_emp_dept 26 2 as 27 3 select empno,ename,dept.deptno,dept.dname,job,hiredate from emp,dept where emp.deptno=dept.deptno; 28 29 View created 301 2 create or replace trigger tri_insert_view 3 instead of insert 4 on view_emp_dept /*创建一个关于 view_emp_dept视图的替换触发器*/ 5 for each row /*行级别视图*/ 6 declare 7 row_dept dept%rowtype; 8 begin 9 select * into row_dept from dept where deptno =:new.deptno;/*查询指定部门编号的记录行*/ 10 if sql%notfound then 11 insert into dept(deptno,dname) values(:new.deptno,:new.dname);/*向dept表中插入数据*/ 12 end if; 13 insert into emp(empno,ename,deptno,job,hiredate) 14 values(:new.empno,:new.ename,:new.deptno,:new.job,:new.hiredate);/*向emp表中插入数据*/ 15 end tri_insert_view; 16 / 17
5:用户事件触发器
1 2 /* 3 常见用户事件: 4 create 、alter 、drop、analyze、comment、grant、revoke、rename、truncate、uspend、logon、logoff 5 使用create table 语句创建一个日志信息表,该表保存的日志信息包括数据对象、数据对象类型、操作行为、操作用户 等操作日期等。 6 */ 7 create table ddl_oper_log( 8 db_obj_name varchar2(20),/*数据对象名称*/ 9 db_obj_type varchar2(20),/*数据对象类型*/ 10 oper_action varchar2(20),/*操作行为*/ 11 oper_user varchar2(20),/*操作用户*/ 12 oper_date date /*操作日期*/ 13 );1 create or replace trigger tri_ddl_oper 2 3 /*关于scott用户的ddl 操作,(这里包括 create alter drop ) 4 创建一个触发器,然后讲DDL操作的相关信息插入到 ddl_oper_log 日志表中*/ 5 before create or alter or drop 6 on scott.schema /*在scott模式下,在创建、修改、删除数据库对象之前将引发触发器运行*/ 7 begin 8 insert into ddl_oper_log values( 9 ora_dict_obj_name,/*操作的数据对象名称*/ 10 ora_dict_obj_type ,/*操作的数据对象类型 */ 11 ora_sysevent,/*系统事件名称*/ 12 ora_login_user,/*登录用户*/ 13 sysdate ); 14 15 end; 16 /View Code1 SQL> create table tb_test(id number);
2
3 Table created
4
5 SQL> create view view_test as select empno,ename from emp;
6
7 View created
8
9 SQL> alter table tb_test add (name varchar2(10));
10
11 Table altered
12
13 SQL> drop view view_test;
14
15 View dropped
16
17 SQL> select * from ddl_oper_log;
18
19 DB_OBJ_NAME DB_OBJ_TYPE OPER_ACTION OPER_USER OPER_DATE
20 -------------------- -------------------- -------------------- -------------------- -----------
21 TB_TEST TABLE CREATE SCOTT 2018/1/7 21
22 VIEW_TEST VIEW CREATE SCOTT 2018/1/7 21
23 TB_TEST TABLE ALTER SCOTT 2018/1/7 21
24 VIEW_TEST VIEW DROP SCOTT 2018/1/7 21
25
四:程序包
1:程序包的规范
View Code1
2 /*创建一个程序包的规范,首先在该程序包中声明一个可以获取指定部门的平均工资的函数,
3 然后在声明一个可以实现按照指定比例上调职务的工资的储存过程*/
4 create or replace package pack_emp is
5 function fun_avg_sal(num_deptno number) return number;/*获取指定部门的平均工资*/
6 procedure pro_regulate_sal(var_job varchar2,num_proportion number) ;/*按照指定比例上调指定职务的工资*/
7 end pack_emp;
8 /
2:程序包主体
1 create or replace package body pack_emp is 2 function fun_avg_sal(num_deptno number) return number is --引入“规范”中的函数
3 num_avg_sal number;--定义内部变量
4 begin
5 select avg(sal)
6 into num_avg_sal
7 from emp
8 where deptno = num_deptno;--计算某个部门的平均工资
9 return(num_avg_sal);--返回平均工资
10 exception
11 when no_data_found then--若未发现记录
12 dbms_output.put_line('该部门编号不存在雇员记录');
13 return 0;--返回0
14 end fun_avg_sal;
15
16 procedure pro_regulate_sal(var_job varchar2,num_proportion number) is--引入“规范”中的存储过程
17 begin
18 update emp
19 set sal = sal*(1+num_proportion)
20 where job = var_job;--为指定的职务调整工资
21 end pro_regulate_sal;
22 end pack_emp;
23 /
24
25
26
27
28
29
30
31
32
33
34
35
36
371 set serveroutput on 2 declare 3 num_deptno emp.deptno%type;--定义部门编号变量 4 var_job emp.job%type;--定义职务变量 5 num_avg_sal emp.sal%type;--定义工资变量 6 num_proportion number;--定义工资调整比例变量 7 begin 8 num_deptno:=10;--设置部门编号为10 9 num_avg_sal:=pack_emp.fun_avg_sal(num_deptno);--计算部门编号为10的平均工资 10 dbms_output.put_line(num_deptno||'号部门的平均工资是:'||num_avg_sal);--输出平均工资 11 12 var_job:='SALESMAN';--设置职务名称 13 num_proportion:=0.1;--设置调整比例 14 pack_emp.pro_regulate_sal(var_job,num_proportion);--调整指定部门的工资 15 end; 16 / 17
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------