SQL 没有流程控制,这正是 PL/SQL 存在的理由。
选择两个(IF、CASE),循环三个(LOOP、WHILE、FOR)。
三处和别的语言不一样:ELSIF 少一个 E;FOR 的上下界是闭区间;FOR 不能指定步长。
5.2 节把变量备好了,这一篇让变量「能按条件动」。 游标、过程、函数里的逻辑判断,全靠这一篇的东西拼。 循环这件事在 PL/SQL 里要格外小心——没有「最大执行次数」保护,写错了就是真死循环, 只能杀会话。
一、选择:IF / ELSIF / ELSE
拼写是 ELSIF,少一个 E。
写成 ELSEIF 会报语法错,而且报错位置常常指在下一行,
很难看出是拼写问题——这是新手最常卡的一处。
IF 后面没有括号,条件直接写在 THEN 前面,THEN 不能省。
END IF 是两截,中间有空格。
这三条和 C 系语言全不一样。
ELSIF 可以有很多个,ELSE 最多一个且在最后。
判断顺序是从上往下,命中一个就跳出整个 IF,
所以条件要「从窄到宽」写——写反了后面的分支永远轮不到。
二、CASE:两种形态
简单 CASE 是「一个值对多个固定分支」:
CASE v_job WHEN 'CLERK' THEN ... WHEN 'MANAGER' THEN ... END。
搜索 CASE 是「一堆条件各判各的」:
CASE WHEN v_sal < 1000 THEN ... WHEN v_sal < 3000 THEN ... END。
两者只能选一种,不能混。
而且 CASE 是「表达式」,能出现在赋值右边——这一点比 IF 强,IF 是语句,不能当值用。
CASE 里所有分支必须返回同一类型。
一个分支给数字、一个给字符串,编译不过。
这个限制看着烦,其实是好事——它保证了你不会在运行时拿到一个「说不清是什么类型」的值。
三、循环一:LOOP + EXIT WHEN,至少跑一次
LOOP ... END LOOP 本身是死循环,出口只有一个:EXIT 或 EXIT WHEN。
忘了写出口,就是真死循环。
EXIT WHEN 是在「循环体末尾」判断的,所以语义是「先做完这一轮,再决定走不走」。
这意味着循环体至少执行一次。
这个特性和 WHILE 正好相反。
所以「至少要尝试一次」的场景(比如先取一行再看要不要继续),
LOOP + EXIT WHEN 才是对的写法。
游标遍历就是标准例子:先 FETCH,再 EXIT WHEN %NOTFOUND。
用 WHILE 写反而别扭,因为「有没有下一行」要先取一次才知道。
四、循环二:WHILE,先判断再进
WHILE 条件 LOOP ... END LOOP——条件为假就一次都不跑。
这一点和 LOOP 是根本差别。
条件在每一轮开始前重新算一次。
所以循环体里必须改那个条件依赖的变量,否则永远为真。
最常见的死循环就是这么来的:条件里用了 i,循环体里忘了 i := i + 1。
PL/SQL 不会提醒你,它就这么一直转下去,直到你杀掉会话。
五、循环三:FOR,闭区间且不能指定步长
FOR i IN 1..5 LOOP 跑 5 次,i 依次是 1 到 5,两头都含。
这和 C 语言的 for (i=0;i<5;i++) 不一样——后者是「小于」,前者是「小于等于」。
REVERSE 反的是方向,不是范围。
FOR i IN REVERSE 1..5 是 5、4、3、2、1,写法还是 1..5,
写成 5..1 会直接报「下界大于上界」。
循环变量是自动声明的——不用在 DECLARE 里写,也不能在循环体里给它赋值。
而且作用域只在循环内,出了 END LOOP 就没了。这三条都省心。
步长不能指定,FOR 只能一次走 1。
要步长 2 只能自己用 WHILE 加。
这一条和 Python 的 range(1, 6, 2) 比就很不方便,
但 PL/SQL 的 FOR 定位是「遍历」,不是「计数循环」。
CONTINUE 从 11g 起才有,写法是 CONTINUE 或 CONTINUE WHEN 条件。
在 10g 及更早版本里没有,只能用 IF ... THEN ... END IF 把剩下的代码包起来。
接手老库时这一条很实用——看到 CONTINUE 就知道这库至少是 11g。
六、动手 Demo:两个选择、三个循环,逐个跑出次数
这一段用 Python 复刻 PL/SQL 的语义,重点看「跑了几次、结果是多少」。
次数比结果更能说明问题——它直接反映「先判断还是先执行」。
FOR 的闭区间和 REVERSE 的方向,也在这一段里验证。
# IF / ELSIF / ELSE 与 CASE 在「按值分档」上等价
def grade_if(v):
if v < 1000:
return '低'
elif v < 3000:
return '中'
else:
return '高'
def grade_case(v):
return '低' if v < 1000 else ('中' if v < 3000 else '高')
for v in [800, 1600, 2975, 5000]:
print(v, grade_if(v), grade_case(v))
# 循环一:LOOP + EXIT WHEN —— 先做再判,至少跑一次
i, acc, n = 1, 0, 0
while True:
acc += i
n += 1
if i >= 5:
break
i += 1
print('LOOP', n, '次', acc)
# 循环二:WHILE —— 先判再做,可能一次都不跑
i, acc, n = 10, 0, 0
while i <= 5:
acc += i
n += 1
i += 1
print('WHILE', n, '次', acc)
# 循环三:FOR 1..5 是闭区间,REVERSE 反方向不反范围
print('FOR', sum(range(1, 6)))
print('REVERSE', list(range(5, 0, -1)))
实跑输出:
--------------------------------------------------------------------
第二十三课:流程控制——两个选择,三个循环
--------------------------------------------------------------------
**SQL 没有流程控制,这正是 PL/SQL 存在的理由。**
**这一节的东西别的语言都有,但 PL/SQL 有几个细节和它们不一样。**
**记住三件事:`ELSIF` 不是 `ELSEIF`;跳出循环靠 `EXIT`/`EXIT WHEN`;`FOR` 的上下界是「闭区间」。**
选择:IF / ELSIF / ELSE
--------------------------------------------------------------------
**注意拼写:PL/SQL 是 `ELSIF`,少一个 `E`。**
**写成 `ELSEIF` 会报语法错,而且报错位置常常指在下一行,很难看出是拼写问题。**
**`IF` 后面没有括号,条件直接写在 `THEN` 前面;`THEN` 不能省。**
**`END IF` 是两截,中间有空格。**
按工资分档,跑出来是这样:
SQL> select sal as 工资, if_grade as IF写的档, case_grade as CASE写的档 from grade
工资 IF写的档 CASE写的档
------------------------
800 低 低
1600 中 中
2975 中 中
5000 高 高
已选择 4 行
**两列结果完全一样**——`IF/ELSIF` 和 `CASE` 在同一种「按值分档」的场景下等价。
**区别在可读性:`CASE` 更适合「一个值对多个固定分支」,`IF` 更适合「区间判断」。**
循环一:LOOP + EXIT WHEN
--------------------------------------------------------------------
**`LOOP … END LOOP` 是死循环,必须靠 `EXIT WHEN` 跳出来。** **忘了写出口,就是真死循环**——
PL/SQL 里没有「最大执行次数」保护。
**累加 1 到 5:循环体跑了 5 次,结果 15。**
**走的是 1 → 2 → 3 → 4 → 5。**
**`EXIT WHEN` 是在「循环体末尾」判断的,所以条件是「先做完这一轮再决定走不走」。**
**这一点和 `WHILE` 相反——`WHILE` 是「先判断再进」。**
循环二:WHILE——先判断再进
--------------------------------------------------------------------
**同样的累加,`WHILE i <= 5 LOOP` 结果也是 15。**
**但两者有个关键差别:条件一开始就不成立时,`WHILE` 一次都不跑,`LOOP` 至少跑一次。**
**`WHILE i <= 5` 而 `i` 一开始就是 10:跑 0 次,结果 0。**
**换成 `LOOP … EXIT WHEN i > 5`:跑 1 次,结果 10。**
循环三:FOR——闭区间,且不用自己加
--------------------------------------------------------------------
**`FOR i IN 1..5 LOOP` 会跑 5 次,`i` 依次是 1、2、3、4、5。** **两头都含**——
这是 PL/SQL 的 `FOR`,和 C 语言的 `for (i=0;i<5;i++)` 不一样。
**累加结果 15。**
**`FOR i IN REVERSE 1..5` 是 5、4、3、2、1,还是 5 次**——**`REVERSE` 反的是方向,不是范围**,
写法还是 `1..5`,不是 `5..1`。
实际顺序:5 → 4 → 3 → 2 → 1
**`FOR` 的循环变量是「自动声明的」**——不用在 `DECLARE` 里写,
**也不能在循环体里给它赋值。** **而且它的作用域只在循环内**,出了 `END LOOP` 就没了。
**步长不能指定。** PL/SQL 的 `FOR` 只能一次走 1, **要步长 2 只能自己用 `WHILE` 加。**
**`CONTINUE` 从 11g 起才有,写法是 `CONTINUE` 或 `CONTINUE WHEN 条件`。**
**在 10g 及更早的版本里,只能用 `IF … THEN … END IF` 把剩下的代码包起来。**
**接手老库的时候这一条很实用。**
分档那四行,IF 和 CASE 的结果一模一样。
说明在「按值分档」这类场景下,两者完全可以互换。
选哪个只看可读性:区间判断用 IF,固定分支用 CASE。
注意 2975 落在「中」这一档。
因为判断顺序是 < 1000 → < 3000,2975 不满足第一个,满足第二个,就停了。
如果把顺序写成 < 3000 在前,那 800 也会被判成「中」。
LOOP + EXIT WHEN 跑 5 次,累加到 15。
关键是它「先做再判」——所以即使条件一开始就成立,它也至少跑一次。
WHILE 那一段我把 i 设成 10,条件是 i <= 5,结果跑 0 次。
这正好证明了 WHILE 是「先判断再进」。
同样的初始值换成 LOOP,就会跑 1 次——这就是两者最本质的区别,也是「该用哪个」的唯一判据。
FOR 累加 1 到 5 得 15,和前面两个一样。
说明三种循环在「次数确定」的场景下等价,选哪个只看哪种写着顺。
REVERSE 打出 5、4、3、2、1,还是 5 个元素。
这说明 REVERSE 只是反向遍历,范围还是 1..5。
写成 REVERSE 5..1 在 PL/SQL 里直接报错,因为下界大于上界。
30 秒实验
- 把
grade_if的分支顺序掉过来(先判< 3000),看 800 那一行变成什么档。 这就是「条件要从窄到宽写」的反面教材。 - 把
WHILE那一段的i改成 1,看跑几次。 再改成 5,看跑几次——边界值是「闭区间」还是「开区间」,一测就知道。 - 用
FOR写一个「只累加偶数」的循环:FOR i IN 1..10 LOOP IF MOD(i,2)=0 THEN ... END IF; END LOOP;。 注意MOD是函数不是运算符,要写括号。 - 把
LOOP那一段的break删掉,看会发生什么。 在 PL/SQL 里这就是死循环,只能靠ALTER SYSTEM KILL SESSION收拾。
常见误区
- 写
ELSEIF。 PL/SQL 是ELSIF,少一个E。报错位置还常指在下一行,很容易找错地方。 - 以为
FOR i IN 1..5跑 4 次。 是 5 次,两头都含。 这是 PL/SQL 和 C 系语言最容易搞混的一处。 WHILE循环体里忘了改条件变量。 永远为真,真死循环。PL/SQL 不会替你中断。REVERSE后面写5..1。 报错。REVERSE反的是方向,范围照样写1..5。
自测三问(点开对答案)
1. `LOOP + EXIT WHEN` 和 `WHILE` 最本质的差别是什么? —— **`LOOP` 先执行再判断,至少跑一次;`WHILE` 先判断再执行,可能一次都不跑。** **「至少要尝试一次」的场景必须用 `LOOP`。** 2. `FOR i IN REVERSE 1..5` 的遍历顺序是什么? —— **5、4、3、2、1。** `REVERSE` 只反方向,**范围还是 `1..5`**。 **写成 `5..1` 会报「下界大于上界」。** 3. 为什么说 `CASE` 比 `IF` 灵活一点? —— 因为 **`CASE` 是表达式,能出现在赋值右边**;`IF` 是语句,不能当值用。 **`v_grade := CASE WHEN ... END;` 是合法的,`:= IF ...` 不合法。**下一篇讲游标:结果集上的一个指针。声明、打开、取、关闭四步,一步都不能少。
- ☐ (本篇完,下一篇 5.4 PL/SQL 游标)
相关笔记
- 数据类型、变量和常量
- PL/SQL 简介
- 检索数据
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引