IF 和 CASE 是表达式,能塞进 SELECT;循环只能写在存储过程里。
CASE 有两种形态:比等值的简单形态、比条件的搜索形态,不能混用。
三种循环的差别只有一个:什么时候判断条件。先判断、后判断、不判断。
7.1 讲的是「怎么算」,这一篇讲「按条件走哪条路」。
它是第 18 章存储过程的前置:循环必须写在存储过程里,这一篇先讲语义。
这一篇的三值逻辑会再出现一次:CASE WHEN NULL 不会命中,会落到 ELSE。
一、IF 是表达式,不是语句
MySQL 的 IF(条件, 真值, 假值) 长得像函数,本质是一个表达式。
表达式意味着它能出现在任何「值能出现的地方」,包括 SELECT 的列表。
SELECT name, price, IF(price > 100, '贵', '便宜') AS level FROM tb_goods;
UPDATE tb_goods SET level = IF(price > 100, '贵', '便宜');
这两句说明它的用处:按条件给一列打标签。
它和编程语言里的 if 语句不是一回事——那个是控制流程,只影响「走哪条路」,不产生值。
区分清楚这一点,后面 CASE 就好懂了:CASE 也是表达式,也是产生一个值。
所以它们能出现在 SELECT、WHERE、ORDER BY 里,但不能用来「跳过某条语句」。
二、CASE 的两种形态
CASE 有两种写法,教材 7.2 都讲了,区别在于「比什么」。
-- 简单 CASE:把一个列拿去和几个值比
SELECT CASE sex WHEN 'M' THEN '男' WHEN 'F' THEN '女' ELSE '未知' END FROM tb;
-- 搜索 CASE:每个 WHEN 后面是完整条件
SELECT CASE WHEN score >= 90 THEN '优' WHEN score >= 60 THEN '及格' END FROM tb;
第一种只在「同一个列比几个固定值」时好用,第二种什么条件都能写。
实践里第二种用得多,因为它能比区间、能组合多个列。
两种形态不能混用。
写了 CASE 列 WHEN 就只能 WHEN 值,写成 WHEN 条件 不一定报错,但结果会很怪。
这属于「语法上过得去、语义上不对」的坑,比报错更难查。
还有一条顺序规则:CASE 是按顺序匹配的,第一个成立的就返回,后面的不再看。
所以 WHEN score >= 60 必须写在 WHEN score >= 90 后面,反了就全变「及格」。
三、CASE 与 NULL 撞在一起
这一节教材没讲,但它一定会撞上。
CASE WHEN 列 >= 60 在列是 NULL 时,条件算出来是 NULL,不算成立。
所以缺考的学生会被分到 ELSE 那一支,变成「不及格」。
可「缺考」和「考零分」应该是两种状态,用这套 CASE 分不出来。
CASE WHEN score IS NULL THEN '缺考'
WHEN score >= 90 THEN '优'
WHEN score >= 60 THEN '及格'
ELSE '不及格' END
把 IS NULL 那一支放最前面,就能分开。
注意它必须放最前面——放后面就轮不到它了,因为前面的 ELSE 已经兜住了。
这条经验可以推广:凡是 CASE 里要处理空值,就把空值那支写在第一个。
它和「WHERE 里判断空要用 IS NULL」是同一条原理的两种表现。
四、三种循环:差别只在什么时候判断
教材 7.2 讲了 WHILE、LOOP、REPEAT 三种。名字不同,功能几乎一样。
真正的差别只有一处:条件在什么时候判断。
| 循环 | 什么时候判断 | 最少跑几次 | 出口怎么写 |
|---|---|---|---|
WHILE |
执行之前 | 0 次 | 条件写在开头 |
REPEAT |
执行之后 | 1 次 | UNTIL 条件 写在末尾 |
LOOP |
不判断 | 0 次也能进去 | 必须自己 LEAVE |
第二列是这一节的全部内容。
WHILE 条件不成立就一次都不跑;REPEAT 至少跑一次,因为它是跑完才判断。
这个差别在「条件一开始就不成立」时最明显。
比如「从 100 加到 10」,WHILE 给 0,REPEAT 给 100——后者多做了一次不该做的事。
LOOP 那一行值得单独说:它自己不带任何条件,所以写成死循环是常态。
必须自己 LEAVE 才能出来。
它的用处是「出口条件很复杂、一句话写不出来」的时候。
五、LEAVE 与 ITERATE:两个陌生的名字
这两个名字第一次见会懵,其实就是 break 和 continue。
| 关键字 | 相当于 | 作用 |
|---|---|---|
LEAVE 标签 |
break |
跳出整个循环 |
ITERATE 标签 |
continue |
跳过本次,回去继续下一次 |
两个都要跟一个「标签」,所以循环得起个名字。
标签的写法是 名字: LOOP ... END LOOP 名字;,冒号在前,名字在 END 后也要写一遍。
ITERATE 比 break 更容易用错。
它「跳过本次」的意思是「本次剩下的语句不执行了,
直接进入下一次判断」——所以写在它前面的累加不会被执行,写在它后面的才有机会。
六、循环只能进存储过程
这一节是教材没强调、但一定撞得上的一条。
WHILE、REPEAT、LOOP 不能出现在普通 SQL 里。
它们只能写在存储过程、函数、触发器、事件这四类对象内部。
所以「用 SQL 写个循环」的前提是先建一个存储过程。
很多人第一次写循环就报语法错,就是因为把它写在了客户端里。
但更该记住的是下一句:能用一条 SQL 解决的,就别写循环。
SQL 是按集合算的,循环是逐行算的。
同样一件事,集合写法可能比循环快几十倍,因为前者一次读写一批页,后者每行都要走一趟。
判断标准很简单:如果循环体里每条语句只处理一行,
那它多半能被一条 UPDATE 或 INSERT ... SELECT 替掉。
真需要循环的场景很少:分批处理大表、按序做有依赖的计算、生成测试数据。
七、动手 Demo:IF、CASE、三种循环,一条一条跑
这一课把 IF 和 CASE 真跑一遍,再用 Python 按语义复刻三种循环。
循环的 MySQL 原码会贴出来对照——沙箱里它们跑不了,但语义是确定的。
其中 CASE 那一段会出现一个真问题:缺考的学生被分成了「不及格」。
# 一、IF 是表达式
c0.execute("SELECT iif(?, '真', '假')", (1,)) # SQLite 把它叫 iif
# 二、CASE 分级:注意 NULL 落到 ELSE
c.execute("SELECT CASE WHEN ? >= 90 THEN '优' "
"WHEN ? >= 80 THEN '良' "
"WHEN ? >= 60 THEN '及格' ELSE '不及格' END", (score, score, score))
# 三、三种循环的语义复刻
def while_sum(start, end):
s, i = 0, start
while i <= end:
s += i; i += 1
return s
def repeat_sum(start, end):
s, i = 0, start
while True:
s += i; i += 1
if i > end: break # UNTIL i > end
return s
# 四、LEAVE 与 ITERATE
if i > 5: break # LEAVE
if i % 2 == 0: continue # ITERATE
实跑输出:
--------------------------------------------------------------------
第三十二课:IF 和 CASE 是表达式,循环只能进存储过程
--------------------------------------------------------------------
**教材 7.2 把流程控制分成两支:`IF`/`CASE` 是「选一个」,三种循环是「重复做」。**
**这一支分得很关键:前者能塞进 `SELECT`,后者只能写在存储过程里。**
**这一课两条都走一遍。能真跑的跑,跑不了的按语义复刻。**
一、IF 是表达式,不是语句
--------------------------------------------------------------------
**MySQL 的 `IF(条件, 真值, 假值)` 是表达式,可以出现在 `SELECT` 的列里。**
**这和编程语言里的 `if` 语句不是一回事。**
**SQLite 对应的是 `iif()`,三个参数位置一样。**
表达式 本次实测 MySQL 写法 MySQL 结果
-----------------------------------------------------------
iif(1, '真', '假') 真 IF(1, '真', '假') 真
iif(0, '真', '假') 假 IF(0, '真', '假') 假
**两个结果都和 MySQL 一致,因为「条件为真取第一个值」这件事没有歧义。**
**真正要注意的是它的位置:它出现在 `SELECT` 的列表里,说明它是表达式,能参与运算。**
**所以可以写 `SELECT name, IF(price > 100, '贵', '便宜') FROM tb`。**
**这种「按条件给一列打标签」的活,用它最省事。**
二、CASE 的两种形态
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| SELECT CASE sex WHEN 'M' THEN '男' WHEN 'F' THEN '女' ELSE '未知' END FROM tb;
| SELECT CASE WHEN score >= 90 THEN '优' WHEN score >= 60 THEN '及格' END FROM tb;
② 教材原码,本沙箱不执行
**第一种叫「简单 CASE」,它比的是「等不等」。**
**第二种叫「搜索 CASE」,每个 `WHEN` 后面是一个完整条件,比分区间用它。**
**两种形态不能混用:写了 `CASE 列 WHEN` 就只能比等值,要写条件必须把列去掉。**
**混着写不一定报错,但结果会很怪。**
**五个人,分数分别是 95、82、61、45 和「没考」。真跑一遍分级:**
姓名 分数 CASE 给的结果
-------------------------
赵一 95 优
钱二 82 良
孙三 61 及格
李四 45 不及格
周五 NULL 不及格
**第五个人没考,分数是 NULL,落到 `ELSE` 那一支,成了「不及格」。**
**这是个真问题:缺考和考零分应该是两种状态,用 CASE 分不出来。**
**原因是 `NULL >= 60` 给的是 NULL,不是假,所以每个 `WHEN` 都不成立。**
**要区分缺考,得在最前面加一句 `WHEN score IS NULL THEN '缺考'`。**
**这一条是 CASE 与三值逻辑撞在一起的地方,很值得记住。**
三、WHILE、REPEAT、LOOP:三种循环
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| -- WHILE:先判断后执行,可能一次都不跑
| WHILE i <= 10 DO
| SET s = s + i; SET i = i + 1;
| END WHILE;
|
| -- REPEAT:先执行后判断,至少跑一次
| REPEAT
| SET s = s + i; SET i = i + 1;
| UNTIL i > 10 END REPEAT;
|
| -- LOOP:没有出口,必须自己 LEAVE
| my_loop: LOOP
| SET s = s + i; SET i = i + 1;
| IF i > 10 THEN LEAVE my_loop; END IF;
| END LOOP my_loop;
③ 教材原码,本沙箱不执行
**三种循环的差别不在语法,在「什么时候判断条件」。**
**`WHILE` 先判断,`REPEAT` 后判断,`LOOP` 不判断、全靠你自己 `LEAVE`。**
写法 1 加到 10 起始值 100(条件一开始就不成立)
---------------------------------------------------
WHILE 55 0
REPEAT 55 100
LOOP 55 100
**第一列三种都是 55,说明三种写法能表达同一件事。**
**第二列才是差别:`WHILE` 给 0(一次没跑),`REPEAT` 和 `LOOP` 都给 100(都跑了一次)。**
**这就是「先判断」和「后判断」的全部区别。**
**实践中 `WHILE` 用得多,`REPEAT` 只在「必须先做一次」时用,`LOOP` 几乎不用。**
四、LEAVE 和 ITERATE:break 与 continue
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| -- LEAVE 相当于 break:跳出整个循环
| IF i > 5 THEN LEAVE my_loop; END IF;
|
| -- ITERATE 相当于 continue:跳过本次,回去继续
| IF MOD(i, 2) = 0 THEN ITERATE my_loop; END IF;
④ 教材原码,本沙箱不执行
**两个名字很陌生,功能一点都不陌生:`LEAVE` 是 `break`,`ITERATE` 是 `continue`。**
**它们后面都要跟一个标签名,所以循环得先起个名字。**
写法 1 加到 10 的结果
--------------------------------------------
正常跑完 55
i > 5 就 LEAVE(break) 15
偶数就 ITERATE(continue) 25
**第二行是 15,正好是 1 到 5 的和,说明从 6 开始就跳出去了。**
**第三行是 25,正好是 1、3、5、7、9 的和,说明偶数被跳过了。**
**这两个数是可验算的,建议自己心算一遍。**
**循环写错的时候,往往就是这两处出了问题:该跳出的没跳出,该跳过的没跳过。**
五、循环只能写在存储过程里
--------------------------------------------------------------------
**这一节是教材没强调、但一定会撞上的一条。**
**`WHILE`、`REPEAT`、`LOOP` 不能出现在普通的 `SELECT` 或 `INSERT` 里。**
**它们只能写在存储过程、函数、触发器、事件这四类对象内部。**
**所以「用 SQL 写个循环」这件事,前提是你得先建一个存储过程。**
**这也是很多人第一次写循环就报语法错的原因——他们把它写在了客户端里。**
**反过来说,能用一条 SQL 解决的事,就别写循环。**
**SQL 是按集合算的,循环是逐行算的,后者往往慢一个数量级。**
**这一点在第 18 章讲存储过程时会再展开。**
第一段两行都和 MySQL 一致。iif() 是 SQLite 的写法,IF() 是 MySQL 的,三个参数位置一样。
要紧的是它在 SELECT 列表里——这说明它是表达式,不是语句。
第二段那张分级表,前四个都正常,第五个出了问题:周五 的分数是 NULL,结果是「不及格」。
因为 NULL >= 90 给的是 NULL,不是假,所以每个 WHEN 都不成立,最后落到 ELSE。
这就是三值逻辑进 CASE 的样子。
修法是在最前面加一支 WHEN score IS NULL THEN '缺考'。
必须放最前面,放后面就轮不到它。
第三段那张表:WHILE 和 REPEAT 在「1 加到 10」时都给 55,看不出区别。
换成「从 100 加到 10」,WHILE 给 0,REPEAT 给 100——差别全出来了。
第四段两个数是 15 和 25。
15 是 1 到 5 的和(6 开始就 LEAVE 了),25 是 1、3、5、7、
9 的和(偶数都 ITERATE 掉了)。
这两个数可以手算验证,建议验一遍。
30 秒实验
- 把
CASE里>= 90和>= 60这两支的顺序对调,再跑一次。95 分的人会变成「及格」。 因为CASE按顺序匹配,第一个成立就返回,后面的不再看。 - 给
WHILE那一段加上i = 0时ITERATE,看看会不会死循环。 会。因为i没机会加一,条件永远成立。 这是continue最经典的坑,写循环时要先确认「自增那一步不会被跳过」。 - 想一想:给 10 万行数据按分数打等级,用循环和用一条
UPDATE ... CASE差多少? 差一个数量级以上。 循环是 10 万次单行更新,语句是 1 次批量更新。
常见误区
- 「
IF和CASE可以控制流程,跳过某条语句。」 它们都是表达式,只产生一个值。 跳过语句是「如果……就不执行」的意思,SQL 里没有这种东西,只有WHERE筛掉行。 - 「
WHILE和REPEAT是一样的,随便用哪个。」 条件一开始就不成立时,WHILE一次不跑,REPEAT跑一次。 这处差别在「必须至少做一次」的场景里是致命的。 - 「循环写在 SQL 里就行,反正语法支持。」
循环只能写在存储过程、函数、触发器、事件里。
普通
SELECT里写WHILE直接语法错。 - 「
CASE里ELSE可以不写。」 不写就是给 NULL。 如果下游拿这一列去比较,NULL 会把整个条件算成「不知道」,又绕回三值逻辑那个坑。
自测三问(点开对答案)
1. `WHILE` 和 `REPEAT` 最本质的差别是什么? —— **条件判断的位置:`WHILE` 在执行前,`REPEAT` 在执行后。** **所以条件一开始不成立时,`WHILE` 跑 0 次,`REPEAT` 跑 1 次。** 2. `LEAVE` 和 `ITERATE` 相当于什么? —— **`LEAVE` 是 `break`(跳出整个循环),`ITERATE` 是 `continue`(跳过本次)。** **两者后面都要跟循环的标签名。** 3. 为什么循环只能写在存储过程里? —— **因为 SQL 是面向集合的语句,一条语句处理一批行,本身没有「逐行控制」的概念。** **循环需要过程化的执行环境,所以只能在存储过程这类对象里。**第 7 章到这儿结束。下一篇进第 8 章,讲表数据的增、删、改——
语句会算了,接下来该真往里写数据,INSERT、UPDATE、DELETE 三条一起讲。
- ☐ (本篇完,下一篇 8.1 添加数据)