书城计算机数据库原理及Oracle应用
31848700000015

第15章 PL/SQL编程基础(2)

6.2.1.3 PL/SQL块的类型

有下列几种不同类型的PL/SQL块。

(1)无名块

嵌入在一个应用程序内的无名PL/SQL块,或者交互式发出命令的PL/SQL块。

(2)有名块

存储在应用程序中的函数和过程,称为有名的PL/SQL块。函数有零个或多个输入参数,只返回一个值。过程有零个或多个输入参数,通过参数返回值。一个有名块可以在同一应用的执行部分或出错处理部分被调用。

(3)存储程序

存储在数据库中的PL/SQL块。任何连接到数据库的应用都可以访问这些存储程序(当然用户需要有必要的权限)。Oracle提供四种类型的存储程序:存储过程、存储函数、包和数据库触发器。当调用存储过程、函数、包,以及数据库触发器被触发时,便执行该存储程序。在服务器端修改存储程序,所有的客户机立即可以看到修改后的存储程序。

(4)应用触发器

与某一应用程序执行时发生相关事件而执行的PL/SQL块,可以自动触发。

6.2.2 变量定义

变量是可以在块内被参考的拥有值的内存的存储位置。变量总是在DECLARE部分中定义,并且总是在其他语句参考之前被定义。定义变量的语法如下:

变量[CONSTANT]数据类型[NOT NULL][:|DEFAULT PL/SQL表达式];

其中:变量的命名规则与SQL对象的命名规则一样,变量用Vname来表示,常量用Cname来表示;CONSTANT表示该标识符是一个常量,在程序内其值不能改变,常量必须赋初值;NOT NULL限制变量必须包含值,NOT NULL变量必须赋初值;表达式可以是任何PL/SQL表达式,它可以是变量、运算符和函数组成的表达式;“”表示给变量赋值。没有赋初值的变量默认初始化为NULL值。

例6-2 举例说明变量的定义和赋初值。

VNUM NUMBER(2):12;或VNUM NUMBER(2)DEFAULT 12;

CZEROVALUE CONSTANT NUMBER:0;

VCHAR VARCHAR2(10):‘ASASAS’;

VDATE DATE:‘05-JUL-99’;

VBOOLEAN BOOLEAN:FALSE

6.2.2.1 变量的数据类型

任何变量和常量都需要指定一个数据类型。PL/SQL变量类型有标量类型(scalar)、参考类型(reference)和LOB类型。标量类型又分四种:NUMBER(数字型)、CHARACTER(字符型)、DATE/TIME(日期型)和BOOLEAN(布尔型)。

(1)数字型

数字型用来存储整数或实数。又可以分成以下几种:NUMBER、BINARYINTEGER、PLSINTEGER。

NUMBER:用于存储任何范围及精度的实际数。这是最常用的数字类型,可以有选择地指定一个精度及小数位。语法格式如下:

NUMBER[(precision,scale)]

其中precision是总长度(含小数点及scale的长度),默认值是38,scale默认值是0。

BINARYINTEGER:存储带符号的整数值。比NUMBER值需要较小的存储空间。

PLSINTEGER:与BINARYINTEGER一样,但使用了机器的算法,获得更快的性能。

例6-3 定义数字型的变量。

VNUM NUMBER(5);

VBINARYNUM BINARYINTEGER

(2)字符型

字符型用来存储字符串或字符数据,长度以字节为单位。常用的字符类型包括VARCHAR2和CHAR。

CHAR:存储固定长度的字符串。如果没定义长度,默认值是1。

VARCHAR2:存储可变长度的字符串。长度必须定义,这是最常用的数据类型。

例6-4 定义字符型的变量。

VCHAR VARCHAR2(20);

(3)日期型

日期型用来存储固定长度日期和时间信息,包括世纪、年、月、日、小时、分钟和秒。唯一的类型为DATE。DATE值总是包含到秒的时间。

例6-5 定义日期型变量。

VDATE DATE;

(4)布尔型

布尔型用于存储逻辑值。BOOLEAN变量只能存储TRUE、FALSE和NULL值。布尔变量用于PL/SQL块的流程控制。

例6-6 定义布尔型的变量。

VBOOLEAN BOOLEAN

(5)LOB类型

LOB类型用来存储非结构化的数据,如:VIDEO、SOUND、IMAGE等,大对象可以是一个二进制数或字符数值,长度可达4 GB。在PL/SQL中操纵大对象可用Oracle提供的包DBMSLOB进行处理。常用的LOB类型有三种:BFILE、BLOB和CLOB。

例6-7 定义大对象型的变量。

VBLOBLOCATOR BLOB;

VCLOBLOCATOR CLOB;

(6)%TYPE和%ROWTYPE类型

当将数据库列的值放到一个变量时,必须保证变量的数据类型与列的数据类型完全一致,否则在执行时发生一个PL/SQL错误。可以使用前面已经定义好的变量的类型和列的类型来定义变量的类型,而不要直接定义变量的数据类型和长度。

%TYPE即一个变量的类型用另一个已经定义的变量的类型定义,或用某一个表的某一列的类型定义。保证变量的类型和表中列的类型兼容。

例6-8 定义参考类型的变量。

BOOKPRINT NUMBER;

VBOOK BOOKPRINT%TYPE;

VEMPNO EMP.EMPNO%TYPE

EMP代表表名,EMPNO代表EMP表中的列名。

%ROWTYPE即一个变量的类型参考基表、视图中记录的类型或游标的结构类型。%ROWTYPE前面是表名或游标名。此类型的变量内的字段与表或视图、游标内的列的名字和类型完全一致。定义后,使用此类型的变量时,用变量名,列名来表示。

例6-9 举例说明参考类型变量的使用。

VREC EMP%ROWTYPE;

emp代表表名或前面已经定义的游标名。

VREC.EMPNO:1001;

表示给VREC变量中的EMPNO列赋值。

这两种类型的好处是:不必了解数据库中列的个数和数据类型,如果表结构改变,PL/SQL程序可以不变,减少了程序的维护工作。当用SELECT语句检索一行时,使用%ROWTYPE很管用,这样可不必定义很多接受列值的变量,简化PL/SQL程序。

(7)用户定义的类型

指根据用户自己的需要,用现有的PL/SQL标量类型组合成一个用户自定义的类型。在Oracle的SQL*Plus工具中创建好用户自定义的类型后,该类型就被存储在数据库的数据字典中。该类型与系统本身定义的标量类型一样,可以在PL/SQL程序中定义变量时引用,可以减少变量的定义,简化程序。

例6-10 定义用户自定义的数据类型studenttype。

SQL>create or replace type studenttype as object(

id number(5),

firstname varchar2(20),

lastname varchar2(20),

major varchar2(30),

currentcredits number(3));

当创建好此类型时,系统会在数据字典USERTYPES和USEROBJECTS中记录此类型。在PL/SQL程序块中定义变量时可以引用。

例6-11 引用用户自定义的数据类型。

Vstudent studenttype;

6.2.2.2 变量赋值

给变量赋值有两种方法。

一种方法是直接用赋值运算符(:)给变量赋值,每一赋值语句结束后以分号(;)结尾。语法格式如下:

变量名:常量 或PL/SQL表达式;

用赋值语句给变量赋值可以在DECLARE 段中定义变量的同时给其赋初值,也可以在PL/SQL语句执行部分赋值。

例6-12 举例说明变量的赋值。

DECLARE

NUMVAR number(5):5;(定义变量的同时给其赋初值)

Vemp emp%type;

BEGIN

Vemp.empno:11011;(语句执行部分赋值)

……

另一种方法是通过SELECT INTO语句或FETCH INTO语句给变量赋值。

SELECT sal INTO NUMVAR FROM emp WHERE empno7788;

……

注意:给变量赋值时,如果变量是字符型的,字符串必须用单引号引起来。如果字符串本身含有单引号,则必须用两个单引号表示。如:

Venamearray(1):′I′′M A MAN′;

6.2.2.3 变量的作用范围

PL/SQL相对于SQL语句来说的一个优点是可以将语句嵌套。其变量的作用范围如下。

一个变量的作用范围是在定义此变量的程序范围中,以及该程序内的子块中。

变量如果不在子块中重新定义,则在PL/SQL 块的所有子块中同样有效。

如果变量在子块内被重新定义,子块内定义的变量优先,此变量的作用范围仅在本子块中有效。子块内定义的变量只能在子块内使用,不能在主块内被参考。

当在子块中用到某一个变量时,系统首先查找本子块内是否有此变量的定义。如果在本子块内找不到该变量,系统会查找该块所在的主块,系统不会查找本子块内的子块。

如果主块内已定义变量A,在子块内也定义了变量A,此时在子块内用到A时,系统默认为是子块内的A。因为主块内的变量在主块和子块内都可以使用,所以如果在子块内使用主块的变量A时,必须用块的标识符加以限制(如:块的标识符.A)。

例6-13 举例说明变量的作用范围。

DECLARE

v1 varchar2(8)/*主程序内的变量*/

emess char(80)

BEGIN

……

——子块1——

DECLARE

v1 number(4)/*子块内的变量*/

BEGIN

SELECT empno INTO v1 FROM emp

WHERE job′PRESIDENT′

EXCEPTION

WHEN toomanyrows THEN

INSERT INTO joberrors VALUES(′more than one president′)

END

——子块2——

DECLARE

v1 number(4)/*子块内的变量*/

BEGIN

SELECT empno INTO v1 FROM emp

WHERE job′MANAGER′

EXCEPTION

WHEN toomanyrows THEN

INSERT INTO joberrors VALUES(′more than one manager′)

END

EXCEPTION

WHEN others THEN

emess:substr(sqlerrm,1,80)

INSERT INTO generalerrors VALUES(emess)

END

/

说明:子块1和子块2的变量虽然都命名为v1,但只作用于各自的子块中。主程序内的变量v1可作用于全过程。

6.2.3 PL/SQL中的运算符和函数

6.2.3.1 PL/SQL中的运算符

与任何其他的编程语言一样,PL/SQL有一组运算符,可以分为以下几类。

算术运算符:主要用于数字值的算术运算,包括+、-、*、/、**运算符。

关系运算符:主要用于条件结构(IF)或在SQL语句的WHERE从句中。关系运算符检查条件,结果总是TRUE和FALSE。包括<,<,>,>,<>,||运算符。

比较运算符:补充了关系运算符,加入了更多的运算符,比较运算符也检查一个条件,结果是TRUE和FALSE。包括IS NULL,LIKE,BETWEEN AND,IN运算符。

逻辑运算符:用于组合多个关系运算符及比较运算符。将一个运算结果(TRUE,FALSE,NULL)与另一个运算结果(TRUE,FALSE,NULL)进行AND,OR,NOT运算。包括AND,OR,NOT运算符。

PL/SQL中这些运算符与SQL中的算术运算符、逻辑运算符、比较运算符和关系运算符的含义、作用、优先顺序都一样,用括号可以控制运算的优先顺序。

6.2.3.2 PL/SQL中的函数

在PL/SQL中支持所有SQL中的单行数字型的函数、单行字符型的函数、数据类型的转换函数、日期型的函数和其他各种函数,但不支持组函数(如:AVG,MIN,MAX,COUNT,SUM等),GREATEST,LEAST函数。组函数只能应用到一张表的一组行上,所以在PL/SQL块内只能在SQL语句中可以用组函数。

例6-14 举例说明PL/SQL块中可以引用的各种函数。

Vmailingaddress:Vname||CHR(10)||Vaddress||CHR(10)

Vcountry||CHR(10)||Vzipcode

Vlastname:UPPER(Vlastname)

Vtotal:SUM(sal)/*错误,不能在赋值语句中用组函数*/

SELECT SUM(sal)INTO Vtotal FROM emp/*正确,这是一条SQL语句*/

其中:CHR是一个SQL函数,将ASCII码转换成相应的字符。

6.2.3.3 数据类型的转换

在一个表达式中,必须保证数据类型是一样的。假如在表达式中有不同的数据类型出现,必须使用合适的转换函数将数据类型转换成一样的,否则将出现一个编译错误,并影响性能。

PL/SQL 用内置的转换函数处理不同类型之间的转换。

例6-15 举例说明数据类型的转换。

下列语句,将数字型的currentcredits转换成字符型后给vcurrentcredits赋值。

SELECT TOCHAR(currentcredits)

INTO vcurrentcredits

FROM students

WHERE id1002;

6.2.3.4 空值处理

在数据库中空值(NULL)是不占存储空间的。在PL/SQL中定义变量,如果没有给变量赋值,该变量的初值是NULL。但是对空值变量不能进行算术运算和比较运算,得到的结果都是空。NULL唯一能做的操作是字符串的联结。要判断某一变量的值是否为空,不能直接用等号去判断,必须用IS NULL或IS NOT NULL来判断该变量的值是空还是非空。

例6-16 举例说明空值可以进行的运算和判断空值的正确方法。

NULL+〈数字〉NULL(空值加、减数字仍是空值)

NULL>〈数字〉NULL(空值与数字比较,结果仍是空值)

NULL||‘字符串’‘字符串’(空值与字符串进行连接运算,结果为原字符串)

判断一个变量的值是否为NULL的正确写法如下:

IF myvar IS NULL THEN/*正确*/

……

END IF

而下列写法是错误的:

IF myvar NULL THEN/*错误*/

……

END IF

使用NULL值进行比较时,可以直接给变量赋初值为NULL,但要注意对NULL值的变量不能进行等于或不等于比较。例如:

x:NULL;/*正确*/

y:NULL;/*正确*/

IF xy THEN/*错误*/

……

END IF

6.3 PL/SQL的控制结构

与其他高级语言一样,PL/SQL提供许多的控制结构以便用户在运行时刻对块的流程进行控制。控制结构包括条件语句和循环语句两种类型。

条件控制结构是根据另一条语句或表达式的结果执行一个操作或一条语句。

循环控制结构重复地执行一条或多条语句,或者循环一定的次数,或者直到满足某一条件时退出。

6.3.1 条件语句

6.3.1.1 IF语句

PL/SQL中的条件语句的结构与别的过程化语言的条件语句很类似。根据条件可以有选择地执行一些动作。语法如下:

IF 条件1 THEN

语句系列1;

[ELSIF 条件2 THEN

语句系列2;

……

[ELSE

语句系列3;]

END IF;

其中:条件是一个布尔型的变量或表达式,其值只能是TRUE、FALSE、NULL。

假如布尔型条件1的值是TRUE,执行THEN后面相关的语句序列1。假如布尔型条件1的值是FALSE或NULL,接着判断条件2,如果条件2的值为TRUE,执行第二个THEN后面的语句序列2。假如条件2的值是FALSE或NULL,第二个THEN后面的语句序列被忽略。以此类推,可根据需要写任意多的ELSIF子句。如果所有的布尔型条件的值都是FALSE或NULL,则执行ELSE后面的语句序列3。但最多只能有一个ELSE子句。只能在这些条件中选其一,执行完相应的语句序列后,执行END IF后面的语句。

6.3.1.2 CASE语句

CASE语句增加程序的可读性,并且使得其更加有效。所以如果有可能,最好用CASE语句替代很长的IF……THEN……ELSIF语句。CASE语句有下列两种格式:含selector的CASE语句、搜索CASE语句。

1.含selector的CASE语句

含selector 的CASE语句的语法如下:

[<<labelname>>]

CASE selector

WHEN 表达式1 THEN 语句序列1

WHEN 表达式2 THEN 语句序列2

……

WHEN 表达式N THEN 语句序列N

[ELSE 语句序列N+1]

END CASE[labelname]

CASE语句以关键字CASE开头,紧跟一个选择符(selector)。在例6-18中selector就是变量GRADE。

selector 后面跟着一个或多个WHEN子句,这些WHEN子句按顺序检查。selector的值决定是哪个WHEN子句被执行。假如selector的值与某一个WHEN子句的值相等时,该WHEN子句被执行,控制转移到下一条语句。在例6-18中grade的值等于′C′,就输出′Good′。

ELSE子句类似于IF 语句的ELSE子句。在上述例子中,假如GRADE的值与WHEN子句所覆盖的选择一个都不匹配,就选择ELSE子句,输出′No such grade′字符串。ELSE子句是可选的。假如缺少了ELSE子句,PL/SQL 隐式地加ELSE子句(ELSE RAISE CASENOTFOUND)。

关键字END CASE用于结束CASE语句。这两个关键字中间以空格分隔。

CASE语句可以有标签。标签是用双尖括号引起来的没有定义的标识符。

例6-18 用CASE语句判断GRADE变量的值是否等于A、B、C、D、F,并分别处理。

CASE grade

WHEN′A′THEN dbmsoutput.putline(′Excellent′)

WHEN′B′THEN dbmsoutput.putline(′Very Good′)

WHEN′C′THEN dbmsoutput.putline(′Good′)

WHEN′D′THEN dbmsoutput.putline(′Fair′)

WHEN′F′THEN dbmsoutput.putline(′Poor′)

ELSE dbmsoutput.putline(′No such grade′)

END CASE

2.搜索CASE语句

PL/SQL提供一个搜索CASE语句,其语法格式如下:

[<<labelname>>]

CASE

WHEN 搜索条件1 THEN 语句序列1

WHEN 搜索条件2 THEN 语句序列2

……

WHEN 搜索条件N THEN 语句序列N

[ELSE 语句序列N+1]

END CASE[labelname]

搜索CASE语句没有selector。也就是说WHEN子句包含产生布尔值的搜索条件。

例6-19 用搜索CASE语句实现与例6-18相同的问题(判断GRADE变量的值是否等于A、B、C、D、F)。

CASE

WHEN grade′A′THEN dbmsoutput.putline(′Excellent′)

WHEN grade′B′THEN dbmsoutput.putline(′Very Good′)

WHEN grade′C′THEN dbmsoutput.putline(′Good′)

WHEN grade′D′THEN dbmsoutput.putline(′Fair′)

WHEN grade′F′THEN dbmsoutput.putline(′Poor′)

ELSE dbmsoutput.putline(′No such grade′)

END CASE

搜索条件按序列计算。假如一个搜索条件产生的值是TRUE,则该条件的WHEN子句被执行,控制传递到下一条语句,所有后序的搜索条件不进行计算。假如所有搜索条件都不是TRUE,则ELSE子句被执行。ELSE子句是可选的。假如缺少ELSE子句,则PL/SQL将加隐式的ELSE子句(即ELSE RAISE CASENOTFOUND)。

6.3.2 循环

PL/SQL提供重复执行一段语句的机制,即循环。循环分为四种类型:简单循环(LOOP……END LOOP)、WHILE循环(WHILE……LOOP……END LOOP)、数字式FOR 循环(FOR……LOOP……END LOOP)和游标式FOR 循环(在游标章节中介绍)。

在循环体内必须有退出循环的语句(EXIT语句),强制无条件地结束一个循环,并且将控制传递给END LOOP后面的语句;否则就是死循环。

退出循环的语法有两种:

EXIT WHEN CONDITION;(有条件地结束一个无限循环)

或:

IF CONDITION THEN

EXIT;

END IF;

6.3.2.1 简单循环

简单循环的语法如下:

LOOP

语句序列;

……

EXIT[WHEN condition]

END LOOP;

例6-20 编写PL/SQL块循环地往TEMPTABLE中插入50行。

DECLARE

Vcounter binaryinteger:1

BEGIN

LOOP

INSERT INTO temptable VALUES(vcounter,′loop index′)

Vcounter:vcounter+1

IF vcounter>50 THEN

EXIT

END IF

END LOOP

END

/

6.3.2.2 WHILE循环

WHILE循环的语法如下:

WHILE condition LOOP

语句序列;

END LOOP;

有一个与循环相关的控制条件:当控制条件被计算的结果是TRUE时,则循环内的语句被执行;如果计算结果是FALSE时,则执行END LOOP后面的语句。

控制条件的判断是在每次循环之前,所以如果第一次判断条件是FALSE,循环内的语句一次都不执行。如果不管条件的结果如何,至少要执行一次循环,就不能使用WHILE循环,而必须使用简单循环。

例6-21 用WHILE循环重新改写与例6-20相同的问题(即往TEMPTABLE中插入50行)。

DECLARE

Vcounter binaryinteger:1

BEGIN

WHILE VCOUNTER<50 LOOP

INSERT INTO temptable VALUES(vcounter,′loop index′)

Vcounter:Vcounter+1

END LOOP

END

/

注意:必须对Vcounter进行初始化,没有初始化的变量其初值为NULL,NULL不能进行任何加减运算。这样条件Vcounter<50的值为NULL,条件永远满足不了,产生死循环。

6.3.2.3 数字式FOR 循环

简单循环和WHILE循环的迭代次数在事先是不知道的,它取决于循环条件。而数字式FOR 循环的迭代次数是一定的。数字式FOR 循环的语法如下:

FOR loopcounter IN[REVERSE]lowbound..highbound LOOP

语句序列;

END LOOP;

其中,IN表示索引变量的值从小值到大值:IN REVERSE表示索引变量的值从大值到小值;loopcounter索引变量不必在循环外事先定义,是在循环语句中隐式定义的INTEGER类型的变量。该变量的初值是lowbound,每循环一次自动加1,直到最高值(highbound)为止。如果含有REVERSE关键字,该变量的初值是highbound,每循环一次自动减1,直到最低值(lowbound)为止。

lowbound:指定索引变量值范围的最小值,highbound:指定索引变量值范围的最大值。

LOOP与END LOOP之间的语句格式与前面两种循环的格式是一致的。

注意:只能在循环内参考该索引变量的值,在循环外不必定义。在一个表达式内可以参考索引变量的值,但不能给索引变量赋值。

例6-22 用FOR 循环重新编写与例6-20相同的问题(即往TEMPTABLE中插入50行)。

DECLARE

Vcounter NUMBER:1

BEGIN

FOR Vcounter IN 1..50 LOOP

INSERT INTO temptable VALUES(Vcounter,′loop index′)

END LOOP

END

例6-23 在FOR循环中考虑索引变量的值从大到小。

DECLARE

Vcounter NUMBER:1

BEGIN

FOR Vcounter IN REVERSE 1..50 LOOP

INSERT INTO temptable VALUES(Vcounter,′loop index′)

END LOOP

END

/

6.3.3 标签

为了提高程序的可读性,可以给语句、块、循环加标签(label)。标签在一个块或一个循环前面,由双尖括号引起来。

6.3.3.1 GOTO语句

PL/SQL提供了GOTO 语句,一个GOTO语句加一个标签可以将控制传递到程序的另一部分。标签必须在可执行语句前面,并且必须是唯一的。

1.GOTO语句的语法

GOTO语句的语法如下:

GOTO label

这里,label是在PL/SQL块中定义的标签,用双尖括号括起来。当执行GOTO 语句时,控制会立即无条件地转到由标签标识的语句。GOTO语句的作用是解决程序跳转问题。

例6-24 举例说明GOTO语句的用法。当Vcounter的值大于50时,使用GOTO语句跳出循环。

DECLARE

Vcounter binaryinteger:1

BEGIN

LOOP

INSERT INTO temptable VALUES(Vcounter,′loop count′)

Vcounter:Vcounter+1

IF Vcounter>50 THEN

GOTO ENDOFLOOP

END IF

END LOOP

<<ENDOFLOOP>>

INSERT INTO temptable(charcol)VALUES(′done!′)

END

/

2.对GOTO语句的限制

GOTO语句的使用有如下限制。

一个GOTO语句可以跳出一个IF语句、一个循环或一个子块。

对于块、循环或IF语句而言,想要从外层跳到内层是非法的。

从一个IF子句跳转到IF的另一个子句中是非法的。

从一个块的出错处理内部跳转到当前块的可执行部分是非法的。

从可执行部分GOTO到当前块的一个出错处理部分是非法的。(如果出错,系统会自动跳转到出错处理部分,不能用GOTO语句跳转。)

例6-25 举例说明GOTO语句的限制。下面的语句是非法的。

BEGIN

GOTO innerblock

BEGIN

<<innerblock>>

……

END

GOTO insideif

IF x>3 THEN

……

<<insideif>>

INSERT INTO……

END IF

END

因为从外块往子块跳,从IF语句外往IF语句内跳,会破坏程序的逻辑。

例6-26 举例说明GOTO语句的限制。下面的语句是非法的。

BEGIN

IF x>3 THEN

……

GOTO nextcondition

ELSE

<<nextcondition>>

……

END IF

END

因为IF语句THEN后面的语句和ELSE后面的语句不能同时执行,两者选其一。如果从THEN后面又GOTO到ELSE后面,表示先执行THEN后面的语句,又转移到ELSE后面去执行。

例6-27 举例说明GOTO语句的限制。下面的语句是非法的。

DECLARE

Vroom rooms%rowtype

BEGIN

SELECT*INTO Vroom FROM rooms WHERE rowid1

<<insert>>

INSERT INTO temptable(charcol)VALUES(′found a row′)

exception

WHEN nodatafound THEN

GOTO insert

END

/

因为当可执行部分的语句执行有错时,系统会转移到EXCEPTION部分执行,执行完出错处理的语句后就结束。如果在出错处理部分又转移到块的执行部分,会造成程序没法结束。

6.3.3.2 循环标签

循环本身是可以被设定标签的。循环标签除了改善程序的可读性以外,循环标签的优越性在于从一个嵌套循环结构中可以退出任意一层循环。如果没有给循环设标签,则最里层的循环只能退出到次外层,必须一层一层往外退。如果设定了循环标签,那么可以在EXIT语句中使用循环标签指明要退出哪层循环。

例6-28 举例说明从里循环inner直接退出外循环outer。

BEGIN

<<outer>>

FOR Vouterindex in 1..50 LOOP

……

<<inner>>

FOR Vinnerindex in 2..10 LOOP

……

IF Vouterindex>40 THEN

EXIT outer

END IF

END LOOP inner

END LOOP outer

6.3.3.3 块标签

有一个标签名的块称为标签块,其结构如下:

<<labelname>>

[DECLARE]

……

BEGIN

……

[EXCEPTION]

……

END labelname;

前面介绍了在主块内定义的变量,可以在主块及主块内的子块中使用。但如果在子块内又定义了同名的变量,则子块内的变量优先级高。如果在子块内想使用同名的主块内的变量,必须给主块的变量名加块的标签限制,否则产生二义性。

例6-29 举例说明块标签的用法和作用。

<<sample>>

DECLARE

Vdeptno number:20;

BEGIN

<<updateblock>>

DECLARE

Vdeptno number:10;

BEGIN

UPDATE emp SET salsal*1.1 WHERE deptnosample.Vdeptno;

commit;

END updateblock;

……

END sample;

/

注意:如果不用块标签给Vdeptno变量加限制,系统就会产生二义性。

6.4 出错处理

许多编写得很好的程序都必须能够正确处理各种出错情况,并且尽可能地从错误中进行恢复。出错处理方法是程序对运行时刻出现的错误作出反应并进行处理的方法。当出现一个错误时,控制便会转给块的出错处理部分。

6.4.1 出错处理的语法和原则

一个PL/SQL块的出错处理(EXCEPTION)部分包含程序处理多个错误的代码。当一个错误发生时,程序控制离开PL/SQL块的执行部分转移到出错处理部分。一旦控制转移到出错处理部分,就不能再转到相同块的可执行部分。处理一个错误需要执行一段放在出错处理部分的代码,该代码是为了一个特定错误而自定义的代码。

6.4.1.1 出错处理的语法

在PL/SQL中错误处理的方法与其他语言的错误处理方法有区别。在C语言中,几乎每一个函数调用都要求进行错误检测,而在PL/SQL中,当错误发生时,程序控制无条件地转移到当前PL/SQL块的出错处理部分。出错处理部分是PL/SQL块中可选部分。如果包含该部分,必须出现在所有可执行语句后面、END语句的前面。其语法如下:

EXCEPTION

WHEN 错误1[OR 错误2]THEN

语句序列1;

WHEN 错误3[OR 错误4]THEN

语句序列2;

……

WHEN OTHERS THEN

语句序列3

END

其中:错误1、2、3等是指标准包中定义的错误名,或用户在DECLARE部分中说明的用户自定义的错误名,也可以是用户给没有命名的服务器错误取的名。每一个错误或多个错误的处理代码放在THEN从句中;OTHERS将处理所有不在上述列表中显式处理的错误。它应该是块中最后一个处理语句,确保所有的错误都能被检测。

当一个错误发生并且控制传递到出错处理部分时,Oracle检查该错误是否有对应的处理语句,如果有处理语句,则执行此语句;假如没有处理语句,但有WHEN OTHERS语句,则执行OTHERS后面的语句序列。

在一个出错处理部分中,WHEN从句的数量没有限制,可以按需要使用。

6.4.1.2 出错处理的原则

出错处理的原则如下。

将WHEN OTHERS从句放置在所有其他出错处理从句的后面。

最多只能有一个WHEN OTHERS从句。

在块中必须以关键字EXCEPTION开始一个出错处理。

可以在块中定义多个出错处理(WHEN语句),每一个出错处理包含一组语句。

当一个错误发生时,在离开块之前,PL/SQL只处理一个出错处理。

6.4.2 各种错误处理

出错情况包括系统预定义的错误和用户自定义的错误。

6.4.2.1 系统预定义的错误

Oracle有一些预定义的命名错误,这些命名错误与相应的Oracle错误代码是对应的。这些错误是在系统的STANDARD包中定义的,可以直接在PL/SQL块的出错处理部分的WHEN从句中进行处理。

6.4.2.2 用户自定义的错误

除了标准包中命名的错误以外,用户可以自定义命名的错误。用户自定义的错误是程序定义的一个错误,并不一定是一个Oracle错误,它可能是与业务相关的一个错误,比如违反了业务规则。

处理用户自定义的错误有三个步骤:定义错误、触发错误和处理错误。

(1)定义错误

用户定义的错误是在PL/SQL块的说明部分进行定义的。与变量类似,错误也有一个作用范围。在主块中定义的错误可以在主块及任何子块中被触发和处理,而在子块中定义的错误不能在主块中被处理。

定义错误的语法如下:

错误名EXCEPTION;

例6-31 定义用户自定义的错误。

DECLARE

myexception EXCEPTION

……

(2)触发错误

当一个相关的条件满足时,就会触发该错误。用户定义的错误是在语句执行部分通过显式使用RAISE 语句来触发的,而预定义的错误是当相关的Oracle错误发生时被PL/SQL执行引擎隐式触发的。触发错误的语法如下:

RAISE 错误名;

例6-32 触发用户自定义的错误。

BEGIN

IF Vcomm IS NULL THEN

RAISE myexception;

END IF

……

(3)处理错误

在程序的出错处理部分对自定义的错误进行处理,处理方法与系统预定义的错误一样。

处理错误的语法如下:

WHEN 错误名THEN……

例6-33 处理用户自定义的错误。

EXCEPTION

WHEN myexception THEN

dbmsoutput.putline(′奖金为空′)

例6-34 查找classes表中历史系101课程的当前学生数与最大学生数,比较这两个数,如果当前学生数超过最大学生数是不允许的,就触发错误。当这种错误发生时,向logtable表中插入一条记录。

6.5 游标

前面已经介绍,PL/SQL是过程化语言,其查询结果不能交互显示,必须将查询结果放入变量。所以PL/SQL中SELECT语句必须带INTO子句,将查询结果INTO到相应的变量中,然后再将变量的值输出。但这种SELECT……INTO语句只能返回一条记录。如果SELECT……INTO语句返回多条记录,就会产生系统预定义错误toomanyrows。为了解决此问题,必须使用游标。

游标有两种类型:显式游标和隐式游标。

当某一条查询语句(SELECT)返回多条记录时,必须显式地定义游标以处理每一行。其他的SQL语句(插入、更新、删除、查询只返回一条记录)都使用隐式游标。

显式游标是由程序员定义和命名的,并且在块的执行部分中通过特定语句操纵的内存工作区。

隐式游标是由PL/SQL为DML语句和返回单行记录的SELECT语句隐式定义的工作区。

游标是一个指向该内存区域的指针,是一个PL/SQL的结构。

6.5.1 显式游标

6.5.1.1 显式游标的处理步骤

显式游标可以被理解为没有上限的数组,可以有任意数量的行数。游标一般指的是显式游标,是在DECLARE部分中定义的。游标有一个名字及一个SELECT语句,并且允许带WHERE,ORDER BY,GROUP BY等子句。显式游标的处理包括以下四个步骤。

定义游标:在DECLARE说明部分定义游标。

打开游标:在语句执行部分或者出错处理部分打开游标。

将当前行结果提取到PL/SQL变量中:在语句执行部分、或者出错处理部分提取结果。

关闭游标:在语句执行部分、或者出错处理部分关闭游标。

1.定义游标

定义游标时定义了游标的名字,并将该游标和一个SELECT 语句相关联。

定义游标的语法如下:

CURSOR 游标名[(参数名1 数据类型[,参数名2 数据类型……])]

IS SELECT 语句;

其中:参数名是一个在SELECT语句的WHERE条件中要用到的参数,这部分可选;SELECT 语句是一个不含INTO 子句的SELECT语句,允许带WHERE,ORDER BY,GROUP BY等子句。因为在定义游标时,还没有将处理的记录放到该工作区中,也没有将记录放到变量中。只有在FETCH语句中才将记录取到变量中。

例6-35 定义游标。

CURSOR c1 IS SELECT ename,deptno FROM emp;

2.打开游标

在定义游标之后,就可以打开游标。打开游标就是在接受实际参数值后,执行游标所对应的SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部。

打开游标的语法如下:

OPEN 游标名[(实际参数值1[,实际参数值2……])];

例6-36 打开游标。

OPEN CCLASSES

(3)取值到变量

取值工作是将游标工作区中的当前指针所指行的数据取出,放入到指定的输出变量中。系统每执行一次FETCH语句只能取一行,每次取出数据之后,指针顺序下移一行,使下一行成为当前行。循环执行FETCH语句,直到整个查询结果集都被返回(循环的控制需要用游标的属性来控制)。

取值到变量的语法如下:

FETCH 游标名INTO 变量1[,变量2……];

或:

FETCH 游标名INTO PL/SQLRECORD;/*PL/SQLRECORD 是记录类型变量*/

FETCH语句中INTO后面的变量列表,应该与定义游标的SELECT语句中的列在个数、类型、前后顺序上相对应。也可以用%ROWTYPE先定义一个与游标结构对应的记录类型变量,然后将游标工作区中的一行放到该记录类型变量中。

可用下列语句定义一个记录类型的变量。

变量名 表名|游标名%ROWTYPE;(必须先定义该游标,然后才能引用该游标名)

(4)关闭游标

完成对SELECT 语句的处理之后,就可以关闭游标。显式打开的游标需要显式关闭。游标关闭后,系统释放与该游标关联的资源,并使该游标的工作区变成无效。关闭以后不能再对游标进行FETCH操作,否则会触发一个INVALIDCURSOR错误。如果需要可以重新打开。

关闭游标的语法如下:

CLOSE 游标名;

例6-38 关闭游标。

CLOSE cclasses;

6.5.1.2 游标的属性

从游标工作区中逐一提取数据,直到所有的记录都取完为止,这些工作可以在循环中完成。但循环的开始、结束及控制,必须以游标的属性为依据。游标有四个属性,其值代表游标的状态信息。使用游标属性时,必须在属性名前面加游标名%。游标属性的值是由系统自动赋值,用户只能使用,不能改变其值。

例6-39 举例说明游标属性%ISOPEN的使用。

在打开游标后才能取数据,所以在FETCH之前使用%ISOPEN属性判断游标是否打开。

……

IF empcursor%ISOPEN THEN/*empcursor 为游标名*/

FETCH empcursor INTO vename,vsal

ELSE

OPEN empcursor

END IF

……

例6-40 举例说明游标属性%ROWCOUNT和%NOTFOUND的使用。

使用%ROWCOUNT属性检索确定数量的行,使用%NOTFOUND属性决定退出循环的时间。

……

LOOP

FETCH empcursor INTO vename,vsal/*empcursor 为游标名*/

EXIT WHEN empcursor%ROWCOUNT>5 or empcursor%NOTFOUND

……

END LOOP

……

例6-41 通过游标用简单循环控制从emp表中取出某一部门的员工姓名和工资。

DECLARE

vdeptno emp.deptno%type:&pdeptno/*&pdeptno 表示可以在运行时接受输入的变量pdeptno*/

vename emp.ename%type

vsal emp.sal%type

CURSOR empcursor IS SELECT ename,sal FROM emp

WHERE deptnovdeptno

BEGIN

OPEN empcursor

LOOP

FETCH empcursor INTO vename,vsal;

EXIT WHEN empcursor%NOTFOUND;

INSERT INTO temp(ename,sal)VALUES(vename,vsal);

END LOOP

CLOSE empcursor

COMMIT

END

从上述例子可以了解游标处理的四个步骤,以及如何用游标的属性控制循环。

6.5.1.3 游标的FOR 循环

在大多数情况下,游标的处理步骤为:定义一个游标→打开一个游标→启动循环→FETCH游标到变量→检查是否所有的行都返回→处理返回的行→结束循环→关闭游标。

使用游标的FOR 循环,可以简化游标的上述操作步骤。使用游标的FOR循环,系统隐式地定义一个游标名%ROWTYPE类型的记录变量。使用游标的FOR循环,必须先定义游标,然后在块内用FOR循环替代打开游标和关闭游标操作。循环自动地处理查询返回的所有行,查询返回的结果放在记录名,列名中。当最后一行被取出后,循环会自动终止。如果游标的操作需要人工干预时,不要使用游标的FOR循环。游标的FOR循环自动地完成下列三个步骤:打开游标;取数据(FETCH游标到变量);关闭游标。

游标FOR循环的语法如下:

FOR 记录名IN 游标名LOOP

语句1;

语句2;

……

END LOOP;

其中,记录名是系统隐式定义的游标名%ROWTYPE类型的记录变量,不必事先定义;游标名必须事先定义,它是一个PL/SQL的标识符。

例6-42 用游标的FOR循环实现从emp表中取出某一部门的员工姓名和工资。

DECLARE

其中记录变量emprecord不必事先定义,是由系统隐式地定义的,并且只能在循环内可用。在本段程序中,没有打开游标、取数据、关闭游标等语句,它们由FOR语句自动完成。

6.5.1.4 通过游标操纵数据库

在PL/SQL中使用UPDATE、DELETE语句可以更新或删除多条记录,INSERT语句也可以同时插入多条记录。当用户从一张表或多张表中查询多条记录时,必须使用一个显式游标。PL/SQL提供了一种删除或更新显式游标刚取出来的那条记录的方法。

要想操纵数据库中的数据,在定义游标的查询语句时,必须加上FOR UPDATE OF 从句,表示要先对表加锁;然后在UPDATE 或DELETE 语句中,加上WHERE CURRENT OF 子句,指定从游标工作区中取出的当前行需要被更新或删除。当会话打开一个带FOR UPDATE 从句的游标时,在游标工作区中的所有行拥有一个行级排他锁,其他会话只能查询,不能更新或删除。

6.5.1.5 带参数的游标

像过程和函数一样,可以在游标中传递参数,并且在查询(SELECT语句)中使用参数。当用户想基于确定的条件去打开一个游标时,使用带参数的游标非常有用,可以提高程序的灵活性。

定义显式游标时,可以加入参数的定义。这样在使用游标时,对参数输入不同的数值,游标工作区中所包含的数据也有所不同。

1.定义带参数的游标

定义带参数的游标的语法如下:

CURSOR 游标名(参数名1 数据类型[{:|DEFAULT}值]

[,参数名2 数据类型[{:|DEFAULT}值]……])

IS SELECT 语句;/*在SELECT语句中使用参数*/

在定义参数时,只定义参数的数据类型,不定义长度。可以有选择地给参数提供一个默认值。只能向游标传递参数值,不能通过参数带出结果。参数的值在打开游标时带入,假如没有给游标传递值,此默认值就起作用。此参数只能在游标的查询语句中使用。

2.打开带参数的游标

打开带参数的游标的语法如下:

OPEN 游标名(&参数1,&参数2……)

其中:参数1和参数2可以是一个具体的值,也可以是一个已经赋值的变量。

当打开游标时,实际值传递给参数。根据传递的值执行查询。这样可以用不同的实际值多次打开显式游标,每次返回不同的结果集。

假如在游标定义时没有为一个参数定义默认值,打开游标时没带实际参数,将返回一个错误。在游标定义中的每一个形式参数,在打开游标时应该有一个对应的实际值。

6.5.2 隐式游标

当执行插入、更新和删除操作,以及查询结果只有一条记录时,PL/SQL内部打开一个名字为SQL的隐式游标,并处理它的结果。语句执行后的状态信息被保存在四个游标属性(SQL%FOUND,SQL%NOTFOUND,SQL%ROWCOUNT和SQL%ISOPEN属性)中,这些属性可以用来控制程序流程。游标是一块包含有查询信息的内存空间,在执行DML语句时,游标被自动打开,当语句完成时,游标被自动关闭。

1.隐式游标的特点

由系统定义,不需要用户明确定义的游标。

用来处理INSERT,UPDATE,DELETE 和单行的SELECT……INTO 语句时系统自动打开,自动处理其中的SQL 语句,自动关闭的游标。

游标名为SQL。

游标的属性存储有关最近一次SQL命令的状态信息。

2.隐式游标的属性

隐式游标的属性与显式游标的属性类似,也有四个属性,其属性的含义和数据类型与显示游标的属性一致。SQL%FOUND和SQL%NOTFOUND属性的值是布尔型,在执行DML语句之前,它们的值是NULL。当插入、删除、更新成功、查询有一行结果时,SQL%FOUND被设成TRUE,SQL%NOTFOUND被设成FALSE。SQL%ROWCOUNT属性的值是整数值,在执行DML语句之前,它的值是0,它返回最后一条DML语句处理的行数。SQL%ISOPEN属性值总是FALSE。因为隐式游标在执行DML语句时打开,在语句执行完立即关闭。

小结

本章详细地介绍Oracle的编程语言PL/SQL的各种语法格式。首先介绍PL/SQL的特点及PL/SQL块的几种类型。同时介绍组成PL/SQL块的几个组成部分,PL/SQL块中变量的定义和赋值方法、变量的数据类型和数据类型的转换方法,以及PL/SQL语句中可以使用的各种运算符和函数。

详细地介绍PL/SQL程序中用于控制程序流程的控制结构:条件语句、CASE语句、循环语句、GOTO语句等各种语句的语法结构,并举例说明其用法。

接着详细地介绍程序中如何处理各种错误,包括系统预定义的错误和用户自定义的错误,以及针对不同的错误所对应的出错处理方法和步骤。为了提高程序的可靠性在程序中必须考虑其出错处理。

最后详细地介绍如何采用游标处理返回多条记录的查询语句,详细地介绍游标的种类、显式游标与隐式游标之间的差别,以及显式游标的定义和处理步骤,游标的属性。为了提高程序的灵活性,可以定义带参数的游标,介绍如何定义和打开带参数的游标,如何通过游标更新和删除数据库的数据。

习题

1.一个PL/SQL块由哪几部分组成?哪些是必须的?哪些是可选的?

2.PL/SQL块有哪几种类型?

3.一个PL/SQL变量可以取哪些数据类型?

4.对拥有NULL值的变量允许进行哪种运算?不允许进行哪种运算?

5.PL/SQL程序中条件语句有哪几种形式?

6.PL/SQL程序中循环语句有哪几种形式?

7.PL/SQL程序中标签有哪几类?各自的作用是什么?

8.PL/SQL程序中出错处理的作用、语法、原则是什么?

9.什么是用户自定义的错误?处理用户自定义错误的步骤有哪些?

10.PL/SQL游标分哪两类?各自适用的范围是什么?

11.PL/SQL游标的属性有哪些?解释这些属性各自的含义和取值。