异常处理
语法:
EXCEPTION
WHEN EXCEPTION_NAME THEN
<异常处理语句>;
END; --必须紧挨着end!
例子: 除数为0
例子: 除数为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;
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;
/
测试结果:
错误代码是
100: ORA-01403: NO 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
声明自定义异常就跟声明变量一样, 一般约定异常名称的前缀是字母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 来处理
- 如果本块的exception when不处理,
- 或者压根没有写异常处理
- 或者写了,但是没有抓住
- 则由外部块的exception来处理
- 如果外部块也不处理, 则由系统来处理
- 即, 系统被迫停止用户程序,并打印系统错误消息
- 如果外部块也不处理, 则由系统来处理
- 如果本块的exception when不处理,
- 首先由本语句块的 exception when 来处理
- 前面的所有实验都属于第一种情况!
第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编译指令出现在语句块的声明部分,如下所示:
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 用户;