Oracle-PL/SQL用户指南与参考-3
第三章 PL/SQL基础
PL/SQL基础
一、字符集
在PL/SQL程序中,允许出现的字符集包括:
- 大小写字母(A-Z和a-z)
- 数字(0-9)
- 符号( ) + - * / < > = ! ~ ^ ; : . ’ @ % , " # $ & _ | { } ? [ ]
- 制表符、空格和回车符
- PL/SQL对大小写不敏感
所以,除了在字符串
和字符
中,小写字母和它对应的大写字母是等价的。
二、词法单元
PL/SQL包含很多词法单元 (lexical unit),先类比我们日常交流的语言逻辑:当我们说一句话时,会先把 “字母” 组合成 “词语”,再用 “词语” 构成句子。在 PL/SQL 中,“词法单元” 就是相当于 “词语” 的角色 —— 它是 PL/SQL 代码中 “最小的、有独立含义的语法单位”,是编译器在 “词法分析阶段” 从代码文本中拆分出来的基本组件。PL/SQL 编译器执行代码前,会先做两步基础工作:
- 词法分析:把连续的代码文本(如v_sal := 5000 + 1000;)拆成一个个独立的 “词法单元”,如:v_sal、:=、5000、+、1000、;
- 语法分析:再根据 PL/SQL 语法规则,把这些 “词法单元” 组合成合法的语句(如赋值语句、循环语句)。
大致可以分为以下几类:
- 分隔符:用于 “分隔其他词法单元” 或 “标记代码边界” 的符号,本身无运算 / 命名含义。例如:;(语句结束符)、()(参数列表分隔符)、,(项分隔符)、///(注释分隔符)
- 标识符:用于给 “变量、常量、子程序、表、列” 等命名的符号,是最常用的词法单元。例如:v_sal(变量名)、emp(表名)、get_sal(函数名)
- 文字:直接表示 “具体值” 的单元(如数字、字符串、日期),是代码中 “固定的值”。例如:5000(数字字面量)、‘Oracle’(字符串字面量)、TO_DATE(‘2024-01-01’,‘YYYY-MM-DD’)(日期字面量)
- 注释:用于标注代码说明的文本,编译器会忽略其含义,但属于独立的词法单元。例如:– 这是单行注释、/* 这是多行注释 */
为改善可读性,我们可以用空格将词法单元分隔开。实际上,我们必须将相邻的两个标识符用空格或标点符号隔开。下面这样的写法是不允许的,因为关键字END和IF连到一起了:
|
|
还有,除了字符串和注释以外,我们不可以在词法单元中嵌入空格。例如,像下面的赋值符号中间就不用被分开:
|
|
为了让层次结构清楚,我们可以用回车符来换行,空格或制表符来进行缩进。比较一下下面两段IF语句的可读性:
1. 分隔符
分隔符是对PL/SQL有着特殊意义的简单或复合的符号。例如,我们使用加号和减号这样的分隔符来表现数学运算。
简单分隔符只有一个字符:
符号 | 含义 | PL/SQL 示例 |
---|---|---|
+ | 加法操作符 | v_total := 5000 + 1000; (计算总和) |
% | 属性指示符 | v_emp_rec emp%ROWTYPE; (声明与emp表结构一致的记录类型) |
' | 字符串分隔符 | v_name := '张三'; (定义字符串常量) |
. | 组件选择器 | v_empno := emp.empno; (访问表的字段) |
/ | 触发操作符 | BEGIN ... END; / (执行PL/SQL匿名块) |
( | 表达式或列表分隔符 | SELECT * FROM emp WHERE deptno IN (10, 20); (包裹列表项) |
) | 表达式或列表分隔符 | v_result := ADD(10, 20); (闭合函数参数列表) |
: | 主变量指示符 | UPDATE emp SET sal = :new_sal WHERE empno = 1001; (引用宿主变量) |
, | 分隔符 | INSERT INTO emp (empno, ename) VALUES (1001, '张三'); (分隔字段和值) |
* | 多应用程序操作符 | SELECT * FROM emp; (查询表中所有字段) |
" | 引用标识符分隔符 | SELECT "EmpName" FROM "Employee"; (访问大小写敏感的标识符) |
= | 关系操作符 | IF v_sal = 5000 THEN ... (判断是否相等) |
< | 关系操作符 | WHERE sal < 6000 (查询薪资小于6000的记录) |
> | 关系操作符 | WHILE i > 0 LOOP ... (当i大于0时执行循环) |
@ | 远程访问指示符 | SELECT * FROM emp@remote_db; (访问远程数据库表) |
; | 语句终结符 | SELECT sal FROM emp; (结束SQL语句) |
- | 减号/负号操作符 | v_diff := 8000 - 3000; (计算差值);v_neg := -500; (表示负数) |
复合分割符由两个字符组成:
符号 | 含义 | PL/SQL 示例 |
---|---|---|
:= | 赋值操作符 | v_name := 'Li Si'; (给变量v_name赋值) |
=> | 关联操作符 | v_result := calc_sal(p_empno => 1001, p_bonus => 2000); 实际参数与函数 / 过程中定义的形式参数进行显式关联,将实际参数 1001 传递给函数 calc_sal 中定义的形式参数 p_empno |
|| | 连接操作符 | v_msg := '员工姓名:' || '张三' || ',部门:研发部'; (拼接多个字符串) |
** | 求幂操作符 | v_power := 3 ** 4; (计算3的4次方,结果为81) |
« | 标签分隔符(开始) | «sal_loop» FOR i IN 1..5 LOOP v_sal := v_sal + 1000; END LOOP sal_loop;(定义循环标签) |
» | 标签分隔符(结束) | 同上方示例,与<<sal_loop>> 搭配,用END LOOP sal_loop; 闭合标签 |
/* | 多行注释分隔符(开始) | /* 批量更新员工薪资: 1. 研发部薪资+15% 2. 财务部薪资+10% */ UPDATE emp SET sal = sal * 1.15 WHERE deptno = 10; (标记多行注释起始) |
*/ | 多行注释分隔符(结束) | 同上方示例,用*/ 标记多行注释结束,后续代码正常执行 |
.. | 范围操作符 | FOR i IN 10..20 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP;(循环输出10到20的整数) |
<> | 关系操作符 | SELECT * FROM emp WHERE deptno <> 30; (查询非30号部门的员工) |
!= | 关系操作符 | IF v_status != 'ACTIVE' THEN DBMS_OUTPUT.PUT_LINE('员工状态异常'); END IF; (判断状态是否非“激活”) |
~= | 关系操作符 | SELECT * FROM emp WHERE ename ~= 'Zhang'; (查询姓名不等于“Zhang”的员工,部分Oracle版本支持) |
^= | 关系操作符 | SELECT * FROM emp WHERE sal ^= 5000; (查询薪资不等于5000的员工,部分Oracle版本支持) |
<= | 关系操作符 | SELECT * FROM emp WHERE hire_date <= TO_DATE('2023-01-01', 'YYYY-MM-DD'); (查询2023年1月1日前入职的员工) |
>= | 关系操作符 | IF v_sal >= 10000 THEN v_level := '高级'; END IF; (判断薪资是否大于等于10000,设置员工级别) |
– | 单行注释提示符 | v_basic := 8000; -- 基础薪资赋值(后续可根据绩效调整) (标记单行注释,注释内容不执行) |
2. 标识符
我们可以使用标识符来为PL/SQL程序中的常量、变量、异常、游标、游标变量、子程序和包命名。下面是一些标识符的例子:
- X
- t2
- phone#
- credit_limit
- LastName
- oracle$number
2.1 标识符命名规则
标识符可以由字母、数字、下划线(_)、美元符号($)和数字符号(#)组成。而像连字符,斜线(/)等符号都是不允许使用的。如下例:
- 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这样意义明确的命名方式。
2.2 保留关键字
对于某些标识符,我们称它们为保留关键字(reserved word),因为对于PL/SQL来说,它们有着特殊含义,不可以被重新定义。例如BEGIN和END,它们代表块或子程序的起始和结束而被PL/SQL 保留下来。在下面的例子中,我们可以看到,如果重定义一个关键字的话,就会产生一个编译错误:
但像下面这样把保留关键字嵌套在标识符中使用是允许的:
通常,保留关键字都是以大写形式存在的,这样能够增强可读性。但是,跟其他PL/SQL标识符一样,保留关键字也可以使用小写或大小写混合的形式。
2.3 预定义标识
在包STANDARD中声明的全局标识符(如INVALID_NUMBER)是可以被重新声明的。但是,不建议重新声明预定义标识符,因为这样做的结果会使本地声明覆盖全局声明。
直接使用STANDARD包预定义的INVALID_NUMBER,能正确捕获 “无效数字” 异常:
你在本地代码中重新声明了INVALID_NUMBER(比如把它定义成一个普通变量),此时 “本地声明会覆盖全局声明”:
|
|
2.4 引用标识符
为了获取更多的灵活性,PL/SQL允许我们用双引号将标识符夹起来。这样的标识符很少使用,但有时它们非常有用。它们可以包含任何可打印字符,其中空格也包含在内,但是,不可以包含双引号。因此,下面这些引用标识符都是有效的:
- “X+Y”
- “last name”
- “on/off switch”
- “employee(s)”
- “*** header info ***”
除了双引号以外,引用标识符最多可以包含30个字符。虽然把PL/SQL保留关键字作为引用标识符是被允许的,但这并不是一个好的编程习惯。
有些PL/SQL保留关键字并不是SQL的保留关键字。例如,我们可以在CREATE TABLE语句中使用TYPE作为字段名。但是,如果程序中的SQL语句要引用到这个字段的话,就会发生编译错误:
|
|
为了避免发生这样的错误,就需要把字段名用双引号夹起来:
|
|
要注意的是,字段名不能采用小写或大小写混合的形式(CREATE TABLE语句中除外)。例如,下面的语句是无效的:
|
|
因为表创建时没加双引号,字段type被自动转成大写TYPE存储;但查询时用双引号写"type",Oracle 强制按小写匹配,找不到对应的大写字段TYPE,所以编译错误。Oracle 中 “双引号是大小写开关”—— 不加双引号默认大写匹配,加了双引号必须原样匹配,还有一种做法就是可以建立视图来为原来的字段名更换一个新名。
3. 文字
文字就是一个数字、字符、字符串或布尔(Boolean)值。它本身是数据而不是对数据的引用,如数字147和布尔值FALSE都是文字。
3.1 数字文字
在算术表达式中有两种数字文字可以使用:整数和实数。整数文字不带小数点,有一个可选的符号,例子如下:
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之间,就会产生编译错误:
3.2 字符文字
字符文字就是由单引号夹起来的一个单独的字符。字符文字包括PL/SQL字符集中所有的可打印字符:字母、数字、空格和特殊符号。如下例所示:
‘Z’, ‘%’, ‘7’, ’ ‘, ‘z’, ‘(’
对于字符文字来说,PL/SQL是大小写敏感的。例如,PL/SQL会把’Z’和’z’当成不同的字符。字符'0’到'9’虽不与整数文字等价,但它们可以被应用于算术表达式中,因为它们会被隐式地转换成整数。
3.3 字符串文字
字符值可以用标识符来表示,或是写成字符串文字,字符串文字就是由单引号夹起来的零个或多个字符,如下例所示:
除了空字符串(’’)之外,所有的字符串文字都是CHAR类型。如果我们想表现一个单引号字符串的话
,可以用两个连续的单引号
来表示:
|
|
PL/SQL对字符串是大小写敏感的。例如,下面两个字符串是不相同的:
3.4 布尔(Boolean)文字
布尔文字可以用值TRUE、FALSE和NULL(表示缺失、未知或不可用的值)来表示。记住,布尔文字本身就是值,而不是字符串。
3.5 日期和时间文字
日期因类型的不同,有很多表现形式,比如下面的例子:
|
|
PL/SQL 中,时间间隔用于表示 “两个时间点之间的差值”,必须明确类型:
- YEAR TO MONTH:只能表示 “年和月” 的间隔(如 3 年 2 个月),不能包含天、时、分、秒。格式:INTERVAL ‘年-月’ YEAR TO MONTH(例如'3-2’表示 3 年 2 个月)。
- DAY TO SECOND:只能表示 “天、时、分、秒” 的间隔(如 5 天 4 小时),不能包含年、月。格式:INTERVAL ‘天 时:分:秒.毫秒’ DAY TO SECOND(例如'5 04:03:02.01’表示 5 天 4 时 3 分 2.01 秒)。
我们可以指定间隔值是YEAR TO MONTH类型还是DAY TO SECOND类型。如:current_timestamp - current_timestamp
上面表达式的结果值类型默认是INTERVAL DAY TO SECONDE。我们还可以使用下面的方法来指定间隔类型:
(interval_expression) DAY TO SECOND
(interval_expression) YEAR TO MONTH
|
|
4. 注释
PL/SQL编译器会忽略注释,但我们不可以这样做。添加注释能让我们的程序更加易读。通常我们添加注释的目的就是描述每段代码的用途。PL/SQL支持两种注释风格:单行和多行。
4.1 单行注释
单行注释由一对连字符(–)开头。如下例:
注释可以出现在一条语句的末端。在测试或调试程序的时候,有时我们想禁用某行代码,就可以用注释给它"注掉"(comment-out),如下面的例子:
|
|
4.2 多行注释
多行注释由斜线星号(/)开头,星号斜线(/)结尾,可以注释多行内容。示例如下:
|
|
我们可以使用多行注释注掉整块代码,如下例所示:
三、声明
在PL/SQL中,我们可以在块、子程序或包的声明部分来声明常量或变量。声明能够分配内存空间,指定数据类型,为存储位置进行命名以便我们能够引用这块存储空间。下面来看一下声明的例子:
第一句声明了一个DATE类型的变量。第二句声明了SMALLINT类型的变量,并用赋值操作符指定了初始值零。下面再看一个稍微复杂一点的例子,用一个声明过的变量来初始化另一个变量:
默认情况下,变量是被初始化为NULL的。所以,下面两个声明是等价的:
对于常量声明要多加一个CONSTANT关键字:
|
|
1. 使用DEFAULT
我们可以使用关键字DEFAULT来替换赋值操作符为变量初始化。下面这个声明
|
|
就可以用DEFAULT来替换:
|
|
我们可以使用DEFAULT来初始化子程序参数、游标参数和用户定义的记录中的域。
2. 使用NOT NULL
除了在声明中做初始化操作外,还可以使用NOT NULL进行约束:
|
|
这样一来,我们就不能为变量acct_id指派空值了。如果这样做的话,PL/SQL就会抛出预定义异常VALUE_ERROR。NOT NULL约束后面必须跟着初始化子句。像下面这样的声明是不允许的:
|
|
NATURALN和POSITIVEN是PL/SQL提供的两个不可为空的预定义子数据类型,NATURALN 是一种预定义的子类型(subtype),属于 Oracle 的STANDARD包,用于约束变量只能存储非负整数(0 及正整数)。下面这两个声明是等价的:
在NATURALN和POSITIVEN声明中,类型分类符后面必须跟上一个初始化子句。否则就会发生编译错误。例如,下面的声明就是不合法的:
|
|
3. 使用%TYPE
%TYPE属性能够为我们提供变量或数据库字段的数据类型。在下面的例子中,%TYPE提供了变量credit的数据类型:
在引用数据库中某个字段的数据类型时,%TYPE显得更加有用。我们可以通过表名加字段来引用,或是使用所有者加表名加字段来引用:
|
|
使用%TYPE声明my_dname有两个好处。首先,我们不必知道dname具体的数据类型。其次,如果数据库中对dname的数据类型定义发生了改变,变量my_dname的数据类型也会在运行时作出相应的改变。但是要注意的是,%TYPE只提供类型信息,并不提供NOT NULL约束信息,所以下面这段代码即时是在emp.empno不可为空的情况下也是可以运行的:
4. 使用%ROWTYPE
%ROWTYPE属性提供数据表(或视图)中一整行数据的类型信息。记录可以完整地保存从游标或游标变量中取出的当前行的信息。下面例子中,我们声明了两个记录,第一个保存emp表的行信息,第二个保存从游标c1取出的行信息。
我们还可以为指定的域进行赋值操作,如下例:
%ROWTYPE同%TYPE一样,只提供类型信息,并不能保证NOT NULL约束。在最后一个例子中,我们使用%ROWTYPE来定义一个打包游标(packaged cursor):
|
|
4.1 聚合赋值
用%ROWTYPE作声明的时候是不可以进行初始化赋值的,但是有两种方法可以一次性为所有字段赋值。
方法一:假如两个记录类型的声明引用了同一数据表或游标,那么它们就可以相互赋值,如:
但是,如果一个类型是引用的是数据表而另一个引用的是游标的话,那么,即使它们表现的内容相同,也是不能相互赋值的:
|
|
方法二:我们可以使用SELECT或FETCH语句将取得的数据赋给记录。但在表或视图中定义的字段名称顺序要与记录中的名称顺序相同。
但是,我们不能使用赋值语句来把字段列表中的值赋给记录。所以,下面的语法形式是不允许的:
|
|
4.2 使用别名
从游标中取出的数据,如果游标定义中含有表达式时,我们就需要使用别名才能正确地为%ROWTYPE类型记录赋值:
|
|
5. 声明的约束
PL/SQL不允许向前引用。也就是说我们在使用变量或常量之前必须先声明。像下面这样的语句就是不合法的:
但是,PL/SQL允许向前声明子程序。对于同样数据类型的每一个变量,都必须单独声明:
像下面这样的声明方式是不允许的:
|
|
小结
在数据库时代,特别是Oracle数据库时代,PL/SQL已经成为了数据库应用程序开发的标准语言。
文章作者 会写代码的小郎中
上次更新 2017-09-16
许可协议 CC BY-NC-ND 4.0