做开发这些年,跟数据库打交道最多的除了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 END) AS shipped_count,
sum(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) AS closed_count
FROM orders;
SUM(CASE WHEN ... THEN 1 ELSE 0 END)的写法内涵是:满足条件的行被计为1,不满足的被计为0,SUM之后就是满足条件的总行数。如果你习惯了MySQL的写法,到PostgreSQL里这依然有效,但如果你愿意用新语法,FILTER在可读性和执行效率上通常会更好一点。不过PostgreSQL的FILTER语法在其他数据库里不能迁移,如果你有跨库兼容需求,SUM(CASE WHEN ...)反而是通用方案。
2.2 条件求和:一个字段要按不同口径汇总
再进阶一点。财务场景里,有一张指标表,同一个指标字段要根据类型分类汇总。比如有两个字段:direction表示资金方向(in/out),amount表示金额。现在想要一天的资金流入总额和流出总额:
sql复制SELECT
date,
sum(CASE WHEN direction = 'in' THEN amount ELSE 0 END) AS total_in,
sum(CASE WHEN direction = 'out' THEN amount ELSE 0 END) AS total_out
FROM fund_flow
WHERE date >= '2024-01-01'
GROUP BY date
ORDER BY date;
这个SQL的实际效果就是:同一份明细数据,在一趟扫描中分别按两个口径做汇总。如果你用传统的WHERE direction = 'in'再UNION ALL一份倒出来的SQL,语句会变长,可读性变差,执行计划也会多扫一遍表。CASE WHEN在里面扮演的角色是“分组器”或者“分流器”,把数据流往不同的累加器里送。
2.3 结合GROUP BY做分组内占比
有时候你不仅想要总量,还想要分类占比。比如统计每个月订单里,VIP用户贡献了多少:
sql复制SELECT
to_char(created_at, 'YYYY-MM') AS month,
sum(CASE WHEN user_type = 'VIP' THEN amount ELSE 0 END) AS vip_amount,
sum(amount) AS total_amount,
round(
sum(CASE WHEN user_type = 'VIP' THEN amount ELSE 0 END)::numeric
/ NULLIF(sum(amount), 0) * 100,
2
) AS vip_ratio
FROM orders
GROUP BY to_char(created_at, 'YYYY-MM')
ORDER BY month;
这个例子里出现了一个除法技巧:用NULLIF(分母, 0)来防止除零错误。如果把sum(amount)换成NULLIF(sum(amount), 0),当月金额为0时,表达式返回NULL而不是报错。这已经是另一个函数的故事了,但配合CASE WHEN用起来很顺手。
3. 用CASE WHEN做行转列,省掉一堆自连接
行转列是报表开发里的高频需求。PostgreSQL有专门的crosstab扩展,但如果你不想装扩展,CASE WHEN完全可以胜任。
3.1 学生成绩转置
假设有一张成绩明细表,结构是这样:
sql复制CREATE TABLE report_card (
student_name text,
subject text,
score numeric
);
数据存在多行里:
| student_name | subject | score |
|---|---|---|
| 张三 | 语文 | 90 |
| 张三 | 数学 | 85 |
| 张三 | 英语 | 88 |
| 李四 | 语文 | 75 |
| 李四 | 数学 | 92 |
| 李四 | 英语 | 70 |
期望结果是一人一行,三科各占一列。写法如下:
sql复制SELECT
student_name,
max(CASE WHEN subject = '语文' THEN score END) AS chinese_score,
max(CASE WHEN subject = '数学' THEN score END) AS math_score,
max(CASE WHEN subject = '英语' THEN score END) AS english_score
FROM report_card
GROUP BY student_name;
这里的灵魂是max配合CASE WHEN。每个学生的每组subject只会产生一行匹配,非匹配科目返回NULL,max在只有一个非NULL值的时候会把那一个值取出来;GROUP BY再把同一学生的三行数据压缩成一行。
如果你不想用max,用sum也完全可行,但对于字符串类型的科目值,sum用不了,所以max是通用选择。
3.2 动态科目怎么办
上面的SQL写死了科目名,如果科目是动态的(比如这学期多了一门物理),SQL就要跟着改。在PostgreSQL里你可以用crosstab函数实现动态转置,也可以用PL/pgSQL拼接动态SQL。但对于绝大多数“科目固定、每周跑固定报表”的场景,写死CASE WHEN是最简单可维护的方案,不需要为了炫技引入额外依赖。
如果科目真的很多,几十列,写SQL会非常长。这个时候可以考虑另一个思路:把统计逻辑放到应用层,用代码去旋转结果集。技术选型没有银弹,最合适的就是最好的。
3.3 多条件分组转置
行转列不限于单字段等值匹配,还可以配合多个条件组合出复杂的列。比如每个学生的月考成绩,1月、2月、3月各一行成绩,要把月度变成列:
sql复制SELECT
student_name,
max(CASE WHEN month = '2024-01' THEN score END) AS jan_score,
max(CASE WHEN month = '2024-02' THEN score END) AS feb_score,
max(CASE WHEN month = '2024-03' THEN score END) AS mar_score
FROM exam_score
GROUP BY student_name;
和科目转置本质一样。
不过有个细节值得提醒:如果同一个学生同一科目在同一个月份有多条记录,max(CASE WHEN ...)会拿到最大值而非任意一条。这可能是你想要的,也可能不是。如果业务上每个月每个学生只有一条,没问题;如果有多条且你想取最新一条,最好在有时间戳的字段上做去重后再转置,或者改用DISTINCT ON。
4. 不只是SELECT:UPDATE和INSERT里的CASE WHEN
很多人会忽略,CASE WHEN在UPDATE、INSERT里同样重要。批量更新时,它能帮你避免写多条SQL重复往返或大量动用应用层循环。
4.1 批量条件更新
举个实际案例。一张用户表里有等级字段level,运营策略要求按用户当前等级做批量调整:
- 等级1的用户升到2
- 等级2的用户降回1
- 等级3的不动
新手写的SQL大概是三条UPDATE:
sql复制UPDATE user_account SET level = 2 WHERE level = 1;
UPDATE user_account SET level = 1 WHERE level = 2;
别笑,真的有人这么写。这样写的问题是:第一条执行后,原本level为1的用户变成2;第二条再执行时,会把这些刚变成2的用户又改回1——结果所有用户最终level全是1,业务全乱了。
正确写法是用CASE WHEN一步到位:
sql复制UPDATE user_account
SET level = CASE level
WHEN 1 THEN 2
WHEN 2 THEN 1
ELSE level
END;
因为CASE是同时基于原始行数据逐一计算的,不会出现先改A再改B的“顺序污染”。这个例子非常经典,建议做运营系统、会员系统的同学都留意一下。
4.2 UPDATE里还能带更复杂的业务映射
不只是等值改动,搜索CASE也能用在UPDATE里。比如订单表根据金额给订单打标:
sql复制UPDATE orders
SET order_tag = CASE
WHEN amount >= 10000 THEN '大单'
WHEN amount >= 1000 THEN '中单'
WHEN amount >= 100 THEN '小单'
ELSE '散单'
END
WHERE created_at >= '2024-06-01'
AND order_tag IS NULL;
这样一条SQL就把整批历史订单按金额把标签补齐了,应用层完全不用参与。
4.3 INSERT里做数据清洗
数据迁移的时候,经常要把A表的数据插到B表,但A表的字段值跟B表定义不一致。比如A表性别字段存的是'M'和'F',B表性别字段需要存中文'男'和'女':
sql复制INSERT INTO member_new (name, gender_text, source_system)
SELECT
name,
CASE gender_code
WHEN 'M' THEN '男'
WHEN 'F' THEN '女'
ELSE '未知'
END,
'legacy'
FROM member_old;
如果你还接手过那种把1、2、3分别映射成状态的脏数据,CASE WHEN几乎是数据清洗的生命线。
提示:在UPDATE里使用CASE WHEN时,如果所有值都要更新成同一类型,注意数据类型的兼容性。
CASE的所有THEN分支最好能隐式转换成同一类型,否则可能报“CASE types X and Y cannot be matched”之类的错误。
5. 最容易翻车的点:CASE WHEN与NULL的恩恩怨怨
写CASE WHEN时,NULL是最大的坑。我跟很多同事排查SQL问题到最后,发现根因十有七八跟NULL有关。
5.1 搜索CASE里直接用等号比较NULL = 永远为假
下面这段SQL想查出email为空的用户并标记为“无邮箱”:
sql复制SELECT
name,
CASE
WHEN email = NULL THEN '无邮箱'
ELSE '有邮箱'
END AS email_status
FROM users;
这段代码会全部返回“有邮箱”。因为在SQL的三值逻辑里,email = NULL的结果不是TRUE也不是FALSE,而是UNKNOWN。搜索CASE只有条件判断为TRUE时才走对应THEN,UNKNOWN会被当作不成立处理,一路落到ELSE兜底。
正确写法:
sql复制SELECT
name,
CASE
WHEN email IS NULL THEN '无邮箱'
ELSE '有邮箱'
END AS email_status
FROM users;
凡是判断NULL,一律用IS NULL或IS NOT NULL,不用等号。这是个基础知识点,但在CASE WHEN里尤为重要——因为CASE WHEN经常用来对残缺数据做分类,NULL的出现频率极高。
5.2 ELSE NULL不是报错,但会带来隐患
如果在员工表里按绩效等级发奖金,忘记了有一部分员工没有绩效等级。原本想的是“没等级就发0”,但因为省略了ELSE,所有NULL会被默默带进结果集:
sql复制SELECT
staff_name,
CASE performance_level
WHEN 'S' THEN 10000
WHEN 'A' THEN 5000
END AS bonus
FROM staff;
绩效等级为B的员工,这里bonus返回NULL。如果你把这批bonus拿去做SUM,NULL会被跳过,合计值会比预期少一截。很多财务系统对账不平,查到最后都是这种“看似没毛病”的写法。
5.3 CASE WHEN之后再套函数要额外小心
CASE WHEN返回NULL的另一个坑是你以为能算出值,结果一整行都被NULL吞掉了。比如要给员工发全勤奖,日薪乘以出勤天数:
sql复制SELECT
staff_name,
(CASE WHEN attendance_rate >= 0.9 THEN salary_daily ELSE 0 END) * work_days AS full_attendance_bonus
FROM staff;
如果salary_daily或者work_days本身就有一个是NULL,算出来的结果还是NULL。要让数据更稳健,你往往得结合COALESCE先把默认值兜住。这一点下面会专门讲。
5.4 NULL在排序和去重时的连带反应
一个不太引人注意的坑是,CASE WHEN把某些行的某个字段变成NULL之后,后续做DISTINCT或者GROUP BY时,这些NULL会被当作一组来处理。如果你用CASE给一个数据打标,想按标签去重,NULL标签的行会全部“合并”在一组里。虽然这在SQL语义里是合理的,但在报表展示时可能让你很困惑。习惯性给每个CASE分支写明确的ELSE值,能少很多莫名其妙的“数据消失”问题。
6. 别让CASE WHEN拖垮性能:WHERE内嵌与索引失效
聊完正确性和坑,进入性能环节。CASE WHEN在SELECT列表里通常不会带来明显的性能问题,一旦跑到WHERE、JOIN条件、ORDER BY里,就要谨慎了。
6.1 WHERE里套CASE WHEN让索引失效
我曾经排查过一个慢查询,结构大概是:
sql复制SELECT *
FROM orders
WHERE
CASE
WHEN status IN ('paid', 'shipped') THEN paid_at >= '2024-01-01'
ELSE created_at >= '2024-01-01'
END;
这段SQL逻辑本身没错,但性能很糟糕。原因很简单:查询优化器没法把一个包着CASE WHEN的条件直接转换成对某个索引列的range scan。一个原本用paid_at索引可以秒出的查询,最后变成全表扫描,几百万行数据跑了十几秒。
大多数情况下,更好的写法是把业务条件拆分成显式的OR或UNION ALL分支,让每一路都能独立命中索引:
sql复制SELECT *
FROM orders
WHERE status IN ('paid', 'shipped')
AND paid_at >= '2024-01-01'
UNION ALL
SELECT *
FROM orders
WHERE status NOT IN ('paid', 'shipped')
AND created_at >= '2024-01-01';
这里用两个简单的WHERE条件分别走索引,再用UNION ALL把结果合并。业务语义需要仔细对齐——上面的拆分可能已经不再等价,但思路是:让每一路条件都可以独立用索引。更通用的拆分方式也可以是:
sql复制SELECT *
FROM orders
WHERE (status IN ('paid', 'shipped') AND paid_at >= '2024-01-01')
OR (status NOT IN ('paid', 'shipped') AND created_at >= '2024-01-01');
如果OR的两边都带索引列,PostgreSQL可能将其转为BitmapOr,同样能命中索引。具体性能表现因数据分布而异,最好实测对比。
总结成一句经验:CASE WHEN适合出现在SELECT列表或者UPDATE的SET子句里;在WHERE和JOIN条件里就要警惕,它常常会阻碍索引选择。
6.2 排序字段上的CASE WHEN也有隐性成本
ORDER BY后面也可以接CASE WHEN,比如“未处理的订单排在最前面,已处理的按时间倒序排”:
sql复制SELECT *
FROM tickets
ORDER BY
CASE
WHEN status = 'open' THEN 0
ELSE 1
END,
created_at DESC;
这个语句从功能上看没错,但当表数据量大时,排序操作基本无法使用索引,而必须创建一个临时排序列做全量排序。若要提升性能,可以考虑在status和created_at上建立部分索引,或者把排序逻辑拆分到应用层去做。报表类查询如果只是刷一次,影响不大;但高频列表接口要留意。
6.3 CASE WHEN分支内部塞子查询
CASE WHEN里每个THEN可以接标量子查询,这一点让它的表达能力非常强,但也可能带来执行计划里的“隐式循环”。比如:
sql复制SELECT
o.id,
CASE
WHEN o.type = 'special'
THEN (SELECT max(discount) FROM special_rule WHERE rule_id = o.rule_id)
ELSE o.discount
END AS final_discount
FROM orders o;
如果special类型的订单很多,而且special_rule上没有合适的索引,每个订单都会执行一次子查询。虽然优化器有时会改成hash join等方式把它变成一次关联,但复杂情况下你没法保证。最佳实践是优先用LEFT JOIN把需要的数据提前关联出来,在SELECT列表里直接引用CASE,不要让CASE依赖子查询。
7. 配合常用函数把CASE WHEN用得游刃有余
CASE WHEN单独用已经很强了,但跟其他函数搭配,能更优雅地解决不少实际问题。
7.1 COALESCE 兜底空值
COALESCE函数返回第一个非NULL参数。当CASE WHEN可能返回NULL而你希望有个默认值时,可以双层嵌套,也可以在外层包COALESCE:
sql复制SELECT
name,
COALESCE(
CASE performance_level
WHEN 'S' THEN 10000
WHEN 'A' THEN 5000
END,
0
) AS bonus
FROM staff;
这样即使performance_level不是S也不是A,bonus也至少是0,下游统计不会丢数据。这里其实也可以直接在CASE上加ELSE 0,效果几乎等价。区别是:当CASE逻辑很长,或者中间还有其他计算时,外层COALESCE可读性更高一些。
7.2 NULLIF 防止除零
NULLIF(a, b)的意思是:如果a等于b就返回NULL,否则返回a。配合CASE WHEN可以做除零保护。比如要计算每个商品的利润率,但成本价可能为0:
sql复制SELECT
product_name,
(sale_price - cost_price)::numeric
/ NULLIF(cost_price, 0) AS profit_ratio
FROM product;
如果还想加业务分级条件,比如“成本为0时利润率标记为99.99%”,可以再来一层CASE:
sql复制SELECT
product_name,
CASE
WHEN cost_price = 0 OR cost_price IS NULL THEN 99.99
ELSE round((sale_price - cost_price)::numeric / cost_price * 100, 2)
END AS profit_ratio
FROM product;
这里用cost_price = 0 OR cost_price IS NULL把缺失成本的情况一起拦下来,也避免了对NULL直接做CASE判断的坑。
7.3 STRING_AGG 结合 CASE WHEN 做更友好的拼接
PostgreSQL的STRING_AGG可以把多行字符串拼成一个。有时候你不想把所有值都拼进去,而是按条件筛选一部分拼接。比如一张用户标签表,要把特定来源的标签都拼起来,其他来源忽略:
sql复制SELECT
user_id,
STRING_AGG(
CASE WHEN tag_source = 'system' THEN tag_name END,
','
) AS system_tags
FROM user_tag
GROUP BY user_id;
这个SQL把每个用户的系统标签拼成一行,非系统标签直接传NULL,STRING_AGG会自动忽略NULL,正好符合需求。比先WHERE过滤再拼接少了外层过滤的开销,还可以在同一趟查询里同时拼好几组标签。
7.4 LEAST/GREATEST 与 CASE 的取舍
PostgreSQL有LEAST和GREATEST函数,可以在多个值里取最小或最大。如果需求是“判断多个条件取最小值”,用LEAST更简洁:
sql复制-- 用CASE
SELECT
CASE
WHEN score_a < score_b THEN score_a
ELSE score_b
END AS min_score
FROM student_scores;
实际上:
sql复制SELECT LEAST(score_a, score_b) AS min_score
FROM student_scores;
如果你的判断条件不单纯是比较取最小值,而是带业务规则的分支,那CASE WHEN才是正确选择。函数和CASE WHEN能互相替代的场景少说,更多时候根据可读性选择即可——LEAST/GREATEST擅长极值,CASE WHEN擅长复杂业务分支。
8. 写CASE WHEN的代码风格,决定你的SQL可维护性
CASE WHEN语法不复杂,但写得很烂的CASE WHEN在项目里比比皆是。代码风格直接影响别人接手时的效率,这部分虽然不涉及技术原理,但很值得花点心思。
最典型的坏味道是:一个CASE分支里的THEN又嵌套另一个长CASE,三层起步,缩进混乱,看得人当场头痛。比如:
sql复制SELECT
CASE WHEN a = 1 THEN
CASE WHEN b = 1 THEN 'A1'
WHEN b = 2 THEN 'A2'
ELSE 'A-other' END
WHEN a = 2 THEN 'B'
ELSE 'C' END AS label
FROM ...
这种嵌套不是不行,但建议写之前先停一下,看看能不能用搜索CASE的复合条件改成扁平结构:
sql复制SELECT
CASE
WHEN a = 1 AND b = 1 THEN 'A1'
WHEN a = 1 AND b = 2 THEN 'A2'
WHEN a = 1 THEN 'A-other'
WHEN a = 2 THEN 'B'
ELSE 'C'
END AS label
FROM ...
等价的逻辑,扁平结构清晰很多。记住CASE WHEN是顺序匹配的,保证AND条件在前、兜底条件在后即可。
另一个好习惯是:把CASE WHEN的关键字段名和含义写进注释。尤其是做状态机流转、金额分档这种业务规则密集的SQL,注释比代码本身更重要。我自己给报表写CASE WHEN时,会习惯把业务口径直接写在SQL里,比如:
sql复制-- VIP系数口径:2024-06之前注册按1.2,之后注册按1.5
SELECT
user_id,
amount * CASE
WHEN register_at < '2024-06-01' THEN 1.2
ELSE 1.5
END AS weighted_amount
FROM ...
这样业务方来问“你这个数怎么算的”,直接看SQL就能对答案,不用翻需求文档。
另外,CASE WHEN不要写得过长。如果一个CASE里有七八个分支,尽量把对应关系抽成数据库字典表,通过JOIN去关联翻译。CASE WHEN适合表达一次性的、固定的映射关系;如果映射关系频繁变化,放表里维护远比改SQL方便。
拿一个真实场景来说:有个后台管理系统的下拉菜单要展示订单状态,状态大概有十几种。最初方案是把状态文本的映射放在SQL里,每加一种状态都要提一次发布流程。后来直接建了一张order_status_dict表,列表查询改成一个LEFT JOIN,运营同事自己就能维护映射关系,效率提高很多。这就是CASE WHEN的使用边界问题——它是强大工具,但不该被滥用成万能字典。
