case比if更通用,优先使用;if仅mysql/sql server支持,不跨库,仅三元逻辑,嵌套易错,复用性差,而case支持多分支、可读性强、便于复用和维护。

CASE 比 IF 更通用,优先用 CASE;IF 仅在 MySQL/SQL Server 中可用,且不支持多分支嵌套
为什么不能无脑用 IF 替代 CASE
很多刚从编程语言转 SQL 的人会下意识写 IF(score > 90, 'A', IF(score > 80, 'B', 'C')),但这个写法有硬伤:
-
IF是 MySQL 和 SQL Server 的扩展函数,PostgreSQL、Oracle、SQLite 等不支持,跨库迁移直接报错function if does not exist -
IF最多只支持三元逻辑(条件、真值、假值),无法表达“>90→A,>80→B,>70→C,否则D”这种四路分支 -
IF嵌套过深时括号极易错位,而CASE的缩进+换行天然可读性强 - 当需要在
WHERE或GROUP BY中复用同一段逻辑时,CASE可提成子查询或 CTE,IF很难复用
CASE WHEN 在 SELECT 中做动态分组统计
真实业务中常见“按规则打标签 + 同时统计各标签人数”,比如:活跃用户定义为「近30天有下单」或「近7天有登录」,新客是「注册不满7天」,其余为普通用户。这时不能只用 WHERE 过滤,得在 SELECT 里生成标签再聚合:
SELECT
CASE
WHEN last_order >= CURRENT_DATE - INTERVAL '30 days' THEN '活跃'
WHEN last_login >= CURRENT_DATE - INTERVAL '7 days' THEN '半活跃'
WHEN registered >= CURRENT_DATE - INTERVAL '7 days' THEN '新客'
ELSE '普通'
END AS user_type,
COUNT(*) AS cnt
FROM users
GROUP BY
CASE
WHEN last_order >= CURRENT_DATE - INTERVAL '30 days' THEN '活跃'
WHEN last_login >= CURRENT_DATE - INTERVAL '7 days' THEN '半活跃'
WHEN registered >= CURRENT_DATE - INTERVAL '7 days' THEN '新客'
ELSE '普通'
END;
注意两点:
- GROUP BY 必须重复写一遍
CASE表达式(部分数据库如 PostgreSQL 支持用别名,但 MySQL 不行) - 所有分支返回字符串类型,不能有的返回
'活跃',有的返回1,否则报错column "user_type" has mismatched types
IF 在 MySQL 中的合法用法与陷阱
如果确定只跑 MySQL,且只是简单二选一(比如把 NULL 转成默认值),IF 确实比 CASE 短:
SELECT name, IF(age IS NULL, 0, age) AS age_clean FROM users;
但要注意:
-
IF(condition, true_value, false_value)三个参数类型必须兼容,比如IF(flag, 1, 'N')会强制把'N'转成数字 0,结果全是 1 和 0,不是你想要的字符串 - 条件里不能直接写
col = NULL,得用col IS NULL,否则整个IF返回 NULL - 和
CASE一样,漏写 else 分支(即false_value)会导致未命中时返回 NULL,线上报表可能突然少数据
复杂条件必须加括号,尤其混用 AND/OR 时
比如要筛选「VIP 用户,或者非 VIP 但最近有下单的用户」,容易错写成:
WHERE is_vip = 1 OR is_vip = 0 AND last_order >= CURRENT_DATE - INTERVAL '30 days'
这实际等价于 WHERE (is_vip = 1) OR (is_vip = 0 AND last_order >= ...),看起来没问题——但一旦加个新条件,比如「且邮箱已验证」,不加括号就危险了:
WHERE is_vip = 1 OR is_vip = 0 AND last_order >= ... AND email_verified = 1
它会被解析为 WHERE is_vip = 1 OR (is_vip = 0 AND last_order >= ... AND email_verified = 1),而你本意可能是 WHERE (is_vip = 1 OR (is_vip = 0 AND last_order >= ...)) AND email_verified = 1。
所以只要出现 OR,就该用括号把每个完整判断包住:
WHERE (is_vip = 1) OR (is_vip = 0 AND last_order >= CURRENT_DATE - INTERVAL '30 days')
更稳妥的做法是统一用 CASE 把逻辑收口:
WHERE
CASE
WHEN is_vip = 1 THEN 1
WHEN is_vip = 0 AND last_order >= CURRENT_DATE - INTERVAL '30 days' THEN 1
ELSE 0
END = 1
这样语义绝对清晰,改逻辑时只动 CASE 内部,不会被运算符优先级坑。










