PL/SQL:异常处理


异常处理


语法:  
EXCEPTION  
  WHEN EXCEPTION_NAME THEN  
    <异常处理语句>; 
END;   --必须紧挨着end!

例子: 除数为0
declare
  v_num1   integer := &sv_num1;
  v_num2   integer := 0;
  v_result number;
begin
  v_result := v_num1 / v_num2;
  dbms_output.put_line(v_num1 || ' 除以 ' || v_num2 || ' 等于: ' || v_result);
EXCEPTION
  WHEN ZERO_DIVIDE THEN --内置异常名
    dbms_output.put_line('除数不能为0'); --    打印错误消息
end;




内置异常

        运行时的错误叫做异常。每个异常都有一个唯一的 错误编号但是不是每一个运行时错误都有名字。 
plsql中,预先给一些运行时错误定义了名字,这些有名字的异常叫做内置异常

内置异常 一共有十几个(查白皮书363页!!) ,在stardard包 中定义的, 常见的有:
  • no_data_found
    • select into 没有返回0行 , 注:若使用了组函数而返回结果为空, 则不会报错, 会返回0
    • 试图访问嵌套表中已经删除的元素
    • 试图在index by表中访问未初始化的元素    
  • too_many_rows
    • select into 返回多行   
  • value_error
    • 发生算数, 类型转换, 截断, 或精度不够时发生错误
  • zero_devide
    • 除数为0    
  • program_error
    • PLSQL内部错误, 比较难检查    
  • dup_value_on_index    
    • 向unique约束的.列插入重复值
  • invalid_number
    • SQL语句中将字符转成数字时失败




异常处理的3种常见写法 :

写法1、写多个when子句。对不同的异常做不同的处理 
例子:
DECLARE
     ...
BEGIN
          select ... into ...
EXCEPTION 
    WHEN  NO_DATA_FOUND THEN
       DBMS_OUTPUT . PUT_LINE  ( '没有! ' );
    WHEN  TOO_MANY_ROWS THEN
       DBMS_OUTPUT . PUT_LINE  ( '不能多于一行 ' );
END ;


写法2、使用 others 异常处理程序 

others可以看做是任何异常的 父异常,可以匹配任何子异常。 当你不知道异常的名字是神马的情况下,可以使用.
例子:
查找并打印特定老师的名字 
DECLARE
   ...
BEGIN
   ...
EXCEPTION
   WHEN OTHERS THEN
      DBMS_OUTPUT.PUT_LINE ('我也不知道会出什么幺蛾子~');
END;


使用others的弊端 :   错误消息比较模糊 

解决方法:

使用两个 函数  这两个函数都是放在exception块中的

  • SQLCODE:返回错误编号 
  • SQLERRM:返回错误消息


例子:
Displaying SQLCODE and SQLERRM Values
DROP TABLE errors_test;
CREATE TABLE errors_test (
       code NUMBER,
       message VARCHAR2(64)
);
create or replace procedure p     AUTHID   DEFINER  --定义者权限 (默认值)
as
       name employees.last_name%type;
begin
       select last_name  into   name   from  employees 
        where employee_id = -1;  --此处会有报错:员工编号没有-1的
exception
        when others then
             dbms_output.put_line('错误代码是 ' ||  SQLCODE ||                -- 返回错误信息编号
                                                                                     ': '  ||  substr ( SQLERRM 1 64 ) );   --返回错误信息文本
             insert into errors_test (code, message)
                  values (v_code, v_errm);   --出错信息插入到我们建好的表中
             RAISE;
end;
/

测试结果:
错误代码是  100ORA-01403NO DATA FOUND



异常写法3、在一个when子句中处理多个异常, 用若干 or 隔开

EXCEPTION WHEN VALUE_ERROR OR INVALID_NUMBER OR NO DATA FOUND OR ... 

   THEN <所有异常做相同处理>;


补充:内部快不能抓外部块中的异常; 但是外部块的exception可以抓内部快抛出的异常!





自定义异常

定义:
       使用自定义异常之前需要在declare部分声明异常名; 系统不会自动抛出自定义异常, 需要手动挡.
语法框架:
        声明 declare --> 抛出 raise --> 捕获 exception when 

声明自定义异常就跟声明变量一样,  一般约定异常名称的前缀是字母e   

DECLARE 
   exception_name EXCEPTION;  --自定义声明异常 
BEGIN 
    ... 
   IF  <condition>  THEN --如果违反业务规则 
      RAISE  exception_name;  --手工抛出异常对象 
   ELSE 
      ... 
   END IF
EXCEPTION 
  WHEN  exception_name  THEN  <处理语句>
END


例子:
处理学生编号为负的异常情况 
DECLARE 
  v_student_id student.student_id%type := &sv_student_id; 
  --1、声明异常对象 e_invalid_id
  E_INVALID_ID exception
begin 
  违反业务规则了吗?学生编号为负的吗? 
  if v_student_id < 0 then 
  --2、抛出
    RAISE E_INVALID_ID; 
  else 
    dbms_output.put_line('ok');   
  end if;   
exception  
  --3、捕获
  WHEN E_INVALID_ID THEN 
    dbms_output.put_line('学生编号不能为负的'); 
end;


    
异常传播 

定义:
        运行时错误除了发生在语句块的可执行部分之外,  也可能发生在语句块的声明部分或者异常处理部分。 
控制在这些环境下异常抛出方式的规则被称为异常传播 

第1种情况: PL/SQL语句块发生异常

  • 过程的某个语句块运行时发出错误
    • 首先由本语句块的 exception when 来处理
      1. 如果本块的exception when不处理,
      2. 或者压根没有写异常处理
      3. 或者写了,但是没有抓住
      •  则由外部块的exception来处理
        • 如果外部块也不处理, 则由系统来处理
          • 即, 系统被迫停止用户程序,并打印系统错误消息 
  • 前面的所有实验都属于第一种情况!


第2种情况:DECLARE部分发生异常

        PL/SQL语句块的declare部分发生异常,该异常本块的exception不能处理。要由外部块的 exception来处理;如果外部块也不处理, 则由系统来处理 
DECLARE
  ...
  v_test1 CHAR(7) := '超精度啦' ; --本块declare部分出现异常不能被本块的exception部分捕获
                                  --只会由系统抛出异常,过程被迫中止
  v_test2 CHAR(7) ;
BEGIN
  ...
  v_test2 := '超精度啦';  --本块begin部分出现的异常,可以被本块的exception捕获
                          --由本块的exception捕获,过程继续执行至结束
EXCEPTION
  WHEN OTHERS THEN ...;
  WHEN INVALID_NUMBER Or VALUE_ERROR OR ... THEN ...;
END;


例子:
DECLARE 
   v_test_var CHAR(3):= 'ABCDE' --异常,精度不够
BEGIN 
   DBMS_OUTPUT.PUT_LINE ('This is a test'); 
EXCEPTION 
   WHEN INVALID_NUMBER OR VALUE_ERROR THEN 
      DBMS_OUTPUT.PUT_LINE ('An error has occurred'); 
END
结果:   系统弹窗报错ORA-06502, PLSQL数字或值错误,程序被迫终止
           而不是dbms_output打印错误信息,程序执行完毕

原因分析:

declare + begin + exception + end 加一起属于一个块

declare部分的报错不能被本块的exception捕获

本块的exception部分只能捕获本块begin 到 exception之间的部分

所以要想正常打印错误信息,需要在外面再套一层!


解决办法:

          在外面再套一层,用来抓里面块的declare的异常

修改上例
begin    --再套一层 begin
  declare 
    v_test_var char(3) := '1234';  --declare部分的异常
  begin 
    dbms_output.put_line('v_test_var: ' || v_test_var); 
  EXCEPTION
    WHEN INVALID_NUMBER OR VALUE_ERROR THEN 
      dbms_output.put_line('内部块异常'); 
  end
exception --再套一层的exception用来获取里面declare部分的异常
  when invalid_number or value_error then 
    dbms_output.put_line(SQLCODE||' -> '||SQLERRM|| ' 外部块异常'); 
end--再套一层


第3种情况: EXCEPTION部分出现异常

解决办法: 参照第二种declare部分出现异常的情况,外面再套一层





异常高级概念


高级概念1. RAISE_ APPLICATION_ERROR 过程

        RAISE_APPLICATION_ERROR是Oracle提供的一个特殊的内置过程,允许程序员创建有意义的错误消息。 
此过程主要用于简化用户定义异常的处理。也可以用于内置异常中,用来显示错误消息 

语法:
exec RAISE_APPLICATION_ERROR ( 错误编号 是错误文本 ); 
参数: 
  • 错误编号, 编号范围在-20999和-20000之间的整数
  • 错误文本,最多包含2048个字符的文本

例子:
使用raise_application_errror过程来简化学生编号为负的自定义异常的处理 
DECLARE
  V_STUDENT_ID STUDENT.STUDENT_ID%TYPE := &SV_STUDENT_ID;
BEGIN
  IF V_STUDENT_ID < 0 THEN    --学生编号为负,直接打印错误信息 
    RAISE_APPLICATION_ERROR(-20000'学生id can not negative'); 
         --本例中不包含异常的名称、RAISE语句和exception部分。
        --直接调用RAISE_APPLICATION_ERROR过程抛出异常即可
         --就不用再写exception when 了
  ELSE
    DBMS_OUTPUT.PUT_LINE('ok');
  END IF;
END;  


RAISE_APPLICATION_ERROR语句弊端:

  • 出错消息的显示格式和系统错误消息的格式完全相同, 最终用户可能不能接受
  • 只要调用了raise_application_error过程,就表示程序出错了。 (程序停止,后面的不去执行)
BEGIN
  DBMS_OUTPUT.PUT_LINE('ok'); --前面两个'ok'是放在put_line缓存里面的
  DBMS_OUTPUT.PUT_LINE('ok'); --前面两个'ok'是放在put_line缓存里面的
  
  RAISE_APPLICATION_ERROR(-20000'大大滴坏了'); --抛出异常,就覆盖了前面的输出,直接打印错误消息!! 
                                          --程序被系统终止, 前面两个'ok'不会被打印
  DBMS_OUTPUT.PUT_LINE('ok');
  DBMS_OUTPUT.PUT_LINE('ok');
END;




高级概念2. EXCEPTION_INIT编译指令 

一个编译指令是针对PL/SQL编译器发出的特殊命令。 
当程序编译时,编译器就根据你给定的编译指令做一些操作
内置异常只有十几个,而系统异常由成败上千个。 
这些没有名字的系统异常是不能由exception来处理的。 
为了处理这些没有名字的系统异常,就需要使用 EXCEPTION_INIT编译指令,该指令用来告诉编译器, 给某个系统异常赋一个名字。
这样,就可以在exception 抓住该异常进行处理
 
语法:
EXCEPTION_INIT编译指令出现在语句块的声明部分,如下所示:  
DECLARE  
  exception_name  EXCEPTION;  
  PRAGMA EXCEPTION_INIT ( 异常的名称 Oracle错误编号 ); 


例子
根据用户提供的邮编从ZIPCODE表中删除相 应的记录,并在屏幕上显示邮编已被删除的消息 
使用06870作为v_zip的值时,会抛出外键异常。错误编号为-02292 
DECLARE 
   v_zip ZIPCODE.ZIP%TYPE := ' 06870 '
BEGIN 
   DELETE FROM zipcode 
    WHERE zip = v_zip; 
   DBMS_OUTPUT.PUT_LINE ('Zip '||v_zip||' has been deleted'); 
END

报错-02292的外键错误没有名字,因此不能使用  exception来抓他 
解决:使用exception_init编译指令来抓-02292错误 

修改如下:
DECLARE
  V_ZIP ZIPCODE.ZIP%TYPE := '06870';
  E_CHILD_EXISTS EXCEPTION--声明自定义异常对象
  PRAGMA EXCEPTION_INIT(E_CHILD_EXISTS, -02292); --将自定义异常和-02292错误联系起来
BEGIN
  DELETE FROM ZIPCODE WHERE ZIP = V_ZIP;
  DBMS_OUTPUT.PUT_LINE('Zip ' || V_ZIP || ' has been deleted');
EXCEPTION
  WHEN E_CHILD_EXISTS THEN
    DBMS_OUTPUT.PUT_LINE('先删除子表中的行');
END;

注意:如果不能完成本实验,或者put_line没有输出,可能是因为权限不足
解决办法:grant DEBUG CONNECT SESSION , DEBUG ANY PROCEDURE to 用户;




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值