写过业务SQL的人几乎都遇到过这样的需求:订单表里存的是1、2、3这样的状态码,页面要展示的是“待支付”“已发货”“已完成”这样的文字;或者考试成绩是数字,报表里要按分数段划分成优秀、良好、及格、不及格。如果在应用层做转换,数据量一大就很别扭,最好直接在SQL查询阶段就把结果算好。这就是CASE WHEN表达式发挥作用的地方——它是SQL标准中用来实现条件分支的语法,功能上等价于其他语言里的if-else和switch-case,而且几乎所有主流数据库都支持。

CASE WHEN的两种基本语法形式
CASE表达式有两种写法,第一种叫简单CASE,第二种叫搜索CASE,两者可以应付绝大多数条件判断场景。简单CASE的语法是CASE 字段 WHEN 值 THEN 结果 END,它把字段写在前面,后面逐个列出可能取值,形式上更像switch-case。搜索CASE的语法是CASE WHEN 条件 THEN 结果 END,每个WHEN后面跟一个完整的布尔表达式,判断顺序从上到下,命中哪个就返回哪个的结果,然后整个表达式结束。
两种写法的核心规则是一样的:从第一个WHEN开始逐条判断,遇到第一个满足条件的分支就返回对应结果,后面的分支不再执行;如果所有条件都不满足,则走ELSE分支;如果没有写ELSE,就返回NULL。这一点和很多语言的switch默认穿透行为不同,SQL的CASE天然自带break效果。下面用一张订单状态表来演示两种写法:
-- 简单CASE:适合字段值与结果一一对应的场景
SELECT
order_id,
CASE status
WHEN 1 THEN '待支付'
WHEN 2 THEN '已发货'
WHEN 3 THEN '已完成'
ELSE '未知状态'
END AS status_text
FROM t_order;
-- 搜索CASE:适合范围判断、多字段组合判断
SELECT
order_id,
CASE
WHEN amount >= 1000 THEN '大额订单'
WHEN amount >= 500 THEN '中额订单'
WHEN amount > 0 THEN '小额订单'
ELSE '异常订单'
END AS order_level
FROM t_order;
值得注意的是,搜索CASE虽然写起来稍长,但能力比简单CASE强得多。简单CASE只能做等值比较,而且比较时会用到等号语义,遇到NULL值就失效(NULL = NULL在SQL里返回UNKNOWN而不是TRUE)。搜索CASE的WHEN后面可以写任意合法的布尔表达式,包括IS NULL判断、IN列表、函数调用、多个字段联合判断,实践中建议优先使用搜索CASE,只有纯粹的枚举映射时才用简单CASE让代码更紧凑。
CASE WHEN在SELECT、排序和分组统计中的实战用法
CASE WHEN最常用的位置是SELECT列表,用来做字段翻译和派生列计算。除了展示层面的转换,它还有一个高频用途:放在聚合函数内部,实现条件统计。比如要统计一张表里男女用户数量,常规做法是写两条带WHERE的SQL,用CASE WHEN则一条SQL就能出结果:
SELECT
COUNT(*) AS 总人数,
SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END) AS 男性人数,
SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END) AS 女性人数,
SUM(CASE WHEN amount >= 1000 THEN 1 ELSE 0 END) AS 大额订单数,
ROUND(AVG(CASE WHEN amount > 0 THEN amount END), 2) AS 有效订单平均金额
FROM t_user_order;
这段SQL里的技巧在于:把CASE WHEN塞进SUM里,满足条件的行记1,不满足记0,求和就得到了条件计数;放进AVG里时故意不写ELSE,让不满足条件的行返回NULL,而AVG会自动忽略NULL,从而实现“只对部分行求平均”。这是行转列统计的经典套路,比子查询或多次查询的效率高得多。
CASE WHEN还可以放在ORDER BY里实现自定义排序。例如要让状态为“已完成”的订单排最前面,其余按时间倒序,直接写死排序字段是做不到的,用CASE把状态转换成一个可排序的数字即可:
SELECT order_id, status, create_time
FROM t_order
ORDER BY
CASE status
WHEN 3 THEN 1 -- 已完成优先
WHEN 1 THEN 2 -- 待支付次之
ELSE 3
END,
create_time DESC;
此外,CASE WHEN也能出现在GROUP BY中,实现按条件分组。比如按消费金额把用户分成几个档位再统计每档人数,直接把档位表达式写进GROUP BY即可,MySQL和PostgreSQL都支持这种写法:
SELECT
CASE
WHEN total_amount >= 5000 THEN 'VIP'
WHEN total_amount >= 1000 THEN '普通会员'
ELSE '低活跃用户'
END AS user_level,
COUNT(*) AS user_count
FROM t_user
GROUP BY
CASE
WHEN total_amount >= 5000 THEN 'VIP'
WHEN total_amount >= 1000 THEN '普通会员'
ELSE '低活跃用户'
END;
这里有个小优化:很多数据库支持用GROUP BY 1按第一列分组,或者先用子查询算出user_level再对外层分组,可以避免重复书写同一段CASE表达式,代码可读性更好。
用CASE WHEN完成条件更新与进阶技巧
UPDATE语句里的SET后面同样可以放CASE WHEN,一次UPDATE就能根据不同条件把不同行更新成不同值,避免了写多条UPDATE或者借助存储过程。典型例子是批量调整价格:库存大于100的商品降价10%,库存小于10的涨价5%,其余不变:
UPDATE t_product
SET price = CASE
WHEN stock > 100 THEN price * 0.9
WHEN stock < 10 THEN price * 1.05
ELSE price
END
WHERE category = '数码';
这种写法在数据迁移、批量修数时非常高效,只需要扫描一遍表。进阶用法还包括嵌套CASE,即在一个CASE的THEN或ELSE里再放一个CASE,用来表达更复杂的多层判断;以及与窗口函数配合,比如按分组计算排名后用CASE打标签。嵌套写法示例如下:
SELECT
student_name,
score,
CASE
WHEN score >= 60 THEN
CASE
WHEN score >= 85 THEN '优秀'
WHEN score >= 70 THEN '良好'
ELSE '及格'
END
ELSE '不及格'
END AS grade
FROM t_score;
最后提醒几个容易踩的坑。第一,所有THEN分支返回值的类型要兼容,如果有的分支返回数字有的返回字符串,部分数据库会报错或产生隐式转换,建议统一用CAST处理。第二,判断NULL时必须用WHEN field IS NULL,写成WHEN field = NULL永远不会命中。第三,CASE分支是顺序匹配的,条件范围有重叠时要把更严格的条件写在前面,比如判断amount的正负之前先排除负数。第四,CASE WHEN是表达式而不是完整语句,它必须返回一个值,不能在里面执行UPDATE或INSERT,这一点和存储过程里的IF语句有本质区别。掌握了这些细节,CASE WHEN基本可以覆盖日常开发中90%以上的条件判断需求。