3.7.2 预定义的异常
Oracle把一些常见的错误定义为有名字的异常,这就是预定义的异常。Oracle有许多预定义的异常,在进行处理时不需要再定义,只需要编写相应的异常处理程序即可。当PL/SQL块执行发生错误时,数据库服务器将自动抛出相应的异常,并执行编写的异常处理程序。表3.5列出了部分预定义的异常。
其中前面的两个异常是最常见的异常。下面的代码演示如何处理这两个异常。
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的员工,程序员根本不知道这个部门中有没有员工,有一个还是有多个员工。所以在编写程序时,程序员应该考虑各种可能出现的异常,在程序中编写这些异常的处理代码,这样的程序才能经受各种错误的考验。