第二章 PL/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  ... -- caus
es 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约束信息,所以下面这段代码即时是在pno不可为空的情况下也是可以运行的:
DECLARE
my_pno%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 ; 
我们还可以为指定的域进行赋值操作,如下例:
ame := 'JOHNSON' ;
e
mp_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, ame);
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 
四、PL/SQL命名规范
同样的命名规约适用于所有的PL/SQL程序,规约涉及的内容包括常量、变量、游标、异常、过程、函数和包。命名可能是简单的