• PL/SQL数据开发那点事


    PL/SQL开发那点事----->PL/SQL开发过程中异常处理

    用户编写的PL/SQL块在执行过程中不可避免地要发生一些错误。

    这里涉及的错误并不是由于程序的语法错误引起的,而是因为处理的数据超出了处理的范围而引发的错误。
    如果给这样的错误起一个名字,这就是异常
    当PL/SQL块在执行过程中检测到一个错误时,就会抛出相应的异常。
    在块中应当处理这样的异常,否则会引起应用程序运行停止。

    异常处理程序

    异常一般是在PL/SQL程序执行错误时由数据库服务器抛出,也可以在PL/SQL块中由程序员在一定的条件下显式抛出。
    无论是哪种形式的异常,都可以在PL/SQL块的异常处理部分编写一段程序进行处理,如果不做任何处理,异常将被传递到调用者,由调用者统一处理。

    下面表示两种不同异常的处理方式:

    方式一(异常处理):

    DECLARE

      定义异常

    BEGIN

      抛出异常

    EXCEPTION

      捕获并处理异常

    END;

    方式二(异常传递):

    DECLARE

      定义异常

    BEGIN

      抛出异常

    EXCEPTION

      不处理异常

    END;

    异常被传递到调用者

    如果要在PL/SQL块中对异常进行处理,就需要在异常处理部分编写处理程序。

    异常处理程序的形式如下:

    EXCEPTION

      WHEN 异常1 OR 异常2 THEN

        异常处理程序1;

      WHEN 异常3 OR 异常4 THEN

        异常处理程序2;

      WHEN OTHERS THEN

        异常处理程序n;

    END;

    异常处理程序以关键字EXCEPTION开始,结束于关键字END 。

    在这部分可以对多个异常分别进行不同的处理,也可以进行相同的处理。
    如果没有列出所有异常,可以用关键字OTHERS代替其他的异常,在异常处理程序的最后加上一条WHEN OTHERS子句,用来处理前面没有列出的所有异常。
    如果PL/SQL块执行出错,或者遇到显式抛出异常的语句,则程序立即停止执行,转去执行异常处理程序。
    异常被处理结束后,整个PL/SQL块的执行便告结束。

    所以一旦发生异常,则在PL/SQL块的可执行部分中,从发生异常的地方开始,以后的代码将不再执行。
    在PL/SQL块中有三种类型的异常,即预定义的异常非预定义的异常用户自定义的异常。
    下面分别介绍这几种异常的使用方法。

    预定义的异常

    Oracle把一些常见的错误定义为有名字的异常,这就是预定义的异常。

    Oracle有许多预定义的异常,在进行处理时不需要再定义,只需要编写相应的异常处理程序即可。
    当PL/SQL块执行发生错误时,数据库服务器将自动抛出相应的异常,并执行编写的异常处理程序。

    下面列出了部分预定义的异常。

    NO_DATA_FOUND  用SELECT命令检索数据时,没有发现满足要求的数据

    TOO_MANY_ROWS  用SELECT命令检索数据时,得到了多行数据

    DUP_VAL_ON_INDEX  在主键列上写入一个重复的值

    CURSOR_ALREADY_OPEN  操作游标时,试图打开一个已经打开的游标

    INVALID_NUMBER  将字符串转换为数字时,字符串不是数字型的字符串

    LOGIN_DENIED  当连接Oracle数据库时被拒绝,可能是因为没有权限

    NOT_LOGGED_ON  在没有登录数据库的情况下试图对数据库进行访问

    ZERO_DIVIDE  在进行算术运算时,0作为除数

    PROGRAM_ERROR  PL/SQL块在运行时发生了内部错误

    INVALID_CURSOR  视图操作一个无效的游标

    VALUE_ERROR  在进行数据运算时发生错误

    其中前面的两个异常是最常见的异常。

    下面的代码演示如何处理这两个异常。

    DECLARE
    name emp.ename%type;
    BEGIN
    SELECT ename INTO name FROM emp WHERE deptno=100; --其实没有编号为100 的部门
    EXCEPTION --这里将引发NO_DATA_FOUND异常
    WHEN NO_DATA_FOUND THEN
    dbms_output.put_line ('没有满足条件的数据');
    WHEN TOO_MANY_ROWS THEN
    dbms_output.put_line('太多的数据');
    END;

    因为编号为100 的部门不存在,所以PL/SQL程序在执行到这条SELECT语句时引发了NO_DATA_FOUND异常。
    但是如果对一个存在的部门进行查询,可能返回多行数据。

    例如,如果将where子句的条件改为“ deptno= 10”,因为部门10高多个员工,这时将返回多行数据,
    从而引发TOO_MANY_ROWS异常。

    由此可见,在PL/SQL程序通过传统的SELECT命令只能查询一行数据,如果查询0行或多行数据,都会引发异常。
    如果要对0行或多行数据的情况进行处理,就要用到游标了。
    在向表的主键列上写入一个重复的值时将引发异常DUP_VAL_ON_INDEX 。

    例如,在部门表中列deptno是主键列,这就要求这个列上的值不能重复。
    如果已经存在部门10 ,再向这个表插入一行数据,部门编号也为10 ,这时将引发异常DUP_VAL ON_INDEX。
    例如:

    BEGIN
    INSERT INTO dept VALUES(10 , 'network', 'nowhere');
    EXCEPTION
    WHEN DUP_VAL_ON_INDEX THEN
    dbms_output.put_line ('主键列上的值重复');
    END;

    在PL/SQL块的异常处理部分,由WHEN 引导的代码即为异常处理程序。

    一般在一个PL/SQL块中有多个异常处理程序,分别用于处理不同的异常。
    但是一般只可能执行其中一段异常处理程序,因为当发生一个异常时, PL/SQL块的执行立即从可执行部分转入异常处理部分,当处理完异常后PL/SQL块的执行便宣告结束,这时将不会有别的异常出现。
    一般针对一个异常可以编写一段单独的异常处理程序,也可以对多个异常编写同一段异常处理程序,如果发生不同的异常,可以进行同样的处理。
    这样在WHEN子句中可以指定多个异常的名字,相互之间用OR分隔。

    WHEN NO_DATA_FOUND OR TOO_MANY_ROWS THEN
    dbms_output.put_line('SELECT语句出错');
    ...

    PL/SQL提供了两个函数, SQLCODE用于返回发生的错误的代码, SQLERRM用于返回错误的原因。

    有了这两个函数,就可以编写通用的异常处理程序,处理所有的异常。

    例如,将错误代码和错误信息显示给用户。
    例如:

    BEGIN
    INSERT INTO dept VALUES(10, 'network', 'nowhere');
    EXCEPTION
    WHEN others THEN
    dbms_output.put_line ('错误代码:' || SQLCODE) ;
    dbms_output.put_line ('错误原因:' || SQLERRM) ;
    END;

    在上面的异常处理的例子中,我们仅仅把发生错误的信息显示出来。

    如果希望把所有发生的错误记录下来,可以创建一个表,在PL/SQL的异常处理部分把错误的情况写入这个表,生成日志信息。
    例如,在数据库中创建表err_ info ,它的结构如下:

    列名      类型          为空    描述
    err_time       DATE          NO  错误发生的时间
    err_user       VARCHAR(30)      YES  因执行PL/SQL而引发错误的用户
    err_code       INTEGER        YES  错误代码
    err_message VARCHAR( 100)      YES  错误原因

    这样在处理异常时,就可以直接将异常的情况写入这个表,而不用显示给用户了。

    如果要对所有的异常进行相同的处理,那么在异常处理部分就不需要分别列出每个异常,只要用OTHERS代替就可以了。
    例如:

    DECLARE
    name emp.ename%type;
    err_code integer;
    err_message varchar(100);
    BEGIN
    SELECT ename INTO name FROM emp WHERE deptno=100;
    EXCEPTION --这里将引发NO_DATA_FOUND异常
    WHEN OTHERS THEN
    err_code:=SQLCODE;
    err_message:=SQLERRM;
    INSERT INTO err_info VALUES(SYSDATE,USER, err_code, err_message);
    COMMIT;
    END;

    这个块的执行结果是将异常的信息记录在表error_info 中。
    引发异常的一个重要原因是处理数时发生错误。

    统计表明, SELECT语句、DML语句以及游标操作语句更容易引发异常。
    编写PL/SQL块的主要目的是处理数据,而PL/SQL块在逻辑上与数据是分开的,程序员根本无法预料数据的变化。
    例如,要查询部门10的员工,程序员根本不知道这个部门中有没有员工,有一个还是有多个员工。
    所以在编写程序时,程序员应该考虑各种可能出现的异常,在程序中编写这些异常的处理代码,这样的程序才能经受各种错误的考验。

    非预定义异常

    在PL/SQL 中还有一类会经常遇到的错误。

    每个错误都有相应的错误代码和错误原因,但是由于Oracle没有为这样的错误定义一个名称,因而不能直接进行异常处理。
    在一般情况下,只能在PL/SQL块执行出错时查看其出错信息。
    编写PL/SQL程序时,应该充分考虑到各种可能出现的异常,并且都作出适当的处理,这样的程序才是健壮的。
    对于这类非预定义的异常,由于它也被自动抛出的,因而只需要定义一个异常,把这个异常的名称与错误的代码关联起来,然后就可以像处理预定义异常那样处理这样的异常了。
    非预定义异常的处理过程如下所示。

    定义异常 把异常与错误代码进行关联  处理异常

    PL/SQL声明部分              PL/SQL异常处理部分

            非预定义异常的处理过程

    异常的定义在PL/SQL块的声明部分进行,定义的格式为:

    异常名称 EXCEPTION

    其中异常名称是用户自己定义的-个名字,此时它仅仅是一个符号,没有任何意义。

    只有把这个名称与某个错误代码关联起来以后,这个异常才代表这个错误。
    把异常的名称与错误代码进行关联的格式是:

    PRAGMA EXCEPTION_INIT(异常名, 错误代码)

    这种关联也是在PL/SQL块的声明部分进行。

    这样这个异常的名字就代表这个特定的错误了,当PL/SQL程序在执行的过程中发生这个错误时,这个异常将被自动抛出,这时就可以对其进行处理了。
    例如,错误代码-02292的含义是违反了关联完整性。

    如果两个表通过主键和外键建立了关联关系,这时要从主表中删除一行,就可能会违反它们之间的关联完整性。
    例如员工表emp 与部门表dept通过列deptno建立了关联关系,如果要从部门表dept删除一行,必须保证EMP表中没有这个部门的员工,否则就违反了它们之间的关联完整性。
    下面的代码演示了试图删除部门10的数据时发生的错误,因为在表emp中还有部门10的员工。

    delete from dept where deptno=10;

    ORA-02292: 违反完整约束条件 (SCOTT.FK_DEPTNO) - 已找到子记录

    如果这时定义一个异常,把这个异常与错误代码-02292关联起来,就可以在PL/SQL块的异常处理部分对其进行处理了。
    下面是处理这个异常的代码。

    DECLARE
    reference_err EXCEPTION;
    PRAGMA EXCEPTION_INIT(reference_err,-02292);
    BEGIN
    DELETE FROM dept WHERE deptno=10;
    EXCEPTION
    WHEN reference_err THEN
    dbms_output.put_line('您所进行的操作违反了关联完整性');
    END;

    用户自定义的异常 

    除了Oracle定义的两种异常外,在PL/SQL中还可以自定义异常。

    程序员可以把一些特定的状态定义为异常。
    这样的异常一般由程序员自己决定,在一定的条件下抛出,然后利用PL/SQL的异常机制进行处理。
    对于用户自定义的异常,有两种处理方法。

    第一种方法是先定义一个异常,并在适当的时候抛出,然后在PL/SQL块的异常处理部分进行处理。
    用户自定义的异常一般在一定的条件下抛出,于是这个条件就成为引发这个异常的原因。
    第二种方法是向调用者返回一个自定义的错误代码和一条错误信息。
    这里先介绍第一种方法。

    异常的定义在PL/SQL块的声明部分进行,定义的格式为:

    异常名称 EXCEPTION

    异常名称这时仅仅是一个符号,仅当在一定条件下抛出时,这个异常才有意义。

    抛出异常的命令是RAISE ,异常的抛出在PL/SQL块的可执行部分进行。
    RAISE命令的格式为:

    RAISE 异常名称

    异常一般在一定的条件下抛出,因此RAISE语句通常跟在某个条件判断的后面,这样就把这个异常与这个条件关联起来了。
    抛出异常的原因可能是数据出错,也可能是满足了某个自定义的条件,处理自定义异常的方法与处理前两种异常的方法相同。
    例如,编写一个PL/SQL程序,求1+2+3 +… 100的值。

    在求和的过程中如果发现结果超出了1000 ,则抛出异常,并停止求和。
    这个块的代码如下:

    DECLARE
    out_of_range EXCEPTION; --定义异常
    result integer := 0;
    BEGIN
    for i in 1 .. 100 loop
    result := result + i;
    if result > 1000 then
    RAISE out_of_range; --抛出异常
    END if;
    END loop;
    EXCEPTION
    WHEN out_of_range THEN --处理异常
    DBMS_OUTPUT.PUT_LINE('当前的计算结果为'||result||',已超出范围');
    END;

    用RAISE命令不仅可以抛出一个自定义的异常,也可以抛出一个预定义异常和非预定义异常。
    例如,在上面求和的例子中,当计算结果超过1000时可以抛出异常VALUE_ERROR 。

    修改后的PL/SQL块代码如下:

    DECLARE
    result integer := 0;
    BEGIN
    for i in 1 .. 100 loop
    result := result + i;
    if result > 1000 then
    RAISE value_error; --抛出预定义的异常
    END if;
    END loop;
    EXCEPTION
    WHEN value_error THEN --处理异常
    DBMS_OUTPUT.PUT_LINE('当前的计算结果为'||result||',已超出范围');
    END;

    现在再来介绍自定义异常处理的第二种方法。

    当PL/SQL块的执行满足一定的条件时,可以向PL/SQL程序返回一个错误代码和一条错误信息。
    错误代码的范围是-20000 到- 20999 ,这个范围的代码是Oracle保留的,本身没有任何意义。
    程序如果把一个错误代码与某个条件关联起来,那么在条件满足时系统将引发这样的错误。
    当然这是人为制造的一种错误,并不表示程序或数据真正出现了错误。
    PL/SQL提供了一个过程,用于向PL/SQL程序返回一个错误代码和一条错误信息。

    这个过程是RAISE_APPLICATION_ERROR ,过程的调用格式为:

    RAISE_APPLICATION_ERROR(错误代码,错误信息)

    例如,对上面求和的例子加以修改,当计算结果大于1000时, PL/SQL程序便得到一个错误代码- 20001 和一条错误信息。
    修改后的代码如下:

    DECLARE
    result integer := 0;
    BEGIN
    for i in 1 .. 100 loop
    result := result + i;
    if result > 1000 then
    RAISE_APPLICATION_ERROR(-20001, '当前的计算结果为'||result||',已超出范围');
    END if;
    END loop;
    END;

    从程序运行的结果来看,程序的执行过程确实发生了错误,返回了指定的错误代码和错误信息。
    在这一点上用户自定义的异常与非预定义异常是相似的。

    只不过非预定义异常是由数据库服务器自动抛出的,并且错误代码和错误信息都是由数据库服务器指定的,而用户自定义的异常是由程序员抛出的,错误代码和错误信息都是由程序员指定的。
    在处理非预定义异常时,我们为每个错误代码指定了一个异常名称,然后就可以根据这个名称进行异常处理。
    既然用户自定义的异常也可以向调用者返回错误代码和错误信息,那么我们也可以采用同样的方法处理这样的异常。
    首先定义一个异常,然后把这个异常与某个错误代码关联起来。

    这两步都在PL/SQL块的声明部分进行。
    然后在PL/SQL程序的可执行部分根据一定的条件,抛出这个异常。

    最后在PL/SQL块的异常处理部分捕捉并处理这个命名的异常。
    例如,用这种方住重新处理上述求和的例子中的异常,代码如下:

    DECLARE
    result integer := 0;
    out_of_range EXCEPTION;
    PRAGMA EXCEPTION_INIT(out_of_range, -20001);
    BEGIN
    for i in 1 .. 100 loop
    result := result + i;
    if result > 1000 then
    RAISE_APPLICATION_ERROR(-20001, '当前的计算结果为'||result||',已超出范围');
    END if;
    END loop;
    EXCEPTION
    WHEN out_of_range THEN 
    dbms_output.put_line('错误代码:'||sqlcode); 
    dbms_output.put_line('错误信息:'||sqlerrm); 
    END;

    从上述PL/SQL块可以看出,我们首先在声明部分定义了一个异常out_of_range ,然后把这个异常与错误代码-20001关联起来,一旦程序在运行过程中发生了这个错误,就是抛出了异常out_of_range 。
    在块的可执行部分,如果在累加的过程中变量result的值超过了1000 ,则返回错误代码- 20001 以及相应的错误信息。
    这样在异常处理部分就可以捕捉并处理异常out_of_range 了。
    在处理用户自定义的异常时,也可以使用函数SQLCODE和SQLERRM ,这两个函数分别用于返回指定的错误代码和错误信息。
    从程序的运行结果可以看出,这两个函数确实返回了指定的错误代码和错误信息。
    这样的错误代码和错误信息是在可执行部分通过过程RAISE APPLICATION ERROR指定的。

    异常的传递

    如果PL/SQL程序在执行的过程中发生了错误, 则转去执行相应的异常处理程序,然后结束块的执行。
    如果没有定义相应的异常处理程序,那么PL/SQL程序将向调用者返回出错的相关信息,也就是把异常传递到程序的调用者,然后结束程序的执行。
    如果这个程序是在SQL*Plus 中执行的,那么异常就会传递到SQL*Plus环境,从而把错误信息显示在屏幕上。
    例如,下面的块在检索数据时引发了TOO_MANY _ROWS异常,并把异常传递到SQL*plus 中。

    DECLARE
    name emp.ename%type;
    BEGIN
    SELECT ename INTO name FROM emp WHERE deptno=10;
    EXCEPTION --这里将引发TOO_MANY_ROWS 异常
    when NO_DATA_FOUND then
    dbms_output.put_line ('没有满足条件的数据');
    END;

    从程序的执行结果可以看出,由于在程序中没有处理异常TOO_MANY_ROWS ,所以这个异常被传递到程序的调用者一-SQL*Plus 中。
    在PL/SQL块中可以定义过程、函数等形式的子程序,在每个子程序中也可以分别定义异常处理程序。
    这样当子程序执行出现错误时,就转去执行相应的异常处理程序。

    然后子程序的执行便告结束, PL/SQL块接着从子程序调用处的下一条语句开始执行。
    如果子程序对出现的异常进行了处理,就可以认为子程序的执行正常结束。
    例如,再来考虑子程序重载的这个例子。
    在这个块中定义了两个重载过程increase_salary ,用来对员工增加工资。

    第一个过程有两个参数,分别是部门编号和增加的额度,用于对指定的部门的员工增加工资。
    第二个过程带有一个参数,即增加的额度,用于对所有员工增加工资。

    这里在第一个过程中添加了处理异常NO_DATA_FOUND的程序,还添加了一条SELECT语句。
    如果在调用过程时指定了一个不存在的部门,那么在查询该部门信息时将引发NO_DATA_FOUND异常,这个过程的执行流程就会转到异常处理部分。

    DECLARE
    procedure increase_salary(d_no emp.deptno%type, amount float)
    is
    d_name dept.dname%type;
    BEGIN
    SELECT dname INTO d_name FROM dept --其实部门100 不存在
    WHERE deptno=d_no;
    UPDATE emp set sal=sal+amount WHERE deptno=d_no;
    EXCEPTION
    WHEN NO_DATA_FOUND THEN
    dbms_output.put_line ('这个部门不存在');
    END;
    procedure increase_salary(amount float)
    is
    BEGIN
    UPDATE emp set sal=sal+amount;
    END;
    BEGIN
    increase_salary(100,100.50); --调用第一个increase_salary过程
    increase_salary(200); --调用第二个increase_salary过程
    END;

    从块的执行结果可以看出,当调用第一个重载过程时,因为传递了一个不存在的部门编号,所以引发了NO_DATA_FOUND异常。
    这个过程在处理异常后便执行结束, PL/SQL块接着执行第二条调用语句,调用第二个过程。
    第一个过程因为处理了出现的异常,所以可以认为是正常结束,它并不会影响块整个程序中其他语句的执行。
    对异常的处理应当遵循“不扩散”的原则。

    在子程序中发生的错误应该在子程序中进行处理,不要扩散到主程序中。
    同样,在PL/SQL块的可执行部分出现的错误应该在块中进行处理,不要扩散到调用该块的SQL*Plus或应用程序中。
    如果在子程序中没有处理出现的错误,情况会怎么样呢?

    再来考虑上面的例子,取消了第一个increase_salary过程中的异常处理部分。
    为了便于测试,在两条调用语句中间添加了一条输出语句。
    修改后的代码如下:

    DECLARE
    procedure increase_salary(d_no emp.deptno%type, amount float)
    is
    d_name dept.dname%type;
    BEGIN
    SELECT dname INTO d_name FROM dept --其实部门100 不存在
    WHERE deptno=d_no;
    UPDATE emp set sal=sal+amount WHERE deptno=d_no;
    END;
    procedure increase_salary(amount float)
    is
    BEGIN
    UPDATE emp set sal=sal+amount;
    END;
    BEGIN
    increase_salary(100,100.50); --调用第一个increase_salary过程
    dbms_output.put_line('第一个过程结束了');
    increase_salary(200); --调用第二个increase_salary过程
    END;

    在调用第一个increase_salary过程时,由于指定了一个不存在的部门编号,所以引发了异常
    NO_DATA_FOUND 。在子程序中没有处理这个异常,所以过程非正常结束。从程序的执行结
    果可以看出,我们指定的输出并没有产生,可以断定,第一条调用语句以下的所有语句都没有
    得到执行。
    如果子程序没有处理出现的错误,那么异常就被传递到它的调用者,即PL/SQL 主程序,
    从而在主程序中也会产生错误。所以主程序将在调用子程序的地方停止执行,而去处理这个异
    常。但是因为主程序也没有定义异常处理程序,所以这个异常又被传递到块的调用者
    SQL*Plus ,从而在屏幕上显示出错的信息。
    从子程序中传递到PL/SQL主程序中的异常,能不能在主程序中进行处理呢?答案是肯定
    的。如果在主程序中定义了异常处理程序,那么异常被从子程序传递到主程序中后,就像在主
    程序中产生的异常一样进行处理。这样我们可以在主程序中编写统一的异常处理程序,无论异
    常是在主程序中抛出的,还是在子程序中抛出的,都可以得到同样的处理。这种做陆虽然是可
    行的,但是它不符合“不扩散”原则。如果程序出现了异常,不容易确定是什么地方出现了错
    误,也无法对程序的不同部分产生的异常进行单独的处理。例如,把上述例子中第一个
    increase_salary 过程的异常处理放在PL/SQL块中。如果调用过程时引发了异常,便可以进行处
    理。

  • 相关阅读:
    每日学习
    解决MySQL下把结果导出到文件权限不足问题
    杀死Windows中的进程镜像taskkill /F /IM 镜像名
    大家,中秋节快乐
    学习AutoIt
    为什么你应该(从现在开始就)对自己投资
    验证PE文件数字签名是否有效
    MySQL Cluster集群搭建与测试
    MySQL主从复制与读写分离
    Python与Zabbix API交互配置监控主机
  • 原文地址:https://www.cnblogs.com/yj716716yj/p/5543891.html
Copyright © 2020-2023  润新知