• 金仓数据库 KingbaseES PL/SQL 过程语言参考手册(12. 异常处理)


    12. 异常处理

    本章解释如何处理 PL/SQL 运行时错误,即异常处理。

    另请参阅

    触发器中的异常处理 " "FORALL 语句完成后处理 FORALL 异常 "

    12.1. 异常处理概述

    异常(PL/SQL 运行时错误)可能来自设计错误、编码错误、硬件故障或其他来源。 您无法预先处理所有可能触发的异常,但您可以编写异常处理程序,让您的程序在触发异常的情况下继续运行。

    任何 PL/SQL 块都可以有一个异常处理部分,它可以处理一个或多个异常。 例如:

    EXCEPTION
      WHEN ex_name_1 THEN statements_1                 -- Exception handler
      WHEN ex_name_2 OR ex_name_3 THEN statements_2    -- Exception handler
      WHEN OTHERS THEN statements_3                    -- Exception handler
    END;

    在上面的语法示例中,ex_name_n 是异常的名称, statements_n 是一个或多个语句。(有关完整的语法和语义,请参阅 异常处理程序 。)

    当 PL/SQL 块的可执行部分触发异常时,可执行部分会停止执行并将控制权转移到异常处理部分。如果抛出异常 ex_name_1,则运行语句 statements_1 。如果抛出异常 ex_name_2 或 ex_name_3,则运行语句 statements_2 。如果抛出其他异常,则运行语句 statements_3。

    异常处理程序运行后,控制权转移到封闭块的下一条语句。如果没有封闭块,则:

    • 如果异常处理程序在子程序中,则将控制权返回给调用者调用之后的语句处。

    • 如果异常处理程序位于匿名块中,则控制权转移到主机环境(例如,ksql)

    如果在没有异常处理程序的 PL/SQL 块中触发异常,则异常会传播。也就是说,异常会在连续的封闭块中向上抛出,直到一个 PL/SQL 块有一个异常处理程序或没有封闭块为止(有关更多信息,请参阅 异常传播 )。如果没有异常处理程序,那么 PL/SQL 会向调用者或主机环境返回一个未处理的异常,这将决定最终的返回结果(有关更多信息,请参阅 未处理的异常 )。

    12.1.1. 异常种类

    KES 异常的种类分为:

    • 系统预定义异常

      系统预定义异常是 PL/SQL 已命名的异常,这些异常都有一个错误代码,且会在系统运行出错时隐式(自动)触发。

      有关详细信息,请参阅   系统预定义异常。

    • 用户自定义异常

      您可以在任何 PL/SQL 匿名块、子程序或包的声明部分中声明自己的异常。例如,您可以声明一个名为 invalid_number 的异常标记一个无效数字。

      用户自定义异常必须显示的触发。

      有关详细信息,请参阅   用户自定义异常。

    表 异常差异 总结了两种异常之间的差别。

    表 12.1.1  异常差异

    异常种类

    定义

    错误代码

    名字

    隐式触发

    显示触发

    系统预定义异常

    系统

    总有

    总有

    可选

    用户自定义异常

    用户

    用户分配

    总有

    总是

    更多详细信息,请参阅 使用 RAISE 语句触发系统预定义的异常 。

    对于命名异常,您可以编写特定的异常处理程序,而不是使用 OTHERS 异常处理程序来处理它。 特定的异常处理程序比 OTHERS 异常处理程序更有效,因为后者必须调用一个函数来确定它正在处理哪个异常。有关详细信息,请参阅 检索异常信息 。

    12.1.2. 异常处理程序的优点

    使用异常处理程序进行异常处理使程序更易于编写和理解,并降低了未处理异常的可能性。

    如果没有异常处理程序,您必须检查所有可能触发的异常,并处理它。但这样很容易忽略可能出现的异常,尤其是在无法立即检测到异常的情况下(例如,使用了错误数据在计算运行之前可能无法检测到)。异常处理代码可以分散在整个程序中。

    使用异常处理程序,您不需要预先知道每个可能触发的异常或它可能发生的位置。您只需在可能发生错误的每个块中包含一个异常处理模块。在异常处理模块中,您可以编写处理特定错误或未知错误的异常处理程序。如果块中的任何地方(包括子块内)发生错误,则异常处理程序都会捕获并处理它。错误处理的相关信息则会被隔离在块的异常处理部分,不会再影响程序后续的执行。

    在 示例 12-1_ 中,一个存储过程使用单个异常处理程序来处理预定义异常 NO_DATA_FOUND,该异常可以出现在两个 SELECT INTO 语句中的任何一个中。

    如果多个语句使用相同的异常处理程序,并且您想知道哪个语句触发了异常,则可以使用变量辅助定位,如 示例 12-2_ 所示。

    如果您可以确定您需要处理哪个异常,您可以为特定异常设置一个异常处理程序。您还可以通过将语句放入具有自己的异常处理程序的块中来检查单个语句中的异常。

    示例 12-1 在单个异常处理程序中处理多个异常

    set serveroutput on
    
    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE select_item (
      t_column VARCHAR2,
      t_name   VARCHAR2
    ) AUTHID DEFINER
    IS
      temp VARCHAR2(30);
    BEGIN
      temp := t_column;  -- For error message if next SELECT fails
    
      -- Fails if table t_name does not have column t_column:
    
      SELECT COLUMN_NAME INTO temp
      FROM USER_TAB_COLS
      WHERE TABLE_NAME = UPPER(t_name)
      AND COLUMN_NAME = UPPER(t_column);
    
      temp := t_name;  -- For error message if next SELECT fails
    
      -- Fails if there is no table named t_name:
    
      SELECT OBJECT_NAME INTO temp
      FROM USER_OBJECTS
      WHERE OBJECT_NAME = UPPER(t_name)
      AND OBJECT_TYPE = 'TABLE';
    
    EXCEPTION
      WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE ('No Data found for SELECT on ' || temp);
      WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE ('Unexpected error');
        RAISE;
    END;
    /
    \set SQLTERM ;

    调用过程(有一个 SYS_PROC 表,但没有 NAME 列):

    \set SQLTERM /
    BEGIN
      select_item('sys_proc', 'name');
    END;
    /
    \set SQLTERM ;

    结果:

    No Data found for SELECT on sys_proc

    调用过程(没有 EMP 表):

    \set SQLTERM /
    BEGIN
      select_item('emp', 'name');
    END;
    /
    \set SQLTERM ;

    结果:

    No Data found for SELECT on emp

    示例 12-2 在共享异常处理程序中使用定位变量

    set serveroutput on
    
    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE loc_var AUTHID DEFINER IS
      stmt_no  POSITIVE;
      name_    VARCHAR2(100);
    BEGIN
      stmt_no := 1;
    
      SELECT table_name INTO name_
      FROM user_tables
      WHERE table_name LIKE 'SYS%' LIMIT 1;
    
      stmt_no := 2;
    
      SELECT table_name INTO name_
      FROM user_tables
      WHERE table_name LIKE 'XYZ%';
    EXCEPTION
      WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE ('Table name not found in query ' || stmt_no);
    END;
    /
    \set SQLTERM ;
    
    CALL loc_var();

    结果:

    Table name not found in query 2

    12.1.3. 异常处理指南

    为了使您的程序尽可能可靠和安全:

    • 使错误检索代码和异常处理程序。

      在输入错误的数据可能导致出错的地方使用错误检查代码。比如不正确或为空的参数和不返回行或返回的行数超出预期的查询语句。可以在程序编写过程中输入错误数据的不同组合来测试您的代码,以查看可能出现的错误。

      有时您可以使用错误检查代码来避免引发异常,如 示例   12-4_  所示。

    • 在可能发生错误的地方添加异常处理程序。

      在数字计算、字符串操作和数据库操作期间尤其容易出错。错误也可能来自与您的代码无关的问题,例如,磁盘存储或内存硬件故障,但您的代码仍然需要异常处理程序来处理他们。

    • 设计您的程序以在数据库未处于您期望的状态时继续工作。

      例如,您查询的表可能添加或删除了列,或者它们的类型已经更改。您可以通过使用 % TYPE 限定符声明变量或者使用 % ROWTYPE 限定符声明记录变量来保存查询结果来避免出错。

    • 尽可能使用命名异常编写异常处理程序,而不是使用 OTHERS 异常处理程序。

      了解预定义异常的名称和原因。如果您知道您的数据库操作可能会引发特定的系统预定义异常,请专门为它们编写异常处理程序。

    • 让您的异常处理程序输出调试信息。

      如果您将调试信息存储在单独的表中,请使用自治事务程序执行此操作,这样即使您回滚主程序所做的工作,您也可以提交调试信息。有关自治事务程序的信息,请参阅 自治事务 。

    • 对于每个异常处理程序,仔细决定是让它进行事物的提交、回滚还是继续执行。

      无论错误的严重程度如何,都需要使数据库保持正常状态并避免存储错误数据。

    • 通过在每个 PL/SQL 程序的顶层包含一个 OTHERS 异常处理程序来避免漏处理某些异常。

      将 OTHERS 异常处理程序中的最后一条语句设为 RAISE, 有关 RAISE 或调用 RAISE_APPLICATION_ERROR 的信息,请参阅 显式触发异常。

    12.2. 系统预定义异常

    针对许多比较常见的异常以及系统运行时会触发的异常, PL/SQL 内部为其预定义了一个名称。例如:除零错误,对应的预定义异常名称为 DIVISION_BY_ZERO。当错误发生时,系统隐式(自动)抛出该异常。

    KingbaseES 常见的系统预定义异常详见 PL/SQL 预定义异常表 。

    表 12.2.1  PL/SQL 预定义异常

    KingbaseES 异常名称

    Oracle 对应异常名称

    异常说明

    CASE_NOT_FOUND

    CASE_NOT_FOUND

    CASE 语句中没有任何 WHEN 子句满足条件,且没有 ELSE 子句。

    COLLECTION_IS_NULL

    COLLECTION_IS_NULL

    调用一个未初始化的嵌套表或者可变数组的方法 (不包含 EXISTS),或为一个未初始化的嵌套表或者可变数组的元素赋值。

    DUPLICATE_CURSOR

    CURSOR_ALREADY_OPEN

    打开一个已经打开的游标。

    UNIQUE_VIOLATION

    DUP_VAL_ON_INDEX

    给一个有唯一约束条件的数据字段保存相同的值。

    INVALID_CURSOR_NAME

    INVALID_CURSOR

    操作一个不合法的游标。例如关闭一个未打开的游标。

    INVALID_TEXT_REPRESENTATION

    INVALID_NUMBER

    出现运算、转换、截位或长度的约束错误。

    NO_DATA_FOUND

    NO_DATA_FOUND

    未获取到数据。

    INTERNAL_ERROR

    PROGRAM_ERROR

    PL/SQL 内部错误。

    SELF_IS_NULL

    SELF_IS_NULL

    调用一个为空对象的 MEMBER 方法。

    OUT_OF_MEMORY

    STORAGE_ERROR

    内存溢出。

    SUBSCRIPT_BEYOND_COUNT

    SUBSCRIPT_BEYOND_COUNT

    调用嵌套表或者可变数组时,使用的下标索引超出对应元素的总个数。

    SUBSCRIPT_OUTSIDE_LIMIT

    SUBSCRIPT_OUTSIDE_LIMIT

    调用嵌套表或者可变数组时,使用的下标索引不在合法范围内,如 (-1)。

    TOO_MANY_ROWS

    TOO_MANY_ROWS

    返回太多的结果行。

    NUMERIC_VALUE_OUT_OF_RANGE

    VALUE_ERROR

    数值类型超过定义域。

    DIVISION_BY_ZERO

    ZERO_DIVIDE

    除零错误。

    更多预定义异常信息可通过数据库系统函数 SYS_GET_PREDEFINED_EXCEPTION_DETAIL 进行查询,详见 SYS_GET_PREDEFINED_EXCEPTION_DETAIL 函数 。

    示例 12-3_ 计算了一家公司的市盈率。如果公司的收益为零,则除法操作会引发预定义的异常 DIVISION_BY_ZERO,并且块的可执行部分将控制权转移到异常处理部分。

    示例 12-4_ 使用错误检查代码来避免 示例 12-3_ 处理的异常。

    另请参阅

    • 使用 RAISE 语句触发预定义的异常_

    示例 12-3 匿名块中处理 DIVISION_BY_ZERO

    set serverout on
    
    \set SQLTERM /
    DECLARE
      stock_price   NUMBER := 9.73;
      net_earnings  NUMBER := 0;
      pe_ratio      NUMBER;
    BEGIN
      pe_ratio := stock_price / net_earnings;  -- raises ZERO_DIVIDE exception
      DBMS_OUTPUT.PUT_LINE('Price/earnings ratio = ' || pe_ratio);
    EXCEPTION
      WHEN ZERO_DIVIDE THEN
        DBMS_OUTPUT.PUT_LINE('Company had zero earnings.');
        pe_ratio := NULL;
    END;
    /
    \set SQLTERM ;

    结果:

    Company had zero earnings.

    示例 12-4 匿名块中避免 DIVISION_BY_ZERO

    \set SQLTERM /
    DECLARE
      stock_price   NUMBER := 9.73;
      net_earnings  NUMBER := 0;
      pe_ratio      NUMBER;
    BEGIN
      pe_ratio :=
        CASE net_earnings
          WHEN 0 THEN NULL
          ELSE stock_price / net_earnings
        END;
    END;
    /
    \set SQLTERM ;

    12.3. 用户自定义异常

    您可以在任何 PL/SQL 块,函数、存储过程或者包中声明一个异常。

    语法格式:

    exception_name EXCEPTION ;

    用户自定义异常必须被显示触发,有关异常触发的详细信息,详见 异常触发 。

    用户自定义异常可以与一个错误码进行绑定,具体语法为:

    PRAGMA EXCEPTION_INIT (exception, error_code) ;

    其中 exception 是用户自定义的异常, error_code 是大于 -1000000 且小于 0 的整数,error_code 可以是系统预定义异常的错误码。

    注意

    EXCEPTION_INIT  仅可为当前声明块中声明的自定义异常进行错误码绑定。

    当为包声明中声明的自定义异常进行错误码绑定时,无法使用包名对异常名称进行修饰。即如下语法是错误的:

    PRAGMA EXCEPTION_INIT (package.exception, error_code) ;

    12.4. 重新声明预定义的异常

    建议不要重新声明系统预定义的异常,即声明一个用户定义的异常名称,该名称是系统预定义的异常名称。(有关系统预定义异常名称的列表,请参见 系统预定义异常 。)

    如果您重新声明了系统预定义的异常,您的本地声明将覆盖 STANDARD 包中的全局声明。为全局声明的异常编写的异常处理程序将无法处理它,除非您使用包名 STANDARD 来限定它的名称。

    示例 12-5_ 对此进行了说明

    示例 12-5 重新声明预定义异常

    DROP TABLE IF EXISTS t CASCADE;
    CREATE TABLE t (c NUMBER(2,1));

    在下面的块中,INSERT 语句隐式引发了 VALUE_ERROR 异常,异常处理程序捕获并处理了这个异常。

    set serverout on
    
    \set SQLTERM /
    DECLARE
      default_number NUMBER := 0;
    BEGIN
      INSERT INTO t VALUES(TO_NUMBER('100.001', '9G999'));
    EXCEPTION
      WHEN VALUE_ERROR THEN
        DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.');
        INSERT INTO t VALUES(default_number);
    END;
    /
    \set SQLTERM ;

    结果:

    Substituting default value for invalid number.

    以下块重新声明了预定义的异常 VALUE_ERROR 当 INSERT 语句隐式触发预定义异常 VALUE_ERROR 时,异常处理程序不会处理它。

    set serverout on
    
    \set SQLTERM /
    DECLARE
      default_number NUMBER := 0;
      value_error EXCEPTION;    -- redeclare predefined exception
    BEGIN
      INSERT INTO t VALUES(TO_NUMBER('100.001', '9G999'));
    EXCEPTION
      WHEN VALUE_ERROR THEN
        DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.');
        INSERT INTO t VALUES(default_number);
    END;
    /
    \set SQLTERM ;

    结果:

    ERROR:  numeric field overflow
    DETAIL:  A field with precision 2, scale 1 must round to an absolute
    value less than 10^1.
    CONTEXT:  SQL statement "INSERT INTO t VALUES(TO_NUMBER('100.001', '9G999'))"
    PL/SQL function inline_code_block line 5 at SQL statement

    如果您在异常处理程序中使用包名 STANDARD 限定异常名称,则上述块中的异常处理程序将处理预定义的异常 VALUE_ERROR:

    set serverout on
    
    \set SQLTERM /
    DECLARE
      default_number NUMBER := 0;
      value_error EXCEPTION;    -- redeclare predefined exception
    BEGIN
      INSERT INTO t VALUES(TO_NUMBER('100.001', '9G999'));
    EXCEPTION
      WHEN STANDARD.VALUE_ERROR THEN
        DBMS_OUTPUT.PUT_LINE('Substituting default value for invalid number.');
        INSERT INTO t VALUES(default_number);
    END;
    /
    \set SQLTERM ;

    结果:

    Substituting default value for invalid number.

    12.5. 显式触发异常

    要显式触发常,请使用 RAISE 语句或 存储过程 RAISE_APPLICATION_ERROR。

    12.5.1. RAISE 语句

    RAISE 语句可以显示的触发一个异常。在异常处理程序之外,RAISE 语句必须指定异常名称。如果在异常处理程序内部,且省略了异常名称,那么该 RAISE 语句将重新引发当前正在处理的异常。

    语法格式:

    RAISE [ exception ];

    其中 exception 可以是已定义的用户自定义异常,也可以是系统预定义异常。

    省略 exception 的 RAISE 子句仅可在异常处理模块中使用。

    关于 RAISE 子句的更多用法,详见 打印语句 。

    12.5.1.1. 使用 RAISE 语句触发用户自定义的异常

    在 示例 12-6_ 中,存储过程声明了一个名为 past_due 的异常,使用 RAISE 语句显式触发它,并使用异常处理程序处理它。

    示例 12-6 声明、触发和处理用户定义的异常

    \set SQLTERM /
    CREATE PROCEDURE account_status (
      due_date DATE,
      today    DATE)
    AUTHID DEFINER IS
      past_due  EXCEPTION;  -- declare exception
    BEGIN
      IF due_date < today THEN
        RAISE past_due;  -- explicitly raise exception
      END IF;
    EXCEPTION
      WHEN past_due THEN  -- handle exception
        RAISE NOTICE 'Account past due.';
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    \set SQLTERM /
    BEGIN
      account_status (TO_DATE('01-JUL-2010', 'DD-MON-YYYY'),
                      TO_DATE('09-JUL-2010', 'DD-MON-YYYY'));
    END;
    /
    \set SQLTERM ;

    输出结果:

    NOTICE:: Account past due.

    12.5.1.2. 使用 RAISE 语句触发系统预定义的异常

    系统预定义异常通常由系统运行时隐式触发,但也可以使用 RAISE 语句显示的触发它们。当一个预定义常拥有对应的异常处理程序时,无论是显式触发还是隐式触发,都会触发异常处理程序对相应的异常进行处理。

    在 示例 12-7_ 中,存储过程不论显式或是隐式触发预定义异常 VALUE_ERROR,异常处理程序始终都会处理它。

    示例 12-7 触发系统预定义异常

    DROP TABLE IF EXISTS t CASCADE;
    CREATE TABLE t (c NUMBER(2,1));
    
    \set SQLTERM /
    CREATE PROCEDURE p (n NUMBER) AUTHID DEFINER IS
      default_number NUMBER := 0;
    BEGIN
      IF n < 0 THEN
        RAISE VALUE_ERROR;  -- 显式触发
      ELSE
        INSERT INTO t VALUES(TO_NUMBER('100.001', '9G999'));  -- 隐式触发
      END IF;
    EXCEPTION
      WHEN VALUE_ERROR THEN
        RAISE NOTICE 'Substituting default value for invalid number.';
        INSERT INTO t VALUES(default_number);
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    \set SQLTERM /
    BEGIN
      p(-1);
    END;
    /
    \set SQLTERM ;

    输出结果:

    NOTICE:: Substituting default value for invalid number.

    再次调用存储过程:

    \set SQLTERM /
    BEGIN
      p(1);
    END;
    /
    \set SQLTERM ;

    输出结果:

    NOTICE:  Substituting default value for invalid number.

    12.5.1.3. 使用 RAISE 语句重新触发当前异常

    在异常处理程序中,可以使用 RAISE 语句重新引发当前正在处理的异常。重新引发的异常会将异常传递给当前块中的的异常处理程序块,若当前块中无相应的异常处理块,则该异常会传播,详见 异常传播 。在重新引发异常时,可以省略异常名称。

    在 示例 12-8_ 中,异常处理从内部块开始,到外部块结束。外部块声明异常,因此异常对两个块中都可见,并且每个块都有一个专门针对该异常的异常处理程序。内部块触发异常,其异常处理程序进行初步处理,然后重新引发异常,将其传递给外部块以进行进一步处理。

    示例 12-8 重新触发异常

    \set SQLTERM /
    DECLARE
        salary_too_high   EXCEPTION;
        current_salary    NUMBER := 20000;
        max_salary        NUMBER := 10000;
        erroneous_salary  NUMBER;
    BEGIN
    
        BEGIN
          IF current_salary > max_salary THEN
            RAISE salary_too_high;   -- raise exception
          END IF;
        EXCEPTION
          WHEN salary_too_high THEN  -- start handling exception
            erroneous_salary := current_salary;
            RAISE NOTICE 'Salary % is out of range.', erroneous_salary;
            RAISE NOTICE 'Maximum salary is %.', max_salary;
            RAISE;  -- reraise current exception (exception name is optional)
        END;
    
    EXCEPTION
        WHEN salary_too_high THEN    -- finish handling exception
          current_salary := max_salary;
    
          RAISE NOTICE 'Revising salary from % to %.', erroneous_salary, current_salary;
    END;
    /
    \set SQLTERM ;

    输出结果:

    NOTICE:  Salary 20000 is out of range.
    NOTICE:  Maximum salary is 10000.
    NOTICE:  Revising salary from 20000 to 10000.

    12.5.2. 存储过程 RAISE_APPLICATION_ERROR

    存储过程 RAISE_APPLICATION_ERROR 通常用来抛出一个用户自定义异常,并将错误码和错误信息传播给调用者。

    要调用 RAISE_APPLICATION_ERROR,请使用以下语法:

    RAISE_APPLICATION_ERROR (error_code, message);

    其中 error_code 是 -20999 .. -20000 之间的整数, message 是长度为 2048 字节的字符串,大于该长度的字符会被自动截断。

    使用 RAISE_APPLICATION_ERROR 抛出的用户定义的异常必须已使用 PRAGMA EXCEPTION_INIT 分配 error_code。

    通过存储过程 RAISE_APPLICATION_ERROR 抛出的异常可以用 OTHERS 进行捕获处理,也可以通过绑定了相同 error_code 的用户自定义异常进行捕获处理,详见 用户自定义异常 。

    在 示例 12-9_ 中,一个匿名块中声明了一个名为 past_due 的异常,并为其分配了错误代码 -20000。然后调用了一个存储过程,存储过程调用带有错误代码 -20000 和消息的存储过程 RAISE_APPLICATION_ERROR ,然后控制权返回到匿名块异常处理部分。 为了检索与异常关联的消息,匿名块中的异常处理程序调用 SQLERRM 函数,详见 检索异常信息 。

    示例 12-9 使用 RAISE_APPLICATION_ERROR 引发用户定义的异常

    \set SQLTERM /
    CREATE PROCEDURE account_status (
      due_date DATE,
      today    DATE
    ) AUTHID DEFINER IS
    BEGIN
      IF due_date < today THEN                  -- explicitly raise exception
        RAISE_APPLICATION_ERROR(-20000, 'Account past due.');
      END IF;
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    \set SQLTERM /
    DECLARE
      past_due  EXCEPTION;                      -- declare exception
      PRAGMA EXCEPTION_INIT (past_due, -20000); -- assign error code to exception
    BEGIN
      account_status (TO_DATE('01-JUL-2010', 'DD-MON-YYYY'),
                      TO_DATE('09-JUL-2010', 'DD-MON-YYYY')); -- invoke procedure
    
    EXCEPTION
      WHEN past_due THEN                        -- handle exception
        RAISE NOTICE '%', SQLERRM(-20000);
    END;
    /
    \set SQLTERM ;

    输出结果:

    NOTICE:  ERRCODE-20000: Account past due.

    12.6. 异常传播

    如果在一个没有异常处理程序的块中抛出异常,则该异常会进行传播。即该异常会在连续的封闭块中重现,直到被某个具有对应异常处理程序的块捕获处理或者没有封闭块为止。若一直没有对应的异常处理程序,那么 PL/SQL 会将未处理的异常返回给调用者或者返回到主机环境中,该异常也会成为当前程序执行的结果。

    用户自定义的异常可以传播到超出其声明的范围(即超出声明它的块),但由于其名称在其超出的范围内不存在。因此,超出其范围后,用户自定义的异常只能使用 OTHERS 来捕获处理。

    在 示例 12-10_ 中,内部块声明了一个名为 past_due 的异常,它没有异常处理程序。 当内部块引发 past_due 时,异常传播到外部块,其中不存在名称 past_due。 外部块使用 OTHERS 异常处理程序处理异常。 如果外部块不处理用户定义的异常,则会发生错误,如 示例 12-11_ 所示。

    示例 12-10 处理超出范围传播的异常

    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS
    BEGIN
    
      DECLARE
        past_due     EXCEPTION;
        due_date     DATE := trunc(SYSDATE) - 1;
        todays_date  DATE := trunc(SYSDATE);
      BEGIN
        IF due_date < todays_date THEN
          RAISE past_due;
        END IF;
      END;
    
    EXCEPTION
      WHEN OTHERS THEN
        NULL;
    END;
    /
    \set SQLTERM ;

    示例 12-11 未处理传播超出范围的异常

    \set SQLTERM /
    BEGIN
    
      DECLARE
        past_due     EXCEPTION;
        due_date     DATE := trunc(SYSDATE) - 1;
        todays_date  DATE := trunc(SYSDATE);
      BEGIN
        IF due_date < todays_date THEN
          RAISE past_due;
        END IF;
      END;
    
    END;
    /
    \set SQLTERM ;

    结果:

    ERROR:  unhandled exception "past_due"
    CONTEXT:  PL/SQL function inline_code_block line 8 at RAISE

    12.6.1. 未传播的异常

    异常在块中被触发并捕获处理,未发生传播,如 示例 12-12_ 所示。

    示例 12-12 未传播的异常

    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE p AS
    BEGIN
    
      DECLARE
        past_due     EXCEPTION;
      BEGIN
        RAISE past_due;
      EXCEPTION
        WHEN past_due THEN
          RAISE NOTICE 'Exception Does Not Propagate';
      END;
    
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    CALL P();

    输出结果:

    NOTICE:  Exception Does Not Propagate

    12.6.2. 从内部块传播到外部块的异常

    异常在内部块中被触发,但未被捕获,传播到上层块中被捕获处理,如 示例 12-13_ 所示。

    示例 12-13 从内部块传播到外部块的异常

    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE p AS
    BEGIN
    
      DECLARE
        past_due     EXCEPTION;
      BEGIN
        RAISE past_due;
      END;
    
    EXCEPTION
      WHEN OTHERS THEN
        RAISE NOTICE 'Exception Propagates from Inner Block to Outer Block';
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    CALL P();

    输出结果:

    NOTICE:  Exception Propagates from Inner Block to Outer Block

    12.6.3. 声明中触发的异常的传播

    当前块中声明阶段抛出的异常会直接传播到上层块或者主机环境,而不是当前块。因此,声明阶段的异常处理程序必须在调用块中,而不是在声明的当前块中。

    在 示例 12-14_ 中,VALUE_ERROR 异常处理程序与引发 VALUE_ERROR 的声明位于同一块中。 因为异常会立即传播到主机环境,所以异常处理程序不会处理它。

    示例 12-15_ 与 示例 12-14_ 类似,只是上层块中处理了内部块中的声明中引发的 VALUE_ERROR 异常。

    示例 12-14 声明中触发的异常未处理

    \set SQLTERM /
    DECLARE
      credit_limit CONSTANT NUMBER(3) := 5000;
    BEGIN
      NULL;
    EXCEPTION
      WHEN VALUE_ERROR THEN
        RAISE NOTICE 'Exception raised in declaration.';
    END;
    /
    \set SQLTERM ;

    输出结果:

    ERROR:  numeric field overflow
    DETAIL:  A field with precision 3, scale 0 must round to an absolute value
    less than 10^3.
    CONTEXT:  PL/SQL function inline_code_block line 3 during statement block
    local variable initialization

    示例 12-15 声明中触发的异常由上层块处理

    \set SQLTERM /
    BEGIN
    
      DECLARE
        credit_limit CONSTANT NUMBER(3) := 5000;
      BEGIN
        NULL;
      END;
    
    EXCEPTION
      WHEN VALUE_ERROR THEN
        RAISE NOTICE 'Exception raised in declaration.';
    END;
    /
    \set SQLTERM ;

    输出结果:

    NOTICE:  Exception raised in declaration.

    12.6.4. 异常处理程序中抛出的异常传播

    异常处理阶段抛出的异常与声明阶段抛出的异常一样,会直接传播到上层块或者主机环境中。因此,异常处理程序必须位于上层块或者调用块中。

    在 示例 12-16_ 中,当 n 为零时,计算 1/n 会引发预定义的异常 ZERO_DIVIDE,并且控制权转移到同一块中的 ZERO_DIVIDE 异常处理程序。当异常处理程序触发 ZERO_DIVIDE 时,异常会立即传播到调用程序。调用者不处理异常,所以 PL/SQL 向主机环境返回一个未处理的异常错误。

    示例 12-17_ 与 示例 12-16_ 类似,只是当存储过程向调用者返回未处理的异常错误时,调用者会处理它。

    示例 12-18_ 与 示例 12-17_ 类似,只是由上层块处理内部块中的异常处理程序引发的异常。

    在 示例 12-19_ 中,存储过程中的异常处理部分具有用于处理用户自定义异常 i_is_one 和预定义异常 ZERO_DIVIDE 的异常处理程序。当 i_is_one 异常处理程序引发 ZERO_DIVIDE 时,异常会立即传播到调用程序中(因此,ZERO_DIVIDE 异常处理程序不会处理它)。调用者不处理异常,所以 PL/SQL 向主机环境返回一个未处理的异常错误。

    示例 12-20_ 与 示例 12-19_ 类似,只是上层中块处理了 i_is_one 异常处理程序引发的 ZERO_DIVIDE 异常。

    示例 12-16 异常处理程序中触发的异常未处理

    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE print_reciprocal (n NUMBER) AUTHID DEFINER IS
    BEGIN
      RAISE NOTICE '%', 1/n;
    EXCEPTION
      WHEN ZERO_DIVIDE THEN
        RAISE NOTICE 'Error:';
        RAISE NOTICE '% is undefined', 1/n;
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    \set SQLTERM /
    BEGIN  -- invoking block
      print_reciprocal(0);
    END;
    /
    \set SQLTERM ;

    输出结果:

    NOTICE:  Error:
    ERROR:  division by zero
    CONTEXT:  PL/SQL function print_reciprocal(numeric) line 7 at RAISE
    SQL statement "CALL print_reciprocal(0)"
    PL/SQL function inline_code_block line 2 at CALL

    示例 12-17 异常处理程序中引发的异常由调用程序处理

    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE print_reciprocal (n NUMBER) AUTHID DEFINER IS
    BEGIN
      RAISE NOTICE '%', 1/n;
    EXCEPTION
      WHEN ZERO_DIVIDE THEN
        RAISE NOTICE 'Error:';
        RAISE NOTICE '% is undefined', 1/n;
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    \set SQLTERM /
    BEGIN  -- invoking block
      print_reciprocal(0);
    EXCEPTION
      WHEN ZERO_DIVIDE THEN  -- handles exception raised in exception handler
        RAISE NOTICE '1/0 is undefined.';
    END;
    /
    \set SQLTERM ;

    输出结果:

    NOTICE:  Error:
    NOTICE:  1/0 is undefined.

    示例 12-18 异常处理程序中引发的异常由上层块处理

    set serverout on
    
    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE print_reciprocal (n NUMBER) AUTHID DEFINER IS
    BEGIN
    
      BEGIN
        DBMS_OUTPUT.PUT_LINE(1/n);
      EXCEPTION
        WHEN ZERO_DIVIDE THEN
          DBMS_OUTPUT.PUT_LINE('Error in inner block:');
          DBMS_OUTPUT.PUT_LINE(1/n || ' is undefined.');
      END;
    
    EXCEPTION
      WHEN ZERO_DIVIDE THEN  -- handles exception raised in exception handler
        DBMS_OUTPUT.PUT('Error in outer block: ');
        DBMS_OUTPUT.PUT_LINE('1/0 is undefined.');
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    \set SQLTERM /
    BEGIN
      print_reciprocal(0);
    END;
    /
    \set SQLTERM ;

    输出结果:

    Error in inner block:
    Error in outer block: 1/0 is undefined.

    示例 12-19 未处理异常处理程序中引发的异常

    set serverout on
    
    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE descending_reciprocals (n INTEGER) AUTHID DEFINER IS
      i INTEGER;
      i_is_one EXCEPTION;
    BEGIN
      i := n;
    
      LOOP
        IF i = 1 THEN
          RAISE i_is_one;
        ELSE
          DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || i || ' is ' || 1/i);
        END IF;
    
        i := i - 1;
      END LOOP;
    EXCEPTION
      WHEN i_is_one THEN
        DBMS_OUTPUT.PUT_LINE('1 is its own reciprocal.');
        DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || TO_CHAR(i-1) ||
                             ' is ' || TO_CHAR(1/(i-1)));
    
      WHEN ZERO_DIVIDE THEN
        DBMS_OUTPUT.PUT_LINE('Error:');
        DBMS_OUTPUT.PUT_LINE(1/n || ' is undefined');
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    \set SQLTERM /
    BEGIN
      descending_reciprocals(3);
    END;
    /
    \set SQLTERM ;

    输出结果:

    ERROR:  division by zero
    CONTEXT:  SQL statement "CALL DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || TO_CHAR(i-1) ||
                                 ' is ' || TO_CHAR(1/(i-1)))"
    PL/SQL function descending_reciprocals(integer) line 17 at CALL
    SQL statement "CALL descending_reciprocals(3)"
    PL/SQL function inline_code_block line 2 at CALL
    Reciprocal of 3 is 0
    Reciprocal of 2 is 0
    1 is its own reciprocal.

    示例 12-20 异常处理程序中引发的异常由上层块处理

    set serverout on
    
    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE descending_reciprocals (n INTEGER) AUTHID DEFINER IS
      i INTEGER;
      i_is_one EXCEPTION;
    BEGIN
    
      BEGIN
        i := n;
    
        LOOP
          IF i = 1 THEN
            RAISE i_is_one;
          ELSE
            DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || i || ' is ' || 1/i);
          END IF;
    
          i := i - 1;
        END LOOP;
      EXCEPTION
        WHEN i_is_one THEN
          DBMS_OUTPUT.PUT_LINE('1 is its own reciprocal.');
          DBMS_OUTPUT.PUT_LINE('Reciprocal of ' || TO_CHAR(i-1) ||
                               ' is ' || TO_CHAR(1/(i-1)));
    
        WHEN ZERO_DIVIDE THEN
          DBMS_OUTPUT.PUT_LINE('Error:');
          DBMS_OUTPUT.PUT_LINE(1/n || ' is undefined');
      END;
    
    EXCEPTION
      WHEN ZERO_DIVIDE THEN  -- handles exception raised in exception handler
        DBMS_OUTPUT.PUT_LINE('Error:');
        DBMS_OUTPUT.PUT_LINE('1/0 is undefined');
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    \set SQLTERM /
    BEGIN
      descending_reciprocals(3);
    END;
    /
    \set SQLTERM ;

    输出结果:

    Reciprocal of 3 is 0
    Reciprocal of 2 is 0
    1 is its own reciprocal.
    Error:
    1/0 is undefined

    12.7. 未处理的异常

    异常在内部块中被触发,但未被捕获,且传播到外层块中。仍未被捕获,最终返回到主机环境中。

    在打开单语句回滚的情况下,如果程序因为未处理的异常退出,PL/SQL 不会回滚程序所做的数据库更改。

    提示

    通过在每个 PL/SQL 程序的顶层包含一个 OTHERS 异常处理程序来避免未处理的异常。

    示例 12-21 未处理异常

    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE p AS
    BEGIN
    
      DECLARE
        past_due     EXCEPTION;
        PRAGMA EXCEPTION_INIT (past_due, -4910);
      BEGIN
        RAISE past_due;
      END;
    
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    CALL P();

    输出结果:

    ERROR:  ERRCODE-4910: non-KingbaseES exception
    CONTEXT:  PL/SQL function p() line 7 at RAISE

    12.8. 检索异常信息

    在异常处理程序中,对于正在处理的异常:

    • 您可以使用 SQLCODE 函数 中描述的 PL/SQL 函数 SQLCODE 检索错误代码。

    • 您可以使用以下任一方法检索错误消息:

      • PL/SQL 函数 SQLERRM,详见 SQLERRM 函数 。

        该函数最多返回 512 字节,这是数据库错误消息的最大长度(包括错误代码、详细的错误信息等)。

      • PL/SQL 系统包 DBMS_UTILITY 中的包函数 DBMS_UTILITY.FORMAT_ERROR_STACK

        此函数返回完整的错误堆栈,最多 2000 个字节。

      • KES 数据库提供的其它异常信息检索方式。

    另请参阅

    PL/SQL 系统包中有关 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE 函数的信息,该函数返回引发异常时的调用堆栈,即使该程序是从上层块的异常处理程序中被调用的

    PL/SQL 系统包中有关 UTL_CALL_STACK 包的信息,其子程序提供有关当前正在执行的子程序的信息,包括子程序名称

    12.8.1. SQLSTATE 变量

    异常发生时,调用 SQLSTATE 变量返回当前异常对应的 SQL 标准错误码(五位字符串)。

    12.8.2. SQLERRM 变量

    异常发生时,调用 SQLERRM 变量返回当前异常相应的错误信息。

    注意

    变量 SQLSTATE 和 SQLERRM 仅在异常处理模块中可用。

    12.8.3. SQLCODE 函数

    语法:

    INT SQLCODE () ;

    说明:

    检索异常兼容 ORACLE 的错误码(负整数)。在异常处理程序之外,SQLCODE 函数始终返回 0, 表示程序正常编译执行。

    对于系统预定义的异常,错误码是关联相关异常错误的编号,该数字通常为一个负整数( NO_DATA_FOUND 除外,其错误码为 +100)。

    对于用户自定义的异常,数字代码为 +1(默认值)或者通过 EXCEPTION_INIT 方法绑定的错误码。

    12.8.4. SQLERRM 函数

    语法:

    TEXT SQLERRM ( [ error_code ] ) ;

    说明:

    检索异常错误信息。

    其中 error_code 为数据库异常对应的错误码,省略该参数时返回当前正在处理的异常错误信息。当 error_code 不是一个标准的错误码或者不存在时,都会进行相应的错误提示。

    12.8.5. ERROR_LINE 函数

    语法:

    INT ERROR_LINE () ;

    说明:

    返回抛出异常的函数(存储过程)中抛出异常的位置行号,若没有在异常处理块中,则返回 NULL。

    12.8.6. ERROR_PROCEDURE 函数

    语法:

    VARCHAR(128) ERROR_PROCEDURE () ;

    说明:

    返回抛出异常的函数(存储过程)或触发器的名称,若没有在异常处理块中,则返回 NULL。

    12.8.7. ERROR_NUMBER 函数

    语法:

    INT ERROR_NUMBER () ;

    说明:

    返回抛出异常的错误号,与 SQLCODE 相同。若没有在异常处理块中,则返回 NULL。

    12.8.8. ERROR_MESSAGE 函数

    语法:

    VARCHAR(4000) ERROR_MESSAGE () ;

    说明:

    返回异常消息的完整文本,与 SQLERRM 相同。若没有在异常处理块中,则返回 NULL。

    12.8.9. ERROR_STATE 函数

    语法:

    INT ERROR_STATE () ;

    说明:

    返回异常状态号,在异常处理块中始终为 1。若没有在异常处理块中,则返回 NULL。

    12.8.10. ERROR_SEVERITY 函数

    语法:

    INT ERROR_SEVERITY () ;

    说明:

    返回异常的严重级别,在异常处理块中始终为 20。若没有在异常处理块中,则返回 NULL。

    12.8.11. SYS_GET_PREDEFINED_EXCEPTION_DETAIL 函数

    语法:

    SETOF RECORD SYS_GET_PREDEFINED_EXCEPTION_DETAIL (
                      OUT exception_name TEXT,
                      OUT sqlstate       TEXT,
                      OUT sqlcode        INTEGER) ;

    说明:

    返回所有的预定义异常的相关信息, exception_name 为预定义异常名称, sqlstate 为预定义异常的 SQL 标准错误码, sqlcode 为预定义异常的 ORACLE 兼容错误码, 如 示例 12-23_ 所示。

    示例 12-22 SQLCODE 函数和 SQLERRM 函数

    set serverout on
    
    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE p AUTHID DEFINER AS
      i INT;
      v_code  NUMBER;
      v_errm  VARCHAR2(64);
    BEGIN
      i := 1/0;
    EXCEPTION
      WHEN OTHERS THEN
        v_code := SQLCODE;
        v_errm := SUBSTR(SQLERRM, 1, 64);
        DBMS_OUTPUT.PUT_LINE
          ('Error code ' || v_code || ': ' || v_errm);
    END;
    /
    \set SQLTERM ;

    调用存储过程:

    CALL P();

    输出结果:

    Error code -1476: division by zero

    示例 12-23 SYS_GET_PREDEFINED_EXCEPTION_DETAIL 函数

    SELECT * FROM SYS_GET_PREDEFINED_EXCEPTION_DETAIL();

    输出结果:

    exception_name                | sqlstate | sqlcode
    ------------------------------------------------------+----------+---------
     sql_statement_not_yet_complete                       | 03000    |  -10000
     connection_exception                                 | 08000    |  -10001
     connection_does_not_exist                            | 08003    |  -10002
     ...
     tablespace_is_readonly                               | F1001    |  -10243
     tablespace_is_write                                  | F1002    |  -10244
     tablespace_operation_notsupport                      | F1003    |  -10245

    12.9. 异常捕获

    异常捕获是异常处理程序的一部分,通常通过 EXCEPTION 子句对异常进行捕获操作。

    通常情况下,一个语句引发异常,将导致当前语句退出执行,与该语句处于同一个事务内的语句也会被回滚。为了处理异常,PL/SQL 块中可以通过 EXCEPTION 子句来捕获异常,进行相应处理,这时,处于同一个事务块中已经执行的的语句不会被回滚,但当前语句被回滚。

    KingbaseES 的 PL/SQL 中,当一条语句执行后,控制权将移交到下一条语句,但是当异常触发后,KingbaseES 的 PL/SQL 将立即捕获、处理异常。

    语法格式:

    [ DECLARE
         [ < VariableDeclaration > ]
         [ < CursorDeclaration > ]
         [ < UserDefinedExceptionDeclaration > ]
        ]
    BEGIN
        < Statements >
    EXCEPTION
        WHEN ExceptionName [ OR ExceptionName... ] THEN
            < HandlerStatements >;
        [ WHEN ExceptionName[ OR ExceptionName... ] THEN
            < HandlerStatements >;
          ... ]
    END;

    功能:

    捕获异常。

    使用说明:

    1. ExceptionName,异常的名称,系统预先定义或用户自定义异常,直接使用即可,异常名称大小写均可,如 division_by_zero ,表示发生 "除零" 错误。异常名是与大小写无关的,一个异常名也可以通过 SQLSTATE 代码指定,例如以下是等价的:

    WHEN division_by_zero THEN ...
    WHEN SQLSTATE '22012' THEN ...
    1. ExceptionName 可以使用关键字 OTHERS ,用于处理在 OTHERS 之前没有显示指定处理的异常。

    2. 如果没有发生异常,这种形式的块只是简单地执行所有 Statements , 然后转到 END 之后的下一个语句。但是如果在 Statements 内发生了一个错误,则会放弃对 Statements 的进一步处理,然后转到 EXCEPTION 子句。系统会在异常条件列表中匹配当前触发的异常。如果匹配成功,则执行对应的 HandlerStatements ,执行完成后转到 END 之后的下一个语句。如果匹配失败,则该异常就会传播出去,就像没有 EXCEPTION 子句一样。异常可以被外层闭合块中的 EXCEPTION 子句捕获,如果没有 EXCEPTION 则中止该程序的执行。

    3. 如果在选中的 HandlerStatements 内触发新的异常,那么它不能被当前这个 EXCEPTION 子句捕获,而是被传播出去。由外层的 EXCEPTION 子句捕获它。

    4. 当一个异常被 EXCEPTION 子句捕获时,PL/SQL 函数的局部变量会保持异常触发时的值,但是该块中所有对数据库状态的改变都会被回滚。如 示例 12-24_ 所示:

    5. 如果 KES 数据库打开了单语句回滚的参数(ora_statement_level_rollback),触发异常的语句不会回滚之前已经执行完成的操作。如 示例 12-25_ 所示:

    6. 进入和退出一个包含 EXCEPTION 子句的块要比不包含 EXCEPTION 的块开销大的多。因此,尽量在必要的时候使用 EXCEPTION 子句。

    示例 12-24 异常触发导致 PL/SQL 函数内已完成的操作被回滚

    DROP TABLE IF EXISTS mytab CASCADE;
    CREATE TABLE mytab(firstname TEXT, lastname TEXT);
    INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
    
    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE PROC() AS
        x int := 1;
    BEGIN
        UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones';
        x := x / 0;
    EXCEPTION
        WHEN division_by_zero THEN
            RAISE NOTICE 'caught division_by_zero';
    END;
    /
    \set SQLTERM ;

    调用函数:

    CALL PROC();
    
    NOTICE:  caught division_by_zero

    查询更改:

    SELECT * FROM mytab;
    
     firstname | lastname
    -----------+----------
     Tom       | Jones
    (1 row)

    当函数执行到对 y 赋值的地方时,它会触发一个 division_by_zero 异常,同时这个异常将被 EXCEPTION 子句捕获。而在 RETURN 语句中返回的值将是 x 执行加法过后的值。不过,在该块之前的 INSERT 将不会被回滚,因此最终的结果是数据库包含 'Tom Jones' 但不包含 'Joe Jones' 。

    示例 12-25 异常触发未影响 PL/SQL 函数内已完成的操作

    set ora_statement_level_rollback to on;
    
    DROP TABLE IF EXISTS mytab CASCADE;
    CREATE TABLE mytab(firstname TEXT, lastname TEXT);
    INSERT INTO mytab(firstname, lastname) VALUES('Tom', 'Jones');
    
    \set SQLTERM /
    CREATE OR REPLACE PROCEDURE PROC() AS
        x int := 1;
    BEGIN
        UPDATE mytab SET firstname = 'Joe' WHERE lastname = 'Jones';
        x := x / 0;
    EXCEPTION
        WHEN division_by_zero THEN
            RAISE NOTICE 'caught division_by_zero';
    END;
    /
    \set SQLTERM ;

    调用函数:

    CALL PROC();
    
    NOTICE:  caught division_by_zero

    查询更改:

    SELECT * FROM mytab;
    
     firstname | lastname
    -----------+----------
     Joe       | Jones
    (1 row)

    12.10. 获取异常状态信息

    在异常处理模块中,除系统变量和系统函数外,KingbaseES 也支持使用 GET STACKED DIAGNOSTICS 命令获取有关当前异常的状态信息,该命令的语法为:

    GET STACKED DIAGNOSTICS variable { = | := } item [ , ... ];

    使用说明:

    每个 item 都是一个异常相关状态项。下表中显示了当前可用的状态项。

    表 12.10.1  异常状态项

    名称

    类型

    描述

    RETURNED_SQLSTATE

    TEXT

    该异常的 SQLSTATE 错误代码

    COLUMN_NAME

    TEXT

    与异常相关的列名

    CONSTRAINT_NAME

    TEXT

    与异常相关的约束名

    SYS_DATATYPE_NAME

    TEXT

    与异常相关的数据类型名

    MESSAGE_TEXT

    TEXT

    该异常的主要消息的文本

    TABLE_NAME

    TEXT

    与异常相关的表名

    SCHEMA_NAME

    TEXT

    与异常相关的模式名

    SYS_EXCEPTION_DETAIL

    TEXT

    该异常的详细消息文本

    SYS_EXCEPTION_HINT

    TEXT

    该异常的提示消息文本

    SYS_EXCEPTION_CONTEXT

    TEXT

    描述产生异常时调用栈 的文本行

    如果触发的异常无法为某个状态项设置值,将对应的状态项将返回一个空字符串。

    DECLARE
      text_var1 TEXT;
      text_var2 TEXT;
      text_var3 TEXT;
    BEGIN
      -- 某些可能导致异常的处理
      ...
    EXCEPTION WHEN OTHERS THEN
      GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
                              text_var2 = SYS_EXCEPTION_DETAIL,
                              text_var3 = SYS_EXCEPTION_HINT;
    END;

    12.11. 获取执行位置信息

    GET DIAGNOSTICS (见 获得结果状态 中描述)命令可获取有关当前执行状态的信息。该命令的 SYS_CONTEXT 状态项可用于记录当前执行位置。状态项 SYS_CONTEXT 会返回一个文本字符串,该字符串为一个描述该调用栈的多行文本。第一行会指向当前函数以及当前正在执行 GET DIAGNOSTICS 命令的位置。第二行及其后的行表示调用栈中更上层的调用位置信息。

    语法格式:

    GET DIAGNOSTICS stack = SYS_CONTEXT;

    示例 12-26 获取执行位置信息 .. code:

    \set SQLTERM /
    CREATE OR REPLACE FUNCTION outer_func RETURN integer AS
    BEGIN
      RETURN inner_func();
    END;
    /
    \set SQLTERM ;
    
    \set SQLTERM /
    CREATE OR REPLACE FUNCTION inner_func RETURN integer AS
      stack text;
    BEGIN
      GET DIAGNOSTICS stack = SYS_CONTEXT;
      RAISE NOTICE E'--- Call Stack ---\n%', stack;
      RETURN 1;
    END;
    /
    \set SQLTERM ;
    
    SELECT outer_func();
    
    NOTICE:  --- Call Stack ---
    PL/SQL function INNER_FUNC() line 5 at GET DIAGNOSTICS
    PL/SQL function OUTER_FUNC() line 3 at RETURN
     OUTER_FUNC
    ------------
              1
    (1 row)

    GET STACKED DIAGNOSTICS ... SYS_EXCEPTION_CONTEXT 语句返回类似的栈跟踪,但是 SYS_EXCEPTION_CONTEXT 记录的是检测到异常的位置而不是当前位置。

    12.12. 检查断言

    ASSERT语句是一种向 PL/SQL 函数中插入调试检查的方法。

    语法格式:

    ASSERT condition [ , message ];

    使用说明:

    1. condition 是一个布尔表达式,该表达式的计算结果被断言为真。如果结果为真, ASSERT 语句不会对程序产生任何影响。但如果结果是假或者空,那么将发生一个 ASSERT_FAILURE 异常(如果在计算 condition 时发生错误,会抛出一个对应的普通异常)。

    2. 如果 message 参数非空, 则在断言失败时, message 会被用来替换默认的错误消息文本 ("assertion failed") 。 message 表达式在断言成功的情况下不会对程序产生任何影响。

    3. 通过配置参数 plsql.check_asserts 可以启用或者禁用断言测试, 这个参数为布尔值且默认为 on 。如果这个参数值被设置为 off ,则 ASSERT 语句什么也不做。

    4. ASSERT 是为了检测程序的 bug,而不是报告普通的错误情况。如果要报告普通错误,请使用前面介绍的 打印语句 。

  • 相关阅读:
    微信小程序 | 人脸识别的最终解决方案
    基于SSM的美食推荐管理系统
    PaddleX解决分类、检测两大场景问题?实战精讲教程来了!
    mysql限制用户登录失败次数,限制时间
    Django模型(二)
    vue-h5移动Web的rem配置
    玉米社:短视频运营主要做哪些工作?
    C# 对RabbitMQ使用
    leetcode top100(20) 搜索二维矩阵 II
    C++11:异常和智能指针
  • 原文地址:https://blog.csdn.net/arthemis_14/article/details/126429189