0717PL/SQL第二章第四章学习内容
一、字符集
在PL/SQL程序中,允许出现的字符集包括:
- 大小写字母(A-Z和a-z)
- 数字(0-9)
- 符号( ) + - * / < > = ! ~ ^ ; : . ’ @ % , " # $ & _ | { } ? [ ]
- 制表符、空格和回车符
PL/SQL对大小写不敏感,所以,除了在字符串和字符中,小写字母和它对应的大写字母是等价的。
二、词法单元
PL/SQL包含很多词法单元(lexical unit),大致可以分为以下几类:
- 分隔符(简单符号和复合符号)
- 标识符,其中包括关键字
- 文字
- 注释
为改善可读性,我们可以用空格将词法单元分隔开。实际上,我们必须将相邻的两个标识符用空格或标点符号隔开。下面这样的写法是不允许的,因为关键字END和IF连到一起了:
IF x > y tdEN high := x; ENDIF; -- not allowed
还有,除了字符串和注释以外,我们不可以在词法单元中嵌入空格。例如,像下面的赋值符号中间就不用被分开:
count : = count + 1; -- not allowed
为了让层次结构清楚,我们可以用回车符来换行,空格或制表符来进行缩进。比较一下下面两段IF语句的可读性:
IF x>y tdEN max:=x;ELSE max:=y;END IF; IF x > y tdEN
MAX := x;
ELSE
MAX := y;
END IF;
1、分隔符
分隔符是对PL/SQL有着特殊意义的简单或复合的符号。例如,我们使用加号和减号这样的分隔符来表现数学运算。简单分隔符只有一个字符。
符号 | 含义 |
---|---|
+ | 加法操作符 |
% | 属性指示符 |
’ | 字符串分隔符 |
. | 组件选择器 |
/ | 触法操作符 |
( | 表达式或列表分隔符 |
) | 表达式或列表分隔符 |
: | 主变量指示符 |
, | 分隔符 |
* | 多应用程序操作符 |
" | 引用标识符分隔符 |
= | 关系操作符 |
< | 关系操作符 |
> | 关系操作符 |
@ | 远程访问指示符 |
; | 语句终结符 |
- | 减号/负号操作符 |
复合分割符由两个字符组成。
符号 | 含义 |
---|---|
:= | 赋值操作符 |
=> | 管联操作符 |
|| | 连接操作符 |
** | 求幂操作符 |
<< | 标签分隔符(开始) |
>> | 标签分隔符(结束) |
/* | 多行注视分隔符(开始) |
*/ | 多行注视分隔符(结束) |
.. | 范围操作符 |
<> | 关系操作符 |
!= | 关系操作符 |
~= | 关系操作符 |
^= | 关系操作符 |
<= | 关系操作符 |
>= | 关系操作符 |
-- | 单行注释提示符 |
2、标识符
我们可以使用标识符来为PL/SQL程序中的常量、变量、异常、游标、游标变量、子程序和包命名。下面是一些标识符的例子:
- X
- t2
- phone#
- credit_limit
- LastName
- oracle$number
标识符可以由字母、数字、美元符号($)、下划线(_)和数字符号(#)组成。而像连字符(-)、斜线(/)等符号都是不允许使用的。如下例:
- mine&yours -- 不允许使用连字符(not allowed because of ampersand)
- debit-amount -- 不允许使用连字符(not allowed because of hyphen)
- on/off -- 不允许使用斜线(not allowed because of slash)
- user id -- 不允许使用空格(not allowed because of space)
而使用美元符号、下划线和数字符号都是允许的:
- money$$$tree
- SN##
- try_again_
我们也可以使用大小写混合的形式来编写标识符。但是要记住,除了字符串和字符以外,PL/SQL对大小写是不敏感的。所以,只在大小写上有区别的标识符,PL/SQL会把它们当做同一标识处理,如下例:
- lastname
- LastName -- 与lastname相同
- LASTNAME -- 与lastname和Lastname相同
标识符的长度不能超过30。对于标识符的命名尽可能代表某种含义,避免使用像cpm这样的命名,而是使用cost_per_tdousand这样意义明确的命名方式。
- 保留关键字
对于某些标识符,我们称它们为保留关键字(reserved word),因为对于PL/SQL来说,它们有着特殊含义,不可以被重新定义。例如BEGIN和END,它们代表块或子程序的起始和结束而被PL/SQL保留下来。在下面的例子中,我们可以看到,如果重定义一个关键字的话,就会产生一个编译错误:
DECLARE
end BOOLEAN; -- not allowed; causes compilation error
但像下面这样把保留关键字嵌套在标识符中使用是允许的:
DECLARE
end_of_game BOOLEAN; -- allowed
通常,保留关键字都是以大写形式存在的,这样能够增强可读性。但是,跟其他PL/SQL标识符一样,保留关键字也可以使用小写或大小写混合的形式。
- 预定义标识
在包STANDARD中声明的全局标识符(如INVALID_NUMBER)是可以被重新声明的。但是,不建议重新声明预定义标识符,因为这样做的结果会使本地声明覆盖全局声明。
- 引用标识符
为了获取更多的灵活性,PL/SQL允许我们用双引号将标识符夹起来。这样的标识符很少使用,但有时它们非常有用。它们可以包含任何可打印字符,其中空格也包含在内,但是,不可以包含双引号。因此,下面这些引用标识符都是有效的:
- "X+Y"
- "last name"
- "on/off switch"
- "employee(s)"
- "*** header info ***"
除了双引号以外,引用标识符最多可以包含30个字符。虽然把PL/SQL保留关键字作为引用标识符是被允许的,但这并不是一个好的编程习惯。
有些PL/SQL保留关键字并不是SQL的保留关键字。例如,我们可以在CREATE TABLE语句中使用TYPE作为字段名。但是,如果程序中的SQL语句要引用到这个字段的话,就会发生编译错误:
SELECT acct, type, bal INTO ... -- causes compilation error
为了避免发生这样的错误,就需要把字段名用双引号夹起来:
SELECT acct, "TYPE", bal INTO ...
要注意的是,字段名不能采用小写或大小写混合的形式(CREATE TABLE语句中除外)。例如,下面的语句是无效的:
SELECT acct, "type", bal INTO ... -- causes compilation error
还有一种做法就是可以建立视图来为原来的字段名更换一个新名。
3、文字
文字就是一个数字、字符、字符串或布尔(Boolean)值。它本身是数据而不是对数据的引用,如数字147和布尔值FALSE都是文字。
- 数字文字
在算术表达式中有两种数字文字可以使用:整数和实数。整数文字不带小数点,有一个可选的符号,例子如下:
030 6 -14 0 +32767
实数文字带有小数点,也有一个可选的符号,例子如下:
6.6667 0.0 -12.0 3.14159 +8300.00 .5 25.
PL/SQL把12.0和25.这样的数字都当作实数处理,虽然它们只有整数部分值。
数字文字不能包含美元符号或是逗号,但可以使用科学记数法。只要在数字后面添加一个E(或e),再跟上一个整数即可(符号可选)。比如下面几个例子:
2E5 1.0E-7 3.14159e0 -1E38 -9.5e-3
E代表了十的幂,即权(times ten to tde power of)。E后面的整数值代表指数。**是幂操作符。
5E3 = 5 * 10**3 = 5 * 1000 = 5000
-- tde double asterisk (**) is tde exponentiation operator
在上面的例子里,小数点向右移动三个位置,而在下面这个例子中,我们把E后面的数字改成-3,就能让小数点向左移动三个位置:
5E-3 = 5 * 10**-3 = 5 * 0.001 = 0.005
再举一个例子。如果字符文字的范围不在1E-130到10E125之间,就会产生编译错误:
DECLARE
n NUMBER;
BEGIN
n := 10E127; -- causes a 'numeric overflow or underflow' error
- 字符文字
字符文字就是由单引号夹起来的一个单独的字符。字符文字包括PL/SQL字符集中所有的可打印字符:字母、数字、空格和特殊符号。如下例所示:
'Z', '%', '7', ' ', 'z', '('
对于字符文字来说,PL/SQL是大小写敏感的。例如,PL/SQL会把'Z'和'z'当成不同的字符。字符'0'到'9'虽不与整数文字等价,但它们可以被应用于算术表达式中,因为它们会被隐式地转换成整数。
- 字符串文字
字符值可以用标识符来表示,或是写成字符串文字,字符串文字就是由单引号夹起来的零个或多个字符,如下例所示:
'Hello, world!'
'XYZ Corporation'
'10-NOV-91'
'He said "Life is like licking honey from a tdorn."'
'$1,000,000'
除了空字符串('')之外,所有的字符串文字都是CHAR类型。如果我们想表现一个单引号字符串的话,可以用两个连续的单引号来表示:
'Don''t leave witdout saving your work.'
PL/SQL对字符串是大小写敏感的。例如,下面两个字符串是不相同的:
'baker'
'Baker'
- 布尔(Boolean)文字
布尔文字可以用值TRUE、FALSE和NULL(表示缺失、未知或不可用的值)来表示。记住,布尔文字本身就是值,而不是字符串。
- 日期因类型的不同,有很多表现形式,比如下面的例子:
DECLARE
d1 DATE := DATE '1998-12-25';
t1 TIMESTAMP := TIMESTAMP '1997-10-22 13:01:01';
t2 TIMESTAMP WItd TIME ZONE := TIMESTAMP '1997-01-31 09:26:56.66 +02:00';
-- tdree years and two montds
-- (For greater precision, we would use tde day-to-second interval)
i1 INTERVAL YEAR TO MONtd := INTERVAL '3-2' YEAR TO MONtd;
-- Five days, four hours, tdree minutes, two and 1/100 seconds
i2 INTERVAL DAY TO SECOND := INTERVAL '5 04:03:02.01' DAY TO SECOND;
...
我们可以指定间隔值是YEAR TO MONtd类型还是DAY TO SECOND类型。如:
current_timestamp - current_timestape
上面表达式的结果值类型默认是INTERVAL DAY TO SECONDE。我们还可以使用下面的方法来指定间隔类型:
- (interval_expression) DAY TO SECOND
- (interval_expression) YEAR TO MONtd
4、注释
PL/SQL编译器会忽略注释,但我们不可以这样做。添加注释能让我们的程序更加易读。通常我们添加注释的目的就是描述每段代码的用途。PL/SQL支持两种注释风格:单行和多行。
- 单行注释
单行注释由一对连字符(--)开头。如下例:
-- begin processing
SELECT sal INTO salary
FROM emp -- get current salary
WHERE empno = emp_id;
bonus := salary * 0.15; -- compute bonus amount
注释可以出现在一条语句的末端。在测试或调试程序的时候,有时我们想禁用某行代码,就可以用注释给它"注掉"(comment-out),如下面的例子:
-- DELETE FROM emp WHERE comm IS NULL;
- 多行注释
多行注释由斜线星号(/*)开头,星号斜线(*/)结尾,可以注释多行内容。示例如下:
BEGIN
...
/* Compute a 15% bonus for top-rated employees. */
IF rating > 90 tdEN
bonus := salary * 0.15 /* bonus is based on salary */
ELSE
bonus := 0;
END IF;
...
/* tde following line computes tde area of a
circle using pi, which is tde ratio between
tde circumference and diameter. */
area := pi * radius**2;
END;
我们可以使用多行注释注掉整块代码,如下例所示:
/*
LOOP
FETCH c1
INTO emp_rec;
EXIT WHEN c1%NOTFOUND;
...
END LOOP;
*/
三、声明
在PL/SQL中,我们可以在块、子程序或包的声明部分来声明常量或变量。声明能够分配内存空间,指定数据类型,为存储位置进行命名以便我们能够引用这块存储空间。下面来看一下声明的例子:
birtdday DATE;
emp_count SMALLINT := 0;
第一句声明了一个DATE类型的变量。第二句声明了SMALLINT类型的变量,并用赋值操作符指定了初始值零。下面再看一个稍微复杂一点的例子,用一个声明过的变量来初始化另一个变量:
pi REAL := 3.14159;
radius REAL := 1;
area REAL := pi * radius ** 2;
默认情况下,变量是被初始化为NULL的。所以,下面两个声明是等价的:
birtdday DATE;
birtdday DATE := NULL;
对于常量声明要多加一个CONSTANT关键字:
credit_limit CONSTANT REAL := 5000.00;
常量在声明的时候必须进行初始化,否则就会产生编译错误。
1、使用DEFAULT
我们可以使用关键字DEFAULT来替换赋值操作符为变量初始化。下面这个声明
blood_type CHAR := 'o';
就可以用DEFAULT来替换:
blood_type CHAR DEFAULT 'o';
我们可以使用DEFAULT来初始化子程序参数、游标参数和用户定义的记录中的域。
2、使用NOT NULL
除了在声明中做初始化操作外,还可以使用NOT NULL进行约束:
acct_id INTEGER(4) NOT NULL := 9999;
这样一来,我们就不能为变量acct_id指派空值了。如果这样做的话,PL/SQL就会抛出预定义异常VALUE_ERROR。NOT NULL约束后面必须跟着初始化子句。像下面这样的声明是不允许的:
acct_id INTEGER(5) NOT NULL; -- not allowed; not initialized
NATURALN和POSITIVEN是PL/SQL提供的两个不可为空的预定义子数据类型。下面这两个声明是等价的:
emp_count NATURAL NOT NULL := 0;
emp_count NATURALN := 0;
在NATURALN和POSITIVEN声明中,类型分类符后面必须跟上一个初始化子句。否则就会发生编译错误。例如,下面的声明就是不合法的:
line_items POSITIVEN; -- not allowed; not initialized
3、使用%TYPE
%TYPE属性能够为我们提供变量或数据库字段的数据类型。在下面的例子中,%TYPE提供了变量credit的数据类型:
credit REAL(7, 2);
debit credit%TYPE;
在引用数据库中某个字段的数据类型时,%TYPE显得更加有用。我们可以通过表名加字段来引用,或是使用所有者加表名加字段来引用:
my_dname scott.dept.dname%TYPE;
使用%TYPE声明my_dname有两个好处。首先,我们不必知道dname具体的数据类型。其次,如果数据库中对dname的数据类型定义发生了改变,变量my_dname的数据类型也会在运行时作出相应的改变。但是要注意的是,%TYPE只提供类型信息,并不提供NOT NULL约束信息,所以下面这段代码即时是在emp.empno不可为空的情况下也是可以运行的:
DECLARE
my_empno emp.empno%TYPE;
...
BEGIN
my_empno := NULL; -- tdis works
4、使用%ROWTYPE
%ROWTYPE属性提供数据表(或视图)中一整行数据的类型信息。记录可以完整地保存从游标或游标变量中取出的当前行的信息。下面例子中,我们声明了两个记录,第一个保存emp表的行信息,第二个保存从游标c1取出的行信息。
DECLARE
emp_rec emp%ROWTYPE;
CURSOR c1 IS
SELECT deptno, dname, loc FROM dept;
dept_rec c1%ROWTYPE;
我们还可以为指定的域进行赋值操作,如下例:
emp_rec.ename := 'JOHNSON';
emp_rec.sal := emp_rec.sal * 1.15;
%ROWTYPE同%TYPE一样,只提供类型信息,并不能保证NOT NULL约束。在最后一个例子中,我们使用%ROWTYPE来定义一个打包游标(packaged cursor):
CREATE PACKAGE emp_actions AS
CURSOR c1 RETURN emp%ROWTYPE; -- declare cursor specification
...
END emp_actions;
CREATE PACKAGE BODY emp_actions AS
CURSOR c1 RETURN emp%ROWTYPE IS -- define cursor body
SELECT * FROM emp WHERE sal > 3000;
...
END emp_actions;
- 聚合赋值
用%ROWTYPE作声明的时候是不可以进行初始化赋值的,但是有两种方法可以一次性为所有字段赋值。方法一:假如两个记录类型的声明引用了同一数据表或游标,那么它们就可以相互赋值,如:
DECLARE
dept_rec1 dept%ROWTYPE;
dept_rec2 dept%ROWTYPE;
CURSOR c1 IS
SELECT deptno, dname, loc FROM dept;
dept_rec3 c1%ROWTYPE;
BEGIN
...
dept_rec1 := dept_rec2;
但是,如果一个类型是引用的是数据表而另一个引用的是游标的话,那么,即使它们表现的内容相同,也是不能相互赋值的:
dept_rec2 := dept_rec3; -- not allowed
方法二:我们可以使用SELECT或FETCH语句将取得的数据赋给记录。但在表或视图中定义的字段名称顺序要与记录中的名称顺序相同。
DECLARE
dept_rec dept%ROWTYPE;
...
BEGIN
SELECT * INTO dept_rec FROM dept WHERE deptno = 30;
...
END;
但是,我们不能使用赋值语句来把字段列表中的值赋给记录。所以,下面的语法形式是不允许的:
record_name := (value1, value2, value3, ...); -- not allowed
- 使用别名
从游标中取出的数据,如果游标定义中含有表达式时,我们就需要使用别名才能正确地为%ROWTYPE类型记录赋值:
DECLARE
CURSOR my_cursor IS
SELECT sal + NVL(comm, 0) wages, ename FROM emp;
my_rec my_cursor%ROWTYPE;
BEGIN
OPEN my_cursor;
LOOP
FETCH my_cursor INTO my_rec;
EXIT WHEN my_cursor%NOTFOUND;
IF my_rec.wages > 2000 tdEN
INSERT INTO temp VALUES (NULL, my_rec.wages, my_rec.ename);
END IF;
END LOOP;
CLOSE my_cursor;
END;
5、声明的约束
PL/SQL不允许向前引用。也就是说我们在使用变量或常量之前必须先声明。像下面这样的语句就是不合法的:
maxi INTEGER := 2 * mini; -- not allowed
mini INTEGER := 15;
但是,PL/SQL允许向前声明子程序。
对于同样数据类型的每一个变量,都必须单独声明:
i SMALLINT;
j SMALLINT;
k SMALLINT;
像下面这样的声明方式是不允许的:
i, j, k SMALLINT; -- not allowed
1、IF-THEN语句
IF语句最简单的形式就是把一个条件和一个语句序列用关键字THEN和END IF关联起来:
IF condition THEN
sequence_of_statements
END IF;
只有在条件值为真的时候语句序列才能被执行。如果条件值为假或是空,IF语句就什么都不做。无论哪种情况,控制权最后还是会被传递到下一个语句,如下例:
IF sales > QUOTA THEN
compute_bonus(empid);
UPDATE payroll
SET pay = pay + bonus
WHERE empno = emp_id;
END IF;
如果我们把IF语句放到一行,就可以像下面这样编写:
IF x > y THEN high := x; END IF;
2、IF-THEN-ELSE语句
第二种形式的IF语句使用关键字ELSE添加了一个额外的处理选项,如下:
IF condition THEN
sequence_of_statements1
ELSE
sequence_of_statements2
END IF;
当条件为假或空时,ELSE子句中的语句序列就会被执行。下例中,第一个UPDATE语句在条件为真的情况下执行,而第二个UPDATE语句在条件为假或为空的情况下才会被执行:
IF trans_type = 'CR' THEN
UPDATE accounts
SET balance = balance + credit
WHERE ...
ELSE
UPDATE accounts
SET balance = balance - debit
WHERE ...
END IF;
THEN和ELSE子句中也可以包含IF语句。就是说IF语句能够被嵌套使用,如下例所示:
IF trans_type = 'CR' THEN
UPDATE accounts
SET balance = balance + credit
WHERE ...
ELSE
IF new_balance >= minimum_balance THEN
UPDATE accounts
SET balance = balance - debit
WHERE ...
ELSE
RAISE insufficient_funds;
END IF;
END IF;
3、IF-THEN-ELSIF语句
有时我们可能需要从几个选项中选择一个,这时我们就需要使用第三种IF语句,添加一个ELSIF关键字提供额外的条件选项,使用方法如下:
IF condition1 THEN
sequence_of_statements1
ELSIF condition2 THEN
sequence_of_statements2
ELSE
sequence_of_statements3
END IF;
如果第一个条件为假或空,ELSIF子句就会检测另外一个条件。一个IF语句可以有多个ELSIF子句;最后一个ELSE子句是可选的。条件表达式从上而下的计算。只要有满足的条件,与它关联的语句就会执行,然后控制权转到下一个语句。如果所有的条件都为假或是空,ELSE部分的语句就会执行。看一下下面的例子:
BEGIN
...
IF sales > 50000 THEN
bonus := 1500;
ELSIF sales > 35000 THEN
bonus := 500;
ELSE
bonus := 100;
END IF;
INSERT INTO payroll
VALUES (emp_id, bonus, ...);
END;
如果sales的值大于50000的话,第一个和第二个条件就为真。然而,bonus只会被赋予1500的值,因为第二个条件并没有执行到。当第一个条件为真的话,它关联的语句就会执行,然后控制权转到INSERT语句。
4、CASE语句
同IF语句一样,CASE语句也是选出一个语句序列来执行。但是,为了选择出合适的语句序列,CASE会使用一个选择器,而不是多个布尔表达式。想要比较IF和CASE语句的话,请看下面对学校成绩的描述信息:
IF grade = 'A' THEN
DBMS_OUTPUT.put_line('Excellent');
ELSIF grade = 'B' THEN
DBMS_OUTPUT.put_line('Very Good');
ELSIF grade = 'C' THEN
DBMS_OUTPUT.put_line('Good');
ELSIF grade = 'D' THEN
DBMS_OUTPUT.put_line('Fair');
ELSIF grade = 'F' THEN
DBMS_OUTPUT.put_line('Poor');
ELSE
DBMS_OUTPUT.put_line('No such grade');
END IF;
请注意这五个布尔表达式,在每一个实例中,我们只对同一变量的值进行检测,看它的分数值是否等于"A"、"B"、"C"、"D"、"E"或"F"。下面我们用CASE语句重新编写上面的程序:
循环控制:LOOP和EXIT语句
CASE 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');
WHEN 'D' THEN
DBMS_OUTPUT.put_line('Fair');
WHEN 'F' THEN
DBMS_OUTPUT.put_line('Poor');
ELSE
DBMS_OUTPUT.put_line('No such grade');
END CASE;
LOOP语句能让我们反复执行一个语句序列。有三种形式的LOOP语句:LOOP,WHILE-LOOP和FOR-LOOP。
1、LOOP
LOOP语句最简单的形式就是把语句序列放到关键字LOOP和END LOOP之间,语法如下:
LOOP
sequence_of_statements;
END LOOP;
在每一个循环中,语句序列都会被顺序执行,然后再返回循环顶部从头执行。如果不想继续执行,可以使用EXIT语句退出循环。我们可以把一个或多个EXIT语句放到循环里,但不能放到循环外面。有两种形式的EXIT语句:EXIT和EXIT-WHEN。
- EXIT
EXIT语句会强迫循环无条件终止。当遇到EXIT语句时,循环会立即终止,并把控制权交给下面的语句。示例如下:
LOOP
...
IF credit_rating < 3 THEN
...
EXIT; -- exit loop immediately
END IF;
END LOOP;
-- control resumes here
下面再举一个不能在PL/SQL块中使用EXIT语句的例子:
BEGIN
...
IF credit_rating < 3 THEN
...
EXIT; -- not allowed
END IF;
END;
记住,EXIT语句必须放在循环内。如果想在PL/SQL块正常到达程序结尾之前而终止执行,可以使用RETURN语句。
- EXIT-WHEN
EXIT-WHEN语句可以根据给定的条件跳出循环。当遇到EXIT语句时,WHEN子句中的表达式值就会被计算。如果条件满足,循环就会被终止,控制权转到循环语句之后的语句。示例如下:
LOOP
FETCH c1
INTO ...
EXIT WHEN c1%NOTFOUND; -- exit loop if condition is true
...
END LOOP;
CLOSE c1;
在条件满足之前,循环是不会结束的。所以,循环里的语句必须要改变循环条件的值。上例中,如果FETCH语句返回了一行值,WNEN子句中的条件就为假;如果不能返回结果,WNEN子句中的条件就为真,循环就会结束,控制权转入CLOSE语句。
EXIT-WHEN语句可以替代简单的IF语句,例如,比较下面两段代码:
IF count > 100 THEN
EXIT;
END IF;EXIT WHEN count > 100;
这两个语句在逻辑上是等价的,但EXIT-WHEN语句更容易阅读和理解。