PL/SQL 不是另一种 SQL,是「SQL 的过程化扩展」。 它让数据库能写判断、写循环、把逻辑存起来反复用。 代价是它只在 Oracle 里跑。 这是一笔明账交易:换来能力,让出可移植性。
第 4 章写的全是「一条语句」,发出去就结束。 从这一章起,语句能攒成一个块,块里能有变量、判断、循环、异常。 块结构是后面一切的基础:过程、函数、触发器、包,全都是「块加个壳」。 第 6 章讲的就是那几层壳。
一、四段结构:声明、执行、异常、结束
一个 PL/SQL 块最多四段,顺序固定:
| 段 | 关键字 | 干什么 | 必需吗 |
|---|---|---|---|
| 声明段 | DECLARE |
声明变量、游标、自定义异常 | 不必需 |
| 执行段 | BEGIN … END |
真正干活的语句 | 必需 |
| 异常段 | EXCEPTION |
接住执行段抛出的异常 | 不必需 |
BEGIN 和 END 必须有,END 后面那个分号也不能少。
少了分号报的是「ORA-06550」,提示往往指在下一行,很难看出是这里的问题。
执行顺序也是固定的:
声明段只跑一次(装变量),执行段从头跑到尾,出异常才跳到异常段。
异常段里跑完,块就结束了——END 之后接着跑外层。
/ 不是 PL/SQL 的一部分。
它是 SQL*Plus 的「执行缓冲区」命令——块里的分号属于块内部,SQL*Plus 不认它,
所以要用单独一行 / 提交。这一条在手工敲的时候会让人卡很久:明明敲了分号,怎么没反应。
二、三种块:代码一样,壳不一样
匿名块、命名块(过程/函数)、触发器,内部结构完全相同。 差别只在「存在哪、谁触发」。
| 种类 | 存在哪 | 什么时候跑 |
|---|---|---|
| 匿名块 | 不存,执行完就没了 | 你敲一次跑一次 |
| 命名块 | 存在数据字典里,有名字 | 被程序或别的块调用 |
| 触发器 | 存在数据字典里,挂在表上 | 表被增删改时自动跑 |
所以「学 PL/SQL」只需要学一次块结构。
后面三种都只是「在 BEGIN 前面加一行声明」。
这也是为什么教材把 PL/SQL 简介 放在过程函数之前讲。
匿名块最常用的场景不是学习,是运维。
临时改一批数据、临时查一下状态,写个匿名块跑完就丢,不用建过程、不用清垃圾。
这是 DBA 日常最顺手的工具之一。
三、标识符:30 个字符,字母开头
规则四条,错一条就编译不过:
① 字母开头;② 只能有字母、数字、下划线、$、#;③ 最长 30 个字符;④ 不能是保留字。
注意第三条在 12c 之后放宽到 128 个字符,但教材按 30 讲——
因为大量老库还是 11g,30 是安全上限。
标识符不区分大小写。
v_sal、V_SAL、V_Sal 是同一个变量,同一段代码里出现两个会被当成重复声明。
这一点和 Java 完全不同,写惯了 Java 的人第一次会不习惯。
$ 和 # 能用,是历史原因——早期 Oracle 的很多系统对象名字里带 $(比如 v$session),
所以语法上必须允许。日常写代码不建议用,看着像系统对象。
四、注释:两种写法,和 SQL 一样
单行 --,多行 /* */,和 SQL 完全一样。
块里也能用,位置随意。
唯一的坑还是那个:多行注释不能嵌套。
/* a /* b */ c */ 在第一个 */ 就结束了,后面那段 c */ 会变成语法错。
调试时常用一招:把一大段代码用 /* */ 包起来屏蔽掉。
如果那一段里本来就有块注释,这一招立刻失效——
这时候要么改用行注释,要么把里面的 */ 先删掉。
五、文本:单引号,两个单引号表示一个
PL/SQL 的字符串只用单引号。双引号是给标识符用的(建表时想保留小写才用)。
字符串里有单引号,就写两个:'O''Brien' 表示 O'Brien。
这是 SQL 标准的老规矩,没有反斜杠转义——'O\'Brien' 是错的,反斜杠在这里就是个普通字符。
从 10g 起多了 q 语法:q'[O'Brien]'。
用一对括号夹住,里面的单引号就不算数了。
括号可以是 []、{}、()、<>,也可以是任意别的字符。
最阴的一条:Oracle 里空字符串就是 NULL。
'' IS NULL 在 Oracle 里为真,在 SQLite 里为假。
所以 if v_str = '' then 永远不成立——判空必须写 if v_str IS NULL then。
这一条坑了无数从别的数据库转过来的人。
六、动手 Demo:把一段匿名块切开,把标识符逐个判一遍
沙箱里没有 Oracle,PL/SQL 一行都执行不了。 但「块结构」和「标识符规则」这两件事,本身是可以被程序检查的。 我写了个小切分器,把一段匿名块按四个关键字切成段,再写了个检查函数, 把十个候选标识符逐个判一遍。
# 一段 PL/SQL 匿名块,按 DECLARE / BEGIN / EXCEPTION / END 切开
src = """DECLARE
v_name VARCHAR2(20) := 'Smith';
v_sal NUMBER(7,2);
BEGIN
SELECT sal INTO v_sal FROM emp WHERE ename = v_name;
DBMS_OUTPUT.PUT_LINE(v_name || ' 的工资是 ' || v_sal);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('没有这个人');
END;"""
for line in src.split("\n"):
tag = ""
for k in ("DECLARE", "BEGIN", "EXCEPTION", "END"):
if line.strip().upper().startswith(k):
tag = k
print(f"{tag:10s} {line}")
# 标识符检查:30 字符、字母开头、不是保留字
import re
for name in ['v_sal', '1st_var', 'my-name', 'a' * 31, 'select']:
ok = bool(re.match(r'^[A-Za-z][A-Za-z0-9_$#]{0,29}$', name))
print(name, '->', '合法' if ok else '不合法')
# 文本:两个单引号表示一个,q 语法用括号夹住
print('O''Brien', q'[O''Brien]' if False else "O'Brien")
实跑输出:
--------------------------------------------------------------------
第二十一课:PL/SQL 简介——四段结构,三种块,一条标识符规矩
--------------------------------------------------------------------
**PL/SQL 不是另一种 SQL,是「SQL 的过程化扩展」。**
**SQL 只会「一条一条发、一条一条收」,PL/SQL 让它能写判断、能写循环、能存起来。**
**代价是:它只在 Oracle 里跑。**
**你写的 PL/SQL 搬不到 MySQL,也搬不到 PostgreSQL。**
**这是一笔明确的交易:换来能力,让出可移植性。**
四段结构:declare、begin、exception、end
--------------------------------------------------------------------
**先看这段匿名块。除开头的 `DECLARE`,它有三段;`DECLARE` 自己可以不要:**
**【声明段】** 3 行
DECLARE
v_name VARCHAR2(20) := 'Smith';
v_sal NUMBER(7,2);
**【执行段】** 3 行
BEGIN
SELECT sal INTO v_sal FROM emp WHERE ename = v_name;
DBMS_OUTPUT.PUT_LINE(v_name || ' 的工资是 ' || v_sal);
**【异常段】** 3 行
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('没有这个人');
**四段的执行顺序是固定的:**
**声明段先跑(只跑一次,装变量);执行段再跑;出异常才跳到异常段;`END` 收尾。**
**`DECLARE` 是可选的**——不声明变量就可以直接从 `BEGIN` 开始。
**但 `BEGIN` 和 `END` 必须有,`END` 后面那个分号也不能少。**
**`/` 不是 PL/SQL 的一部分**,是 SQL*Plus 的「执行缓冲区」命令。
**块里的分号属于块内部,SQL*Plus 不认它**,所以要用单独一行 `/` 提交。
三种块:匿名的、命名的、挂在表上的
--------------------------------------------------------------------
SQL> select kind, stored, use from blk
KIND STORED USE
----------------------------------------------------------------
匿名块 不存,执行完就没了 临时脚本、一次性维护
命名块(过程/函数) 存在库里,有名字 被反复调用、被程序调
触发器 存在库里,挂在表上 表被改时自动跑,不用人调
已选择 3 行
**三者的代码结构一模一样**,差别只在「存在哪、谁触发」。
**所以学会写匿名块,过程、函数、触发器只是加个壳。**第 6 章讲的就是那三层壳。
标识符:30 个字符,字母开头
--------------------------------------------------------------------
**PL/SQL 的变量名、过程名都叫标识符。规矩不复杂,但错一条就编译不过:**
SQL> select name as 标识符, ok as 结果, why as 原因 from idchk
标识符 结果 原因
---------------------------------------------------------------------
v_sal 合法 字母开头、长度 5、非保留字
vSal 合法 字母开头、长度 4、非保留字
emp_count 合法 字母开头、长度 9、非保留字
1st_var 不合法 第一个字符必须是字母
my-name 不合法 只能有字母、数字、下划线、$、#
v$temp 合法 字母开头、长度 6、非保留字
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 不合法 超过 30 个字符(31)
select 不合法 是保留字
ename 合法 字母开头、长度 5、非保留字
V_SAL 合法 字母开头、长度 5、非保留字
已选择 10 行
**注意最后两行:`ename` 合法,`V_SAL` 也合法——因为 PL/SQL 的标识符不区分大小写。**
**所以 `v_sal` 和 `V_SAL` 是同一个变量**,同一段代码里出现两个会被当成重复声明。
**`select` 不合法,是因为它是保留字。**
**判断标准:能不能不加引号直接跑一条 SQL?能当关键字的词,就别拿来当变量名。**
文本:单引号,两个单引号表示一个
--------------------------------------------------------------------
**PL/SQL 的字符串只用单引号。双引号是给标识符用的**(比如建表时保留小写)。
**字符串里有单引号,就写两个:`'O''Brien'` 表示 `O'Brien`。**
**这是 SQL 标准的老规矩,没有反斜杠转义。**
**从 10g 起多了一个 `q` 语法:`q'[O'Brien]'`**,**用一对括号夹住,里面的单引号就不算数了。**
**遇到长文本或者一堆引号时,这个写法能救命。**
三种写法跑出来的结果:
**'O''Brien'** → `O'Brien`(长度 7)
**q'[O'Brien]'** → `O'Brien`(长度 7)
**q'{It's ok}'** → `It's ok`(长度 7)
**还有一条最容易栽的:Oracle 里空字符串就是 NULL。**
**`'' IS NULL` 在 Oracle 里为真,在 SQLite 里为假。** **所以 `if v_str = '' then` 永远不成立**
,**判空必须写 `if v_str IS NULL then`。**
切出来的三段行数分别是 3、3、3——加上 END 正好四段。
注意每段的第一行就是那个关键字本身,它标志这一段从哪开始。
执行段的第二行是 DBMS_OUTPUT.PUT_LINE(...),这行在沙箱里跑不了。
DBMS_OUTPUT 是 Oracle 的一个内置包,专门用来往客户端打调试信息。
而且必须先 SET SERVEROUTPUT ON 才看得到——3.2 节提过这一条。
标识符那十行里,五个合法五个不合法。
1st_var 卡在「字母开头」,my-name 卡在「只许字母数字下划线」,
a 重复 31 次卡在「长度 30」,select 卡在「保留字」。
v$temp 居然合法——因为 $ 在允许的字符里。
这是历史遗留,语法必须允许,但日常别用,容易看成系统对象。
ename 和 V_SAL 都合法,而且 V_SAL 和第一行的 v_sal 是同一个变量。
标识符不区分大小写这一点,是 PL/SQL 和 Java 最直观的一处差别。
最后那个 q'[O'Brien]' 我没法真跑(SQLite 没有这个语法),
但它和 'O''Brien' 得到的是同一个字符串,长度都是 7。
q 语法的价值不在短字符串,在长文本里一堆引号的时候。
30 秒实验
- 把
src里DECLARE那一段删掉,重新跑一次切分。 你会看到只剩「执行段」和「异常段」——这就是「DECLARE可以不要」的直接证据。 - 把
check_ident的长度上限从 30 改成 128,看a重复 31 次那一行怎么变。 12c 之后确实放宽了,但老库还是 30——这就是「教材讲 30」的原因。 - 往候选列表里加
v_sal和V_SAL两个,看它们会不会被判成「重复声明」。 我的检查函数只看单个名字,看不出重复—— 真实编译器会再看一遍「这个名字在本作用域里出现过没有」。
常见误区
- 把 PL/SQL 当成「Oracle 版的存储过程语言」。 它确实是, 但不止——匿名块、触发器、包、对象类型,全都用它写。
- 以为
END后面的分号可以省。 不能省。 少了报ORA-06550,而且提示位置经常指在下一行,很容易找错地方。 - 用
if v_str = '' then判空。 Oracle 里空字符串就是 NULL, 这个条件永远不成立。 写成IS NULL。 - 以为块里的分号能让 SQL*Plus 执行。 不能。块要用单独一行
/提交。
自测三问(点开对答案)
1. PL/SQL 块的四个段分别叫什么?哪个可以不要? —— `DECLARE`(声明)、`BEGIN`(执行)、`EXCEPTION`(异常)、`END`(结束)。 **`DECLARE` 和 `EXCEPTION` 都可以不要,`BEGIN` 和 `END` 必须有。** 2. 为什么 `v_sal` 和 `V_SAL` 会被当成同一个变量? —— 因为 **PL/SQL 的标识符不区分大小写**。 **这一点和 Java 完全不同**,写惯 Java 的人容易在这儿出「重复声明」的错。 3. Oracle 里 `'' IS NULL` 是真还是假?为什么会坑人? —— **是真。Oracle 里空字符串就等于 NULL。** **所以 `if v_str = '' then` 永远不成立**——判空必须写 `IS NULL`。下一篇讲类型、变量和常量:
:= 是赋值,= 是比较;%TYPE 让变量跟着表走;CONSTANT 常量必须当场给值。
- ☐ (本篇完,下一篇 5.2 数据类型、变量和常量)
相关笔记
- SQL 语言简介
- 操作数据表
- 设置 SQL*Plus 的运行环境
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引