提示公众号代码会自动换行,建议横屏阅读或者左右滑动代码观看

为了提高应用程序的健壮性,使得应用程序可以安全正常的运行。程序猿们应该考虑到PL/SQL块可能出现的各种异常情况,并进行相应的处理。通常使用异常(Exception),可以处理PL/SQL块的各种异常情况。
一 异常简介
异常(exception)是PL/SQL标识符,它包括预定义异常,非预定义异常和自定义异常三种异常。当编写PL/SQL块时,应该捕捉并处理各种异常可能出现的异常,如果不捕捉和处理异常,那么Oracle会将错误传递到调用环境;如果捕捉并处理异常,那么Oracle会在PL/SQL块内解决运行错误。
1.传递异常到调用环境
当编写PL/SQL块时,如果没有提供异常处理部分,那么当执行PL/SQL块时会将错误传递到调用块或PL/SQL运行环境。下面以使用替代变量输入雇员号,并输出雇员姓名为例,说明在PL/SQL中不捕捉并处理异常的运行错误:
1DECLARE
2 v_name emp.ename%TYPE;
3 BEGIN
4 SELECT ename INTO v_ename FROM emp WHERE empno=&input;
5 DBMS_OUTPUT.PUT_LINE('雇员名:'||v_ename);
6 END;
当输入的雇员号在表emp中不存在时,PL/SQL会把异常传递到调用环境。
2.捕捉并处理异常
为了提高应用程序的健壮性,应用程序开发人员应该分析PL/SQL块可能出现的各种异常情况,并使用异常处理部分有效地解决各种可能的错误。捕捉并处理异常是使用异常处理部分来完成的,语法如下:
1EXCEPTION
2 WHEN exception1 [ OR exception2 ... ] THEN
3 statement1;
4 statement2;
5 ...
6 [WHEN exception3 [ OR exception4 ... ] ] THEN
7 statement1;
8 statement2;
9 ...
10 WHEN OTHERS THEN
11 statement1;
12 statement2;
13 ...
WHEN OTHERS … THEN语句(可选)用于捕捉和处理所有其他异常情况。并且该语句必须是异常处理部分的最后一条捕捉子句。下面以使用替代变量输入雇员号输出雇员姓名,并处理NO_DATA_FOUND异常为例,说明在PL/SQL中捕捉并处理异常的方法:
1DECLARE
2 v_ename emp.ename%TYPE;
3 BEGIN
4 SELECT ename INTO v_ename FROM emp WHERE empno=&input;
5 DBMS_OUTPUT.PUT_LINE('雇员名:'||v_ename);
6 EXCEPTION
7 WHEN NO_DATA_FOUND THEN
8 DBMS_OUTPUT.PUT_LINE('雇员号不正确,请核实雇员号!');
9 END;
二 捕捉并处理异常
1.预定义异常处理
预定义异常是指由PL/SQL所提供的系统异常。Oracle为应用开发人员提供了大约21(不同Oracle版本存在数量差异)个预定义异常,每个预定义异常对应一个特定的Oracle错误,当PL/SQL块出现这些Oracle错误时,会隐含地触发相应的预定义异常。
使用下面的SQL语句可与查询到Oracle中的预定义异常:
1SELECT*FROM DBA_SOURCE WHEN NAME='STANDARD' AND TEXT LIKE '%EXCEPTION_INIT%';
下面介绍一些常用的预定义异常:
(1) CURSOR_ALREADY_OPEN:该异常对应于ORA-06511错误当在已打开游标上执行OPEN操作时,会触发该异常。
(2) NO_DATA_FOUND:该异常对应于ORA-01403错误。当执行SELECT INTO未返回行,或引用了未初始化的PL/SQL表元素时,则会触发该异常。
(3) TOO_MANY_ROWS:该异常对应于ORA-01422错误,当执行SELECT INTO语句时,如果返回超过一行,则会触发该异常。
下面使用替代变量输入工资输出雇员名,并处理NO_DATA_FOUND和TOO_MANY_ROWS异常为例,说明 捕捉并处理异常预定义异常的方法:
1DECLARE
2 v_ename emp.%TYPE;
3 BEGIN
4 SELECT ename INTO v_name FROM emp WHERE sal=&InputSalary;
5 DBMS_OUTPUT.PUT_LINE('姓名:'||v_ename);
6 EXCEPTION
7 WHEN NO_DATA_FOUND THEN
8 DBMS_OUTPUT.PUT_LINE('不存在该工资的员工');
9 WHEN TOO_MANY_ROWS THEN
10 DBMS_OUTPUT.PUT_LINE('多个雇员具有该工资');
11 END;
2.非预定义异常
非预定义异常用于处理与预定义异常无关的Oracle错误。预定义异常只能用于大约21种的Oracle错误,而PL/SQL块可能还会遭遇其他Oracle错误,例如:完整性约束ORA-02291,代码如下所示:
1BEGIN
2 UPDATE emp SET deptno=&input WHEN empno=&eno;
3 END;
如果更新的deptno在主键中不存在则会触发该异常,当然如果SQL违法了约束条件时也会触发,例如CHECK,NOT NULL,UNIQUE,PRIMARY KEY等。
为了提高程序健壮性,要使用非预定义异常处理Oracle错误,具体步骤如下:
(1) 定义异常标识符。为了处理与预定义异常无关的Oracle错误,必须在定义部分定义异常标识符。
(2) 在Oracle错误号和异常之间建立关联。为了关联异常和Oracle错误,需要在定义部分引用伪过程EXCEPTION_INIT。
(3) 捕捉并处理异常。为了防止在执行SQL块时将Oracle错误传递到调用环境,需要在异常处理部分捕获并处理异常。
Dept表和emp表之间具有主外键关系,当更新雇员的部门号时,部门号必须在dept表中存在,否则会触发ORA-02291错误,并且在更新雇员手机号时需要检查手机号的有效性长度是否为11位,相关代码如下:
1DECLARE
2 e_primarry EXCEPTION;
3 e_check EXCEPTION;
4 PRAGMA EXCEPTON_INIT(e_primary,-2291);
5 PRAGMA EXCEPTION_INIT(e_check,-02290);
6 name emp.ename%TYPE:=LOWER('&InputName');
7 dno emp.deptno%TYPE:=&InputDno;
8 phone emp.ephone%TYPE:=&InputPhone;
9 BEGIN
10 UPDATE emp SET deptno=dno WHERE LOWER(ename)=name;
11 EXCEPTION
12 WHEN e_integrity THEN
13 DBMS_OUTPUT.PUT_LINE('该部门不存在');
14 UPDATE emp SET ephone=phone WHERE LOWER(ename)=name;
15 EXCEPTION
16 WHEN e_check THEN
17 DBMS_OUTPUT.PUT_LINE('手机号错误! 请重新输入...');
18 END;
3.自定义异常
自定义异常是用于处理与Oracle错误的其他异常情况。为了在PL/SQL块中处理Oracle的其他异常情况,只能使用自定义异常。当执行和上面例子一样的代码时,输入的雇员名和部门号都不存在时则不会触发ORA-02291错误,如下:
1DECLARE
2 e_primarry EXCEPTION;
3 PRAGMA EXCEPTON_INIT(e_primary,-2291);
4 name emp.ename%TYPE:=LOWER('&InputName');
5 dno emp.deptno%TYPE:=&InputDno;
6 BEGIN
7 UPDATE emp SET deptno=dno WHERE LOWER(ename)=name;
8 EXCEPTION
9 WHEN e_integrity THEN
10 DBMS_OUTPUT.PUT_LINE('该部门不存在');
11 END;
注意,当执行内嵌UPDATE或者DELETE语句时,如果没有更新或者删除行,那么PL/SQL将不会给出任何提示信息。为了使得最终用户可以获得有用,更有意义的信息,需要使用自定义异常。使用自定义异常的具体步骤如下:
(1) 定义异常标识符。为了使用自定义异常,必须在定义部分定义异常标识符。
(2) 触发异常。为了处理与Oracle错误无关的异常情况,需要在执行部分出现异常情况时使用RAISE语句显示地触发异常。
(3) 捕捉并处理异常。需要在异常处理部分使用WHEN语句捕捉并处理异常。
下面以使用替代变量输入雇员名和部门号,处理ORA-2291错误以及雇员不存在的情况:
1DECLARE
2 e_primarry EXCEPTION;
3 e_check EXCEPTION;
4 e_no_rows EXCEPTION;
5 PRAGMA EXCEPTON_INIT(e_primary,-2291);
6 PRAGMA EXCEPTION_INIT(e_check,-02290);
7 name emp.ename%TYPE:=LOWER('&InputName');
8 dno emp.deptno%TYPE:=&InputDno;
9 phone emp.ephone%TYPE:=&InputPhone;
10 BEGIN
11 UPDATE emp SET deptno=dno WHERE LOWER(ename)=name;
12 IF SQL%NOTFOUND THEN
13 RAISE e_no_rows;
14 END IF;
15 EXCEPTION
16 WHEN e_integrity THEN
17 DBMS_OUTPUT.PUT_LINE('该部门不存在');
18 WHEN e_no_rows THEN
19 DBMS_OUTPUT.PUT_LINE('该员工不存在');
20 UPDATE emp SET ephone=phone WHERE LOWER(ename)=name;
21 EXCEPTION
22 WHEN e_check THEN
23 DBMS_OUTPUT.PUT_LINE('手机号错误! 请重新输入...');
24 END;
三 使用异常处理函数
异常处理函数用于取得Oracle错误号和错误信息,其中函数SQLCODE用于取得错误号,SQLERRM用于取得错误信息,当编写PL/SQL块时,通过在异常处理部分引用函数SQLCODE和SQLERRM,可以取得未预计到的Oracle错误。另外,通过使用内置过程RAISE_APPLICATION_ERROR,可以在建立子程序(过程,函数和包)时自定义错误号和错误消息
1.使用SQLCODE和SQLERRM
为了获得未预期的Oracle错误,在异常处理部分的WHEN OTHERS子句后可以引用函数SQLCODE和SQLERRM。下面以捕获并处理未预期的Oracle错误为例,说明使用函数SQLCODE和SQLERRM的方法,示例如下:
1BEGIN
2 DELETE FROM dept WHERE deptno=&input;
3 EXCEPTION
4 WHEN OTHERS THEN
5 DBMS_OUTPUT.PUT_LINE('错误号:'||SQLCODE);
6 DBMS_OUTPUT.PUT_LINE('错误信息:'||SQLERRM);
7
8END;
2.RAISE_APPLICATION_ERROR
该过程用于在PL/SQL子程序中自定义错误信息。注意,该过程只适用于数据库子程序(过程,函数,包,触发器),而适用于匿名块和客户端子程序,语法如下:
1RAISE_APPLICATION_ERROR(error_number,message[ , (TRUE | FALSE)]);
其中error_number用于定义错误号(-20000~-20999),message用于指定错误信息,长度不能超过2048字节。第三个参数为可选项,如果设置为TRUE,则错误会存放到先前错误堆栈,否则会替换先前所有错误。下面以在过程update_sal中引用RAISE_APPLICATION_ERROR自定义错误信息,并执行该过程为例,说明该过程的方法:
1CREATE OR REPLACE PROCEDURE update_sal(name VARCHAR2,salary NUMBER)
2 IS
3 BEGIN
4 UPDATE emp SET sal=salary WHERE LOWER(ename)=LOWER(name);
5 IF SQL%NOTFOUND THEN
6 RAISE_APPLICATION_ERROR(-20000,'该雇员不存在');
7 END IF;
8 END;
四 使用编译警告
编译警告是Oracle Database 10g的新特性,它用于提高PL/SQL子程序的健壮性并避免运行问题。通过使用编译警告特征,在编译PL/SQL子程序时会自动检查程序可能存在的各种问题。PL/SQL警告信息包括以下三种类型:
(1) SERVER:用于检查不可预料结果或错误结果(例如参数别名问题)。
(2) PERFORMANCE:用于检查性能问题,例如为NUMBER列提供了VARCHAR2类型的数据。
(3) INFORMATIONAL:用于检查子程序死代码。
通过配置初始化参数plsql_warnings或者使用DBMS_WARNING包,可以实现编译警告特征。当编译PL/SQL子程序时,查询数据字典USER_ERRORS或执行SHOW ERRORS命令可以查看警告消息。
1.不使用编译预警
在10g之前,只要子程序符合SQL和PL/SQL的语法及语义规则,Oracle就会成功编译,并且不会提示任何信息,下面以编写死代码的过程dead_code为例,说明不使用编译预警的编译结果:
1CREATE OR REPLACE PROCEDURE update_sal(name VARCHAR2,salary NUMBER)
2 IS
3 BEGIN
4 UPDATE emp SET sal=salary WHERE LOWER(ename)=LOWER(name);
5 IF SQL%NOTFOUND THEN
6 RAISE_APPLICATION_ERROR(-20000,'该雇员不存在');
7 END IF;
8 END;
2.使用初始化参数plsql_warnings配置编译警告
初始化参数PLSQL_WARNING用于配置编译警告,它不仅可以在系统级(ALTER SYSTEM)或会话级(ALTER SESSION)设置,而且可以在ALTER PROCEDURE命令中进行设置,该参数具有以下值:
(1) DISABLE:ALL(默认值)禁止编译警告。
(2) ENABLE:ALL 激活所有编译警告。
(3) ENABLE:SERVER只激活严重警告。
(4) ENABLE:PERFORMANCE 只激活性能警告。
(5) ENABLE:INFORMATIONAL 只激活消息警告。
下面以编写包含死代码的过程dead_code,并显示编译警告信息为例,说明使用编译预警的编译结果:
1ALTER SESSION SET plsql_warnings='ENABLE:ALL';
2CREATE OR REPLACE PROCEDURE dead_code
3 AS
4 BEGIN
5 IF TRUE THEN
6 DBMS_OUTPUT.PUT_LINE('TRUE');
7 ELSE
8 DBMS_OUTPUT.PUT_LINE('FALSE');
9 END IF;
10 END;
3.使用包DBMS_WARNING配置编译警告
当编写PL/SQL子程序时,不仅可以使用初始化参数PLSQL_WARNINGS配置编译警告,而且可以使用包过程DBMS_WARNING.SET_WARNING_SETTIING_STRING配置编译警告。下面编写包含隐含类型转换的过程UPDATE_SAL,并显示编译警告消息为例,说明使用包DBMS_WARNING配置编译警告的方法:
1EXEC DBMS_WARNING.SET_WARNING_SETTING_STRING('ENABLE:ALL','SESSION');
2CREATE OR REPLACE PROCEDURE update_sal(name VARCHAR2,salary VARCHAR2) --salary在表中的类型为NUMBER,此处会报错
3 IS
4 BEGIN
5 UPDATE emp SET sal=salary WHERE ename=name;
6 END;
下面通过要求设计异常处理,要求根据输入的产品ID得到产品数量,如果产品数量小于0,则抛出异常,不做出提示,脚本如下:
1DECLARE
2v_proid productinfo.productid%TYPE:='&产品ID';
3v_qunty productinfo.quantity%TYPE;
4b_buy BOOLEAN; --购买是否成功
5e_quantity EXCEPTION;
6v_buys INT; --用户购买产品数量
7v_cancel BOOLEAN; --用户取消订单
8PRAGMA EXCEPTION_INIT(e_quantity,-20001);
9BEGIN
10 SELECT quantity INTO v_qunty FROM productinfo WHERE productid=v_proid;
11 IF SQL%ROWCOUNT >1 THEN
12 b_buy:=FALSE;
13 ELSIF v_qunty < 0 THEN
14 b_buy:=FALSE;
15 RAISE e_quantity;
16 ELSE
17 b_buy:=TRUE;
18 DBMS_OUTPUT.PUT_LINE('该产品的数量是:'||v_qunty);
19 IF b_buy THEN --用户购买产品成功,产品数量减少
20 UPDATE productinfo SET quantity = quantity-v_buys WHERE productid=v_proid;
21 COMMIT;
22 ELSE
23 ROLLBACK;
24 IF v_cancel THEN
25 UPDATE productinfo SET quantity = quantity+v_buys WHERE productid=v_proid;
26 COMMIT;
27 EXCEPTION
28 WHEN e_quantity THEN
29 DBMS_OUTPUT.PUT_LINE('出现产品数量为空,请核查!');
30 ROLLBACK;
31 WHEN NO_DATA_FOUND THEN
32 DBMS_OUTPUT.PUT_LINE('没有对应的数据');
33 WHEN TOO_MANY_ROWS THEN
34 DBMS_OUTPUT.PUT_LINE('出现过多数据,请核实!');
35
36END;
扫描下方二维码关注公众号,了解相关更新





