Oracle_PL/SQL 程序设计

1、PL/SQL简介

Oracle PL/SQL 语言(Procedural Language/SQL)是结合了结构化查询与 Oracle 自身过程控制为一体的强大语言,PL/SQL 不但支持更多的数据类型,拥有自身的变量声明、赋值语句,而且还有条件、循环等流程控制语句。过程控制结构与 SQL 数据处理能力无缝的结合形成了强大的编程语言,可以创建过程和函数以及程序包。

Oracle PL/SQL 语言(Procedural Language/SQL)是结合了结构化查询与 Oracle 自身过程控制为一体的强大语言,PL/SQL 不但支持更多的数据类型,拥有自身的变量声明、赋值语句,而且还有条件、循环等流程控制语句。过程控制结构与 SQL 数据处理能力无缝的结合形成了强大的编程语言,可以创建过程和函数以及程序包。

PL/SQL 块发送给服务器后,先被编译然后执行,对于有名称的 PL/SQL 块(如子程序)可以单独编译,永久的存储在数据库中,随时准备执行。PL/SQL 的优点还有:

  • 支持 SQL:SQL 是访问数据库的标准语言,通过 SQL 命令,用户可以操纵数据库中的数据。PL/SQL支持所有的 SQL 数据操纵命令、游标控制命令、事务控制命令、SQL 函数、运算符和伪列。同时 PL/SQL 和 SQL 语言紧密集成,PL/SQL 支持所有的 SQL 数据类型和 NULL 值。
  • 支持面向对象编程:PL/SQL 支持面向对象的编程,在 PL/SQL 中可以创建类型,可以对类型进行继承,可以在子程序中重载方法等。
  •  更好的性能:SQL 是非过程语言,只能一条一条执行,而 PL/SQL 把一个 PL/SQL 块统一进行编译后执行,同时还可以把编译好的 PL/SQL 块存储起来,以备重用,减少了应用程序和服务器之间的通信时间,PL/SQL 是快速而高效的。
  • 可移植性:使用 PL/SQL 编写的应用程序,可以移植到任何操作系统平台上的 Oracle 服务器,同时还可以编写可移植程序库,在不同环境中重用。
  • 安全性:可以通过存储过程对客户机和服务器之间的应用程序逻辑进行分隔,这样可以限制对Oracle 数据库的访问,数据库还可以授权和撤销其他用户访问的能力。

2. PL/SQL 块

 PL/SQL 是一种块结构的语言,一个 PL/SQL 程序包含了一个或者多个逻辑块,逻辑块中可以声明变量,变量在使用之前必须先声明。除了正常的执行程序外,PL/SQL 还提供了专门的异常处理部分进行异常处理。每个逻辑块分为三个部分,语法是:

语法结构:PL/SQL 块的语法

[DECLARE
--declaration statements] 
BEGIN
--executable statements 
[EXCEPTION
--exception statements] 
END;

语法解析:

  • 声明部分:声明部分包含了变量和常量的定义。这个部分由关键字 DECLARE 开始,如果不声明变量或者常量,可以省略这部分。
  • 执行部分:执行部分是 PL/SQL 块的指令部分,由关键字 BEGIN 开始,关键字 END结尾。所有的可执行 PL/SQL 语句都放在这一部分,该部分执行命令并操作变量。其他的 PL/SQL 块可以作为子块嵌套在该部分。PL/SQL 块的执行部分是必选的。注意 END 关键字后面用分号结尾。
  • 异常处理部分:该部分是可选的,该部分用 EXCEPTION 关键字把可执行部分分成两个小部分,之前的程序是正常运行的程序,一旦出现异常就跳转到异常部分执行。

PL/SQL 是一种编程语言,与 Java 和 C#一样,除了有自身独有的数据类型、变量声明和赋值以及流程控制语句外,PL/SQL 还有自身的语言特性:

PL/SQL 对大小写不敏感,为了良好的程序风格,开发团队都会选择一个合适的编码标准。比如有的团队规定:关键字全部大些,其余的部分小写。

PL/SQL 块中的每一条语句都必须以分号结束,SQL 语句可以是多行的,但分号表示该语句结束。一行中可以有多条 SQL 语句,他们之间以分号分隔,但是不推荐一行中写多条语句。

PL/SQL 中的特殊符号说明:

类型 符号 说明
赋值运算符 := Java 和 C#中都是等号,PL/SQL 的赋值是:=
特殊字符 || 字符串连接操作符。
- PL/SQL 中的单行注释。
/*,*/ PL/SQL 中的多行注释,多行注释不能嵌套。
<<,>> 标签分隔符。只为了标识程序特殊位置。
.. 范围操作符,比如:1..5 标识从1到5
算术运算符 +,-,*,/ 基本算术运算符。
** 求幂操作,比如:3**2=9
关系运算符 >,<,>=,<=,= 基本关系运算符,=表示相等关系,不是赋值。
<>,!= 不等关系。
逻辑运算符 AND,OR,NOT AND,OR,NOT

(1)变量声明

PL/SQL 支持 SQL 中的数据类型,PL/SQL 中正常支持 NUMBER,VARCHAR2,DATE 等 Oracle SQL 数据类型。声明变量必须指明变量的数据类型,也可以声明变量时对变量初始化,变量声明必须在声明部分。声明变量的语法是:

语法格式:声明变量

变量名 数据类型[ :=初始值]

语法解析:
数据类型如果需要长度,可以用括号指明长度,比如:varchar2(20)。

代码演示:声明变量

代码解析:

  • 声明一个变量 sname,初始化值是“jerry”。字符串用单引号,如果字符串中出现单引号可以使用两个单引号(’’)来表示,即单引号同时也具有转义的作用。
  • 对变量 sname 重新赋值,赋值运算符是“:=”。
  • dbms_output.put_line 是输出语句,可以把一个变量的值输出,在 SQL*Plus 中输出数据时,可能没有结果显示,可以使用命令:set serveroutput on 设置输出到 SQL*Plus控制台上。

对变量赋值还可以使用 SELECT…INTO 语句从数据库中查询数据对变量进行赋值。但是查询的结果只能是一行记录,不能是零行或者多行记录。

代码演示:变量赋值

代码解析:

  • 变量初始化时,可以使用 DEFAULT 关键字对变量进行初始化。
  •  使用 select…into 语句对变量 sname 赋值,要求查询的结果必须是一行,不能是多行或者没有记录。

(2)声明常量

常量在声明时赋予初值,并且在运行时不允许重新赋值。使用 CONSTANT 关键字声明常量。

代码演示:声明常量

代码解析:

  • 声明常量时使用关键字 CONSTANT,常量初值可以使用赋值运算符(:=)赋值,也可以使用 DEFAULT 关键字赋值。

在 SQL*Plus 中还可以声明 Session(会话,也就是一个客户端从连接到退出的过程称为当前用户的会话。)全局级变量,该变量在整个会话过程中均起作用,类似的这种变量称为宿主变量。宿主变量在 PL/SQL 引用时要用“:变量名”引用。

代码演示:宿主常量

代码解析:

  • 可以使用 var 声明宿主变量。
  • PL/SQL 中访问宿主变量时要在变量前加“:”。
  • 在 SQL*Plus 中,使用 print 可以输出变量中的结果。

3. PL/SQL 数据类型

PL/SQL 的数据类型包括标量数据类型,引用数据类型和存储文本、图像、视频、声音等非结构化的大数据类型(LOB 数据类型)等。

(1) 标量数据类型

标量数据类型的变量只有一个值,且内部没有分量。标量数据类型包括数字型,字符型,日期型和布尔型。这些类型有的是 Oracle  SQL 中定义的数据类型,有的是 PL/SQL 自身附加的数据类型。字符型和数字型又有子类型,子类型只与限定的范围有关,比如 NUMBER 类型可以表示整数,也可以表示小数,而其子类型 POSITIVE 只表示正整数。

类型 说明
VARCHAR2(长度) 可变长度字符串,Oracle SQL 定义的数据类型,在 PL/SQL 中使用时最常 32767 字节。在 PL/SQL 中使用没有默认长度,因此必须指定。
NUMBER(精度,小数) Oracle SQL 定义的数据类型
DATE Oracle SQL 定义的日期类型
TIMESTAMP Oracle SQL 定义的日期类型
CHAR(长度) Oracle  SQL 定义的日期类型,固定长度字符,最长 32767 字节,默认长度是 1,如果内容不够用空格代替。
LONG Oracle SQL 定义的数据类型,变长字符串基本类型,最长 32760 字节。在 Oracle SQL 中最长 2147483647 字节。
BOOLEAN PL/SQL 附加的数据类型,逻辑值为 TRUE、FALSE、NULL
BINARY_INTEGER PL/SQL 附加的数据类型,介于-2^31 和 2^31 之间的整数。
PLS_INTEGER PL/SQL 附加的数据类型,介于 -2^31 和 2^31 之间的整数。类似于BINARY_INTEGER,只是 PLS_INTEGER 值上的运行速度更快。
NATURAL PL/SQL 附加的数据类型,BINARY_INTEGER 子类型,表示从 0 开始的自然数。
NATURALN 与 NATURAL 一样,只是要求 NATURALN 类型变量值不能为 NULL。
POSITIVE PL/SQL 附加的数据类型,BINARY_INTEGER 子类型,正整数。
POSITIVEN 与 POSITIVE 一样,只是要求 POSITIVE 的变量值不能为 NULL。
REAL Oracle SQL 定义的数据类型,18 位精度的浮点数
INT,INTEGER,SMALLINT Oracle SQL 定义的数据类型,NUMBERDE 的子类型,38 位精度整数。
SIGNTYPE PL/SQL 附加的数据类型,BINARY_INTEGER 子类型。值有:1、-1、0。
STRING 与 VARCHAR2 相同。

(2)属性数据类型

当声明一个变量的值是数据库中的一行或者是数据库中某列时,可以直接使用属性类型来声明。Oracle 中存在两种属性类型:%TYPE 和%ROWTYPE。

  • % ROWTYPE

引用数据库表中的一行作为数据类型,即 RECORD 类型(记录类型),是 PL/SQL 附加的数据类型。表示一条记录,就相当于 C#中的一个对象。可以使用“.”来访问记录中的属性。

代码解析:

  1. 声明一个 myemp 对象,该对象表示 EMP 表中的一行。
  2. 从 EMP 表中查询一条记录放入 myemp 对象中。
  3. 访问该对象的属性可以使用“.”。
  •  %TYPE

引用某个变量或者数据库的列的类型作为某变量的数据类型。

代码演示:%TYPE 应用

代码解析:

  • 定义变量 sal 为 emp 表中 sal 列的类型。
  • 定义 totalsal 是变量 mysal 的类型。

%TYPE 可以引用表中的某列作的类型为变量的数据类型,也可以引用某变量的类型作为新变量的数据类型。

4. PL/SQL 条件控制和循环控制

 PL/SQL 程序可通过条件或循环结构来控制命令执行的流程。PL/SQL 提供了丰富的流程控制语句,与 C#一样也有三种控制结构:

  • 顺序结构
  • 条件结构
  • 循环结构

(1)条件控制

C#中的条件控制使用关键字 if 和 switch。PL/SQL 中关于条件控制的关键字有 IF-THEN、IF-THEN-ELSE、IF-THEN-ELSIF 和多分支条件 CASE。

  •  IF-THEN

该结构先判断一个条件是否为 TRUE,条件成立则执行对应的语句块,与 C#中的 if 语句很相似,具体语法是:

C#中 if 语法 PL/SQL 中 IF 语法
if (条件){
       //条件结构体
}
IF 条件 THEN
        --条件结构体
END IF;

说明:

  1. 用 IF 关键字开始,END IF 关键字结束,注意 END IF 后面有一个分号。
  2. 条件部分可以不使用括号,但是必须以关键字 THEN 来标识条件结束,如果条件成立,则执行 THEN 后到对应 END IF 之间的语句块内容。如果条件不成立,则不执行条件语句块的内容。
  3. C#结构用一对大括号来包含条件结构体的内容。PL/SQL 中关键字 THEN 到 END IF 之间的内容是条件结构体内容。
  4. 条件可以使用关系运算符合逻辑运算符。

案例 1:查询 JAMES 的工资,如果大于 900 元,则发奖金 800 元。

代码演示:IF-THEN 应用


SQL> declare
      newSal emp.sal %TYPE;
begin 
      select sal into newSal from emp where ename='JAMES';
      if newSal>900 then
        update emp
        set comm=800
        where ename='JAMES';
       end if;
       commit;
end;
/

PL/SQL procedure successfully completed.

代码解析:

  1. 先判断条件,如果条件为 TRUE,则执行条件结构体内部的内容。
  2. 在 PL/SQL 块中可以使用事务控制语句,该 COMMIT 同时也能把 PL/SQL 块外没有提交的数据一并提交,使用时需要注意。
  •  IF-THEN-ELSE

语法格式:IF-THEN-ELSE

C#中 if 语法 PL/SQL 中 IF 语法
if (条件){
    //条件成立结构体
}
else{
    //条件不成立结构体
}
IF 条件 THEN
    --条件成立结构体
ELSE
    --条件不成立结构体
END IF;

语法解析:
把 ELSE 与 IF-THEN 连在一起使用,如果 IF 条件不成立则执行就会执行 ELSE 部分的语句。

案例 2:查询 JAMES 的工资,如果大于 900 元,则发奖金 800 元,否则发奖金 400 元。

代码演示:IF-THEN-ELSE 应用

SQL> declare
       newSal emp.sal %TYPE;
begin
       select sal into newSal from emp
       where ename='JAMES';
       if newSal>900 then
         update emp
         set comm=800
         where ename='JAMES';
        else
         update emp
         set comm=400
         where ename='JAMES';
        end if;
end;
/  

PL/SQL procedure successfully completed.
  • IF-THEN-ELSIF

语法格式:IF-THEN-ELSIF

C#中 if 语法 PL/SQL 中 IF 语法
if (条件 2){
    //条件成立结构体
}
else if(条件 2){
    //条件不成立结构体
}
else{
    //以上条件都不成立结构体
}
IF 条件 1 THEN
    --条件 1 成立结构体
ELSIF 条件 2 THEN
    --条件 2 成立结构体
ELSE
   --以上条件都不成立结构体
END IF;

语法解析:
PL/SQL 中的再次条件判断中使用关键字 ELSIF,而 C#使用 else if。

案例 3:查询 JAMES 的工资,如果大于 1500 元,则发放奖金 100 元,如果工作大于 900元,则发奖金 800 元,否则发奖金 400 元。

代码演示:IF-THEN-ELSIF 应用

SQL> declare
        newSal emp.sal % TYPE;
begin
        select sal into newSal from emp
        where  ename='JAMES';
        if newSal>1500 then
          update emp
          set comm=1000
          where ename='JAMES';
         elsif newSal>1500 then
           update emp
           set comm=800
           where ename='JAMES';
          else
            update emp
            set comm=400
            where ename='JAMES';
           end if;
end;
/

PL/SQL procedure successfully completed.
  • CASE

CASE 是一种选择结构的控制语句,可以根据条件从多个执行分支中选择相应的执行动作。也可以作为表达式使用,返回一个值。类似于 C#中的 switch 语句。语法是:

语法格式:CASE

CASE [selector]
WHEN 表达式 1 THEN 语句序列 1;
WHEN 表达式 2 THEN 语句序列 2;
WHEN 表达式 3 THEN 语句序列 3;
……
[ELSE 语句序列 N];
END CASE;

语法解析:
如果存在选择器 selector,选择器 selector 与 WHEN 后面的表达式匹配,匹配成功就执行 THEN 后面的语句。如果所有表达式都与 selector 不匹配,则执行 ELSE 后面的语句。

案例 4:输入一个字母 A、B、C 分别输出对应的级别信息。

代码演示:CASE 中存在 selector,不返回值

declare
           v_grade char(1):=upper('&p_grade');
begin
           case v_grade
             when 'A' then
               dbms_output.put_line('Excellent');
              when 'B' then
                dbms_output.put_line('Very Good');
               when 'C' then
                 dbms_output.put_line('Good');
                else
                  dbms_output.put_line('No such grade');
            end case;
end;

代码解析:

  • & grade 表示在运行时由键盘输入字符串到 grade 变量中。
  • v_grade 分别于 WHEN 后面的值匹配,如果成功就执行 WHEN 后的程序序列。

CASE 语句还可以作为表达式使用,返回一个值。

代码演示:CASE 中存在 selector,作为表达式使用

declare
 v_grade char(1):=upper('&grade');
 p_grade varchar(20);
begin
 p_grade := 
 case v_grade
   when 'A' then
     'Excellent'
    when 'B' then
     'Very Good'
    when 'C' then
     'Good'           
    else
      'No such grade'
  end;
  dbms_output.put_line('Grade:' || v_grade||',the result is ' || p_grade);
end;

代码解析:

  • CASE 语句可以返回一个结果给变量 p_grade

PL/SQL 还提供了搜索 CASE 语句。也就是说,不使用 CASE 中的选择器,直接在 WHEN后面判断条件,第一个条件为真时,执行对应 THEN 后面的语句序列。

代码演示:搜索 CASE

declare
  v_grade char(1):=upper('&grade');
  p_grade varchar(20);
begin
  p_grade :=
  case
    when v_grade='A' then
      'Excellent'
    when v_grade='B' then
      'Very Good'
      when v_grade='C' then
      'Good'
      else
        'No such grade'
  end;
  dbms_output.put_line('Grade:' ||v_grade||',the result is '||p_grade);
end;

(2)循环结构

PL/SQL提供了丰富的循环结构来重复执行一些列语句。 Oracle 提供的循环类型有:

  1. 无条件循环 LOOP -END LOOP 语句
  2. WHILE循环语句
  3. FOR循环语句

在上面的三类循环中 EXIT 用来强制结束循环,相当于 C#循环中的 break。

  •  LOOP 循环

LOOP 循环是最简单的循环,也称为无限循环,LOOP 和 END LOOP 是关键字。

语法格式:LOOP 循环

LOOP
--循环体
END LOOP;

语法格式:

  1. 循环体在 LOOP 和 END LOOP 之间,在每个 LOOP 循环体中,首先执行循环体中的语句序列,执行完后再重新开始执行。
  2. 在 LOOP 循环中可以使用 EXIT 或者[EXIT  WHEN 条件]的形式终止循环。否则该循环就是死循环。

案例 5:执行 1+2+3+…+100 的值

declare 
  counter number(3):=0;
  sumResult number:=0;
begin
  loop
    counter:=counter+1;
    sumResult:=sumResult+counter;
    if counter >= 100 then
      exit;
    end if;
   end loop;
     dbms_output.put_line('result is:'||to_char(sumResult));
end;

代码解析:

  1. LOOP 循环中可以使用 IF 结构嵌套 EXIT 关键字退出循环
  2. 注释行,该行可以代替①中的循环结构,WHEN 后面的条件成立时跳出循环。
  • WHILE 循环

与 C#中的 while 循环很类似。先判断条件,条件成立再执行循环体。

语法格式:WHILE

C#中 while 语法 PL/SQL 中 WHILE 语法
while (条件){
    //循环体体
}
WHILE 条件 LOOP
    --循环体
END LOOP;

案例 6:WHILE 循环

代码演示:WHILE 循环

declare
  counter number(3):=0;
  sumResult number:=0;
begin
  while counter<100 loop
    counter := counter+1;
    sumResult := sumResult+counter;
  end loop;
    dbms_output.put_line('result is:' || sumResult);
end;
  • FOR 循环

FOR 循环需要预先确定的循环次数,可通过给循环变量指定下限和上限来确定循环运行的次数,然后循环变量在每次循环中递增(或者递减)。FOR 循环的语法是:

语法格式:FOR 循环

FOR 循环变量 IN [REVERSE] 循环下限..循环上限 LOOP LOOP
--循环体
END LOOP;

语法解析:
循环变量:该变量的值每次循环根据上下限的 REVERSE 关键字进行加 1 或者减 1。

REVERSE:指明循环从上限向下限依次循环。

案例 7:FOR 循环

代码演示:FOR 循环

declare
  counter number(3):=0;
  sumResult number:=0;
begin
  for counter in 1..100 loop
    sumResult:=sumResult+counter;
  end loop;
    dbms_output.put_line('result is:'||sumResult);
end;

(3)顺序结构

  • 顺序结构

在程序顺序结构中有两个特殊的语句。GOTO 和 NULL

  • GOTO 语句

GOTO 语句将无条件的跳转到标签指定的语句去执行。标签是用双尖括号括起来的标示符,在 PL/SQL 块中必须具有唯一的名称,标签后必须紧跟可执行语句或者 PL/SQL 块。GOTO 不能跳转到 IF 语句、CASE 语句、LOOP 语句、或者子块中。

  • NULL 语句

NULL 语句什么都不做,只是将控制权转到下一行语句。NULL 语句是可执行语句。NULL语句在 IF 或者其他语句语法要求至少需要一条可执行语句,但又不需要具体操作的地方。比如 GOTO 的目标地方不需要执行任何语句时。

案例 8:GOGO 和 NULL

代码演示:GOTO 和 NULL

declare
  sumsal emp.sal%type;
begin
  select sum(sal) into sumsal from emp;
  if sumsal>20000 then
    goto first_label;
  else
    goto second_label;
  end if;
  <<first_label>>
  dbms_output.put_line('above 20000:' || sumsal);
  <<second_label>>
  null;
end;

代码解析:

  • 跳转到程序 first_label 位置,就是②的位置,first_label 是一个标签,用两个尖括号包含。
  • 无条件跳转到 sedond_label 位置,就是④的位置。④处不执行任何内容,因此是一个 NULL 语句。

与 C#一样,在 PL/SQL 中,各种循环之间可以相互嵌套。

5. PL/SQL 中劢态执行 SQL 语句

在 PL/SQL 程序开发中,可以使用 DML 语句和事务控制语句,但是还有很多语句(比如DDL 语句)不能直接在 PL/SQL 中执行。这些语句可以使用动态 SQL 来实现。

PL/SQL 块先编译然后再执行,动态 SQL 语句在编译时不能确定,只有在程序执行时把SQL 语句作为字符串的形式由动态 SQL 命令来执行。在编译阶段 SQL 语句作为字符串存在,程序不会对字符串中的内容进行编译,在运行阶段再对字符串中的 SQL 语句进行编译和执行,动态 SQL 的语法是:

语法格式:动态 SQL

EXECUTE IMMEDIATE 动态语句字符串
[INTO 变量列表]
[USING 参数列表]

语法解析:
如果动态语句是 SELECT 语句,可以把查询的结果保存到 INTO 后面的变量中。如果动态语句中存在参数,USING 为语句中的参数传值。
动态 SQL 中的参数格式是:[:参数名],参数在运行时需要使用 USING 传值。

案例 9:动态 SQL

代码演示:动态 SQL

declare
  sql_stmt varchar2(200);  --动态sql
  emp_id   number(4):=7566;
  salary   number(7,2);
  dept_id  number(2):=90;
  dept_name varchar2(14):='personnel';
  location varchar2(13):='dallas';
  emp_rec  emp%rowtype;
begin
  --无子句execute immediate
  execute immediate 'create table bonusl (id number,amt number)';
  -- using子句execute immediate 
  sql_stmt:='insert into dept values(:1,:2,:3)';
  execute immediate sql_stmt using dept_id,dept_name,location;
  --into子句的execute immediate 
  sql_stmt:='select * from emp where empno=:id';
  execute immediate sql_stmt into emp_rec using emp_id;
  --returning into 子句的execute immediate 
  sql_stmt:='update emp set sal=2000 where empno=:1
    returning sal into :2';
  execute immediate sql_stmt using emp_id returning into salary;
  execute immediate 'delete from dept where deptno=:num'
    using dept_id;
end;

代码解析:

  1. 动态执行一个完整的 SQL 语句。
  2. SQL 语句中存在 3 个参数分别标识为:[:1、:2、:3],因此需要用 USING 关键字对三个参数分别赋值。
  3. 对动态查询语句可以使用 INTO 子句把查询的结果保存到一个变量中,要求该结果只能是单行。
  4. 在 Oracle 的 insert,update,delete 语句都可以使用 RETURNING 子句把操作影响的行中的数据返回,对 SQL 语句中存在 RETURNING 子句时,在动态执行时可以使用RETURNING INTO 来接收。
  5. 动态执行参数中可以是:[:数字]也可以是[:字符串]。

6. PL/SQL 的异常处理

在程序运行时出现的错误,称为异常。发生异常后,语句将停止执行,PL/SQL 引擎立即将控制权转到 PL/SQL 块的异常处理部分。异常处理机制简化了代码中的错误检测。PL/SQL中任何异常出现时,每一个异常都对应一个异常码和异常信息。比如:

(1)预定义异常

为了 Oracle 开发和维护的方便,在 Oracle 异常中,为常见的异常码定义了对应的异常名称,称为预定义异常,常见的预定义异常有:

异常名称 异常码 描述
DUP_VAL_ON_INDEX ORA-00001 试图向唯一索引列插入重复值
INVALID_CURSOR ORA-01001 试图进行非法游标操作。
INVALID_NUMBER ORA-01722 试图将字符串转换为数字
NO_DATA_FOUND ORA-01403 SELECT INTO 语句中没有返回任何记录。
TOO_MANY_ROWS ORA-01422 SELECT INTO 语句中返回多于 1 条记录。
ZERO_DIVIDE ORA-01476 试图用 0 作为除数。
CURSOR_ALREADY_OPEN ORA-06511 试图打开一个已经打开的游标

PL/SQL 中用 EXCEPTION 关键字开始异常处理。具体语法是:

语法格式:异常处理

BEGIN
  --可执行部分
  EXCEPTION -- 异常处理开始
  WHEN 异常名 1 THEN
    --对应异常处理
  WHEN 异常名 2 THEN
    --对应异常处理
  ……
  WHEN OTHERS THEN
    --其他异常处理
  END;

语法解析:
异常发生时,进入异常处理部分,具体的异常与若干个 WHEN 子句中指明的异常名匹配,匹配成功就进入对应的异常处理部分,如果对应不成功,则进入 OTHERS 进行处理。

案例 10:异常处理

declare
    newSal emp.sal%type;
begin
    select sal into newSal from emp;
exception  
    when too_many_rows then
      dbms_output.put_line('rows too much!!!');
    when others then
      dbms_output.put_line('unknow exception!!');
end;
/

(2)自定义异常

除了预定义异常外,用户还可以在开发中自定义异常,自定义异常可以让用户采用与PL/SQL 引擎处理错误相同的方式进行处理,用户自定义异常的两个关键点:

  • 异常定义:在 PL/SQL 块的声明部分采用 EXCEPTION 关键字声明异常,定义方法与定义变量相同。比如声明一个 myexception 异常方法是:myexception EXCEPTION;
  • 异常引发:在程序可执行区域,使用 RAISE 关键字进行引发。比如引发 myexception方法是:RAISE myexception;

案例 11:自定义异常

代码演示:自定义异常

declare
 sal emp.sal%type;
 myexp exception;
begin
  select sal into sal from emp where ename='JAMES';
  if sal<5000 then
    raise myexp;
  end if;
exception 
  when no_data_found then
    dbms_output.put_line('No recordset find!');
  when myexp then
    dbms_output.put_line('sal is to less!');
end;
/

代码解析:

  1. 用 EXCEPTION 定义一个异常变量 myexp
  2. 在一定条件下用 RAISE 引发异常 myexp
  3.  在异常处理部分,捕获异常,如果不处理异常,该异常就抛给程序执行者。

(3) 引发应用程序异常

在 Oracle 开发中,遇到的系统异常都有对应的异常码,在应用系统开发中,用户自定义的异常也可以指定一个异常码和异常信息,Oracle 系统为用户预留了自定义异常码,其范围介于-20000 到-20999 之间的负整数。引发应用程序异常的语法是:
RAISE_APPLICATION_ERROR(异常码,异常信息)

案例 12:引发系统异常

代码演示:引发应用系统异常

代码解析:

  1. 引发应用系统异常,指明异常码和异常信息。
  2. 在控制台上显示异常码和异常信息。

如果要处理未命名的内部异常,必须使用 OTHERS 异常处理器。也可以利用 PRAGMA EXCEPTION_INIT 把一个异常码与异常名绑定。

PRAGMA 由编译器控制,PRAGMA 在编译时处理,而不是在运行时处理。EXCEPTION_INIT告诉编译器将异常名与 ORACLE 错误码绑定起来,这样可以通过异常名引用任意的内部异常,并且可以通过异常名为异常编写适当的异常处理器。PRAGMA EXCEPTION_INIT 的语法是:PRAGMA EXCEPTION_INIT(异常名,异常码)

这里的异常码可以是用户自定义的异常码,也可以是 Oracle 系统的异常码。

案例 13:PRAGMA EXCEPTION_INIT 异常

代码演示:PRAGMA EXCEPTION_INIT 异常

<<outterseg>>
DECLARE
  null_salary EXCEPTION;
  PRAGMA EXCEPTION_INIT(null_salary, -20101); 
BEGIN
  <<innerStart>>
  DECLARE
   curr_comm NUMBER;
  BEGIN
     SELECT comm INTO curr_comm FROM emp WHERE empno = &empno;
     IF curr_comm IS NULL THEN
        RAISE_APPLICATION_ERROR(-20101, 'Salary is missing'); 
     ELSE
        dbms_output.put_line('有津贴');
     END IF;
  END;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
       dbms_output.put_line('没有发现行');
  WHEN null_salary THEN
       dbms_output.put_line('津贴未知'); 
  WHEN OTHERS THEN
       dbms_output.put_line('未知异常');
END;
/

代码解析:

  1. 把异常名称 null_salary 与异常码-20101 关联,该语句由于是预编译语句,必须放在声明部分。也就是说-20101 的异常名称就是 null_salary。
  2. 嵌套 PL/SQL 语句块
  3. 在内部 PL/SQL 语句块中引发应用系统异常-20101。
  4. 在外部的 PL/SQL 语句块中就可以用异常名 null_salary 进行捕获。

猜你喜欢

转载自blog.csdn.net/ageeklet/article/details/82665943