Oracle中的异常_oracle 异常-程序员宅基地

技术标签: oracle  # Oracle数据库基础  数据库  开发语言  sql  

目录

一、关于PL/SQL异常 

二、区分ERROR 和 EXCEPTION

三、异常分类

四、PL/SQL抛出异常方法

五、处理异常

5.1捕获异常:

5.1.1如何捕获异常

5.1.2如何捕获预定义异常

5.1.3如何捕获非预定义异常

5.1.4捕获异常的两个函数

5.1.5捕获用户自定义异常

5.1.5raise_application_error

5.2传播异常:

六、异常应用实例

七、PL/SQL异常处理的优势


一、关于PL/SQL异常 

在 Oracle 数据库中,异常是指发生错误或不正常情况时抛出的一种对象,它可以通过异常处理机制来捕获和处理。一个例外是在程序执行期间引发的PL / SQL错误,由TimesTen隐式或由程序显式引发。通过使用处理程序捕获异常或将其传播到调用环境来处理异常。异常通常包括两个部分:异常定义和异常处理

二、区分ERROR 和 EXCEPTION

在 Oracle 数据库中,ERROR 和 EXCEPTION 是两个相关但不同的概念。

①Error 指程序执行过程中出现的不可恢复的错误,表示程序无法继续运行下去。常见的错误类型包括系统级别错误和用户自定义的错误。例如:ORA-01722(值无效)或 ORA-00955(名称已存在)。对于错误,一般采用异常处理机制无法解决问题,因为它们通常需要进行彻底的错误修复才能恢复正常情况

②Exception 则是指在代码中我们预料到的或捕获到的异常情况,即应用程序可以从某些非致命错误中恢复并继续外部任务。能够在应用程序环境中正确处理异常情况有助于提高应用程序的稳定性和可靠性。在 Oracle 数据库中,可以通过定义和抛出一些标准或自定义异常来实现异常处理机制异常可以通过自身参数和 code 值的组合来传递错误信息

③错误和异常的区别主要在于它们发生时处理的方式和目的:

错误判断的是程序是否能够继续正常执行,而异常的处理则是为了禁止产生错误,减轻错误带来的影响,并且使得应用程序更加健壮。

总结:

一般建议在能够预见到错误的情形时使用异常处理机制,而在不可预见的情况下使用错误处理。例如,在应用程序开发中,“用户名或密码错误”是可以预料的异常事件,而某些可恢复的故障(如网络问题或库存操作可能出现故障)则需要通过错误处理机制来解决。

在处理 Oracle 数据库的代码中,需要清楚的了解什么情况应该使用错误处理机制什么情况应该使用异常处理机制,并且对标准和自定义异常都应具有足够的理解和掌握。

三、异常分类

在 PL/SQL程序中可以使用的异常共分为如下三种类型:

  • 1. 预定义的Oracle服务器错误(异常),它由 PL/SQL 定义的错误条件。
  • 2. 非预定义的Oracle服务器错误(异常),非预定义的异常包括任何标准的 TimesTen 错误。
  • 3. 用户定义的错误(异常):用户自定义异常(User-Defined Exceptions),由用户在代码中定义的异常,通过 RAISE EXCEPTION 语句来手动抛出并传递给异常处理程序进行处理。用户定义的异常是特定于应用程序的异常。

在以上三种异常中,第一和第二种异常是隐式抛出的,而第3种要显式抛出。

备注:

这里的Oracle服务器错误是指由Oracle服务器发现并能处理的错误。在多数情况下产生错误的原因很可能是人为因素,如在一个SELECT INTO语句中提取了多行数据时所产生的ORA-01422错误,而与Oracle软件没有关系。

三种异常的详细描述及处理方法如下

异常种类 描述 如何处理
预定义的Oracle 服务器错误

PL/SQL 代码中最常出现的大约 20 个错误之一

您不需要声明这些异常。它们由TimesTen预定义。TimesTen隐含地提出了这个错误。

非预定义的Oracle服务器错误

除了预定义的之外,任何其他标准的Oracle 服务器错误

这些必须在应用程序的声明性部分中声明。TimesTen 隐式引发错误,您可以使用异常处理程序来捕获错误。

用户定义的错误

应用程序定义和引发的错误

这些必须在声明性部分中声明。开发人员显式引发异常,并且要用代码显式地抛出。

简单理解就是:

  1. 预定义异常 :有名字的异常,同时有错误代码和错误描述
  2. 非预定义异常:没有系统给出的名字,但是有错误代码和错误描述
  3. 自定义异常 :物理存储上没有错误,逻辑上有错,用户自定义的异常

提示:

一些客户端的PL/SQL开发、部署工具(如Oracle Developer FROMS)有一些他们自己的异常。

四、PL/SQL抛出异常方法

PL/SQL程序编译时的错误不是能被处理的异常,只有在运行时的异常能被处理。在 PL/SQL 程序设计中异常的抛出和处理是非常重要的内容,应用得当可以使代码量急剧下降。

抛出异常的方法有以下两种:

①当出现一个Oracle错误时,相关的异常被自动地抛出。

例如,一个ORA-01422错误发生。即在一个 SELECT INTO 语句中提取了多行数据时,PL/SQL引擎抛出 TOO_MANY_ROWS异常,这些错误被转换成一些预定义的异常。

②基于业务功能由程序员的程序实现,程序员可能必须要显式地抛出一个异常。

通过在程序中使用RAISE 语句程序显式地抛出一个异常,这个抛出的异常既可以是用户定义的,也可以是预定义的。还有一些非预定义的Oracle错误,这些错误是那些非预定义的标准Oracle错误。程序员可以显式地声明异常并将这些异常与非预定义的Oracle错误关联在一起。

五、处理异常

PLSQL 处理异常的方式有两种,一种是捕获(捕捉)异常(Trapping an Exception),而另一种是传播异常(Propagating an Exception),如下图给出了PL/SQL引擎异常处理的流程示意图。

5.1捕获异常:

在PL/SQL 程序中包含了一个异常段(EXCEPTION section)以捕获异常。如果异常在这个程序的执行段中被抛出,那么处理就自动跳转到这个程序的异常段中的相应异常处理程序。如果异常处理程序成功地处理了这个异常,那么这个异常就不会传播到包含它的程序段,也不会传播到调用环境,而且这个PL/SQL程序块成功地结束。

5.1.1如何捕获异常

可以通过在异常处理段中包含一个对应的处理程序来捕获任何错误,每一个异常处理程序是由一个带有一个已经声明了的异常名的 WHEN 子句和紧随其后的一个语句序列组成(这个语句是在异常被抛出时执行的),异常段通常是存储过程和函数中的一个块,用于执行抛出异常时要执行的特定代码。其 异常段语法如下:

BEGIN
  -- 可能会引发异常的代码块
EXCEPTION
  WHEN exception1 THEN
    -- 处理 exception1 异常的代码块
  WHEN exception2 THEN
    -- 处理 exception2 异常的代码块
  WHEN OTHERS THEN
  -- 其他异常情况
END;

在此语法中,EXCEPTION 关键字引入了异常处理段,并列出了可能引发的异常和对应的处理方法。每个 WHEN 子句定义了一种异常和该异常的处理代码。如果引发了列表中的任何一个异常,将跳转到相应的 EXCEPTION 处理段并执行相应代码。如果没有异常被触发,则跳过 EXCEPTION 块并正常继续执行程序。

注意:

在 EXCEPTION 块中,可以使用多个 WHEN 子句来捕获不同类型的异常。也可以使用 OTHERS 子句来捕获所有其他未列出的异常。除了专门进行调试之外,不建议在生产环境中使用 OTHERS 子句。

OTHERS异常是指未被程序员显式定义的异常类型。当一个命令引发了不能归类为其他已知异常类型的错误时,就会抛出others异常。一旦如果使用了OTHERS异常,那么它一定是所定义的最后一个异常处理程序。

例如,在PL/SQL存储过程中,如果尝试打开一个不存在的表,系统会自动抛出ORA-04043异常(exceptions之一),但是如果没有捕获这个异常,则会抛出others异常,因为系统无法确定该错误的确切类型,也就无法做出有针对性的处理。在这种情况下,系统可能会返回一个默认的“Unhandled Exception”错误消息。

备注:

由于others异常具有不充分信息和通用性较高的特点,因此在实际编程中应尽量避免出现它。合理使用异常处理机制可以提高程序的稳定性和可靠性,并且能够更快地检测出错误并解决问题。

5.1.2如何捕获预定义异常

预定义异常是由Oracle提供的一些通用异常类型,例如除零、数据溢出以及未定义对象等异常。可以通过捕获和处理这些异常来完善程序的稳定性。

以下是一个基本的捕获预定义异常的语法:

BEGIN
  -- 可能抛出异常的代码块
EXCEPTION
  WHEN exception1 THEN
    -- 对exception1异常的处理逻辑
  WHEN exception2 THEN
    -- 对exception2异常的处理逻辑
  ...
  WHEN others THEN
    -- 对其他异常的处理逻辑
END;

在此代码块中,可以包含可能发生异常的PL/SQL代码。如果其中的任何一个命令引发了预定义异常,则会跳转到相应的异常处理程序,而不是直接终止程序执行。

可以根据需要添加对多个不同异常的处理程序,每个程序都需要指定其特定的异常类型。如果发生了未指定的异常,则会进入`others`程序段,用于处理所有其他未知异常。

注意:

使用`EXCEPTION`块可以捕获系统或Oracle提供的预定义异常,但无法捕获用户定义的异常。为了捕获用户定义的异常,必须自己显式地声明并抛出出问题的异常。

Ⅰ、在Oracle中,有许多预定义的异常类型。下面有一些最最常见的:

  • 1. `NO_DATA_FOUND`:用于检测SELECT语句未找到数据时的异常。
  • 2. `DUP_VAL_ON_INDEX`:当在一个唯一索引上插入重复值时抛出的异常。
  • 3. `TOO_MANY_ROWS`:用于检测SELECT语句返回多个行时的异常。
  • 4. `INVALID_NUMBER`:在将不是数字的字符转换为数字时抛出的异常。
  • 5. `VALUE_ERROR`:当表达式或变量的值超出其有效范围时抛出的异常。
  • 6. `PROGRAM_ERROR`:当PL/SQL程序中出现了内部错误时抛出的异常。
  • 7. `STORAGE_ERROR`:当存储空间被耗尽时,例如在堆栈溢出时抛出的异常。
  • 8. `TIMEOUT_ON_RESOURCE`:当等待太久且在指定时间内无法获得系统资源(例如通过锁)时抛出的异常。

以上只是预定义异常列表的一小部分,还有许多其他的预定义异常类型可以在Oracle文档中查询到。在编写PL/SQL程序时,理解这些异常类型以及如何处理它们对于确保程序正确性、提高代码健壮性和可靠性非常重要。

下面列出了可能经常使用的预定义异常参考

异常名称 甲骨文数据库错误号 SQLCODE 描述

ACCESS_INTO_NULL

ORA-06530

-6530

程序试图为未初始化对象的属性赋值。

CASE_NOT_FOUND

ORA-06592

-6592

语句子句中的任何选项均未被选中,并且没有子句。WHENCASEELSE

COLLECTION_IS_NULL

ORA-06531

-6531

程序试图将 EXISTS 以外的收集方法应用于未初始化的嵌套表或 varray,或者程序试图将值分配给未初始化的嵌套表或 varray 的元素。

CURSOR_ALREADY_OPENED

ORA-06511

-6511

程序试图打开已打开的游标。

DUP_VAL_ON_INDEX

ORA-00001

-1

程序试图在受唯一索引约束的列中插入重复值。

INVALID_CURSOR

ORA-01001

-1001

存在无效的游标操作。

INVALID_NUMBER

ORA-01722

-1722

将字符串转换为数字失败。

NO_DATA_FOUND

ORA-01403

+100

单行不返回任何行,或者程序引用了嵌套表中的已删除元素或关联数组(索引依据表)中的未初始化元素。SELECT

PROGRAM_ERROR

ORA-06501

-6501

PL/SQL有一个内部问题。

ROWTYPE_MISMATCH

ORA-06504

-6504

赋值语句中涉及的主机游标变量和 PL/SQL 游标变量具有不兼容的返回类型。

STORAGE_ERROR

ORA-06500

-6500

PL/SQL 内存不足或内存已损坏。

SUBSCRIPT_BEYOND_COUNT

ORA-06533

-6533

程序使用大于集合中元素数的索引号引用嵌套表或 varray。

SUBSCRIPT_OUTSIDE_LIMIT

ORA-06532

-6532

程序使用索引号引用嵌套表或 varray 元素 超出有效范围(例如 -1)。

SYS_INVALID_ROWID

ORA-01410

-1410

将字符串转换为通用 rowid 失败,因为字符串不表示值。ROWID

TOO_MANY_ROWS

ORA-01422

-1422

单行返回多行。SELECT

VALUE_ERROR

ORA-06502

-6502

发生算术、转换、截断或大小约束错误。

ZERO_DIVIDE

ORA-01476

-1476

程序试图将数字除以零。

相关内容参考ORACLE文档,可以在官网查看,相关链接: Trapping Exceptions (oracle.com)

Ⅱ、预定义异常示例:

例题1、在此示例中,PL/SQL 程序尝试除以 0。预定义的异常用于在异常处理例程中捕获错误。ZERO_DIVIDE

DECLARE v_invalid PLS_INTEGER;
BEGIN
v_invalid := 100/0;
EXCEPTION
WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE ('Attempt to divide by 0');
END;

输出结果如下:

例题2、查询emp表123号员工的薪水,没有就抛出异常,如果查询到数据返回行数多余一行也抛出异常,如下:

DECLARE
  v1 emp.sal%TYPE; -- 使用数据类型
BEGIN
  SELECT sal INTO v1 FROM emp WHERE empno = 123; -- 添加 WHERE 条件
  DBMS_OUTPUT.PUT_LINE(v1);
EXCEPTION
  WHEN NO_DATA_FOUND THEN -- 改为处理未找到数据的异常
    DBMS_OUTPUT.PUT_LINE('没有符合条件的记录');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('返回行太多了');
END;

 执行后输出结果如下:

 解析下上面语句:

首先,定义了一个v1变量,并使用%TYPE方法对其进行了初始化,这样会根据emp表中的sal列动态推断出正确的数据类型。在SELECT语句中有一个WHERE子句,是确定所选择的员工。如果没有符合条件的记录,则会抛出NO_DATA_FOUND异常。在程序中也要添加相应的异常处理程序来提供用户有用的反馈信息。然后too_many_rows异常设置了异常处理过程,在这种情况下输出“返回行太多了”错误消息。

5.1.3如何捕获非预定义异常

预定义异常通常可以被系统如SQL等自动抛出,并提供有关错误信息和上下文的详细信息,使其易于调试或查询问题。因此,在 PL/SQL 程序中,通常使用这些预定义异常来捕获和处理常见的错误情况,以确保代码的健壮性和可靠性。与之相反,非预定义异常是指由用户声明和抛出的异常。可以通过使用PRAGMA EXCEPTION_INIT函数为标准Oracle错误创建异常。

PRAGMA EXCEPTION_INIT是一种指示编译器在程序块中处理异常时应该如何处理异常的方式。以下是使用PRAGMA EXCEPTION_INIT声明异常的基本语法:

PRAGMA EXCEPTION_INIT(exception_name, error_code);
  • exception_name是要创建的异常名称。命名约定为:以"EX_"前缀开头、后面跟着大写字母,多个单词可以用下划线分隔。

  • error_code是要与异常相关联的错误代码。必须大于或等于-20000且小于-20999。

如果在程序块中抛出这个异常,那么Oracle会将代码$ERROR_CODE设置为相关联的错误代码,进而向SQLERRM函数返回给定的异常描述信息。

例如,在下面的代码块中,声明一个名为EX_INVALID_VALUE的自定义异常,并使用PRAGMA EXCEPTION_INIT将其与错误代码-20001相关联:

DECLARE
  ex_invalid_value EXCEPTION;
  PRAGMA EXCEPTION_INIT(ex_invalid_value, -20001);
  v_value NUMBER := 0;
BEGIN
  IF v_value = 0 THEN
    RAISE ex_invalid_value;
  END IF;
EXCEPTION
  WHEN ex_invalid_value THEN
    DBMS_OUTPUT.PUT_LINE('无效值错误: ' || SQLERRM);
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('其他错误: ' || SQLERRM);
END;

 执行后输出结果如下:

在这个例题中,如果条件v_value= 0为真,则程序将抛出异常ex_invalid_value。在EXCEPTION块中,为每种可能的异常情况也提供处理,并使用SQLERRM函数(下面5.1.4会描述)来输出有关错误的详细信息。如果出现与自定义异常无关的其他异常,则第二个处理程序就会捕获它们并输出相应的错误消息。

pragma exception_init可以把自定的错误名称和Oracle内部错误号关联起来,这样就可以在程序异常处理部分按错误名称抛出异常执行。
注意:

EXCEPTION_INIT命令必须在声明部分定义。

5.1.4捕获异常的两个函数

为了更方便地处理异常、调试问题以及优化程序的健壮性和可靠性等方面,Oracle提供了多个函数来处理异常。以下是Oracle捕获异常的两个常用函数:

Ⅰ、SQLERRM函数:

  • 用法:SQLERRM返回当前定义异常错误信息。如果当前没有定义异常,则它返回一条未知错误信息
  • 示例:
DECLARE
  ex_divide_by_zero EXCEPTION;
  PRAGMA EXCEPTION_INIT(ex_divide_by_zero, -1476);

  v_result NUMBER;

BEGIN
  v_result := 10/0;
EXCEPTION
  WHEN ex_divide_by_zero THEN
    DBMS_OUTPUT.PUT_LINE('错误:' || SQLERRM);
END;

执行后结果如下:

在这个例子中,由于除数为零,程序抛出一个名为ex_divide_by_zero的异常。在EXCEPTION块中,通过调用SQLERRM函数返回有关异常的详细描述信息,帮助我们解决问题。

Ⅱ、SQLCODE函数:

  • 用法:SQLCODE返回最近执行操作(INSERT、UPDATE、DELETE)时产生的最后一个 SQL 错误码或与未处理的异常相关联的错误代码。
  • 示例:
DECLARE
  ex_invalid_salary EXCEPTION;
  PRAGMA EXCEPTION_INIT(ex_invalid_salary, -20001);

  v_employee_id NUMBER := 100;
  v_new_salary NUMBER := 1000000;
BEGIN
  IF v_new_salary > 99999 THEN
    RAISE ex_invalid_salary;
  ELSE
    UPDATE employees SET salary = v_new_salary WHERE employee_id = v_employee_id;
    DBMS_OUTPUT.PUT_LINE('员工薪水已更新');
  END IF;
EXCEPTION
  WHEN ex_invalid_salary THEN
    DBMS_OUTPUT.PUT_LINE('错误代码:' || SQLCODE);
    DBMS_OUTPUT.PUT_LINE('错误信息:员工薪资无效');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('其他错误:' || SQLERRM);
END;

执行后结果如下:

在例子中,如果v_new_salary的值大于99999,则程序将抛出名为ex_invalid_salary的异常。在EXCEPTION块中,通过调用SQLCODE函数返回有关最后一个 SQL 错误码的信息(本例中为-20001),使我们感知到了异常的意义。

  • SQLCODE的值描述

SQLCODE是一个内置函数,用于返回最近执行的 SQL 语句或 PL/SQL 块产生的错误代码。如果没有错误,则SQLCODE返回0。而且SQLCODE函数的具体值由特定的异常、PL/SQL块或SQL命令所产生的错误码决定。以下是一些可能出现在SQLCODE函数中的常见错误代码的含义:

  • 0:表示SQL操作已成功完成。
  • -1:FETCH语句未找到任何行或已经执行完对结果集的所有操作
  • -9:SELECT ... INTO 子句执行时没有找到任何行
  • -1403:NO_DATA_FOUND 异常,SQL 或 PL/SQL 没有查询到数据
  • -2292:存在外键约束无法删除主表记录
  • -4091:已经创建当前触发器的原因被拒绝
  • 1004:程序检测到数据库“锁死”或请求超时
  • 1:表示错误退出(通常由用户发出自定义异常引起)。如果程序遇到任何未处理的异常,则默认情况下会抛出这种类型的异常。因此,在编写 PL/SQL 程序时,开发人员要确保对所有可能出现的异常进行捕获和处理,以免出现未知异常。
  • +100:表示在FETCH语句或孤立的SELECT INTO子句中未找到记录(NO_DATA_FOUND异常)。这意味着使用条件时无法查询到期望结果或无符合条件的当前行。在这种情况下,最好添加额外的检查,以确保您收到预期的结果。

注意

上述值只是SQLCODE函数返回的一小部分可能的值,即使是同一个错误代码也可以有不同参数值的更详细描述,但是SQLCODE具体的返回结果取决于特定的 PL/SQL 区块、SQL 语句或应用程序逻辑的具体操作。因此,在实际使用中,我们应该全面考虑所有可能与 SQLCODE 相关的问题。为了确保平稳的异常处理,我们也要注意尽量使用具体的错误码进行处理。

总结:

SQLCODE 为错误代码返回一个数值(可以将其赋予一个数字变量)
SQLERRM 返回字符串数据,包含了与错误号相关的错误信息

5.1.5捕获用户自定义异常

使用EXCEPTION块捕获用户自定义异常,需要通过显式地抛出异常来处理可能发生的错误情况,使程序具有更高的可读性和容错性。

以下是创建和捕获用户自定义异常的基本步骤:

①声明异常并给它命名。可以定义一个名为EX_MY_EXCEPTION的异常,或者使用继承自其他异常类别(如PL/SQL预定义异常)的异常。

DECLARE
  ex_my_exception EXCEPTION;
  PRAGMA EXCEPTION_INIT(ex_my_exception, -20001);

②在程序块中识别可能会引发异常的代码块。

BEGIN
  -- Some code that may raise an exception.
END;

 ③当异常和其关联的条件发生时进行异常处理。

EXCEPTION
  WHEN ex_my_exception THEN
    -- Handle the specific exception here.
  WHEN OTHERS THEN
    -- Handle any other exceptions here.
END;

例如,假设我们要处理在除以负数时引发的“无效除数”异常。代码以下:

DECLARE
  ex_invalid_divisor EXCEPTION;
  PRAGMA EXCEPTION_INIT(ex_invalid_divisor, -20001);

  v_dividend NUMBER := 100;
  v_divisor NUMBER := -1;
  v_result NUMBER;
BEGIN
  IF v_divisor <= 0 THEN
    RAISE ex_invalid_divisor;
  ELSE
    v_result := v_dividend/v_divisor;
    DBMS_OUTPUT.PUT_LINE('结果:' || v_result);
  END IF;
EXCEPTION
  WHEN ex_invalid_divisor THEN
    DBMS_OUTPUT.PUT_LINE('无效的除数:divisor不能小于等于零');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('其他错误:' || SQLERRM);
END;

执行后结果如下:

在上面例子中,我定义了一个名为ex_invalid_divisor的异常,并将其与错误代码-20001相关联。当v_divisor小于等于0时,程序将会抛出异常并执行异常处理程序。如果没有抛出自定义异常,则程序将捕获所有未知异常并输出详细信息。 

5.1.5raise_application_error

RAISE_APPLICATION_ERROR函数用于抛出自定义异常。我们可以使用这个函数来处理除预定义异常以外的异常(非预定义异常和用户自定义异常)。以下是使用RAISE_APPLICATION_ERROR函数来捕获非预定义异常的基本语法:

RAISE_APPLICATION_ERROR(error_code, error_message);
  • error_code是一个整数值,必须是大于等于-20000和小于等于-20999之间的数字不能与任何其他错误代码重复。

  • error_message是一个字符串,包含有关错误的详细信息。最大长度为2048个字符

RAISE APPLICATION_ERROR过程的主要用处是处理SQLCODE和 SQLERRM函数的返回值。该过程在日志表中提供了一致的记录错误信息的方法。

注意:RAISE_ APPLICATION ERROR过程将终止所在PLSQL程序块中语句的进一步执行。

RAISE_APPLICATION_ERROR 过程既可以用在 PL/SQL 程序的执行段中,也可以用在PLSQL程序的异常段中,或同时用在这两个段中。无论是预定义、非预定义,还是用户定义的错误/异常,该过程都会返回一致的错误信息,都是以错误号码和错误信息的方式显示给用户。

 例如,假设我要在检查除数为0时抛出一个自定义异常。代码如下:

DECLARE
  v_dividend NUMBER := 100;
  v_divisor NUMBER := 0;
  v_result NUMBER;
BEGIN
  IF v_divisor = 0 THEN
    RAISE_APPLICATION_ERROR(-20001, '无效的除数: 除数不能为零');
  ELSE
    v_result := v_dividend/v_divisor;
    DBMS_OUTPUT.PUT_LINE('结果:' || v_result);
  END IF;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;

执行后结果如下:

在例子中,如果除数为零,就会抛出自定义异常并显示详细错误消息。参数-20001是自定义错误代码,当发生异常时将其传递给SQLERRM函数(该函数返回当前错误的详细信息)。

注意:

EXCEPTION块中,用WHEN OTHERS来捕获所有其他异常 。

5.2传播异常:

如果一个异常在程序的执行段被抛出并且没有对应的异常处理程序,那么这个PLSQL 程序块以失败而终止,并且这个异常被传播到包含它的程序块或调用环境,调用环境可以是任何应用程序(如调用PL/SQL程序的SQL*Plus)。也就是说,当在内部块中引发异常时,该异常会被传递到调用它的外部块中,直到捕获或处理该异常的代码段。

通过使用这种传播异常机制,开发人员可以创建更加模块化和易于维护的PL/SQL代码,并将错误处理逻辑从具体的子程序或过程中提取出来。如果在其中一个块中发生异常,该异常可以向上层块传播,并处理异常。

例如,内部块抛出异常并立即结束:

DECLARE 
  --- 声明变量
BEGIN
  -- 执行一些操作
  BEGIN
    -- 在内部块中执行某些操作(可能警告:此处仅作演示)
    RAISE_APPLICATION_ERROR(-20001, '内部块逻辑错误');
  END;
EXCEPTION
  WHEN OTHERS THEN
    -- 异常处理
    DBMS_OUTPUT.PUT_LINE('发生错误 :' || SQLERRM);
END;

输出结果如下:

上例中的问题就是内部块抛出了一个自定义的应用程序错误。但由于该异常没有被内部块中的任何异常处理程序捕获,因此它会自动传播到外部块,并答应在最后的 DBMS_OUTPUT.PUT_LINE 中。

注意:

异常传播可以提高代码的可读性和维护性,但在某些情况下也可能导致意外的结果。例如,在一个示例中,如果一个异常没有被内部块或外部块捕捉到,程序就会异常终止,并显示原始错误消息及其堆栈信息。因此,在编写用于生产环境的PL/SQL代码时,请务必考虑好异常传播的情况,并谨慎处理任何可能引发异常的代码。

六、异常应用实例

异常的定义通常通过 DECLARE EXCEPTION 语句来创建,其中包括异常名称、异常号码以及一个可选的异常消息。例如:

DECLARE
  my_exception EXCEPTION;
BEGIN
   -- do something
   IF (something_wrong) THEN
      RAISE my_exception;
   END IF;
   -- do something else
EXCEPTION 
   WHEN my_exception THEN
      dbms_output.put_line('My Exception Occured. Error Code: '||sqlcode||' Msg: '||sqlerrm);
END;

通过这种方式,我们可以为特定的异常类型定义实现自定义错误明确说明,使得代码变得更清晰易懂。同时,异常的处理也可以帮助我们更好地进行调试和维护。 下面就列举几个例子:

例题1.向emp9表插入一条数据,故意违反外键约束,但是不弹窗报错,而是打印错误编号,错误代码,并插入到t_err表(错误编码,错误描述,错误时间)中.

在执行上述任务之前,需要确保 emp9 和 t_err 两个表已经创建。语句如下:

CREATE TABLE emp9 (
  emp_id NUMBER PRIMARY KEY,
  emp_name VARCHAR2(50),
  dept_id NUMBER REFERENCES dept(deptno)
);

CREATE TABLE t_err (
  error_code NUMBER,
  error_desc VARCHAR2(4000),
  error_time DATE
);

我们是首先创建一个名为 emp9 的表,其中包含员工ID(emp_id)、员工姓名(emp_name)和部门ID(dept_id)等列,并将 dept 表中的部门ID设置为外键约束。然后,再创建一个名为 t_err 的表,我们用于存储任何捕获的异常的详细信息。

接下来,我们可以使用以下代码向 emp9 表中插入一行数据,以故意触发外键约束并记录错误信息,语句如下:

DECLARE
  v_err_code NUMBER;
  v_err_msg VARCHAR2(4000);
BEGIN
  INSERT INTO emp9 (emp_id, emp_name, dept_id)
  VALUES (1001, '张三', 999); -- 将不存在的部门ID插入到雇员表中

EXCEPTION
  WHEN OTHERS THEN
    v_err_code := SQLCODE; -- 获取错误编号
    v_err_msg := SUBSTR(SQLERRM, 1, 4000); -- 获取错误消息
	-- 插入到错误日志表中并输出错误信息
    INSERT INTO t_err (error_code, error_desc, error_time)
    VALUES (v_err_code, v_err_msg, SYSDATE);
    DBMS_OUTPUT.PUT_LINE('错误编号:' || v_err_code || ',错误信息: ' || v_err_msg);
END;

我们执行以下,因为我在语句中没有添加commit,所以记得点击提交。然后我们查询 t_err表,结果如下:

我尝试将一个不存在的部门ID(999)插入到 emp9 表中,因此会触发外键约束。如果不进行异常处理,我将会在页面看到一个弹窗错误提示框,指出该操作违反了外键约束,如下图所示:

此外,还通过 SQLCODE 函数和 SQLERRM 函数获取错误代码和错误描述。在例题中,我使用 SUBSTR 函数来限制错误信息的长度,以确保它不超过 VARCHAR2 数据类型的允许范围。如果错误超过此长度,则只记录错误的前4000个字符(主要是为了以防外一,懂得都懂哈!)。

最后,使用 DBMS_OUTPUT 函数打印错误信息,以便在调试或记录目的下进行检查。

小提示:

在实际生产环境中,我们可能需要调整日志记录级别或将它们直接插入到关系型数据库或日志管理系统中进行存储。

例题2. 向emp1表插入一条数据(表结构同emp),要求员工编号,必须以7开头并且是4位,否则抛出异常'不符合要求,请重新输入',语句如下:

--如果没有创建emp1,就先创建表,语句如下:
create table emp1 as select * from emp where 1=2;
---------------------------------------------------------
declare
err exception;
begin
  if &a not like '7___' then
    raise err;  --抛出异常
  end if;
  insert into emp1(empno) values(&a);
exception
  when err then 
    dbms_output.put_line
    ('不符合要求,请重新输入');
  raise_application_error(-20001,'不符合要求,请重新输入');
end;

执行后在弹框内输入不符合要求的编号,如图所示:

 然后页面出现下面错误弹框提示:

 解释下ORA-06512:

ORA-06512是Oracle数据库中的一种常见错误代码,它表示出现了PL/SQL引起的错误。通常,Oracle会显示类似以下消息的错误堆栈信息:

ORA-06512: 在line 4
报错  ORA-01403: 未找到数据

“ORA-06512”提供运行程序时出现问题的位置,在本例中是第4行。详细的错误信息,即“ORA-01403:未找到数据”,则指示由于查询结果集为空而导致错误。错误代码详情可查看【5.1.2如何捕获预定义异常-Ⅰ

注意:

ORA-06512并不是一个具体的错误,而只是表明存在另一个错误,其根本原因可能很多,并且在不同情况下可能用于指示不同类型的错误。因此,在解决该错误之前,必须先了解发生错误的具体上下文以及与错误码相关的其他详细信息。

解决 ORA-06512 常用的方法是检查 PL/SQL 代码中是否存在错误或异常,或者查看 SQL 命令是否遇到了逻辑错误或库表不存在。可以使用PL/SQL块或SQL*Plus工具来捕捉和记录异常的详细信息,例如:

BEGIN
  some_procedure;
EXCEPTION
  WHEN OTHERS THEN
    INSERT INTO error_log (error_message)
    VALUES (DBMS_UTILITY.FORMAT_ERROR_STACK || ' - ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RAISE;
END;

异常处理块捕获任何未处理的异常并将其记录在数据库日志表中,包括详细错误堆栈信息和SQL语句调用的代码位置。我们通过这种方式,就可以更快速、更准确地定位并解决PL/SQL程序中的错误。

例题3.向emp1表插入一条empno,sal,job(&a &b &c),要求empno必须是4位数字并且7开头,工资必须大于3000,工作不能为空,如果不符合规定  就报错 内容是'..必须..' ,语句如下:

declare
err1 exception;
err2 exception;
err3 exception;
v1 number:=&a;
v2 number:=&b;
v3 varchar2(20):='&c';
begin 
  if v1 not like '7___' then
    raise err1;
  end if;
  if v2<=3000 then 
    raise err2;
  end if;
  if v3 is null then
     raise err3;
  end if;
 insert into emp1 (empno,sal,job) values(v1,v2,v3);
 commit;
exception
  when err1 then
    raise_application_error(-20001,'empno必须是4位数字并且7开头');
  when err2 then
    raise_application_error(-20002,'工资必须大于3000');
  when err3 then
    raise_application_error(-20003,'工作不能为空');
end;

执行上述语句,开始测试,然后a输入654,b输入2,c不输入,如图所示:

结果如图所示:

结果发现他会先报第一个when的错误异常,那就接着测试,a=7788正常员工编号,b=2,c不输入,执行看结果如下:

结果发现他会先报第二个when的错误异常,那就接着测试,a=7788正常员工编号,b=3001,c不输入,执行看结果如下:

通过以上结果可以清楚错误是逐一返回,而且有没有发现这些错误都是直接弹窗的哦,我们自己定义错误代码和错误描述,是不是感觉和数据库系统自带的弹窗一毛一样啦!

总结:

当匿名块中有多条语句的时候如果前面出现了异常,后面的语句不再执行直接到异常处理部分。如果后面出现了异常前面的语句正常执行。

但是我们不需要弹窗显示,怎么解决?异常的处理,比如自定义应用程序错误消息或记录日志(或两者兼而有之),和例题1那样把错误异常记录到一张表中,看例题4。

例题4.直接放代码了哈!

第一步:创建日志表t_error_log以存储异常信息

CREATE TABLE t_error_log (
  id          NUMBER(10) PRIMARY KEY,
  error_time  DATE DEFAULT SYSDATE,
  error_trace VARCHAR2(4000)
);

第二步:创建一个序列seq_ph,用于日志表ID的自动排序

CREATE SEQUENCE seq_ph
    START WITH 0
    INCREMENT BY 1
    MINVALUE -9999
    MAXVALUE 9999
    CYCLE;

第三步:编写PL/SQL块,输入一个薪资,如果薪资小于3000,就记录'工资太低,请增加工资。',如果薪资大于100000,就记录'工资太高,请减少工资。':

DECLARE
  err1 EXCEPTION;
  err2 EXCEPTION;
  v_salary NUMBER := &salary;
BEGIN
  IF v_salary < 3000 THEN
    RAISE err1;
  ELSIF v_salary > 100000 THEN
    RAISE err2;
  ELSE
    DBMS_OUTPUT.PUT_LINE('OK');
  END IF;

EXCEPTION
  WHEN err1 THEN
    INSERT INTO t_error_log (id,error_time, error_trace) 
    VALUES(seq_ph.NEXTVAL,SYSDATE, '工资太低,请增加工资。');
    
  WHEN err2 THEN
    INSERT INTO t_error_log (id,error_time, error_trace) 
    VALUES(seq_ph.NEXTVAL,SYSDATE, '工资太高,请减少工资。');
   
  WHEN OTHERS THEN
    INSERT INTO t_error_log (id,error_time, error_trace) 
    VALUES(seq_ph.NEXTVAL,SYSDATE, DBMS_UTILITY.format_error_backtrace());
    RAISE;
END; 

 开始测试,输入2000,200000,然后查看日志表

符号&解释:

我在上面的多个例题中使用到了一个特殊符号&,在Oracle 11g及其之前的版本中,&符号用于表示它后面的部分是一个变量或参数,需要由用户输入值。该符号主要用于替换SQL或PL/SQL语句中不确定的值。

例如,在下面的例子中,输入Emp_ID时将被自动插入到sql语句中:

SELECT *
FROM Employees
WHERE employee_id = &Emp_ID;

当运行上述代码时,Oracle会提示用户输入一个值,并在您提供了所需值之后执行查询并返回结果。

需要注意:

尽管此符号在Oracle 11g及之前的版本中使用广泛,但在新版本 (12c、18c等) 中已经废弃。现代的版本中通常使用`:variable_name`或 `:n`作为参数占位符,这样应用程序可以使用绑定变量来避免 SQL 注入攻击(网络安全中的相关知识,有兴趣可以去了解哈)并优化执行时间。

七、PL/SQL异常处理的优势

 异常处理的优势包括以下几个方面:

  • 1. 增强程序可读性和可维护性:异常处理使开发人员能够将特定异常视为单独的事件,从而使代码更清晰地表达其功能,并减少代码复杂度。异常处理通常会在逻辑上隔离程序中可能引发异常的部分,并为每种异常提供单独的处理器,这有助于增强程序的可读性和可维护性。
  • 2. 提高程序稳定性:通过在程序中捕获异常并采取正确措施进行处理,可以避免程序崩溃或不良行为等问题,有助于提高程序的健壮性、稳定性和可用性。
  • 3. 简化调试:当程序出现错误时,异常处理使得开发人员能够快速定位错误和警告信息,并快速采取措施进行修复问题,缩短了调试时间。此外,在错误处理程序中记录详细的错误消息还可以为未来日志记录和故障排除提供帮助。
  • 4. 改善用户体验:有效的异常处理可以向终端用户提供友好的错误提示,解释他们所遭遇的问题,并为解决问题提供指导。这有助于改善使用者体验,并让用户感觉到程序是安全、可靠和易于使用的。

总之,异常处理对于减少代码复杂性、提高程序健壮性、缩短调试时间以及改善用户体验等方面都具有重要而显著的优势。良好的异常处理机制可以帮助开发人员编写更稳定、更健壮、更清晰且易于维护的程序。

最后给大家留下一个查看Oracle数据库异常弹窗错误的官网文档查询链接:

Oracle Database Database Error Messages, 19c

Oracle数据库官网链接:

甲骨文 |云应用和云平台 (oracle.com)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_71406734/article/details/130757225

智能推荐

Linux内存管理:(十二)Linux 5.0内核新增的反碎片优化_linux内存分配水位-程序员宅基地

文章浏览阅读1.1k次,点赞26次,收藏22次。文章说明:Linux内核版本:5.0架构:ARM64参考资料及图片来源:《奔跑吧Linux内核》_linux内存分配水位

HDU-1828 Picture(扫描线 求矩形并的周长)-程序员宅基地

文章浏览阅读144次。http://acm.hdu.edu.cn/showproblem.php?pid=1828Time Limit: 6000/2000 MS (Java/Others)Memory Limit: 32768/32768 K (Java/Others)Problem DescriptionA number of rectangular posters, photogra..._k - picture(扫描线)

结构化伪类选择器_结构化伪类选择器有哪些-程序员宅基地

文章浏览阅读1w次,点赞9次,收藏45次。1.:root选择器 :root选择器用于匹配文档根元素,在HTML中,根元素始终是html元素。也就是说使用“:root选择器”定义的样式,对所有页面元素都生效。对于不需要该样式的元素,可以单独设置样式进行覆盖。&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;titl..._结构化伪类选择器有哪些

查看 CUDA cudnn 版本 查看Navicat GPU版本_查看nvicc-程序员宅基地

文章浏览阅读406次。用于记录写小东西。 查看 CUDA cudnn 版本cuda 版本 cat /usr/local/cuda/version.txtcudnn 版本 cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 212345查看Navicat GPU版本nvidia-smi//10s显示一次watch -n 10 nvidia-smi..._查看nvicc

【自学总结】11 - Passthrough配置文件 linux-la.config 内容分析_display bringup-程序员宅基地

文章浏览阅读461次。【自学总结】11 - Passthrough配置文件 linux-la.config 内容分析_display bringup

单片机C语言流水灯花样编程,51单片机学习之陆 —— 1.3 流水灯的花样玩法-程序员宅基地

文章浏览阅读2.3k次,点赞2次,收藏23次。原标题:51单片机学习之陆 —— 1.3 流水灯的花样玩法上一次我们点亮了一个1,其实仅仅点亮一个流水灯还是有些单调的。这一次让我们试着尝试些别的花样1 多个流水灯的点亮a 打开将原来的c文件关掉 左边的也删掉,因为我并不像重新创建工程按照昨天的方法新建一个c文件,并添加到工程中(以后也这样,所以请有自己的命名规则,否则都分不清了,而我这里命名的是LED1.1) b 将代码写上。(假如你要点亮1...

随便推点

【文献翻译】基于深度学习的脑电信号癫痫自动检测系统_深度学习驱动的?检测:创新方法与技术实现【?检测实战-程序员宅基地

文章浏览阅读4.5k次。摘要癫痫是一种神经系统疾病,对于其检测,脑电图(EEG)是一种常用的临床方法。脑电信号的人工检测是一个费时费力的过程,这给神经科医生带来了沉重的负担,并影响了他们的表现。已经提出了几种使用传统方法的自动技术来帮助神经学家检测二元癫痫场景,例如癫痫与非癫痫或正常与发作。这些方法在对三元病例进行分类时表现不佳,例如,发作期与正常期与发作间期;采用最先进的方法,这种情况下的最大准确度为97±1%。为了克服这个问题,我们提出了一个基于深度学习的系统,它是金字塔一维卷积神经网络(P-1D-CNN)模型的集成。在CN_深度学习驱动的?检测:创新方法与技术实现【?检测实战

gtest中ASSERT与EXPECT断言的区别_assert和expect-程序员宅基地

文章浏览阅读1.2w次。参考资料查找到ASSERT断言与EXPECT断言的区别:ASSERT_* 系列的断言,当检查点失败时,退出当前函数(注意:并非退出当前案例)。EXPECT_* 系列的断言,当检查点失败时,继续往下执行。其中,对退出当前函数但是不退出当前案例不理解,于是查看了官方文档进行学习gtest文档地址:https://github.com/google/googletest/blob/mas..._assert和expect

Java中List集合的三种遍历方式(全网最详)_list example-程序员宅基地

文章浏览阅读7.7k次,点赞11次,收藏37次。List集合在Java日常开发中是必不可少的,要懂得运用各种各样的方法可以大大提高我们开发的效率,当然是要在对应的需求上使用合适的方法才会事半功倍。List集合:List exampleList = new ArrayList&amp;lt;&amp;gt;();实体类(Example):package ListExample;import java.io.Serializable;/** * ..._list example

Python基于Flask的电影论坛系统_flask论坛-程序员宅基地

文章浏览阅读268次。该系统的应用可以减少工作人员的劳动强度,提高工作效率与管理水平,具有很大的价值。它可以使电影网站操作简单,成功率高,使网上电影的管理向一个更高层次前进。在这个以大数据为基础的基础上,以 Django为基础, b/s模式和 MySql为背景,在幕后运行的数据库中, Tomcat为系统的服务器。该系统的主要包含了如下几个模块:首页、个人中心、用户管理、电影管理、电影分类管理、电影信息管理、电影论坛、系统管理等模块。通过对上述模块的进行的设计,可以基本达到对用户的电影论坛的要求。_flask论坛

AI项目五:结印动作识别_火影结印手势yolo数据集-程序员宅基地

文章浏览阅读241次。若该文为原创文章,转载请注明原文出处。_火影结印手势yolo数据集

Scala学习05——Scala集合_val list = list (1,2,3) list(0) for(i<-list){ prin-程序员宅基地

文章浏览阅读630次。一、Stringval s = "bjsxt"val s1 = "BJSXT"println(s.equals(s1))println(s.equalsIgnoreCase(s1))println(s.indexOf(98))println(s.indexOf("b"))println(s==s1)/* compareToIgnoreCase * 如果参数字符串等于此字符串,则返回值 0; * 如果此字符串小于字符串参数,则返回一个小于 0 的值; _val list = list (1,2,3) list(0) for(i<-list){ println(i) } list.foreach(x =>