做开发这些年,跟数据库打交道最多的除了SELECT、UPDATE这些基础语句,可能就是条件逻辑了。遇到“根据不同情况返回不同值”这种需求,很多从ORM或者框架入手的同事第一反应是写一堆if-else,甚至把数据查出来到代码里再判断一遍。其实在PostgreSQL里,CASE WHEN就是干这个的标准工具,纯SQL就能把这些事处理得明明白白,还省掉一次不必要的网络交互。这篇我就把CASE WHEN的常见用法和容易踩的坑一起梳理一下,希望对日常写SQL的你有帮助。
CASE WHEN是SQL标准里的语法,PostgreSQL的实现很规整,功能也完整。用得好的话,它不光是“增强版if-else”,还能做条件聚合、行转列、动态UPDATE,甚至是性能优化的利器。很多人只用它在SELECT里做字段翻译,有点浪费。
1. 两种基本形态:简单CASE和搜索CASE到底差在哪
刚开始接触CASE WHEN的人,最容易困惑的就是为什么有时候看到的是CASE field WHEN value THEN ...,有时候又是CASE WHEN condition THEN ...。两种写法都合法,但适用场景不一样,我建议直接把这两种都掌握,看到别人的SQL不至于发懵。
1.1 简单CASE:适合等值判断
先看第一种写法:
sql复制SELECT
name,
level,
CASE level
WHEN 1 THEN '青铜'
WHEN 2 THEN '白银'
WHEN 3 THEN '黄金'
ELSE '传说'
END AS level_desc
FROM user_account;
这种写法叫简单CASE,它做的是等值比较:拿level的值依次跟每个WHEN后面的值比较,相等就返回对应的THEN。
它有个容易被忽略的限制——只能做等值判断。如果你想表达“分数大于90是优秀,大于80是良好”,简单CASE就无能为力了,因为level >= 90没法写进WHEN后面做匹配值。
1.2 搜索CASE:真正的条件判断
更常用的是第二种,搜索CASE:
sql复制SELECT
name,
score,
CASE
WHEN score >= 90 THEN '优秀'
WHEN score >= 80 THEN '良好'
WHEN score >= 60 THEN '及格'
ELSE '不及格'
END AS grade
FROM student_exam;
搜索CASE在每个WHEN后面直接写完整的布尔表达式,可以是范围判断、模糊匹配、子查询等任意合法的条件组合。它跟编程语言里的if-else if-else结构几乎一一对应。
这里有个执行顺序的细节需要说清楚:PostgreSQL会从上到下依次判断每个WHEN条件,一旦命中就不再继续往后判断。所以上例里,一个考了95分的学生,第一个条件score >= 90就命中了,直接返回“优秀”,根本不会走到后面的WHEN。
这个特性也带来一个实战建议:条件顺序要按优先级从高到低排列。如果把范围写反了,比如先写WHEN score >= 60 THEN '及格'再写WHEN score >= 90 THEN '优秀',那么95分的学生会被错误地标成“及格”,因为第一个条件已经把它拦住了。这种bug很隐蔽,查数据时不容易发现,往往要等到导报表、对账的时候才暴露。
1.3 ELSE到底要不要写
很多初学者喜欢省略ELSE,觉得“反正我条件全列举了”。这个习惯最好改掉。
PostgreSQL里,如果所有WHEN条件都没命中,且没有ELSE,表达式会返回NULL。NULL在后续计算、比对、展示时带来的麻烦比大多数开发新手想象的要多。比如你拿结果去做SUM,NULL会被直接跳过,结果跟预期可能就差一大截。
所以我的习惯是:除非你能100%保证覆盖所有情况,否则一定写ELSE。实在不想写业务值,哪怕ELSE 0或者ELSE NULL(显式写出来)都行——至少读代码的人知道你是有意为之。
注意:简单的CASE写法和搜索CASE写法在语义上是不同的。简单CASE实际会被转换为等值比较,而且用的是
=比较。如果要写非等值条件、复杂表达式,请使用搜索CASE。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 被低估的组合拳:CASE WHEN 配合聚合函数做统计
很多报表需求,用CASE WHEN加聚合函数可以一条SQL搞定,不用开多趟子查询。这是CASE WHEN最容易被低估的场景之一。
2.1 条件计数:SUM(CASE WHEN ...) 替代 COUNT 加 WHERE
场景很常见:统计一个订单表里,各个状态各有多少单。大部分人会写三条SQL:
sql复制SELECT count(*) FROM orders WHERE status = 'paid';
SELECT count(*) FROM orders WHERE status = 'shipped';
SELECT count(*) FROM orders WHERE status = 'closed';
三条SQL要跑三遍全表/索引扫描,返回三行结果。其实一次就能查完:
sql复制SELECT
count(*) AS total_orders,
count(*) FILTER (WHERE status = 'paid') AS paid_count,
count(*) FILTER (WHERE status = 'shipped') AS shipped_count,
count(*) FILTER (WHERE status = 'closed') AS closed_count
FROM orders;
实际上PostgreSQL有专门的FILTER语法来做条件计数,比SUM(CASE WHEN ...)更优雅。但很多老项目、很多把PostgreSQL当MySQL写的同事,还是习惯这样:
sql复制SELECT
count(*) AS total_orders,
sum(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_count,
sum(CASE WHEN status = 'shipped' THEN 1 ELSE 0 EN
