写报表 SQL 时遇到一个需求:查订单,普通订单按创建时间过滤,退款订单按退款时间过滤。同事一开始写了两条 SQL 用 UNION 拼起来,其实一个 CASE WHEN 就能解决。
基本写法
CASE WHEN 在 WHERE 里的本质是「返回一个值」,然后拿这个值和条件比较:
SELECT * FROM orders
WHERE CASE WHEN type = 'refund' THEN refund_time ELSE create_time END
>= '2026-01-01';这样两类订单各用各的时间字段,一条语句搞定。
用法一:按条件选字段比较
就是上面这种,适合不同业务类型走不同字段的场景。注意 CASE 的所有分支返回类型要一致,否则某些数据库会隐式转换,索引也用不上。
用法二:在 WHERE 里做条件分支
比如管理员能看全部数据,普通用户只能看自己的:
WHERE CASE WHEN :role = 'admin' THEN 1
WHEN user_id = :uid THEN 1
ELSE 0 END = 1这种写法可读性好,但要注意执行计划。把参数放在 CASE 里,优化器往往没法预估选择率,可能走全表扫描。数据量大时,用动态拼接 SQL 或者 OR 条件反而更快。
用法三:配合聚合做条件统计
这个更常见于 SELECT 而不是 WHERE,顺便提一下:
SELECT
COUNT(CASE WHEN status = 1 THEN 1 END) AS 已支付,
COUNT(CASE WHEN status = 2 THEN 1 END) AS 已退款
FROM orders;比写多个子查询快得多,也省去了 GROUP BY 的麻烦。
几个必须注意的点
- NULL 的处理:CASE WHEN 里条件为 NULL 时走 ELSE 分支,别指望它等于空字符串。
- 索引失效:对字段套 CASE 之后,原本的索引基本用不上。要么改写逻辑,要么建函数索引。
- 顺序执行:CASE 是短路求值,前面的分支命中就不再往后判断,把最可能命中的放前面能省一点计算。
- 方言差异:MySQL 的 CASE 返回类型会做隐式转换,Oracle 对类型要求更严格,跨库迁移时要留意。
什么时候别用它
如果 CASE 的分支超过四五个,或者嵌套了 CASE,说明业务逻辑本身复杂,这时候写在 SQL 里会很难维护。把判断放到应用层,或者建一张映射表 JOIN 进来,都比堆 CASE 更好读,也更容易优化。