第7章 PL/SQL基础

target

掌握什么是pl/sql
掌握pl/sql的变量
掌握pl/sql的表达式
掌握pl/sql的结构控制
了解pl/sql中的异常
掌握pl/sql函数的编写

1. 什么是PL/SQL

PL/SQL的使用几乎贯穿了整个Oracle的学习过程,也是作为一个初级开发人员必须掌握 的重要知识点。

1.1 认识 PL/SQL

结构化査询语言(Structured Query Language, SQL)是用来访问和操作关系型数据库的 一种标准通用语言,它属于第四代语言(4GL),简单易学,使用它可以很方便地调用相应语句来取得结果。该语言的特点就是非过程化。也就是说,使用的时候不用指明执行的具体方法和途径,即不用关注任何的实现细节。但这种语言也有一个问题,就是在某些情况下满足不了 复杂业务流程的需求,这就是第四代语言的不足之处。

Oracle中的PL/SQL语言正是为了解决这一问题,PL/SQL属于第三代的语言(3GL),也就是过程化的语言,同Java、C#一样可以关注细节,用它可以实现复杂的业务逻辑,是数据库开发人员的利器。

PL/SQL (Procedural Language/Structured Query Language)是Oracle公司在标准SQL语言 基础上进行扩展而形成的一种可以在数据库上进行设计编程的语言。PL/SQL完全可以像Java语言一样实现逻辑判断、条件循环以及异常处理等,这是标准的SQL很难办到的事情。由于它的基础是标准的SQL语句,这就使得数据库开发人员能快速 地掌握并运用。总的来说,PL/SQL有 以下几个特点:

  • 支持事务控制和SQL数据操作命令。
  • 它支持SQL的所有数据类型,并且在此基础上扩展了新的数据类型,也支持SQL的函数以及运算符。
  • PL/SQL可以存储在Oracle服务器中。
  • 服务器上的PL/SQL程序可以使用权限进行控制。
  • Oracle有自己的DBMS包,可以处理数据的控制和定义命令。

1.2 PL/SQL的优势

(1) 可以提高程序的运行性能

标准的SQL被执行时,只能一条一条地向Oracle服务器发送。假如完成一个业务逻辑需要几条甚至几十条SQL语句,那么在这个过程中,客户端会几十次地连接数据库服务器,而连接数 据库本身是一个很耗费资源的过程,当这个业务被完成时,会浪费大最的资源在网络连接上。

如果此时换用PL/SQL语句,结果则不一样了。PL/SQL的语句块可以包含多条SQL语句, 而语句块可以嵌入到程序中,甚至可以存储到Oracle服务器上。这样用户只需要连接一次数据。

(2) 可以使程序模块化

在程序块中可以实现一个或几个功能。

例如,当想把一个动物的模型存到数据库里时,可能涉及几张表,如果使用标准的SQL完成该功能需要多条语句。而如果使用块,则可以把对多张表的操作都放到一个块内,而对外只提供一个调用方式和需要传入的参数。

使用块也可以把数据库数据同客户程序隔离开来,使得数据库表结构发生变化时,对调用者的影响减小到最低程度。

(3) 可以采用逻辑控制语句来控制程序结构

如果一个PL/SQL程序块中只能顺序地执行基本的SQL语句,那么它的意义实在有限。而实际当中PL/SQL可以利用条件或循环语句来控制程序的流程,这么做就大大地增加了PL/SQL 的实用性,我们可以利用逻辑控制语句完成复杂的普通SQL语句完成不了的业务。

(4) 利用处理运行时的错误信息

标准的SQL在遇到错误时会提示异常。一旦有异常就会终止,但是调用者 却很难快速地发现错误点在哪儿,即使发现出问题的地方也只能是告诉开发人员该语句程序本身有问题,而不是逻辑上有问题。

例如,在产品表里増加数据时,数量只是要求数值型,并没有更细的要求。假如增加的数据中该字段部分是一个负数,正常来说是可以进入数据库的,但 这在逻辑上是不允许的,因为没有数量为负的产品,而利用PL/SQL就可以完全避免类似的问题,我们可以利用流程拒绝这部分记录进入数据库。

利用PL/SQL还可以处理一些程序上的异常,不至于因终止SQL操作,而造成调用SQL的 展示页面出现生硬的错误提示。

(5) 良好的可移植性

PL/SQL可以成功地运行到不同的服务器中。例如,从Windows的数据库服务器下移植到 Linux的数据库服务器下。也可把PL/SQL从一个Oracle版本移植到其他版本的Oracle中。

1.3 PL/SQL 的结构

PL/SQL程序的基本单位是块(block),PL/SQL块很明确地分三部分,其中包括声明部分、执行部分和异常处理部分。

其中,声明部分以DECLARE作为开始标志,执行部分用BEGIN作为开始标志,而异常处理部分则以EXCEPTION为开始标志。其中的执行部分是必需 的,而其余的两个部分则可选。

需要记住:无论PL/SQL程序段的代码量有多少,它的基本结构只是由这三部分组成。

(1) 只有执行体

begin
    dbms_output.put_line('Hello World!');
end;
/

运行效果:

SQL> set serveroutput on;
SQL> begin
  2  dbms_output.put_line('Hello World!');
  3  end;
  4  /
Hello World!

PL/SQL 过程已成功完成。

注意:需要加上 set serveroutput on,作用是打开输出功能,否则看不到输出语句。

(2) 包含声明和执行体两部分的结构

DECLARE
    v_result NUMBER(8,2);     --声明变量
BEGIN
    v_result := 100/6;        --为变量赋值
    DBMS_OUTPUT.PUT_LINE('结果是:' || v_result);
END;
/

(3) 包含声明、执行体和异常部分的结构

:将从产品表productInfo中査询产品类型“手机”对应的产品编码,并把该编码存储到变量中,最后输出到屏幕。脚本如下:

DECLARE 
    v_productid VARCHAR2(12);
BEGIN
    SELECT productId
    INTO v_productid
    FROM productInfo
    WHERE category='手机';
    DBMS_OUTPUT.PUT_LINE('手机对应的编码是:' || v_productid);
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('没有对应的编码!');
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('对应数据过多,请确认!');
END;
/

1.4 PL/SQL的基本规则

(1) PL/SQL必须遵守的要求:

  • 标识符不区分大小写。例如,TEST同Test、test是一样的。所有的名称在存储时都被修改成大写
  • 标识符中只允许字母、数字、下划线,并且以字母开头。
  • 标识符最多30个字符。
  • 不能使用保留字。如与保留字同名必须使用双引号括起来。
  • 语句使用分号结束。即使多条语句在同一行,只要它们都正常结束,那么就没有问题。 而且在语句块的结束标志END后面同样需要使用分号。
  • 语句的关键词、标识符、字段的名称以及表的名称等都需要空格的分隔。
  • 字符类型和日期类型需要使用单引号括起。

(2) 以下是为了增强代码的阐读性的相关建议,这些不是必须要遵守的

  • 每行只写一条语句。
  • 全部的保留字、Oracle的内置函数、程序包以及用户定义的数据类型都用大写。
  • 所有的过程名称大写。
  • 所有的变量以及自建的过程或游标、触发器名称都要使用有意义的名称命名。
  • 命名应以"_"的连接方式,而不是用大小写混合的方式(如果只为了方便自己的阅读,可以使用大小写混合)。
  • 变量前最好加上前缀,以表示该变量的数据类型、作用范围等。
  • 每个变量都应加上注释。
  • 在重要的程序段处都应加上注释。
  • 建议3个半角空格替代TAB键进行缩进。
  • 逗号后面以及操作符的前后都应加空格。

以上只是比较基本的规则,可以提高代码的可读性,在企业的毎个项目小组中会根据实际的情况做出更细的要求,甚至形成规范文档。在日常开发中应注意这些规范,形成良好的编程习惯。

1.5 PL/SQ L中的注释

Oracle为使用者提供了 两种注释方式,它们分别是:

  • 单行注释:使用”--“两个短划线,可以注释掉后面的语句。

  • 多行注释:使用"/*...*/”,可以注释掉这两部分包含的部分。

2. PL/SQL变量的使用

2.1 变量

变量指的就是可变化的量,程序运行过程中可以随时改变其数据存储结构

标准语法格式:

<变量名><数据类型>[(长度):=<初始值>]

declare
    v_name varchar2(100):='JACK';     --定义的一个name变量,并且赋予初始值
begin 
  v_name:='张三';
  dbms_output.put_line('name变量的值为:' || v_name);
end;

2.2 常量

常量指的是不会变化的量,例如一年四个季度,9月有30天,圆周率等

语法格式:

<常量名>constant<数据类型>:=<常量值>;

declare
    september_day constant integer:=30; --定义的一个september_day常量
begin 
    -- september_day:=29;  不能赋值,否则标错
    dbms_output.put_line('september_day变量的值为:'||september_day);
end;

2.3 复合类型的变量

所谓复合类型的变量,就是每变量包含几个元素,可以存储多个值。

最常用的是三种类型,一种是记录类型,一种是索引表类型,还有一种是VARRAY数组。

(1) 记录类型

① 第一种方式:

declare 
type product_rec is record
(
    v_productid productinfo.productid%TYPE,         --产品ID
    v_productname varchar(20),                      --产品名称
    v_productprice number(8,2)                      --价格
);

v_product product_rec;                              --声明记录类型变量
begin
    select productid,productname,productprice
    into v_product
    from productinfo
    where productid = '1';
    
    dbms_output.put_line('productid = '    ||  v_product.v_productid);
    dbms_output.put_line('productname = '  ||  v_product.v_productname);
    dbms_output.put_line('productprice = ' ||  v_product.v_productprice);
end;
/

输出:

productid = 1
productname = 华为Mate40Pro
productprice = 7999

PL/SQL 过程已成功完成。

注意:

%TYPE就是直接引用productinfo表中productid的类型。

② 利用%ROWTYPE声明记录类型数据

%ROWTYPE是直接引用表中的行作为变量类型。

declare
    v_product productinfo%ROWTYPE;
begin
    select * 
    into v_product
    from productinfo
    where productid = '2';
    
    dbms_output.put_line('productid = '     ||  v_product.productid);
    dbms_output.put_line('productname = '   ||  v_product.productname);
    dbms_output.put_line('productprice = '  ||  v_product.productprice);
end;
/

输出:

productid = 2
productname = 华为P40Pro
productprice = 6999

PL/SQL 过程已成功完成。

(2) 索引表类型

该类型和数组相似,它利用键值査找对应的值。这里键值同真正数组的下标不同,索引表中下标允许使用字符串。数组的长度不是固定值,它可以根据需要自动增长。

① 数字为键值的索引表

declare 
    type prod_tab_fst is table of productinfo%ROWTYPE        --定义索引表,表中元素是productinfo的行记录
        index by binary_integer;
    type prodt_tab_sec is table of varchar(30)               --定义索引表,表中记录是字符型
        index by pls_integer;
    
    v_prt_row prod_tab_fst;                                 --声明变量v_prt_row
    v_prt prodt_tab_sec;                                    --声明变量v_prt
    
begin
    
    select * 
    into v_prt_row(1)
    from productinfo
    where productid = '3';
    
    dbms_output.put_line('v_prt_row(1)=' || v_prt_row(1).productid 
                                         || '---'
                                         || v_prt_row(1).productname) ;
                                                                 
  select productname 
    into v_prt(1)
    from productinfo
    where productid = '3';
                                                                 
  dbms_output.put_line('v_prt(1)='  || v_prt(1)) ;

end;
/

输出:

v_prt_row(1)=3---华为荣耀10
v_prt(1)=华为荣耀10

PL/SQL 过程已成功完成。

补充:
Binary_Integer类型变量值计算是由Oracle来执行,不会出现溢出,但是执行速度较慢,因为它是由Oracle模拟执行。而Pls_Integer的执行是由硬件即直接由CPU来运算,因而会出现溢出,但其执行速度较前者快许多。
② 字符串为键值的索引表

declare 
    type prodt_tab_thd is table of number(8)        --定义number类型的索引表
        index by varchar2(20);
        
    v_prt_chr prodt_tab_thd;                        --声明v_prt_chr变量
    
begin
    v_prt_chr('test') :=123;                         --为变量赋值
    v_prt_chr('test2') :=0;                          --为变量赋值
    
    dbms_output.put_line('v_prt_chr(test) = '  || v_prt_chr('test'));
    dbms_output.put_line('v_prt_chr(test2) = ' || v_prt_chr('test2'));
    dbms_output.put_line('第一个键 = '    || v_prt_chr.first);
    dbms_output.put_line('最后一个键 = '  || v_prt_chr.last);

end;
/

输出:

v_prt_chr(test) = 123
v_prt_chr(test2) = 0
第一个键 = test
最后一个键 = test2

PL/SQL 过程已成功完成。

(3) varray变长数组

该类型的元素个数是需要限制的,它是一个存储有序元素的集合。集合下标从1开始,比较适合较少的数据使用。

declare 
    --定义varray数组,名称为varr,长度为100,元素为varchar2类型
    type varr is varray(100) of varchar2(20);    
    
    --声明变量,变量名为v_product,类型是varr,该类型被初始化了两个元素(最多可初始化100个)。也就是只能向下标为1和2的位置赋值
    v_product varr := varr('1','2');     
    
begin
    v_product(1) := 'THIS IS A';
    v_product(2) := 'TEST';
    
    dbms_output.put_line('productid = ' || v_product(1));
    dbms_output.put_line('productid = ' || v_product(2));

end;
/

输出:

productid = THIS IS A
productid = TEST

PL/SQL 过程已成功完成。

3. 表达式

3.1 数值表达式

算数运算符有:加号(+)、减号(-)、乘号(*)、除号(/)、乘方(**)

:计算 (58+25*3)的平方根

declare 
    v_result number(10,4);
begin
    v_result := sqrt(58+25*3);
    dbms_output.put_line('结果为:' ||  v_result);
end;
/

输出:

结果为:11.5326

PL/SQL 过程已成功完成。

3.2 关系表达式

由关系运算符连接起来的字符或数值称为关系表达式。其中关系运算符主要有以下几种:

  • 等于号=
  • 小于号<
  • 大于号>
  • 小于等于号<=
  • 大于等于>=
  • 不等于号 !=和 <>。

关系表达式返回的结果是布尔类型值。

3.3 逻辑表达式

  • 逻辑非 not
  • 逻辑或 or
  • 逻辑与 and

4. PL/SQL结构控制

4.1 IF条件控制语句

(1) IF结构

当条件为TRUE的时候程序会执行IF语句对应的statements。

需要注意的是,IF后面有 关键词“THEN”,这一点和其他编程语言中的IF语句不一样。

declare 
    v_result number(8,4);
begin
    v_result := sqrt(101);
    if v_result > 10 then
        dbms_output.put_line('结果是:' || v_result);
    end if;
    dbms_output.put_line('if语句执行完毕!');
end;
/

输出:

结果是:10.0499
if语句执行完毕!

PL/SQL 过程已成功完成。

(2) IF...ELSE...结构

该类型的结构表示不是选A就是选B。

该结构表示要么执行IF后面的语句,要么执行ELSE后面的语句,是二选一的模式。该结构执行完成后,程序会继续向后执行。

declare 
    v_if_con number(10);
begin
    v_if_con :=10;
    if v_if_con > 20 then
        dbms_output.put_line('是个大于20的数字!');
    else
        dbms_output.put_line('是个小于20的数字!');
    end if;
        dbms_output.put_line('if...else语句执行完毕!');
end;
/

输出:

是个小于20的数字!
if...else语句执行完毕!

PL/SQL 过程已成功完成。

(3) IF...ELSIF...结构

该结构是前面两种使用方式的综合,它可以提供多个IF条件选择,当程序执行到该结构部分时,它会对每一个条件进行判断,一旦条件为TRUE,程序会执行对应的语句,而后继续判断下一个条件,直到所有条件判断完成。

语法:

if 条件1 then
    语句1;
elsif 条件2 then
    语句2;
...
[else 
    语句n;]
end if;

:成一个100~200的随机数,然后利用IF语句判断该' 随机数所在范围

declare 
    v_result number(10);
begin
    v_result := dbms_random.value(100,200);
    if v_result > 100 and v_result < 150 then
        dbms_output.put_line('在100到150区间内!');
    elsif v_result > 150 and v_result < 180 then 
        dbms_output.put_line('在150到180区间内!');
    elsif v_result > 180 and v_result < 200 then 
        dbms_output.put_line('在150到180区间内!');
    else
        dbms_output.put_line('是一个边缘值!');
    end if;
        dbms_output.put_line('值为:' || v_result);
end;
/       

输出:

在150到180区间内!
值为:159

PL/SQL 过程已成功完成。

(4) 嵌套使用IF语句

IF语句可以嵌套使用,这使得判断的条件更加精细。
:首先判断产品表中产品的价格范围,并做出提示,然后在价格范围的基础上进行产品数量的判断,并给出产品数量是否满足需求的提示。

declare 
    V_product productinfo%rowtype;                                      --声明变量
begin
    select * 
    into V_product
    from productinfo
    where productid='1';
    
    dbms_output.put_line('产品价格:' || V_product.productprice);
    dbms_output.put_line('产品数量:' || V_product.quantity);
    
    if V_product.productprice > 5000 then                               --高端产品
        dbms_output.put_line('该产品属于高端产品!');
        if V_product.quantity > 50 then
            dbms_output.put_line('该产品库存高于50,不缺货!');
        else
            dbms_output.put_line('该产品库存低于50,缺货!');
        end if;
    elsif V_product.productprice<5000 and V_product.productprice>= 3000 then   --中端产品
        dbms_output.put_line('该产品属于中端产品!');
        if V_product.quantity > 80 then
            dbms_output.put_line('该产品库存高于80,不缺货!');
        else
            dbms_output.put_line('该产品库存低于80,缺货!');
        end if;
    else
        dbms_output.put_line('该产品属于低端产品!');                       --低端产品
        if V_product.quantity > 100 then
            dbms_output.put_line('该产品库存高于100,不缺货!');
        else
            dbms_output.put_line('该产品库存低于100,缺货!');
        end if;
    end if;
end;
/

输出:

产品价格:7999
产品数量:12
该产品属于高端产品!
该产品库存低于50,缺货!

PL/SQL 过程已成功完成。

4.2 CASE条件控制语句

CASE语句同IF语句类似,也是根据条件选择对应的语句执行。
CASE语句可以分为以下两种类型:

(1) 简单的CASE语句。

它给出一个表达式,并把表达式结果同提供的几个可预见的结果做比较,如果比较成功,则执行对应的语句序列。

:查询员工所在的部门地址

declare 
    v_deptno varchar2(10);
begin
    select deptno into v_deptno
    from emp
    where empno='7839';
    
    case v_deptno
    when '10' then
        dbms_output.put_line('NEW YORK');
    when '20' then
        dbms_output.put_line('DALLAS');
    when '30' then
        dbms_output.put_line('CHICAGO');
    when '40' then
        dbms_output.put_line('BOSTON');
    else
        dbms_output.put_line('没有该部门!');
    end case;
end;
/

输出:

NEW YORKPL/SQL 过程已成功完成。

(2) 搜索式的CASE语句

它会提供多个布尔表达式,然后选择第一个为TRUE的表达式,执行对应的脚本。一旦确立了第一个true,后面的布尔表达式将不执行。

declare 
    V_productprice number(8,2);                                  --声明变量
begin
    select productprice
    into V_productprice
    from productinfo
    where productid='1';
    
    dbms_output.put_line('产品价格:' || V_productprice);
    
    case
    when V_productprice > 5000 then                               --高端产品
        dbms_output.put_line('该产品属于高端产品!');
    when V_productprice < 5000 and V_productprice >= 3000 then    --中端产品
        dbms_output.put_line('该产品属于中端产品!');
    when V_productprice < 3000 and V_productprice > 0 then
        dbms_output.put_line('该产品属于低端产品!');                 --低端产品
    else
        dbms_output.put_line('价格错误!');                         --价格有误
    end case;
end;
/

输出:

产品价格:7999该产品属于高端产品!

PL/SQL 过程已成功完成。

4.3 LOOP循环控制语句

LOOP语句也叫循环语句,它能让我们重复地执行指定的语句块。LOOP语句有以下四种形式:

  • LOOP,
  • WHILE...LOOP,
  • FOR...LOOP,
  • CURSOR FOR LOOP.

(1) 基本的LOOP

该形式的语句属于LOOP循环控制语句中最基本的结构, 它会重复不断地执行LOOPEND LOOP之间的语句序列。 由于基本的LOOP语句本身没有包含中断循环的条件,所以 通常情况下都是和其他的条件控制语句一起使用,利用exitGOTO等可以中断LOOP循环。当然,异常也能使LOOP语句 中断。

:初值为1的变量,每循环一次加1,直到大于5,退出循环

declare 
    v_num number(8) :=1;
begin
    <>                           --定义一个loop标签(名字随便起)
    loop
        dbms_output.put_line('当前值:' || v_num);
        v_num := v_num + 1;
        if v_num > 5 then 
            dbms_output.put_line('退出!当前值:' || v_num);
            exit base_loop;
        end if;
    end loop;
end;
/

输出:

当前值:1
当前值:2
当前值:3
当前值:4
当前值:5
退出!当前值:6

PL/SQL 过程已成功完成。

还可以写成:

declare 
    v_num number(8) :=1;
begin
    <>               --定义一个loop标签(名字随便起)
    loop
        dbms_output.put_line('当前值:' || v_num);
        v_num := v_num + 1;
        exit base_loop when v_num > 5;  
    end loop;
    dbms_output.put_line('退出!当前值:' || v_num);
end;
/

(2) WHILE.. LOOP语句

WHILE-LOOP结构的语句本身可以终止LOOP循 环,当WHILE后面的布尔表达式为TRUE时,LOOP和 END LOOP之间的语句集将执行一次,而后会重新判断 WHILE后面的表达式是否为TRUE。

:输出20以内能被3整除的数

declare
    v_num number(8) :=1;
begin
    dbms_output.put_line('当前值:' || v_num);
    
    while v_num < 20
        loop 
            if mod(v_num,3)=0 then
                dbms_output.put_line(v_num || '可以被3整除');
            end if;
            
            v_num := v_num + 1;
        end loop;
        
        dbms_output.put_line('退出!当前值为:' || v_num);
end;
/

输出:

当前值:1
3可以被3整除
6可以被3整除
9可以被3整除
12可以被3整除
15可以被3整除
18可以被3整除
退出!当前值为:20

(3) FOR...LOOP

FOR...LOOP语句循环遍历指定范围内的整数。该范围被FOR和LOOP关键词封闭。当第一次进入循环时,其循环范围会被确定,并且以后不会再次计算。每循环一次,其循环次数将会增加1。

:列出1-20之间所有数字之和

declare 
    v_sum number(8) :=0;
begin
    dbms_output.put_line('1-20整数之和为:');
    <>                                    --loop标记(可以省略)
    for inx in 1..20 loop
        v_sum := v_sum + inx;
    end loop;
    
    dbms_output.put_line(v_num);
end;
/

输出:

1-20整数之和为:
210

PL/SQL 过程已成功完成。

5. PL/SQL中的异常

5.1 什么是异常

PL/SQL运行过程中有可能会出现错误,这些错误有的来自程序本身,也有的来自开发人 员自定义的数据,而所有的这些错误我们称之为异常(编译时的错误不能称为异常)。

:演示异常

declare
    v_result number(10);
begin
    v_result := 1/0;
    dbms_output.put_line('结果是:' || v_result);
end;
/

输出:

ORA-01476: 除数为 0
ORA-06512: 在 line 4

其中,ORA-01476是错误号,除数为 0为错误内容。

5.2 异常的种类

Oracle中的异常可以分为三类:

  • 预定义异常
  • 非预定义异常
  • 自定义异常

(1) 预定义异常

Oracle提供了一些已经定义好名称的常用异常,这就是预定义异常。常用的预处理异常如下:

exception ORA Error 触发条件
case_not_found ORA-06592 case语句中,when没有匹配的条件,而且没有else,会触发该异常
no_data_found ORA-01403 select..into语句没有返回记录,触发该异常
too_many_rows ORA-01422 select..into语句返回记录多于1条,触发该异常
dup_val_on_index ORA-00001 表中唯一索引所对应的列上出现重复值
value_error ORA-06502 赋值时,长度不够会触发该异常
zero_divide ORA-01476 除数为零
storage_error ORA-06500 内存溢出或破坏引发的异常
timeout_on_resource ORA-00051 等待资源超时引发的异常
cursor_already_open ORA-06511 打开一个已经打开的游标引发的异常

declare
    v_result number(10);
begin
    v_result := 1/0;
    dbms_output.put_line('结果是:' || v_result);
exception
    when zero_divide then
    dbms_output.put_line('除数是零' );
end;
/

输出:

除数是零

PL/SQL 过程已成功完成。

(2) 非预定义异常

Oracle中的异常更多都是非预定义异常。也就是说,它们只有错误编号和相关的错误描述, 而没有名称的异常是不能被捕捉的。为了解决该问题,Oracle允许开发人员为这样的异常添加 一个名称,使得它们能够被异常处理模块捕捉到。
为一个非预定义异常定义名称需要如下两步:
① 声明一个异常的名称。
② 把这个名称和异常的编号相互关联。

Oracle处理预定义异常和非预定义异常并没有区别.

由于员工表emp中的部门编号引用了部门表dept的编码,表emp中可以使用外键约束。这时如果直接修改emp表中部门编号,就有可能导致ORA-02291错误。

declare 
    my_2291_exp Exception;
    pragma exception_init(my_2291_exp,-2291);
begin
    update emp set deptno = 11 where empno = 7934;
exception
    when my_2291_exp then
    dbms_output.put_line('违反完整约束条件,未找到父项的关键字!');
    dbms_output.put_line('SQLERRM:' || SQLERRM);
    dbms_output.put_line('SQLCODE:' || SQLCODE);
    rollback;
end;
/   

输出:

违反完整约束条件,未找到父项的关键字!
SQLERRM:ORA-02291: 违反完整约束条件 (SCOTT.FK_DEPTNO) - 未找到父项关键字
SQLCODE:-2291

PL/SQL 过程已成功完成。

(3) 自定义异常

如果开发当中遇到与实际业务相关的错误,例如产品数量:不允许为负数,生产日期 必须在质保日期之前等,这些和业务相关的 问题并不能算系统错误,也不能使用预定义 和非预定义异常来捕捉它们。需要自定义异常捕获。

:要求输入产品id得到产品数量,若产品数量小于0,则抛出异常。

declare
    v_pid productInfo.productid%type :='&产品ID';
    v_quantity productinfo.quantity%type;
    
    quantity_exp exception;
    pragma exception_init(quantity_exp,-20001);
    
begin
    select quantity into v_quantity
    from productinfo
    where productid = v_pid;
    
    if v_quantity < 0 then
        raise quantity_exp;
    end if;
    
    dbms_output.put_line('该产品数量为:' || v_quantity);
    
exception
    when quantity_exp then
        dbms_output.put_line('产品数量为空,请核查!');
    when no_data_found then
        dbms_output.put_line('没有对应的数据');
    when too_many_rows then
        dbms_output.put_line('对应数据过多,请确认!');
end;
/

输出:

输入 产品id 的值:  1
原值    2: v_pid productInfo.productid%type :='&产品ID';
新值    2: v_pid productInfo.productid%type :='1';
该产品数量为:12

PL/SQL 过程已成功完成。

6. PL/SQL函数编写

函数由PL/SQL定义,可以操作各种数据项目完成计算并返回计算的结果值。利用函数可以把复杂的计算过程封装起来,可以重复使用。

6.1 函数的组成

函数主要由以下几个部分组成:

  • 输入部分
  • 逻辑计算部分。函数内部将完成对各种数据项目的计算,它可以进行很简单的算术表 达式运算,也可以调用多个SQL内置函数或自定义函数进行运算。
  • 输出部分。函数要求必须有返回值。

6.2 函数语法

函数的创建语句结构如下:

create [ or replace ] function [ schema.] function_name
[
  (parameter_declaration[,parameter_declaration])
]
return datatype;
{ is | as}
[declare_section]

begin
    statement...
[exception]

end[name];

:根据当前产品数量对产品进行打折计算,数量低于10就打七五折,高于或等于10打九折

create function fun_pri
  (v_pric in number,v_quantity in number)
  return number
is
begin
  if v_pric < 10 then 
    return (v_pric * 0.75);
  else
    return (v_pric * 0.9);
  end if;
end;
/

6.3 查看函数

函数一旦创建成功,就会存储在Oracle服务器中,随时可以调用。

(1) SQLPlus下执行査询脚本,査看已有的函数名称*

COL OBJECT_NAME FORMAT A30

格式化字段长度,避免出现自动换行的情况.

SELECT object_name,object_id,object_type 
FROM USER_PROCEDURES
order by object_type;

(2) 查看 “fun_pri”函数的源脚本

col name format a15
col text format a80
select name,line,text from user_source where name='FUN_PRI';

6.4 函数的修改、删除

函数内容的修改需要 利用REPLACE关键词,在创建的函数中加OR REPLACE关键词,从而达到完成函数修改的目的,也就是覆盖。
函数的删除同样简单。

:删除函数“FUN_PRI”

DROP FUNCTION FUN_PRI;

习题

一、填空题

  1. PL/SQL块分为( )、( )和( )3部分。
  2. IF语句块有( )、( )和( )3种形式。
  3. PL/SQL结构控制语句有( )和( )2种。
  4. 异常分为( )、( )和( )3种。

二、简答题

编写PL/SQL程序,自定义异常,当价格低于10 时抛出自定义异常。

你可能感兴趣的:(第7章 PL/SQL基础)