必须使用CASE WHEN而非IF()函数,因其是ANSI标准、跨数据库兼容;务必显式声明ELSE分支以防NULL静默丢失数据;避免在WHERE中滥用CASE WHEN导致索引失效;聚合时需根据语义选择COUNT或SUM配合ELSE 0。

SQL里想根据字段值返回不同结果,用CASE WHEN,别用IF
MySQL虽支持IF()函数,但它是非标准SQL,只在MySQL、MariaDB里能用;PostgreSQL、SQL Server、SQLite、Oracle全不认。跨数据库迁移或写通用脚本时,CASE WHEN才是唯一可靠选择。
常见错误现象:SELECT IF(status = 'active', '启用', '停用') FROM users; 在 PostgreSQL 里直接报错 ERROR: function if(unknown, unknown, unknown) does not exist。
-
CASE WHEN是 ANSI 标准,所有主流数据库都支持 -
IF()是 MySQL 特有函数,语义简单但不可移植 - 嵌套逻辑多时,
CASE WHEN可读性反而更好(比如带多个WHEN分支)
写CASE WHEN时漏掉ELSE,空值可能悄悄吞掉数据
没写ELSE分支时,所有不匹配的行都会返回NULL——不是报错,而是静默变成空值。线上查报表发现“部分用户状态显示为空”,大概率就是这个原因。
使用场景:给订单表加状态中文描述,原始字段是order_status('pending'/'paid'/'shipped'/'cancelled'),但漏写了ELSE:
SELECT
id,
CASE WHEN order_status = 'paid' THEN '已支付'
WHEN order_status = 'shipped' THEN '已发货'
END AS status_label
FROM orders;
这时pending和cancelled全变成NULL,前端渲染就成空白。
- 务必显式写
ELSE '未知'或ELSE order_status兜底 - 开发阶段可加
WHERE order_status NOT IN ('paid','shipped')临时查漏 - 如果业务上真不允许未覆盖的值,
ELSE NULL也得写出来,让意图明确
CASE WHEN放WHERE里做条件过滤,性能比想象中差
有人把CASE WHEN当万能开关,连WHERE条件都用它写:WHERE CASE WHEN type = 'vip' THEN created_at > '2023-01-01' ELSE 1=1 END。这会导致索引失效,执行计划里出现Seq Scan(全表扫描)。
根本原因:CASE WHEN在WHERE中无法被优化器提前剪枝,数据库没法利用type或created_at上的索引。
- 等价且高效写法:
WHERE (type = 'vip' AND created_at > '2023-01-01') OR type != 'vip' - 更清晰的拆分:
WHERE type = 'vip' AND created_at > '2023-01-01' OR type != 'vip' - 如果分支太多,考虑用
UNION ALL分拆查询,各自走索引
聚合函数里嵌套CASE WHEN,记得加ELSE 0
统计各状态订单数时写:SUM(CASE WHEN status='paid' THEN 1 END),看着没问题,但结果可能比总数少——因为CASE没匹配时返回NULL,而SUM(NULL)忽略该行,等价于SUM(1, NULL, 1) = 2,但人容易误以为漏了数据。
真正要的是计数,不是求和,所以更稳妥的是:
SELECT COUNT(*) AS total, COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_count, SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_sum FROM orders;
-
COUNT(CASE ... THEN 1 END)统计满足条件的行数(NULL不计入) -
SUM(CASE ... THEN 1 ELSE 0 END)强制补零,避免NULL干扰求和逻辑 - 二者结果一样,但语义不同:前者是“计数”,后者是“加权求和”,别混用
最常被忽略的是ELSE分支的默认行为——它不只影响可读性,还决定空值、索引使用、聚合结果是否可信。写完每个CASE WHEN,先问自己:不走任何WHEN分支时,它到底返回什么。









