3.7.2 预定义的异常

Oracle把一些常见的错误定义为有名字的异常,这就是预定义的异常。Oracle有许多预定义的异常,在进行处理时不需要再定义,只需要编写相应的异常处理程序即可。当PL/SQL块执行发生错误时,数据库服务器将自动抛出相应的异常,并执行编写的异常处理程序。表3.5列出了部分预定义的异常。

figure_0125_0024

其中前面的两个异常是最常见的异常。下面的代码演示如何处理这两个异常。


DECLARE

name emp.ename%type;

BEGIN

SELECT ename INTO name FROM emp WHERE deptno=100;—其实没有编号为100的部门

EXCEPTION—这里将引发NO_DATA_FOUND异常

WHEN NO_DATA_FOUND THEN

dbms_output.put_line('没有满足条件的数据');

WHEN TOO_MANY_ROWS THEN

dbms_output.put_line('太多的数据');

END;


这个块的执行结果为:


没有满足条件的数据


因为编号为100的部门不存在,所以PL/SQL程序在执行到这条SELECT语句时引发了NO_DATA_FOUND异常。但是如果对一个存在的部门进行查询,可能返回多行数据。例如,如果将where子句的条件改为“deptno=10”,因为部门10有多个员工,这时将返回多行数据,从而引发TOO_MANY_ROWS异常。由此可见,在PL/SQL程序通过传统的SELECT命令只能查询一行数据,如果查询0行或多行数据,都会引发异常。如果要对0行或多行数据的情况进行处理,就要用到游标了。

在向表的主键列上写入一个重复的值时将引发异常DUP_VAL_ON_INDEX。例如,在部门表中列deptno是主键列,这就要求这个列上的值不能重复。如果已经存在部门10,再向这个表插入一行数据,部门编号也为10,这时将引发异常DUP_VAL_ON_INDEX。例如:


BEGIN

INSERT INTO dept VALUES(10,'network','nowhere');

EXCEPTION

WHEN DUP_VAL_ON_INDEX THEN

dbms_output.put_line('主键列上的值重复');

END;


这个块的执行结果为:


主键列上的值重复


在PL/SQL块的异常处理部分,由WHEN引导的代码即为异常处理程序。一般在一个PL/SQL块中有多个异常处理程序,分别用于处理不同的异常。但是一般只可能执行其中一段异常处理程序,因为当发生一个异常时,PL/SQL块的执行立即从可执行部分转入异常处理部分,当处理完异常后PL/SQL块的执行便宣告结束,这时将不会有别的异常出现。

一般针对一个异常可以编写一段单独的异常处理程序,也可以对多个异常编写同一段异常处理程序,如果发生不同的异常,可以进行同样的处理。这样在WHEN子句中可以指定多个异常的名字,相互之间用OR分隔。例如:


WHEN NO_DATA_FOUND OR TOO_MANY_ROWS THEN

dbms_output.put_line('SELECT语句出错');

……


PL/SQL提供了两个函数,SQLCODE用于返回发生的错误的代码,SQLERRM用于返回错误的原因。有了这两个函数,就可以编写通用的异常处理程序,处理所有的异常。例如,将错误代码和错误信息显示给用户。例如:


BEGIN

INSERT INTO dept VALUES(10,'network','nowhere');

EXCEPTION

WHEN others THEN

dbms_output.put_line('错误代码:'||SQLCODE);

dbms_output.put_line('错误原因:'||SQLERRM);

END;


这个块的执行结果为:


错误代码:-1

错误原因:ORA-00001:违反唯一约束条件(SCOTT.PK_DEPT)


在上面的异常处理的例子中,我们仅仅把发生错误的信息显示出来。如果希望把所有发生的错误记录下来,可以创建一个表,在PL/SQL的异常处理部分把错误的情况写入这个表,生成日志信息。例如,在数据库中创建表err_info,它的结构如下:


列名 类型 为空 描述

err_time DATE NO 错误发生的时间

err_user VARCHAR(30)YES 因执行PL/SQL而引发错误的用户

err_code INTEGER YES 错误代码

err_message VARCHAR(100)YES 错误原因


这样在处理异常时,就可以直接将异常的情况写入这个表,而不用显示给用户了。如果要对所有的异常进行相同的处理,那么在异常处理部分就不需要分别列出每个异常,只要用OTHERS代替就可以了。例如:


DECLARE

name emp.ename%type;

err_code integer;

err_message varchar(100);

BEGIN

SELECT ename INTO name FROM emp WHERE deptno=100;

EXCEPTION—这里将引发NO_DATA_FOUND异常

WHEN OTHERS THEN

err_code:=SQLCODE;

err_message:=SQLERRM;

INSERT INTO err_info VALUES(SYSDATE, USER, err_code, err_message);

COMMIT;

END;


这个块的执行结果是将异常的信息记录在表error_info中。

引发异常的一个重要原因是处理数时发生错误。统计表明,SELECT语句、DML语句以及游标操作语句更容易引发异常。编写PL/SQL块的主要目的是处理数据,而PL/SQL块在逻辑上与数据是分开的,程序员根本无法预料数据的变化。例如,要查询部门10的员工,程序员根本不知道这个部门中有没有员工,有一个还是有多个员工。所以在编写程序时,程序员应该考虑各种可能出现的异常,在程序中编写这些异常的处理代码,这样的程序才能经受各种错误的考验。