As we all known,程序的错误一般分为两类:编译错误和运行时错误。其中运行时错误被称为异常。PL/SQL语句块中处理异常的部分即为异常处理部分。在异常处理部分,可以指定当特定异常发生时所采取的动作。

PL/SQL有两种类型的异常:内置异常和用户自定义异常。

其中,内置异常又分为预定义异常和非预定义异常。

一、内置异常

首先试举一例来抛砖引玉。

  1. DECLARE
  2. v_ename varchar2(10);
  3. v_empno number(4) := &v_empno;
  4. BEGIN
  5. SELECT ename
  6. INTO v_ename
  7. FROM EMP
  8. WHERE empno = v_empno;
  9. DBMS_OUTPUT.PUT_LINE('Employee name is '||v_ename);
  10. END;

该语句块通过输入员工的编号来得出员工的姓名。当输入的员工编号存在时,输出员工姓名,当员工编号不存在时,会有运行错误。如下所示:

  1. SQL> /
  2. Enter value for v_empno: 7788
  3. Employee name is SCOTT -->> 输入的员工编号存在,输出员工姓名
  4.  
  5. PL/SQL procedure successfully completed.
  6.  
  7. SQL> /
  8. Enter value for v_empno: 1234
  9. DECLARE
  10. *
  11. ERROR at line 1:
  12. ORA-01403: no data found -->> 输入的员工编号不存在,报运行时错误
  13. ORA-06512: at line 5

由此可见,编译器无法检测运行错误。为在程序中处理这种类型的错误,必须添加异常处理部分。异常处理部分的语法结构如下:

EXCEPTION
       WHEN EXCEPTION_NAME THEN
             ERROR-PROCESSING STATEMENTS;

在语句块中,异常处理部分位于可执行部分之后,上例可修改如下:

  1. DECLARE
  2. v_ename varchar2(10);
  3. v_empno number(4) := &v_empno;
  4. BEGIN
  5. SELECT ename
  6. INTO v_ename
  7. FROM EMP
  8. WHERE empno = v_empno;
  9. DBMS_OUTPUT.PUT_LINE('Employee name is '||v_ename);
  10. EXCEPTION
  11. WHEN NO_DATA_FOUND THEN
  12. DBMS_OUTPUT.PUT_LINE('There is no such employee');
  13. END;

使用异常处理部分,可以使得程序能够正常结束,而不是非正常终止。同时,输出结果更加面向用户,而不是编程人员。

上述NO_DATA_FOUND即为Oracle预定义异常。

下面列出一些常见的预定义异常:

TOO_MANY_ROWS : SELECT INTO返回多行
INVALID_CURSOR :非法指针操作(关闭已经关闭的游标)
ZERO_DIVIDE :除数等于零
DUP_VAL_ON_INDEX :违反唯一性约束
ACCESS_INTO_NULL: 未定义对象
CASE_NOT_FOUND: CASE 中若未包含相应的 WHEN ,并且没有设置 ELSE 时
COLLECTION_IS_NULL: 集合元素未初始化
CURSER_ALREADY_OPEN: 游标已经打开
DUP_VAL_ON_INDEX: 唯一索引对应的列上有重复的值
INVALID_NUMBER: 内嵌的 SQL 语句不能将字符转换为数字
NO_DATA_FOUND: 使用 select into 未返回行,或应用索引表未初始化的元素时
SUBSCRIPT_BEYOND_COUNT:元素下标超过嵌套表或 VARRAY 的最大值
SUBSCRIPT_OUTSIDE_LIMIT: 使用嵌套表或 VARRAY 时,将下标指定为负数
VALUE_ERROR: 赋值时,变量长度不足以容纳实际数据
LOGIN_DENIED: PL/SQL 应用程序连接到 oracle 数据库时,提供了不正确的用户名或密码
NOT_LOGGED_ON: PL/SQL 应用程序在没有连接 oralce 数据库的情况下访问数据
PROGRAM_ERROR: PL/SQL 内部问题,可能需要重装数据字典& pl./SQL 系统包
ROWTYPE_MISMATCH: 宿主游标变量与 PL/SQL 游标变量的返回类型不兼容
SELF_IS_NULL: 使用对象类型时,在 null 对象上调用对象方法
STORAGE_ERROR: 运行 PL/SQL 时,超出内存空间
SYS_INVALID_ID: 无效的 ROWID 字符串
TIMEOUT_ON_RESOURCE: Oracle 在等待资源时超时

others可以代表所有异常,oracle预定义的异常在20000以内。

二、 用户自定义异常

通常,在自己的程序里,也许需要处理与所写程序相关的问题。例如,在上个语句块中,需要输入员工编号。通常,希望员工编号是正值。但是无意间,用户输入一个负数。但是,没有发生任何错误,因为变量v_empno被定义为数值类型。这时,你希望自定义异常来处理这种情况,这种类型的异常被称为用户自定义异常。在使用该异常之前,必须首先进行声明。语法结构如下所示:

DECLARE
     exception_name EXCEPTION;
BEGIN
     ...
     IF  CONDITION THEN
          RAISE exception_name;
     ELSE
          ...
     END IF;
EXCEPTION
     WHEN exception_name THEN
           ERROR-PROCESSING STATEMENTS;
END;

故上例可修改为:

  1. DECLARE
  2. v_ename varchar2(10);
  3. v_empno number(4) := &v_empno;
  4. e_invalid_no exception;
  5. BEGIN
  6. IF v_empno < 0 THEN
  7. RAISE e_invalid_no; -->> 注意:RAISE语句应该与IF语句一起使用,否则,每次执行时,执行权都会转到该语句块的异常处理部分
  8. ELSE
  9. SELECT ename
  10. INTO v_ename
  11. FROM EMP
  12. WHERE empno = v_empno;
  13. DBMS_OUTPUT.PUT_LINE('Employee name is '||v_ename);
  14. END IF;
  15. EXCEPTION
  16. WHEN e_invalid_no THEN
  17. DBMS_OUTPUT.PUT_LINE('Employee number can not be negative');
  18. END;

三、RAISE_APPLICATION_ERROR

RAISE_APPLICATION_ERROR是oracle提供的一种特殊的内置过程,允许编程人员为特定应用程序创建有意义的错误信息。RAISE_APPLICATION_ERROR过程适用于未命名的用户定义异常。它负责将错误编号和错误文本关联起来,它的语法为:

RAISE_APPLICATION_ERROR(error_number,error_message);

error_number是与特定错误信息相关联的错误编号。这个编号的范围在-20999到-20000之间。error_message是错误文本,最多包含2048个字符。

上例可修改为:

  1. DECLARE
  2. v_ename varchar2(10);
  3. v_empno number(4) := &v_empno;
  4. BEGIN
  5. IF v_empno < 0 THEN
  6. RAISE_APPLICATION_ERROR(-20000,'Employee number can not be negative');
  7. ELSE
  8. SELECT ename
  9. INTO v_ename
  10. FROM EMP
  11. WHERE empno = v_empno;
  12. DBMS_OUTPUT.PUT_LINE('Employee name is '||v_ename);
  13. END IF;
  14. END;

当输入的员工编号为负数时,运行结果如下所示:

  1. SQL> /
  2. Enter value for v_empno: -1234
  3. DECLARE
  4. *
  5. ERROR at line 1:
  6. ORA-20000: Employee number can not be negative
  7. ORA-06512: at line 6

借助于RAISE_APPLICATION_ERROR过程,编程人员能够遵循与Oracle错误一致的方式返回错误信息。

四、 EXCEPTION_INIT

在上文内置异常中,预定义异常的个数其实是非常有限的,当程序抛出其它不在上述预定义范围内的异常时,该如何捕捉呢?

譬如下例:

  1. DECLARE
  2. v_deptno number(2) := &v_deptno;
  3. BEGIN
  4. DELETE FROM dept
  5. WHERE deptno= v_deptno;
  6. END;

当部门编号输入10时,我们来看看运行结果:

  1. SQL> /
  2. Enter value for v_deptno: 10
  3. DECLARE
  4. *
  5. ERROR at line 1:
  6. ORA-02292: integrity constraint (SCOTT.FK_DEPTNO) violated - child record found
  7. ORA-06512: at line 4

违反父键约束,但是,我们如何捕捉此种错误呢?在这里,我们可以用到EXCEPTION_INIT。

使用EXCEPTION_INIT指令,可以将某Oracle错误编号和用户定义异常的名称建立关联。EXCEPTION_INIT指令出现在语句块的声明部分,如下所示:

DECLARE
      exception_name EXCEPTION;
      PRAGMA EXCEPTION_INIT(exception_name,error_code);

注意,用户定义异常的声明出现在所使用的EXCEPTION_INIT指令之前,EXCEPTION_INIT指令有两个参数:exception_name和error_code。exception_name是异常的名称,error_code是希望与该异常建立关联的Oralce错误编号。

上例可修改为:

  1. DECLARE
  2. v_deptno number(2) := &v_deptno;
  3. e_child_exists EXCEPTION;
  4. PRAGMA EXCEPTION_INIT(e_child_exists,-2292);
  5. BEGIN
  6. DELETE FROM dept
  7. WHERE deptno= v_deptno;
  8. EXCEPTION
  9. WHEN e_child_exists THEN
  10. DBMS_OUTPUT.PUT_LINE('Delete employees for No.'||v_deptno||' dept first');
  11. END;

同样将部门编号输入为10,来看看结果:

  1. SQL> /
  2. Enter value for v_deptno: 10
  3. Delete employees for No. 10 dept frist
  4.  
  5. PL/SQL procedure successfully completed.

可以正常捕捉错误!

五、 SQLCODE和SQLERRM

所以Oracle错误都可以使用OTHERS异常处理程序进行捕获和处理,如下例所示:

  1. DECLARE
  2. v_deptno number(4) := &v_deptno;
  3. v_dname varchar2(5);
  4. v_loc varchar2(10);
  5. BEGIN
  6. SELECT dname,loc
  7. INTO v_dname,v_loc
  8. FROM dept
  9. WHERE deptno = v_deptno;
  10. DBMS_OUTPUT.PUT_LINE(v_dname||','||v_loc);
  11. EXCEPTION
  12. WHEN OTHERS THEN
  13. DBMS_OUTPUT.PUT_LINE('An error has occurred');
  14. END;

当输入10作为部门编号的值时,我们来看看输出:

  1. SQL> /
  2. Enter value for v_deptno: 10
  3. An error has occurred
  4.  
  5. PL/SQL procedure successfully completed.

上述输出说明,在程序运行时发生一个错误。如果你对表结构及数据不是很熟悉的话,你很难知道这个错误是什么,以及是什么原因导致错误发生的。也许在运行时,dept表中不存在对应的部门编号,或者SELECT INTO语句所导致的数据类型匹配问题。尽管这只是一个简单地例子,但仍旧可能会发生很多意想不到的运行错误。

当然,你永远无法知道程序执行时所有可能发生的运行错误,因此,最好在自己的程序中添加OTHERS异常处理程序。为改进自己程序的异常处理接口,Oracle提供了两个内置函数-SQLCODE和SQLERRM-用于实现OTHERS异常处理程序。SQLCODE函数会返回Oracle错误编号,SQLERRM函数返回错误信息。

修改上例如下:

  1. DECLARE
  2. v_deptno number(4) := &v_deptno;
  3. v_dname varchar2(5);
  4. v_loc varchar2(10);
  5. BEGIN
  6. SELECT dname,loc
  7. INTO v_dname,v_loc
  8. FROM dept
  9. WHERE deptno = v_deptno;
  10. DBMS_OUTPUT.PUT_LINE(v_dname||','||v_loc);
  11. EXCEPTION
  12. WHEN OTHERS THEN
  13. DBMS_OUTPUT.PUT_LINE(SQLCODE||chr(10)||SQLERRM); -->> chr(10)代表回车键
  14. END;

同样,当输入10作为部门编号时,看看输出:

  1. SQL> /
  2. Enter value for v_deptno: 10
  3. -6502
  4. ORA-06502: PL/SQL: numeric or value error: character string buffer too
  5. small
  6.  
  7. PL/SQL procedure successfully completed.

这样可捕捉任何运行时错误的错误编号和错误信息

总结:

1> 预定义异常的错误代码有名称,譬如上文提到的NO_DATA_FOUNG

2> 非预定义异常只有错误代码,没有名称,如上文提到的ora-02292。这时可以通过EXCEPTION_INIT编译指令进行错误代码和名称的关联。

3> 当PL/SQL语句块的可执行部分出现某个运行错误时,会抛出不同类型的异常。但是,运行错误也可能发生在语句块的声明部分或者异常处理部分。控制在这些环境下异常抛出方式的规则称为异常传播。

4> 当PL/SQL语句块的声明部分或者异常处理部分出现运行错误时,该语句块的异常处理部分不能捕获此项错误。如果不存在外部语句块,该程序执行会终止,并将执行权转到主机环境。如果存在外部语句块,该异常会立即传播到外部语句块。如下例所示:

  1. --outer block
  2. BEGIN
  3. --inner block
  4. DECLARE
  5. v_test CHAR(3) := 'ABC';
  6. BEGIN
  7. v_test := '';
  8. DBMS_OUTPUT.PUT_LINE('v_test: '||v_test);
  9. EXCEPTION
  10. WHEN INVALID_NUMBER OR VALUE_ERROR THEN
  11. v_test := 'ABCD';
  12. DBMS_OUTPUT.PUT_LINE('An error has occurred in the inner block');
  13. END;
  14. EXCEPTION
  15. WHEN INVALID_NUMBER OR VALUE_ERROR THEN
  16. DBMS_OUTPUT.PUT_LINE('An error has occurred in the program');
  17. END;

当执行时,得到如下输出:

  1. SQL> /
  2. An error has occurred in the program
  3.  
  4. PL/SQL procedure successfully completed.

最后试举一例练练思维:

  1. DECLARE
  2. my_error1 EXCEPTION;
  3. PRAGMA EXCEPTION_INIT(my_error1, -20001);
  4. my_error2 EXCEPTION;
  5. PRAGMA EXCEPTION_INIT(my_error2, -20002);
  6. BEGIN
  7. IF 1=2 THEN
  8. raise_application_error(-20001,'err_1');
  9. ELSE
  10. raise_application_error(-20002,'err_2');
  11. END IF;
  12. EXCEPTION
  13. WHEN NO_DATA_FOUND THEN
  14. DBMS_OUTPUT.PUT_LINE('Not found');
  15. WHEN TOO_MANY_ROWS THEN
  16. DBMS_OUTPUT.PUT_LINE('More data');
  17. WHEN MY_ERROR1 THEN
  18. dbms_output.put_line('This is a err_1 test');
  19. WHEN MY_ERROR2 THEN
  20. dbms_output.put_line('This is a err_2 test');
  21. END;

PL/SQL异常处理的更多相关文章

  1. PL/SQL异常处理方法

    PL/SQL异常处理方法   1:什么是异常处理: PL/SQL提供一个功能去处理异常,在PL/SQL块中叫做异常处理,使用异常处理我们能够测试代码和避免异常退出. PL/SQL异常信息包含三个部分: ...

  2. Oracle数据库之PL/SQL异常处理

    Oracle数据库之PL/SQL异常处理 异常指的是在程序运行过程中发生的异常事件,通常是由硬件问题或者程序设计问题所导致的. PL/SQL程序设计过程中,即使是写得最好的程序也可能会遇到错误或未预料 ...

  3. ORACLE PL/SQL异常处理(Exception)学习笔记

    1.PL/SQL错误类型 错误类型 报告者 处理方法 编译时错误 PL/SQL编译器 交互式地处理:编译器报告错误,你必须更正这些错误 运行时错误 PL/SQL运行时引擎 程序化地处理:异常由异常处理 ...

  4. PL/SQL 异常处理

    SQL> set serveroutput on SQL> declare   name varchar2(10); begin   select ename into name from ...

  5. Oracle笔记 七、PL/SQL 异常处理

    --异常处理 declare sNum number := 0; begin sNum := 5 / sNum; dbms_output.put_line(sNum); exception when ...

  6. Oracle PL/SQL 异常处理

    Oracle数据库中的异常:没有异常的转移,因为没有受检异常和非受检异常得区分. 1.异常的产生: 2.异常的处理: declare --变量定义,初始化赋值. begin --变量的赋值,函数调用, ...

  7. ORACLE PL/SQL编程详解

    ORACLE PL/SQL编程详解 编程详解 SQL语言只是访问.操作数据库的语言,并不是一种具有流程控制的程序设计语言,而只有程序设计语言才能用于应用软件的开发.PL /SQL是一种高级数据库程序设 ...

  8. PL/SQL 基础编程

    PL/Sql 编程 PL/Sql结构 [declare] --声明变量 begin --执行部分 [exception] ---异常处理部分 end PL/Sql  基本数据类型 数值类型 1. nu ...

  9. [推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼、百战不殆)

    原文:[推荐]ORACLE PL/SQL编程之五:异常错误处理(知已知彼.百战不殆) [推荐]ORACLE PL/SQL编程之五: 异常错误处理(知已知彼.百战不殆) 继上三篇:ORACLE PL/S ...

随机推荐

  1. [XAF] Keep the DetailView open in a popup window

    public class ViewController1 : ViewController { ListViewProcessCurrentObjectController controller; p ...

  2. java基础2_算术运算

    一 算术运算符,包括+,-,*,/,%, 1. 如果在一个算术运算中有int,double,float那么最终运算的结果是double,那么也就是说参与运算的类型和得到的结果:结果一定是参与运算的精度 ...

  3. code review

    代码审核,开发者代码完工,就可以下一个环节,即代码审核,通过代码审核,可以看到多个开发者的代码规范,隐藏的BUG,一些注释等规范. 通过代码审核,我们开发者可以互相学习,使得代码逻辑正确,重用

  4. 网上搜集了点资料,学web的人互相分享共同进步吧(php编码的好习惯必须养成)

    网上搜集了点资料,学web的人互相分享共同进步吧 一.优秀的代码应该是什么样的? 优秀的PHP代码应该是结构化的.大段的代码应该被分割整理成一个个函数或方法,而那些不起眼的小段代码则应该加上注释,以便 ...

  5. hander消息机制原理

    基本原理 线程中调用Handler.sendMsg()方法(参数是Message对象),将需要Main线程处理的事件 添加到Main线程的MessageQueue中,Main线程通过MainLoope ...

  6. 分布式系统一致性问题和Raft一致性算法

    一致性问题 一致性算法是用来解决一致性问题的,那么什么是一致性问题呢? 在分布式系统中,一致性问题(consensus problem)是指对于一组服务器,给定一组操作,我们需要一个协议使得最后它们的 ...

  7. Java IO5:字符流

    字符流 字节流提供了处理任何类型输入/输出操作的功能(因为对于计算机而言,一切都是0和1,只需把数据以字节形式表示就够了),但它们不可以直接操作Unicode字符,因为上一篇文章写了,一个Unicod ...

  8. Wix 安装部署教程(十二) -- 自动更新WXS文件

    上一篇分享了一个QuickWIX,用来对比两个工程前后的差异,但是这样还是很繁琐,而且昨天发现有Bug,目录对比有问题.这次改变做法,完全让程序自动去更新WXS文件,然后再用CCNet去自动编译,这样 ...

  9. 使用亚马逊的Route53服务

    自从自己的博客从github迁移到AWS以上,再也不用担心Github被墙了.再加上CloudFront的CDN功能,那访问速度真是杠杆的,无论是在中国内陆,还是澳洲海边,秒开无压力. 但是这几天突然 ...

  10. GDT,LDT,GDTR,LDTR 详解,包你理解透彻(转)

    引自:http://www.techbulo.com/708.html 一.引入 保护模式下的段寄存器 由 16位的选择器 与 64位的段描述符寄存器 构成 段描述符寄存器: 存储段描述符 选择器:存 ...