环境:centos7.6.1810
归类:database/oracle/11.2.0.4.0/plsql
简介:整理记录编写存储过程需要注意的一些知识点
1.游标
1.1.普通游标
--打印scott.emp雇员表中所有雇员信息(雇员编号和雇员姓名)
create or replace procedure proc_emp_test(out_msg out varchar2)
is
cursor emps is select * from emp;
begin
for emp in emps loop
dbms_output.put_line(emp.empno||'-'||emp.ename);
end loop;
out_msg := 'OK';
exception
when others then
out_msg := sqlcode || '-' || sqlerrm;
end proc_emp_test;
--执行存储过程
declare
v_out_msg varchar2(1000);
begin
proc_emp_test(v_out_msg);
end;
- 游标不可以在begin之后定义
1.2.嵌套游标
--打印scott.emp雇员表中所有雇员及部门信息(雇员编号、雇员姓名、部门编号、部门名称)
create or replace procedure proc_emp_test2(out_msg out varchar2)
is
cursor emps is select * from emp;
cursor depts is select * from dept;
begin
for emp in emps loop
for dept in depts loop
if emp.deptno = dept.deptno then
dbms_output.put_line(emp.empno || '-' || emp.ename || ',' || dept.deptno || '-' || dept.dname);
end if;
end loop;
end loop;
out_msg := 'OK';
exception
when others then
out_msg := sqlcode || '-' || sqlerrm;
end proc_emp_test2;
--执行存储过程
declare
v_out_msg varchar2(1000);
begin
proc_emp_test2(v_out_msg);
end;
1.3.带参游标
--根据输入的雇员编号打印scott.emp雇员表中所有雇员信息(雇员编号、雇员姓名)
create or replace procedure proc_emp_test3(in_empno in varchar2,out_msg out varchar2)
is
cursor emps(v_emp_no varchar2) is select * from emp where empno = v_emp_no;
begin
for emp in emps(in_empno) loop
dbms_output.put_line(emp.empno || '-' || emp.ename);
end loop;
out_msg := 'OK';
exception
when others then
out_msg := sqlcode || '-' || sqlerrm;
end proc_emp_test3;
--执行存储过程
declare
v_emp_no varchar2(32);
v_out_msg varchar2(1000);
begin
v_emp_no := '7839';
proc_emp_test3(v_emp_no,v_out_msg);
end;
- 注意输入参数和游标参数的互传,游标参数必须定义数据类型。游标参数v_emp_no看似可以用in_empno代替,但是不建议这样做。
2.变量
2.1.输入输出参数
输入输出参数不建议和存储过程中出现的表字段一样,否则会出现一些意想不到的异常。
oracle数据库的存储过程里,如果变量名和表的字段名相同了,会优先当做字段名来看待。
--将empno=7369的smith,工作由CLERK调整为MANAGER
create or replace procedure proc_emp_test4(job out varchar2,out_msg out varchar2)
is
begin
job := 'MANAGER'; --job是输出参数,直接赋值以后,不能用
update emp t set t.job = job where t.empno = '7369'; --此时相当于t.job=t.job,不变化
commit;
out_msg := 'OK';
exception
when others then
rollback;
out_msg := sqlcode || '-' || sqlerrm;
end proc_emp_test4;
declare
v_job varchar2(32);
v_out_msg varchar2(1000);
begin
proc_emp_test4(v_job,v_out_msg);
end;
- update没有被正确执行,数据没有任何变化,也不会报错。
--根据输入的empno,将其职位调整为MANAGER
create or replace procedure proc_emp_test5(empno in varchar2,out_msg out varchar2)
is
begin
update emp t set t.job = 'MANAGER' where t.empno = empno; --此时相当于 where 1=1; 全表
commit;
out_msg := 'OK';
exception
when others then
rollback;
out_msg := sqlcode || '-' || sqlerrm;
end proc_emp_test5;
declare
v_empno varchar2(32);
v_out_msg varchar2(1000);
begin
v_empno := '7369';
proc_emp_test5(v_empno,v_out_msg);
end;
- update没有被正确执行,所有职员的职位全部变成了MANAGER。
输入参数不能被加工赋值。
create or replace procedure proc_emp_test6(v_date in varchar2,out_msg out varchar2)
is
v_count number;
begin
--如果输入日期为空,则默认为1984年1月1日
if v_date is null then
v_date := to_char('1984-01-01','yyyy-mm-dd'); --输入参数不能被赋值,编译报错
end if;
--根据输入日期参数统计入职时间在输入日期之后的职员人数
select count(1)
into v_count
from emp t where t.hiredate > to_date(v_date,'yyyy-mm-dd');
dbms_output.put_line(v_count);
out_msg := 'OK';
exception
when others then
out_msg := sqlcode || '-' || sqlerrm;
end proc_emp_test6;
--上述存储过程编译报错,根据需求描述,可修改成以下
create or replace procedure proc_emp_test6(v_date in varchar2,out_msg out varchar2)
is
v_count number;
v_in_date varchar2(32); --定义变量解决v_date输入参数不能赋值
begin
--如果输入日期为空,则默认为1984年1月1日
if v_date is null then
v_in_date := to_char('1984-01-01','yyyy-mm-dd');
else
v_in_date := v_date;
end if;
--根据输入日期参数统计入职时间在输入日期之后的职员人数
select count(1)
into v_count
from emp t where t.hiredate > to_date(v_in_date,'yyyy-mm-dd');
dbms_output.put_line(v_count);
out_msg := 'OK';
exception
when others then
out_msg := sqlcode || '-' || sqlerrm;
end proc_emp_test6;
2.2.自定义变量
自定义变量在into使用时,一定要注意判空和判唯一。
--v_job输入的job不唯一或者不存在
create or replace procedure proc_emp_test7(v_job varchar2,out_msg out varchar2)
is
v_empno varchar2(32);
v_ename varchar2(32);
begin
select t.empno,t.ename
into v_empno,v_ename
from emp t where t.job = v_job;
dbms_output.put_line('empno:' || v_empno || ',v_ename:' || v_ename);
out_msg := 'OK';
exception
when others then
out_msg := sqlcode || '-' || sqlerrm;
dbms_output.put_line(out_msg);
end proc_emp_test7;
declare
v_job varchar2(32);
v_out_msg varchar2(1000);
begin
--不唯一
v_job := 'CLERK'; ---1422-ORA-01422: exact fetch returns more than requested number of rows
--不存在
--v_job := 'xxx'; ---100-ORA-01403: no data found
proc_emp_test7(v_job,v_out_msg);
end;
--建议修改,在into到v_empno,v_ename之前进行一次判断
create or replace procedure proc_emp_test7(v_job varchar2,out_msg out varchar2)
is
v_count number;
v_empno varchar2(32);
v_ename varchar2(32);
begin
select count(1)
into v_count
from emp t
where t.job = v_job;
if v_count = 1 then
select t.empno,t.ename
into v_empno,v_ename
from emp t where t.job = v_job;
dbms_output.put_line('empno:' || v_empno || ',v_ename:' || v_ename);
out_msg := 'OK';
elsif v_count = 0 then
out_msg := '输入的job:' || v_job || ',查询不到职员信息';
elsif v_count > 1 then
out_msg := '输入的job:' || v_job || ',查询到多个职员信息';
end if;
dbms_output.put_line(out_msg);
exception
when others then
out_msg := sqlcode || '-' || sqlerrm;
dbms_output.put_line(out_msg);
end proc_emp_test7;
3.远程授权
通过dblink远程访问的表,必须得到远程用户显式的授权,存储过程中才能进行查询。
- 创建testa用户,初始赋予CONNECT、RESOURCE角色权限,不能访问scott用户下的emp雇员信息表,然后赋予DBA角色权限,可以访问scott.emp,但是在存储过程中是不能直接访问的,会报错
PL/SQL:ORA-00942:table or view does not exist
,需要显式的对testa授权访问才能在存储过程中访问scott.emp。
/* sys用户下操作,生产环境下建议创建dba角色的用户,不要随意用sys用户操作 */
--创建用户
create user testa identified by testa;
--赋予初始角色权限,允许登录和增删改查表视图等
grant connect,resource to testa;
--此时无权限查询scott.emp,赋予dba角色
grant dba to testa;
/* testa用户下操作 */
--查询
select * from scott.emp;
--创建存储过程,编译报错
create or replace procedure proc_emp_test(out_msg out varchar2)
is
cursor emps is select * from scott.emp;
begin
for emp in emps loop
dbms_output.put_line(emp.empno||'-'||emp.ename);
end loop;
out_msg := 'OK';
exception
when others then
out_msg := sqlcode || '-' || sqlerrm;
end proc_emp_test;
/* sys用户下操作 */
grant select on scott.emp to testa;
/* testa用户下操作 */
--重新编译存储过程,编译成功,不再报错
4.中断循环
return、goto、exit、continue在存储过程中的使用及区别(10g版本无continue方法):
continue:结束本次循环,后面不再执行,继续本次循环的下一次循环
exit:跳出本次循环,转而执行本循环的上一层循环的下一次循环
return:直接跳出PLSQL执行,后面都不再执行,直接结束
--构造两个序列,f_1为11,12,13;f_2为21,22,23
begin
for f_1 in (select '1'||level lv1 from dual connect by level < 4) loop
for f_2 in (select '2'||level lv2 from dual connect by level < 4) loop
if f_2.lv2 = '22' then
null;
--continue;
--exit;
--return;
end if;
dbms_output.put_line(f_1.lv1 || '--' || f_2.lv2);
end loop;
end loop;
end;
--null
11--21
11--22
11--23
12--21
12--22
12--23
13--21
13--22
13--23
--continue
11--21
11--23
12--21
12--23
13--21
13--23
--exit
11--21
12--21
13--21
--return
11--21
goto是跳转语句,从一个位置直接跳转到指定位置,<<标志>>不能直接与exception、end loop等关键字相邻,需要用null间隔一下。
begin
for f_1 in (select '1'||level lv1 from dual connect by level < 4) loop
for f_2 in (select '2'||level lv2 from dual connect by level < 4) loop
if f_2.lv2 = '22' then
goto my_handle; --oracle10g实现continue的效果
end if;
dbms_output.put_line(f_1.lv1 || '--' || f_2.lv2);
<<my_handle>>
NULL;
end loop;
end loop;
end;
11--21
11--23
12--21
12--23
13--21
13--23
5.定时器
dbms_jobs定义日期的时候,注意细化时分秒,便于在固定日期重复执行。
--创建日期:2021-08-01,下次执行日期2021-08-02 02:20:00,以后每天2:20执行
declare
jobno number;
begin
sys.dbms_job.submit(jobno,
'declare
yeste_date varchar2(48);
begin
yeste_date:=to_char(sysdate,''yyyy-mm-dd'');
pkg_rpt_stat.run_day_stat(yeste_date);
end;',
to_date('2021-08-02 02:20:00','yyyy-mm-dd hh24:mi:ss'),,
'to_date(to_char(sysdate+1,''yyyy-mm-dd'')||''02:20:00'',''yyyy-mm-dd hh24:mi:ss'')');
commit;
end;