在编写查询语句时,经常遇到这样的场景:同一列的数据需要根据取值不同展示不同的文字说明,或者需要按照不同条件统计到不同的结果列中。这类需求本质上都是条件分支问题。SQL不像编程语言那样有if-else语句块,但它提供了CASE WHEN表达式以及一系列条件函数来实现同样的逻辑。掌握这些写法,是从会写SQL到写好SQL的重要一步。

一、CASE WHEN:SQL条件分支的标准写法
CASE WHEN是SQL标准中定义的条件表达式,几乎所有主流数据库都支持,是条件分支的首选方案。它有两种形式:简单CASE和搜索CASE。简单CASE形如CASE col WHEN v1 THEN r1 WHEN v2 THEN r2 ELSE r3 END,适合对单个列做等值判断;搜索CASE形如CASE WHEN cond1 THEN r1 WHEN cond2 THEN r2 ELSE r3 END,条件可以是任意布尔表达式,更加灵活。
两者的执行逻辑是从上到下依次判断,命中第一个满足条件的分支就返回对应结果,后面的分支不再执行。这意味着分支顺序很重要,例如判断成绩区间时,必须把大于等于90的判断放在前面,否则条件永远无法正确命中。ELSE分支在所有条件都不满足时生效,如果省略ELSE,则返回NULL,这是很多新手容易忽略的坑。
-- 搜索CASE:将数值映射为文字等级
SELECT
student_name,
score,
CASE
WHEN score >= 90 THEN '优秀'
WHEN score >= 80 THEN '良好'
WHEN score >= 60 THEN '及格'
ELSE '不及格'
END AS grade_level
FROM student_score;注意THEN后面返回的值类型需要保持兼容。如果第一个分支返回数字,第二个分支返回字符串,某些数据库会报错或产生隐式转换。建议显式统一类型,比如都用字符串或都用数字,避免隐式转换带来的意外结果。
二、各数据库专有的条件函数
除了标准写法,不同数据库还提供了自己的条件函数。Oracle的DECODE函数是最典型的例子,它只能做等值匹配,写法比CASE更紧凑:DECODE(col, v1, r1, v2, r2, default)。MySQL则提供了IF函数IF(cond, t, f)和IFNULL函数,写法简短但可移植性差,一旦项目换数据库就需要重写。
还有两个处理NULL相关的函数值得掌握。COALESCE返回参数列表中第一个非NULL的值,常用于多列取值兜底,例如优先取手机号、没有手机号就取邮箱:COALESCE(phone, email, '无联系方式')。NULLIF则相反,当两个参数相等时返回NULL,常用于避免除零错误:total / NULLIF(count, 0),这样分母为零时结果为NULL而不是报错。
-- Oracle DECODE 与标准 CASE 的等价写法
SELECT
DECODE(status, 1, '待支付', 2, '已支付', 3, '已发货', '其他') AS status_desc,
CASE status
WHEN 1 THEN '待支付'
WHEN 2 THEN '已支付'
WHEN 3 THEN '已发货'
ELSE '其他'
END AS status_desc_std
FROM orders;
-- 通用写法:多列兜底与防除零
SELECT
COALESCE(phone, email, '无联系方式') AS contact,
total_amount / NULLIF(order_count, 0) AS avg_amount
FROM customer_summary;从工程角度建议:除非确定项目不会更换数据库,否则尽量使用CASE WHEN和COALESCE这类标准语法。DECODE和IF虽然在特定数据库上写起来更快,但牺牲了SQL的可移植性,团队协作时代码也更容易被误读。
三、条件分支的四个实战场景
第一个场景是分支统计,也叫条件聚合。在SELECT中把CASE放进聚合函数里,可以一条语句统计出多个口径的结果,比写多条查询再拼接高效得多。例如统计订单表中各状态的金额小计,SUM内部套CASE即可实现。
第二个场景是动态排序。ORDER BY中同样可以使用CASE,让不同类型的数据按自定义顺序排列,比如让状态为紧急的排最前。第三个场景是数据脱敏,对手机号、身份证等敏感字段按条件截取展示。第四个场景是行转列,将行数据按维度拆成多列,这是报表开发的常见需求。
-- 场景1:分支统计,一条语句出多个统计口径
SELECT
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN status = 2 THEN amount ELSE 0 END) AS paid_amount,
SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) AS shipped_count
FROM orders;
-- 场景2:动态排序,紧急工单优先
SELECT id, title, priority
FROM work_order
ORDER BY CASE priority WHEN 'urgent' THEN 1 WHEN 'high' THEN 2 ELSE 3 END, created_at DESC;
-- 场景3:数据脱敏,中间四位打码
SELECT
user_name,
CASE
WHEN LENGTH(mobile) = 11
THEN CONCAT(LEFT(mobile, 3), '****', RIGHT(mobile, 4))
ELSE '无效号码'
END AS masked_mobile
FROM user_info;四、条件分支对性能的影响与优化建议
需要特别注意的是,在WHERE子句中对列使用CASE或函数会导致索引失效。例如WHERE CASE WHEN age >= 18 THEN 1 ELSE 0 END = 1这种写法,数据库无法利用age列上的索引,只能全表扫描。正确的做法是直接写WHERE age >= 18。CASE主要用于SELECT列表中做展示层转换,不要把它当成过滤条件的替代品。
在SELECT列表中使用CASE对性能的影响通常很小,因为它是逐行计算的标量表达式,开销远低于额外的表连接或子查询。真正需要警惕的是用CASE模拟复杂业务逻辑导致单条SQL过于庞大,几百行的CASE嵌套不仅难维护,也容易掩盖设计问题。这种情况下更好的做法是把固定映射关系存入字典表,用JOIN替代长串的CASE分支,既清晰又便于业务人员自行维护映射规则。
总结一下:SELECT中的条件分支首选标准CASE WHEN表达式,NULL处理配合COALESCE和NULLIF,分支统计用SUM加CASE的组合,过滤条件尽量保持列的原始形态以利用索引。掌握这些套路,绝大多数条件展示需求都能写出优雅高效的SQL。