暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Oracle PL/SQL Exception异常

数据库DBA 2021-04-13
2820

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

为了提高应用程序的健壮性,使得应用程序可以安全正常的运行。程序猿们应该考虑到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_FOUNDTOO_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块时,通过在异常处理部分引用函数SQLCODESQLERRM,可以取得未预计到的Oracle错误。另外,通过使用内置过程RAISE_APPLICATION_ERROR,可以在建立子程序(过程,函数和包)时自定义错误号和错误消息


1.使用SQLCODESQLERRM

为了获得未预期的Oracle错误,在异常处理部分的WHEN OTHERS子句后可以引用函数SQLCODESQLERRM。下面以捕获并处理未预期的Oracle错误为例,说明使用函数SQLCODESQLERRM的方法,示例如下:


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;


扫描下方二维码关注公众号,了解相关更新


最后修改时间:2021-04-14 09:10:58
文章转载自数据库DBA,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论