Oracle存储过程编写注意事项

环境: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);
       <>
       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;

你可能感兴趣的:(Oracle存储过程编写注意事项)