即使是写得最好的PL/SQL程序也会遇到错误或未预料到的事件。
一个优秀的程序都应该能够正确处理各种出错情况,并尽可能从错误中恢复。任何ORACLE错误(报告为ORA-xxxxx形式的Oracle错误号)、PL/SQL运行错误或用户定义条件(不一写是错误),都可以。当然了,PL/SQL编译错误不能通过PL/SQL异常处理来处理,因为这些错误发生在PL/SQL程序执行之前。
ORACLE提供异常情况(EXCEPTION)和异常处理(EXCEPTION HANDLER)来实现错误处理。
1.概念
异常情况处理(EXCEPTION)是用来处理正常执行过程中未预料的事件,程序块的异常处理预定义的错误和自定义错误,由于PL/SQL程序块一旦产生异常而没有指出如何处理时,程序就会自动终止整个程序运行。
程序执行过程中,出现编程人员认为的非正常情况。对这种异常情况的处理,需要用户在程序中定义,然后显式地在程序中将其引发。
异常处理部分一般放在PL/SQL程序体的后半部,结构为:
DECLARE
BEGIN
语句块;
EXCEPTION
WHEN first_exception THEN
WHEN second_exception THEN
WHEN OTHERS THEN
END;
异常处理可以按任意次序排列,但OTHERS必须放在最后。
有三种类型的异常错误:
1. 预定义( Predefined )错误
ORACLE预定义的异常情况大约有24个。对这种异常情况的处理,无需在程序中定义,由ORACLE自动将其引发。
错误号异常错误信息名称说明
ORA-0001 Dup_val_on_index 违反了唯一性限制
ORA-0051 Timeout-on-resource 在等待资源时发生超时
ORA-0061 Transaction-backed-out 由于发生死锁事务被撤消
ORA-1001 Invalid-CURSOR 试图使用一个无效的游标
ORA-1012 Not-logged-on 没有连接到ORACLE
ORA-1017 Login-denied 无效的用户名/口令
ORA-1403 No_data_found SELECT INTO没有找到数据
ORA-1422 Too_many_rows SELECT INTO返回多行
ORA-1476 Zero-divide 试图被零除
ORA-1722 Invalid-NUMBER 转换一个数字失败
ORA-6500 Storage-error 内存不够引发的内部错误
ORA-6501 Program-error 内部错误
ORA-6502 Value-error 转换或截断错误
ORA-6504 Rowtype-mismatch 宿主游标变量与PL/SQL变量有不兼容行类型
ORA-6511 CURSOR-already-OPEN 试图打开一个已处于打开状态的游标
ORA-6530 Access-INTO-null 试图为null对象的属性赋值
ORA-6531 Collection-is-null 试图将Exists以外的集合( collection)方法应用于一个null pl/sql表上或varray上
ORA-6532 Subscript-outside-limit 对嵌套或varray索引得引用超出声明范围以外
ORA-6533 Subscript-beyond-count 对嵌套或varray索引得引用大于集合中元素的个数。
例子:
BEGIN
. . .
EXCEPTION
WHEN NO_DATA_FOUND THEN
statement1;
WHEN TOO_MANY_ROWS THEN
statement1;
WHEN OTHERS THEN
statement1;
statement2;
END;
OTHERS的处理: Others表明我们程序员未能预计到这种错误,所以全部归入到others 里面去了,单发生这种
情况是,我们还是希望了解当时发生的Oracle错误号和相关描述信息,怎样才能取到呢?
Oracle 提供了两个内置函数 SQLCODE 和 SQLERRM 分别用来返回Oracle 错误号和错误描述
DECLARE
v_error_code NUMBER;
v_error_message VARCHAR2(255);
BEGIN
...
EXCEPTION
...
WHEN OTHERS THEN
ROLLBACK;
v_error_code := SQLCODE ;
v_error_message := SQLERRM ;
INSERT INTO errors
VALUES(v_error_code, v_error_message);
END;
2. 非预定义( Predefined )错误
即其他标准的ORACLE错误。对这种异常情况的处理,需要用户在程序中定义,然后由ORACLE自动将其引发。
处理非预定义的Oracle错误:此类错误属于Oracle错误,有编号,但无错误名称定义,使用时需要先声明,并
进行错误初始化:

DEFINE p_deptno = 10
DECLARE
e_emps_remaining EXCEPTION;
PRAGMA EXCEPTION_INIT
(e_emps_remaining, -2292);
BEGIN
DELETE FROM departments
WHERE department_id = &p_deptno;
COMMIT;
EXCEPTION
WHEN e_emps_remaining THEN
DBMS_OUTPUT.PUT_LINE ('Cannot remove dept ' ||
TO_CHAR(&p_deptno) || '. Employees exist. ');
END;
3. 用户定义(User_define)错误
处理非预定义的Oracle错误:此类错误属于Oracle错误,有编号,但无错误名称定义,使用时需要先声明,并
进行错误初始化:

例子:
DEFINE p_deptno = 10
DECLARE
e_emps_remaining EXCEPTION;
PRAGMA EXCEPTION_INIT
(e_emps_remaining, -2292);
BEGIN
DELETE FROM departments
WHERE department_id = &p_deptno;
COMMIT;
EXCEPTION
WHEN e_emps_remaining THEN
DBMS_OUTPUT.PUT_LINE ('Cannot remove dept ' ||
TO_CHAR(&p_deptno) || '. Employees exist. ');
END;
4.RAISE_APPLICATION_ERROR() 函数
对于用户自定义的业务错误,如果觉得先定义再使用很麻烦,那么
也可以简单的使用raise_application_error() 来简化处理。它可以无需预先定义错误,而在需要抛出错误的
地方直接使用此函数抛出例外,例外可以包含用户自定义的错误吗和错误描述;
BEGIN
...
DELETE FROM employees
WHERE manager_id = v_mgr;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20202,
'This is not a valid manager');
END IF;
...
...
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR (-20201,
'Manager is not a valid employee.');
END;
5.运行时异常,业务异常整合
create or replace package sys_raise_app_error_pkg is
-- Author : CZH
-- Created : 2020/8/1 10:21:30
-- Purpose : 异常处理(运行时异常,业务异常)
procedure is_running_error(p_err_msg in varchar2 default null,
p_is_running_error in varchar2);
end sys_raise_app_error_pkg;
/
create or replace package body sys_raise_app_error_pkg is
/*============================================*
* Author : SANFU
* Created : 2020-08-01 15:18:15
* ALERTER :
* ALERTER_TIME :
* Purpose : 异常处理
* Obj_Name : IS_RUNNING_ERROR
* Arg_Number : 2
* X_MESSAGE :错误消息
* P_IS_RUNNING_ERROR :(T:运行时异常,F:业务提示)
*============================================*/
procedure is_running_error(p_err_msg in varchar2 default null,
p_is_running_error in varchar2) is
v_error_type varchar2(100);
--v_err_msg varchar2(1000);
running_error_exp exception;
not_running_error_exp exception;
error_msg_rec nbw.sys_app_error_msg%rowtype;
begin
if 'T' = p_is_running_error then
v_error_type := '系统报错(Oracle 异常)';
raise running_error_exp;
elsif 'F' = p_is_running_error then
v_error_type := '业务提示';
raise not_running_error_exp;
end if;
error_msg_rec.error_type := v_error_type;
exception
when not_running_error_exp then
error_msg_rec.error_id := nbw.SYS_APP_ERROR_MSG_S.NEXTVAL;
error_msg_rec.error_line_num := dbms_utility.format_error_backtrace;
--业务异常,可自行编写提示信息
error_msg_rec.error_msg := nvl(p_err_msg,
dbms_utility.format_error_stack);
/* if p_err_msg is null then
v_err_msg := error_msg_rec.error_msg;
end if;*/
--插入到错误信息表,记录日志
insert into nbw.SYS_APP_ERROR_MSG
(ERROR_ID, ERROR_TYPE, ERROR_LINE_NUM, ERROR_MSG, CREATE_TIME)
values
(error_msg_rec.error_id,
error_msg_rec.error_type,
error_msg_rec.error_line_num,
error_msg_rec.error_msg,
sysdate);
commit;
raise_application_error(-20002,
v_error_type || ':' ||
error_msg_rec.error_msg);
when running_error_exp then
error_msg_rec.error_id := nbw.SYS_APP_ERROR_MSG_S.NEXTVAL;
error_msg_rec.error_line_num := dbms_utility.format_error_backtrace;
error_msg_rec.error_msg := nvl(p_err_msg,
dbms_utility.format_error_stack);
/* if p_err_msg is null then
v_err_msg := error_msg_rec.error_msg;
end if;*/
--插入到错误信息表,记录日志
insert into nbw.SYS_APP_ERROR_MSG
(ERROR_ID, ERROR_TYPE, ERROR_LINE_NUM, ERROR_MSG, CREATE_TIME)
values
(error_msg_rec.error_id,
error_msg_rec.error_type,
error_msg_rec.error_line_num,
error_msg_rec.error_msg,
sysdate);
commit;
raise_application_error(-20002,
v_error_type || ',' || '请联系平台管理员!!' ||
'错误编号:' || error_msg_rec.error_id);
end is_running_error;
end sys_raise_app_error_pkg;
/