PLSQL总结-3(异常处理及嵌套块).ppt
PL/SQL 总结-3,目录,处理异常捕获 Oracle Server 异常捕获用户定义异常识别变量作用域,处理异常,本部分将讲解:在 PL/SQL 代码中包含异常处理代码的优点PL/SQL 块异常处理部分的目的 异常处理指南,处理异常,前面学了如何在 PL/SQL 块中编写声明部分和执行部分.所有要执行的 SQL 和 PL/SQL 代码都写在执行部分.迄今为止我们假设代码只有编译错误.然而,代码会发生难以预料的运行时错误.本部分学习如何在 PL/SQL 块中处理这些运行时错误.,什么是异常?,异常是程序的运行时错误,异常会中断程序的正常执行.异常的产生有多种原因:用户输入错误;硬件错误;网页不存在;等等.你使用应用程序和网站时遇到过错误吗?,PL/SQL 中的异常,下例运行正常.但是如果输入的是 Korea,South 而不是 Republic of Korea 呢?DECLARE v_country_name wf_countries.country_name%TYPE:=Republic of Korea;v_elevation wf_countries.highest_elevation%TYPE;BEGIN SELECT highest_elevation INTO v_elevation FROM wf_countries WHERE country_name=v_country_name;DBMS_OUTPUT.PUT_LINE(v_country_name);END;,PL/SQL 中的异常,DECLARE v_country_name wf_countries.country_name%TYPE:=Republic of Korea;v_elevation wf_countries.highest_elevation%TYPE;BEGIN SELECT highest_elevation INTO v_elevation FROM wf_countries WHERE country_name=v_country_name;DBMS_OUTPUT.PUT_LINE(v_country_name);END;,PL/SQL 中的异常,代码没有按预期的运行.没有找到有关 Korea,South 的数据,因为国家名实际上是按 Republic of Korea 存储的.这类 PL/SQL 错误称为异常.异常发生时,我们说异常被“抛出”.异常被抛出时,异常点之后 PL/SQL 块执行部分的剩余代码不再执行.,什么是异常处理代码?,异常处理代码定义了异常发生后应当执行的恢复操作.编写代码时,程序员应当预见到代码执行时可能发生的错误的类型.然后为每一种错误编写异常处理代码.异常处理代码是程序员为代码错误编制的处理预案.,什么是异常处理代码?,程序员使用异常处理代码来处理哪些错误?系统错误(比如磁盘空间耗尽)数据错误(比如,主键值重复)用户错误(比如,数据输入错误)很多其它的可能性!,为什么异常处理重要?,你能说明为什么异常处理如此重要吗?可能的原因包括:减轻错误对用户的 影响(频繁的错误使用户沮丧甚至拒绝使用程序)保护数据库(避免数据丢失或被覆盖)错误消耗大量系统资源(错误发生后,纠正错误代价高昂;用户频繁打电话请求帮助).代码更加易读,因为错误处理代码可在同一个块中的独立部分处理错误.,处理 PL/SQL异常,PL/SQL 抛出异常,块执行结束但可编写异常处理代码,在块结束前执行最后的操作.异常处理部分以关键字 EXCEPTION 开始.,处理 PL/SQL异常,异常如果被处理,PL/SQL 程序就不会突然中断.异常抛出后,控制转到异常处理部分,执行相应的处理代码.之后,PL/SQL 块正常、成功结束.一个时刻只能发生一个异常.异常发生时,PL/SQL 在块结束前只执行一个异常处理代码.,处理 PL/SQL异常,点 A 的代码不会执行,因为 SELECT 语句失败.,处理 PL/SQL异常,下面是另一示例.块中select 语句用于获取 John 的last_name.DECLARE v_lname VARCHAR2(15);BEGIN SELECT last_name INTO v_lname FROM employees WHERE first_name like El%;DBMS_OUTPUT.PUT_LINE(Ellens last name is:|v_lname);END;然而,因为有多个 John 所以会产生异常.,处理 PL/SQL异常,下例中异常处理代码用于处理 预定义的Oracle 服务错误 TOO_MANY_ROWS.下部分将详细学习预定义的Oracle 服务错误.DECLARE v_lname employees.last_name%TYPE;BEGIN SELECT last_name INTO v_lname FROM employees WHERE first_name like El%;DBMS_OUTPUT.PUT_LINE(Ellens last name is:|v_lname);EXCEPTION WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE(Your select statement retrieved multiple rows.Consider using a cursor.);END;,捕获异常,你可通过在PL/SQL块的异常处理部分增加相应的处理代码来处理或捕获(trap)任何错误.语法:,捕获异常,每个异常处理代码包含一个 WHEN 子句,其后是异常名,然后是 THEN 引导的异常处理语句.在 EXCEPTION 部分可包含任意多个异常处理代码来处理特定的异常.然而,不能为同一个异常编写多个异常处理代码.,捕获异常,语法中:exception 是预定义的异常名或用户在声明部分定义的异常名statement 是一个或多个 PL/SQL 或 SQL 语句OTHERS 是可选的异常处理子句,用于捕获 前面没有明确捕获的任何异常,OTHERS 关键字,异常处理部分只捕获那些 WHEN 子句说明的异常;其它任何异常都不会被捕获,除非后面也使用了 OTHERS 子句.OTHERS 子句捕获前面没有捕获的异常.如果使用,OTHERS 必须是异常处理最后一个子句.,OTHERS 关键字,考虑下面的例子:如果程序抛出 NO_DATA_FOUND 异常,则执行 statement1 如果程序抛出 TOO_MANY_ROWS异常,则执行 statement2如果程序抛出了其他异常,则执行 statement3,捕获异常指南,如果有可能发生错误,就要添加异常处理代码.在计算时,字符串操作时,执行 SQL 语句时都有可能发生错误.如果有可能,尽量按名称处理异常 而不是用 OTHERS 捕获异常.掌握预定义异常的名称及其产生的原因.用不同的坏数据及其组合测试代码,发现可能出现的错误.在异常处理代码中输出调试信息.仔细考虑每个异常处理代码是需要提交事务,还是撤销事务,或是让事务继续.不管错误多么严重,我们都要使数据库处于一致状态,避免保存任何坏数据.,处理异常,本部分讲解了:在 PL/SQL 代码中包含异常处理代码的优点PL/SQL 块异常处理部分的目的 异常处理指南,捕获 Oracle Server 异常,本部分将讲解:Oracle 服务异常用户自定义异常显式和隐式抛出的异常捕获预定义的 Oracle Server异常捕获非预定义的 Oracle Server 错误通过错误代码和错误消息识别异常,捕获 Oracle Server 异常,PL/SQL 错误处理非常灵活,允许程序员处理用户定义异常和 Oracle定义异常.本部分学习预定义和非预定义 Oracle 服务错误.预定义错误是常见的 Oracle 错误,为了方便,PL/SQL 已经为其定义了异常名.非预定义错误使用 ORA 错误代码和消息.两种错误处理语法不同,但你可在 EXCEPTION 处理部分捕获所有这两种错误.,Exception Types,在 PL/SQL中处理异常,有两种方式抛出异常:Oracle 服务隐式(自动)抛出:Oracle 服务发生错误时,异常自动抛出.例如,单行SELECT 查询如果没有返回数据,则发生编号 ORA-01403 的错误,then PL/SQL 抛出 NO_DATA_FOUND 异常.程序员显式(明确)抛出:根据程序要实现的功能,有时需要显式抛出异常.可通过 RAISE 语句明确的抛出异常.RAISE 语句抛出的异常可以是用户定义的,也可是Oracle预先定义好的.下一部分详细解释.,两种 Oracle 服务错误,Oracle 服务发生错误时,相关异常自动抛出,执行部分剩余代码被忽略,在异常处理部分寻找对应异常处理代码.有两种Oracle 服务错误:预定义Oracle 服务错误:此类错误有预先定义好的异常名.如,错误 ORA-01403 的异常名为 NO_DATA_FOUND.非预定义Oracle 服务错误:此类错误没有预先定义好的异常名,只有形如(ORA-nnnnn)的标准错误编号和错误描述消息.可以为此类错误声明一个异常名,从而可以在异常处理部分使用此名称捕获该异常.,捕获预定义Oracle 服务错误,在异常处理代码中引用预定义的异常名.下面是几个预定义异常的例子:NO_DATA_FOUNDTOO_MANY_ROWSINVALID_CURSORZERO_DIVIDEDUP_VAL_ON_INDEXplsql_s06_l02_predefined_errors.doc 文档中给出了部分预定义异常.完整异常列表参看 PL/SQL Users Guide and Reference.,捕获预定义Oracle 服务错误,下例中使用了预定义Oracle错误TOO_MANY_ROWS 注意预定义Oracle错误无需声明,直接使用DECLARE v_lname VARCHAR2(15);BEGIN SELECT last_name INTO v_lname FROM employees WHERE first_name like El%;DBMS_OUTPUT.PUT_LINE(Ellens last name is:|v_lname);EXCEPTION WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE(Your select statement retrieved multiple rows.Consider using a cursor.);END;,捕获多个预定义Oracle 服务错误,下例处理了 TOO_MANY_ROWS 和 NO_DATA_FOUND 异常,并使用 OTHERS 来处理其它可能的异常.DECLARE v_lname VARCHAR2(15);BEGIN SELECT last_name INTO v_lname FROM employees WHERE first_name=John;DBMS_OUTPUT.PUT_LINE(Johns last name is:|v_lname);EXCEPTION WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.PUT_LINE(Select statement found multiple rows);WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE(Select statement found no rows);WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(Another type of error occurred);END;,捕获非预定义Oracle 服务错误,非预定义Oracle 异常和预定义异常类似;但在 PL/SQL 中没有预定义的名称.它们是标准Oracle 服务错误,有 ORA-错误编号.在 DECLARE 部分程序员自己为错误定义一个异常名,并使用 PRAGMA EXCEPTION_INIT 函数将其与ORA-错误编号关联在一起.,捕获非预定义Oracle 服务错误,要捕获非预定义Oracle 服务错误,必须先声明异常.声明的异常是自动抛出的.在PL/SQL中,PRAGMA EXCEPTION_INIT 指示编译器将异常名和 Oracle 错误编号关联.这样,就可以通过异常名来捕获非预定义Oracle 服务错误,并为其提供专门的异常处理代码.,非预定义Oracle 服务错误,示例BEGIN INSERT INTO departments(department_id,department_name)VALUES(280,NULL);END;INSERT 语句试图为departments表 的department_name 列插入空值.然而语句失败,因为 department_name 定义为 NOT NULL.违反 NOT NULL 约束这种错误没有预定义好的异常名称.处理这种错误的方法是自己声明异常名并将其与 ORA-1400 错误编号关联在一起.,非预定义Oracle 服务错误,1.在声明部分定义一个异常名.,非预定义Oracle 服务错误,2.使用 PRAGMA EXCEPTION_INIT 函数将异常名和标准Oracle错误编号关联.,非预定义Oracle 服务错误,3.在异常处理代码中引用前面定义的异常名,异常相关函数,异常发生时,可通过下面两个函数获取相关的错误代码和错误消息.基于错误代码和错误消息可以决定后续操作.SQLERRM 返回文本类型的错误消息SQLCODE 返回数值类型的错误代码(可赋予 NUMBER 类型变量.),异常相关函数,不能直接在SQL语句中使用 SQLCODE 或 SQLERRM.必须先将其值赋予局部变量,然后在 SQL 语句中使用变量,如下例所示:,捕获 Oracle Server 异常,本部分讲解了:Oracle 服务异常用户自定义异常显式和隐式抛出的异常捕获预定义的 Oracle Server异常捕获非预定义的 Oracle Server 错误通过错误代码和错误消息识别异常,捕获用户定义异常,此部分将讲解:编写 PL/SQL 代码定义用户定义异常编写 PL/SQL 代码抛出异常处理抛出的异常使用 RAISE_APPLICATION_ERROR,捕获用户定义异常,PL/SQL 处理的另一类错误是用户定义异常.此类错误不是由 Oracle 服务自动抛出,而是程序员在自己的代码中定义并抛出的.自定义错误的一个例子是非法经理 ID:INVALID_MANAGER_ID.也可以为自定义错误定义错误编号和错误消息.,异常类型,本部分学习用户定义异常.,捕获用户定义异常,PL/SQL 允许自定义异常.根据应用程序需要定义自己的异常.,捕获用户定义异常,需要自定义异常的一个例子是,你需要处理和强调输入数据的错误.例如,假设 程序需要提示用户输入部门编号和姓名,用于修改部门数据.DECLARE v_name VARCHAR2(20):=Accounting;v_deptno NUMBER:=27;BEGIN UPDATE departments SET department_name=v_name WHERE department_id=v_deptno;END;如果用户输入不存在的部门编号会怎样?上面代码不会出现 Oracle 服务错误.你需要自定义异常来提示数据输入的错误.,捕获用户定义异常,自定义异常的方法是:1.在声明部分定义异常的名称.2.使用 RAISE 语句在执行部分显式抛出异常.3.在异常处理部分通过自定义异常名捕获异常.,捕获用户定义异常,下面是完整的代码.,捕获用户定义异常,1.在声明部分定义异常的名称.语法:exception EXCEPTION;其中:exception 是异常的名称,捕获用户定义异常,2.使用 RAISE 语句在执行部分显式抛出异常.语法:RAISE exception;其中:exception 是前面定义的异常名,捕获用户定义异常,3.在异常处理部分通过自定义异常名捕获异常.,RAISE 语句,RAISE 语句用于抛出已命名的异常.可以抛出:自定义异常Oracle 服务异常,RAISE_APPLICATION_ERROR,可用 RAISE_APPLICATION_ERROR 过程从存储程序中返回给用户自定义的错误消息.使用 RAISE_APPLICATION_ERROR 的主要优点是,和 RAISE 相比,RAISE_APPLICATION_ERROR 允许为自定义的异常指定错误编号和错误消息.错误编号必须在-20000 和-20999 之间.语法:,RAISE_APPLICATION_ERROR,error_number 是自定义错误编号,自定义的异常错误编号必须在20000 和 20999 之间Message 是为错误自定义的错误消息.是可长达 2,048 字节的字符串.TRUE|FALSE 是可选的Boolean参数(TRUE,此错误在前面出现的所有错误之前显示.FALSE,此错误取代前面出现的所有错误,前面的其它错误不再有提示信息.)范围-20000 到-20999 的错误编号预留给程序员使用,预定义的 Oracle 服务错误不会使用.,RAISE_APPLICATION_ERROR,RAISE_APPLICATION_ERROR 可用于:执行部分异常处理部分,执行部分的 RAISE_APPLICATION_ERROR,RAISE_APPLICATION_ERROR 过程为用户显示错误编号和消息.这和 Oracle 服务错误的处理方式是一致的.DECLARE v_mgr PLS_INTEGER:=123;BEGIN DELETE FROM employees WHERE manager_id=v_mgr;IF SQL%NOTFOUND THEN RAISE_APPLICATION_ERROR(-20202,This is not a valid manager);END IF;END;,异常部分的 RAISE_APPLICATION_ERROR,DECLARE v_mgr PLS_INTEGER:=27;v_employee_id employees.employee_id%TYPE;BEGIN SELECT employee_id into v_employee_id FROM employees WHERE manager_id=v_mgr;DBMS_OUTPUT.PUT_LINE(The employee who works for manager_id|v_mgr|is:|v_employee_id);EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20201,This manager has no employees);WHEN TOO_MANY_ROWS THEN RAISE_APPLICATION_ERROR(-20202,Too many employees were found.);END;,保留前面的错误,DECLARE v_mgr PLS_INTEGER:=27;v_employee_id employees.employee_id%TYPE;BEGIN SELECT employee_id into v_employee_id FROM employees WHERE manager_id=v_mgr;EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20201,This manager has no employees,true);END;,RAISE_APPLICATION_ERROR 与自定义异常合用,捕获用户定义异常,此部分讲解了:编写 PL/SQL 代码定义用户定义异常编写 PL/SQL 代码抛出异常处理抛出的异常使用 RAISE_APPLICATION_ERROR,识别变量作用域,此部分将讲解:描述在嵌套块中变量的作用域.使用标签限定嵌套块中的变量描述异常的作用域识别嵌套块中异常的作用域问题描述异常在嵌套块中的传播方式及其影响,识别变量作用域,前面已学习过嵌套块,变量的作用域和异常的传播.理解了如何正确处理异常,本部分先复习前面知识,然后再讲述新的内容.命名异常是一种 PL/SQL 变量.要正确处理异常,需要理解异常变量的作用域和可见性.这在使用嵌套块时尤其重要.,复习嵌套块,下例有外层(父)块(蓝色显示)和 嵌套(子)块(红色显示).变量 v_outer_variable 在父块中定义,变量v_inner_variable 在子块中定义.,复习变量作用域,变量作用域是变量在其中可被访问和使用的一个块或多个块.PL/SQL中,变量作用域是其定义所在块及其所有子块.下例两个变量的作用域是什么?,复习变量作用域,阅读下面代码.每个变量的作用域是什么?,复习变量作用域,下面代码为什么出错?,复习变量作用域,下面代码正确吗?为什么?,PL/SQL 怎样寻找变量?,在块中使用变量时,PL/SQL 先在当前块中寻找该变量(局部变量).如果未找到,PL/SQL 继续在父块中寻找.如果还未找到,PL/SQL 在父块的父块中寻找(嵌套可有三层或更多层).依此类推.下例中有三层嵌套.,三层嵌套示例,每个变量的作用域是什么?,复习变量命名,下面变量声明对不对?,以上声明合法,但在子块中无法访问父块定义的变量 v_myvar.,复习变量命名,变量 v_date_of_birth 声明了两次.DBMS_OUTPUT.PUT_LINE 语句中引用的是哪个 v_date_of_birth?,复习变量可见范围,变量可见范围是变量作用域中,不加限定符就可使用变量的部分.下面每个变量的可见范围是什么?,复习变量可见范围,父块的 v_date_of_birth 变量作用域包括子块.此变量在父块中可见.然而,该变量在子块中不可见(被隐藏),因为子块中有一个同名的局部变量.变量 v_father_name 在父子块中都可见.变量 v_child_name 只在子块中可见.怎样才能在子块中引用父块定义的变量 v_date_of_birth 呢?,复习块标签,可使用 语法为块加标签.然后通过标签使用在作用域内但被隐藏的变量.下例中,父块标签为outer.不仅父块,任何块都可加标签.,通过块标签使用被隐藏变量,通过使用标签 outer 限定变量 v_date_of_birth,在子块中就可输出显示父亲的生日了.,嵌套块中的异常处理,异常处理方式有两种:在发生异常的块处理(捕获)该异常将异常传递给调用环境(比如其父块),传播异常到父块,如果异常自子块的执行部分抛出,子块无相应处理代码,PL/SQL 子块以失败结束,异常传递给其父块.,传播异常到父块,下例中,子块执行时产生了一个异常.子块 EXCEPTION 部分 没有处理此异常.子块失败结束,PL/SQL 将异常传递给父块.父块 EXCEPTION 部分成功处理此异常.,从子块向外传递异常,如果 PL/SQL 产生异常,而当前块没有捕获该异常 异常连续传递给上层块,直到找到相应异常处理程序.异常传递给上层块时,当前块剩余执行代码被跳过,不再执行.这样做的优点是在块中处理此块可以处理的异常,将更一般的异常处理(如 WHEN OTHERS)交给上层块去.下面是一个例子.,从子块向外传递预定义异常,不存在编号 999 的员工.下面代码执行结果是什么?,从子块向外传递用户定义的异常,下面代码执行结果怎样?,异常名的作用域,预定义 Oracle 服务异常如 NO_DATA_FOUND,TOO_MANY_ROWS(以及 OTHERS)不需要用户声明.可在任何块中使用(抛出或捕获).用户命名的异常(非预定义 Oracle 服务异常和 自定义异常)是程序员定义的 EXCEPTION 类型的变量其作用域和其它变量遵循相同规则.因此,子块声明的自定义异常不能在父块中使用,识别变量作用域,此部分讲解了:描述在嵌套块中变量的作用域.使用标签限定嵌套块中的变量描述异常的作用域识别嵌套块中异常的作用域问题描述异常在嵌套块中的传播方式及其影响,