IF...ELSE 只能跟一条语句;跟多条必须用 BEGIN...END,漏了不报错。
CASE 是唯一的标准流程控制,四家都认;它是表达式不是语句,能写在 SELECT 里。
WHILE 是 T-SQL 的过程化扩展;能用一条 SQL 解决的,别写循环——慢一个量级。
上一节(6.4)只讲了语句块和运算符,真正的分支和循环全在这一节。教材 6.5 有七个小节:
BEGIN...END、IF...ELSE、CASE、WHILE、WHILE...CONTINUE...BREAK、RETURN、GOTO。
七个里只有 CASE 能真跑——它是标准 SQL。
其余六个是 T-SQL 的过程化扩展,本沙箱跑不了,
演示里用 Python 复刻语义并说清「复刻的是行为不是语法」。
最该带走的三件事: IF 只能管一条语句、CASE 是从上往下匹配、WHILE 慢所以少用。
一、IF...ELSE:只能管一条语句
IF @score >= 60
PRINT '及格'
ELSE
PRINT '不及格'
这个结构本身很简单,难在「只能管一条」。 想让 IF 管住三条语句,
必须用 BEGIN...END 包起来——这一点上一节说过,但它是这一节最常犯的错,值得再说一次。
漏写 BEGIN...END 不报错。 只有第一条语句受 IF 控制,后面的无条件执行。
结果是「有时候对、有时候不对」——这种 bug 可能埋几个月。
还有一条容易忽略的:ELSE 是可选的,但 IF 嵌套超过三层就该重构了。三层 IF 套在一起,
读代码的人要在脑子里维护一个栈——这种逻辑用 CASE 或者查表更清楚。
二、CASE:唯一能真跑的
CASE 有两种写法,这是本节最容易混的地方:
| 写法 | 长什么样 | 什么时候用 |
|---|---|---|
| 简单 CASE | CASE 列 WHEN 值 THEN 结果 |
列等于某几个固定值 |
| 搜索 CASE | CASE WHEN 条件 THEN 结果 |
条件不是等值判断 |
-- 简单 CASE:适合等值判断
CASE sex WHEN 'M' THEN '男' WHEN 'F' THEN '女' ELSE '未知' END
-- 搜索 CASE:适合范围判断
CASE WHEN score >= 90 THEN '优秀'
WHEN score >= 60 THEN '及格'
ELSE '不及格' END
关键的一条:CASE 是「从上往下匹配,第一个成立的就返回」。所以条件顺序决定结果——
把 score >= 60 写在最前面,所有 60 分以上的都会变成「及格」,后面的分支永远走不到。
CASE 是表达式,不是语句。 这一点很重要:它能写在 SELECT 里、ORDER BY 里、WHERE 里,
甚至能当 UPDATE 的赋值——因为它返回一个值。
没有 ELSE 的话,所有条件都不满足时返回 NULL。 这个 NULL 会跟着结果走,
后面再参与计算就变成了 NULL——所以「兜底」那个 ELSE 建议写上。
三、WHILE:能用 SQL 就别用循环
DECLARE @i INT = 1
WHILE @i <= 5
BEGIN
PRINT @i
SET @i = @i + 1
END
WHILE 是 T-SQL 的过程化扩展,纯查询语句里没有循环。标准 SQL 的替代品是「递归 CTE」,
用查询表达循环——写法完全不一样。
更要紧的一条是性能。 逐行处理的循环,比一条集合操作慢一个量级——十万行数据,集合操作一秒,
循环可能十分钟。这就是所谓的「RBAR」(row by agonizing row)。
所以判断标准是:能不写循环就不写。 更新一张表用 UPDATE ... WHERE,不要 WHILE 一条条改;
汇总用 GROUP BY,不要循环累加。
真的需要循环的场景很少: 分批处理海量数据(一次一万行,避免长事务)、按顺序执行有依赖的操作、
生成测试数据。除了这些,基本都是集合操作能解决的。
四、CONTINUE、BREAK、RETURN、GOTO
| 关键字 | 作用 | 该不该用 |
|---|---|---|
CONTINUE |
跳过本次,进入下一轮 | 可以用,注意死循环 |
BREAK |
跳出整个循环 | 可以用 |
RETURN |
立即退出,可带返回码 | 存储过程里常用 |
GOTO |
跳到指定标签 | 不要用 |
CONTINUE 的死循环风险: 如果「跳过」的那一轮没有推进循环变量,
条件永远成立——循环就出不来了。
RETURN 在存储过程里可以带一个整数返回码。 0 表示成功,
非 0 是错误码——这个习惯跟 C 语言一样,调用方靠这个数判断成败。
GOTO 是唯一一个「教材列了但建议别用」的。 它让控制流可以往任何方向跳,代码读起来像一团线。
现代语言里基本都废弃了,T-SQL 保留它是为了兼容几十年前的老代码。
顺带说一句:GOTO 在老代码里最常见的用法是「出错跳到统一清理段」。
现在这个场景用 TRY...CATCH 代替——能看到 GOTO 的地方,基本都是没重构过的代码。
五、动手 Demo:IF、CASE、WHILE 与 GOTO
这一段贴 IF 和 WHILE 的原码(本沙箱不执行),真跑 CASE——它是标准 SQL,四家都认,
最后用 Python 复刻一次循环,并列出 CONTINUE、BREAK、RETURN、GOTO 四个关键字的用法。
import sqlite3
c = sqlite3.connect(u":memory:")
c.execute(u"CREATE TABLE tb_score (sno TEXT, score INT)")
c.executemany(u"INSERT INTO tb_score VALUES (?,?)",
[('S01', 92), ('S02', 78), ('S03', 55), ('S04', 61), ('S05', 100)])
q = u"""SELECT sno, score,
CASE WHEN score >= 90 THEN '优秀'
WHEN score >= 80 THEN '良好'
WHEN score >= 60 THEN '及格'
ELSE '不及格' END AS grade
FROM tb_score ORDER BY score DESC"""
for r in c.execute(q):
print(r)
# 用 Python 复刻 WHILE 的语义(不是语法)
i = 1
while i <= 5:
print(i)
i += 1
实跑输出:
--------------------------------------------------------------------
IF、CASE、WHILE 与 GOTO
--------------------------------------------------------------------
① IF...ELSE:只能跟一条语句
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| IF @score >= 60
| PRINT '及格'
| ELSE
| PRINT '不及格'
要跟多条语句,必须用 BEGIN...END 包起来。这是 T-SQL 里最容易漏的一处。
漏了不会报错——只有第一条语句受 IF 控制,后面的无条件执行。
这种错叫「悬空 else」,查起来很费劲,因为它不报错、结果只是「有时候对不上」。
② CASE:唯一能真跑的流程控制
--------------------------------------------------------------------
sno score 等级
S05 100 优秀
S01 92 优秀
S02 78 及格
S04 61 及格
S03 55 不及格
CASE 是标准 SQL,四家都认,所以这一段能真跑。
注意它是「从上往下匹配,第一个成立的就返回」——所以条件的顺序很关键。
把 score >= 60 写在最前面,那所有及格以上的都会变成「及格」,后面的分支永远走不到。
③ WHILE:SQL Server 有,标准 SQL 没有
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| DECLARE @i INT = 1
| WHILE @i <= 5
| BEGIN
| PRINT @i
| SET @i = @i + 1
| END
WHILE 是 T-SQL 的过程化扩展,纯查询语句里没有循环。
标准 SQL 的替代品是「递归 CTE」,用查询表达循环——写法完全不一样。
用 Python 复刻这段循环,输出:1、2、3、4、5
复刻的是「循环 5 次、每次打印」这个语义,不是 T-SQL 的语法。
实际项目里,能用一条 SQL 解决的不要写循环——逐行处理比集合操作慢一个量级。
④ CONTINUE、BREAK、RETURN、GOTO
--------------------------------------------------------------------
| 关键字 | 作用 | 该不该用 |
|---|---|---|
| CONTINUE | 跳过本次,进入下一轮 | 可以用,注意死循环 |
| BREAK | 跳出整个循环 | 可以用 |
| RETURN | 立即退出,可带返回码 | 存储过程里常用 |
| GOTO | 跳到指定标签 | 不要用 |
GOTO 是唯一一个「教材列了但建议别用」的。
它让控制流可以往任何方向跳,代码读起来像一团线——现代语言里基本都被废弃了。
T-SQL 保留它,是为了兼容几十年前的老代码。新写的代码用 WHILE 和 IF 就够了。
RETURN 在存储过程里可以带一个整数返回码,0 表示成功,非 0 是错误码——
这一点跟 C 语言的习惯一样。
CASE 这段能真跑,是因为它是标准 SQL。 五行数据分成了四档——注意 92 是「优秀」不是「良好」,
因为 CASE 从上往下匹配,>= 90 先命中。
如果把 >= 60 那个分支挪到最前面,结果会全变成「及格」。这不是 bug,
是 CASE 的定义——条件顺序就是优先级。
IF 和 WHILE 那两段只能贴原码。 本沙箱没有 SQL Server,
Python 那个 while 复刻的是「循环 5 次、每次打印」的语义,不是 T-SQL 的语法。
GOTO 那段没写代码,只列了表。 因为它在现代 SQL 里不该出现——
看到了就当技术债,能重构就重构。
30 秒实验
- 把
CASE里的条件顺序倒过来写(先>= 60再>= 90),看结果变成什么。 这就是「从上往下匹配」的直接证据——顺序错了,逻辑就全错了。 - 写一段
WHILE循环,往一张表里插一万行(一行一行插),再跟一条INSERT ... SELECT比耗时。 差距会让你以后都不想写循环。 - 想一想:为什么
CASE能写在SELECT里,而IF不能? 因为CASE是表达式,返回一个值;IF是语句,控制流程。表达式能放在任何「需要值」的位置, 语句不行。
常见误区
- 「
IF后面可以直接跟多条语句」。只能跟一条。 要跟多条必须BEGIN...END,漏了不报错,只是后面的语句无条件执行。 这是本节最贵的一个错。 - 「
CASE的条件顺序无所谓」。顺序就是优先级。 把范围大的条件写在前面,后面的分支永远走不到,而且不报错、不警告——只是结果悄悄不对。 - 「
WHILE能做的事,一条 SQL 做不了」。绝大多数时候反过来。 逐行循环比集合操作慢一个量级,而且代码更长、更容易错。 先想「能不能用一条 SQL」,不行再写循环。
自测三问(点开对答案)
1. 简单 `CASE` 和搜索 `CASE` 有什么区别? —— **简单 `CASE` 是 `CASE 列 WHEN 值 THEN 结果`,做等值判断;** **搜索 `CASE` 是 `CASE WHEN 条件 THEN 结果`,能做范围判断。** **两种都能写在 `SELECT` 里,因为 `CASE` 是表达式。** 2. `CASE` 里条件顺序写反了会怎样? —— **结果全错,而且不报错。** 因为它从上往下匹配,第一个成立的就返回。 **把 `>= 60` 写在 `>= 90` 前面,所有及格以上都会变成「及格」。** 3. 为什么说「能用 SQL 就别用循环」? —— **性能差一个量级。** 集合操作是内核批量处理,循环是逐行处理(RBAR)。 **而且循环代码更长、更容易出错**——除非是分批处理海量数据这种特殊场景。下一篇 6.6 讲注释和 GO。
GO 不是 SQL 语句,是客户端的分隔符——这句话解释了「为什么变量的作用域会被切断」,
也解释了「为什么程序里不能带 GO」。
- ☐ (本篇完,下一篇 6.6 注释)