写SQL做报表这件事,时间一长你就会发现,真正卡住效率的往往不是那些复杂到头皮发麻的窗口函数,反而是像 PostgreSQL 的 CASE WHEN 这种看起来平平无奇的基础表达式。我接触 PG 是在几个业务系统陆续迁到开源数据库之后,日常干得最多的事,就是把一张流水表按业务口径拆成各种统计结果。这类需求如果全丢给应用层去处理,代码会绕一整圈;直接在 SQL 里把条件分支写清楚,三五行就能让结果集变成报表需要的结构。最近看到 PostgreSQL 相关检索里“case when”“count(case when”一直是高频词,说明大家写 SQL 时对这个表达式还是有不少纠结的地方,所以我打算把平时用得比较多、也踩过坑的几个场景整理成一篇偏实战的笔记。
这篇文章会紧扣 PostgreSQL 来写,覆盖 CASE WHEN 的基础语法、条件聚合、条件更新、执行计划视角下的性能边界,以及最容易翻车的那几个细节。适合刚接触 PG 的读者照着敲一遍,也适合已经写过一些 SQL 但对 CASE WHEN 的认知还停留在“if-else 的替代品”的同学,帮你把它真正用出价值。
1. 先回归本质:CASE WHEN 是标量表达式,不是控制流程
不少从 Java、Python 转过来写 SQL 的人,第一反应会把 CASE WHEN 理解成 if-else。这个类比能帮助你快速上手,但它会带来一个隐患:你会下意识觉得 CASE WHEN 能“执行某段逻辑”,而实际上它在 PostgreSQL 里只是一个会返回单个值的表达式。理解这一点,是所有高级用法的基础。
1.1 表达式和语句的区别在哪
if-else 是过程式语言里的控制结构,它可以决定“接下来执行哪一段代码”。SQL 是声明式语言,你在 SELECT 列表里写的每一列,本质都是一个表达式,CASE WHEN 只是众多表达式中的一种。它做的事情是:对每一行输入,根据 WHEN 后面的条件决定返回哪个值。
举个例子就很好懂:
sql复制SELECT
order_id,
order_status,
CASE order_status
WHEN 'P' THEN '已下单'
WHEN 'S' THEN '已发货'
WHEN 'D' THEN '已完成'
WHEN 'C' THEN '已取消'
ELSE '未知状态'
END AS status_name
FROM orders
WHERE order_date >= date '2024-01-01';
这里 CASE ... END 和 order_id 没有本质区别,都是返回一个值。这个值的类型由所有 THEN/ELSE 分支共同决定。既然它只是表达式,那它可以出现的位置就非常广:SELECT 列表、WHERE 条件、GROUP BY、ORDER BY、HAVING 里都能写,甚至可以嵌在另一个表达式内部。
1.2 状态码翻译:最朴素的实战场景
状态码翻译是 CASE WHEN 最直观的用途。很多业务表为了省空间,不会直接存中文状态,而是用单个字母或数字表示。直接暴露给运营看肯定是灾难,于是你会在查询里做一层翻译。这种情况我通常推荐用“简单 CASE”写法,代码会更短:
sql复制SELECT
logistics_code,
shipping_status,
CASE shipping_status
WHEN 0 THEN '待揽收'
WHEN 1 THEN '运输中'
WHEN 2 THEN '派送中'
WHEN 3 THEN '已签收'
WHEN 4 THEN '异常件'
ELSE '未同步'
END AS status_text
FROM logistics
LIMIT 100;
除了翻译字段,还有一类高频场景是“分档”。比如订单金额要分成大额、普通、小额,或者用户年龄要分桶,这种区间判断用简单 CASE 写不了,需要用到搜索 CASE:
sql复制SELECT
user_id,
total_amount,
CASE
WHEN total_amount >= 5000 THEN '大额订单'
WHEN total_amount >= 1000 THEN '普通订单'
ELSE '小额订单'
END AS order_level
FROM user_orders
WHERE stat_date = date '2024-06-30';
看明白了吗?前一种 CASE 直接跟一个字段,后面 WHEN 后面写的是“值”;后一种 CASE 后面不跟字段,WHEN 后面写的是完整条件表达式。这两种形态也就是 PostgreSQL 文档里说的简单 CASE 和搜索 CASE。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 简单 CASE 和搜索 CASE:两种写法背后的执行逻辑差异
我见过有人把两种写法混着用,看着不报错,但出了逻辑问题半天定位不到。事实上 PostgreSQL 对两种写法的执行逻辑有明确区分,尤其是遇到 NULL 和范围条件时,差异会直接决定结果对不对。
2.1 简单 CASE 只能做等值判断
简单 CASE 的语法是:
sql复制CASE expression
WHEN value THEN result
[WHEN ...]
[ELSE result]
END
它会把 expression 的计算结果依次和每个 WHEN value 做等值比较。这个比较用的是普通等号语义,不涉及模糊匹配,也不支持大于小于。所以如果你要判断“金额超过 1000 元”,是不能写成 CASE total_amount WHEN > 1000 THEN ... 的,语法直接报错。这种场景只能换成搜索 CASE:
sql复制CASE
WHEN total_amount > 1000 THEN '满足'
ELSE '不满足'
END
简单 CASE 最大的价值是可读性。当你要翻译的状态码有几十个时,CASE status WHEN 0 ... WHEN 1 ... 这种写法一眼就能看出在枚举取值,比写一长串 WHEN status = 0 要干净得多。
2.2 简单 CASE 对 NULL 的处理是个经典坑
先看这段
