做SQL开发这些年,我见过最密集的翻车现场,不是复杂的多表连接,也不是深不见底的窗口函数,而是看起来人畜无害的条件分支。一个CASE WHEN写反了顺序,一条UPDATE忘记写ELSE,结果就是整张表的数据被改得七零八落。SQL里的条件分支,核心就是CASE表达式,以及它衍生出来的各类写法。这篇我把自己在实际项目里积累的用法、坑点和排查思路一次性整理出来,希望能帮你把这块彻底吃透。
如果你刚接触SQL,条件分支可以简单理解成"按条件决定结果":满足A条件给A结果,满足B条件给B结果,都不满足就给兜底结果。它能用在SELECT里做字段加工,也能用在WHERE、UPDATE、HAVING甚至窗口函数里做逻辑控制。这篇会从一条完整的SQL语句,从SELECT到WHERE到GROUP BY再到UPDATE,把条件分支能做的事全部过一遍。
1. CASE WHEN的求值规则:先和普通if-else做个区分
1.1 简单CASE表达式和搜索式CASE的区别
CASE表达式在SQL里有两种写法:一种是简单表达式,一种是搜索式表达式。
简单CASE的写法是CASE后面跟着一个列名,然后WHEN后面写值:
sql复制SELECT
order_id,
CASE order_status
WHEN 1 THEN '待支付'
WHEN 2 THEN '已支付'
WHEN 3 THEN '已发货'
ELSE '未知状态'
END AS status_text
FROM orders;
简单CASE实际上做的是等值匹配,它把CASE后面的列值和每个WHEN后面的值做等号比较。注意这里的等值比较用的是=,不是IS,所以一旦列值是NULL,这个CASE表达式不会匹配任何WHEN,直接落入ELSE分支。
搜索式CASE则是CASE后面不跟列名,WHEN后面写完整的布尔表达式:
sql复制SELECT
order_id,
amount,
CASE
WHEN amount >= 1000 THEN 'A类客户'
WHEN amount >= 500 THEN 'B类客户'
WHEN amount IS NULL THEN '金额未知'
ELSE 'C类客户'
END AS customer_level
FROM orders;
搜索式CASE能处理范围判断、NULL判断、子查询、函数调用,比简单CASE灵活得多。实际开发里我90%的情况都写搜索式CASE,简单CASE多用在枚举状态字段的翻译上。
1.2 短路求值:WHEN子句的执行顺序
CASE表达式从第一个WHEN开始,从上到下逐个判断,一旦某个WHEN条件为TRUE,就返回对应的THEN结果,后面的WHEN直接跳过。这个行为和很多编程语言的if-else if链一样,叫短路求值。
正因为有这个执行顺序,WHEN子句的排列顺序会直接影响结果。举个例子:
sql复制SELECT
order_id,
amount,
CASE
WHEN amount >= 500 THEN '中额'
WHEN amount >= 1000 THEN '高额'
ELSE '低额'
END AS amount_level
FROM orders;
这段SQL逻辑上就是错的。金额1500的订单,先判断amount >= 500为TRUE,直接返回"中额",永远走不到amount >= 1000的分支。这不是数据库的问题,而是CASE求值顺序决定的:条件满足一个就收手,后面的分支爱莫能助。
写完CASE表达式,建议自己心里跑一遍每条数据,确认分支顺序符合业务语义。特别是范围判断,从大到小写或者从小到大写都要有意识,不是怎么顺手怎么来。
1.3 为什么"没有ELSE就返回NULL"这个细节很要命
CASE表达式的ELSE是可选的。不写ELSE,当所有WHEN分支都不满足时,CASE表达式返回NULL。
这个行为在SELECT里做字段加工可能影响不大,最多少一个值。但在UPDATE里就危险了:
sql复制UPDATE orders
SET order_status = CASE
WHEN order_type = 'normal' THEN 1
WHEN order_type = 'vip' THEN 2
END;
如果表里存在order_type既不是'normal'也不是'vip'的行,这条更新会把那行的order_status置成NULL。NULL在业务系统里往往意味着"异常"或"未知",一旦出现,后续查询、统计、展示全都会出问题。
我自己的原则是:凡是UPDATE里用CASE,必须写ELSE保留原值,或者显式处理所有可能的分支。宁愿多写一个ELSE order_status,也不要偷懒留隐患。
注意:CASE表达式里所有THEN返回值的类型必须一致或能隐式转换。如果一条返回字符串,一条返回数字,数据库会做隐式转换,转换失败就直接报错,转换成功也可能影响索引和性能。这个细节放到后面专门讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 最容易踩的三个分支坑:NULL、WHEN顺序、返回类型
2.1 NULL与比较运算符:永远UNKNOWN
SQL里有三值逻辑:TRUE、FALSE、UNKNOWN。NULL参与任何比较运算,结果都是UNKNOWN,既不是TRUE也不是FALSE。
这句话翻译成人话就是:NULL = 1不成立,NULL = '正常'不成立,NULL = NULL同样不成立。NULL和任何值做等值比较,结果都不是TRUE。
所以下面这两个写法,效果完全不一样:
sql复制-- 错误写法:永远不会匹配到NULL
CASE
WHEN order_status = NULL THEN '未知状态'
ELSE '已知状态'
END
-- 正确写法:用IS NULL判断
CASE
WHEN order_status IS NULL THEN '未知状态'
ELSE '已知状态'
END
同样的道理也适用于NOT IN、NOT LIKE这些否定操作。WHERE order_status NOT IN ('待支付', '已支付'),如果表里有NULL的订单状态,这条SQL不会把它查出来,因为NULL NOT IN任何集合的结果都是UNKNOWN,这个WHERE条件会直接过滤掉NULL行。
条件分支里遇到NULL,先想清楚业务上NULL代表什么:是未填写、是不适用、还是数据缺失?判断方式用IS NULL或者IS NOT NULL,不要用等号。
2.2 WHEN子句顺序错位:一个范围判断的经典翻车
范围判断的条件分支,WHEN顺序写反是最高频的翻车场景。前面提过一个金额例子,这里再说一个更隐蔽的。
假设一家电商平台对订单做分账,0到100元手续费5元,100到500元手续费10元,500元以上手续费20元。新手可能这样写:
sql复制SELECT
order_id,
amount,
CASE
WHEN amount > 0 AND amount <= 100 THEN 5
WHEN amount > 100 AND amount <= 500 THEN 10
WHEN amount > 500 THEN 20
END AS fee
FROM orders;
这段逻辑没问题。但如果把第一个条件改成WHEN amount <= 100,第二个条件保持WHEN amount > 100 AND amount <= 500,也没有问题。真正容易错的是把区间顺序倒过来,先判断大区间,小区间永远不会命中。
比如:
sql复制WHEN amount BETWEEN 1 AND 500 THEN 10
WHEN amount BETWEEN 1 AND 100 THEN 5
这个写法第一个分支把所有500以内的订单都拦走了,100以内的订单根本走不到第二个分支。结果就是小额订单也被收了10元手续费,业务上直接引起投诉。
这里有个实操心得:写完条件分支后,拿边界值逐一手动推演一遍。0、100、500、501这几个边界值都过一遍,如果都能落到预期分支,基本可以放心。
2.3 返回类型必须一致:隐式转换的代价
CASE表达式每个THEN分支的返回类型会做合并推导。如果各分支返回类型不一致,数据库会尝试隐式转换。隐式转换带来的问题有两类:
第一类是报错。比如一个分支返回数字,另一个分支返回'未知',在某些数据库里会运行时报错。
第二类是索引失效。用一张1000万行的大表做测试,WHERE条件里写CASE WHEN col = 1 THEN 1 ELSE 0 END = 1,这个条件放在WHERE里,数据库可能因为表达式计算无法走索引,只能全表扫描。把WHEN分支的返回类型统一成数字,或者改写成普通条件,执行计划才会恢复正常。
判断返回类型是否匹配,最简单的标准是:CASE表达式最终会返回一个通用的类类型,比如数字和数字、字符串和字符串,尽量不要混搭。万一业务上确实需要同时输出数字和文案,可以考虑把数字转成字符串,或者把文案用编码代替。
3. 不同数据库的分支写法:IF、IIF、DECODE与CASE怎么选
3.1 SQL Server的IIF和IF-ELSE
SQL Server从2012版本开始提供了IIF函数,语法是IIF(condition, true_value, false_value)。可以理解成CASE WHEN的语法糖:
sql复制SELECT
order_id,
IIF(amount >= 500, '大单', '小单') AS order_scale
FROM orders;
IIF和CASE WHEN没有性能差异,SQL Server在编译时会把IIF改写为CASE表达式。实际项目里,IIF适合简单的一对一判断,一旦条件超过两个,IIF的可读性会急剧下降。因为IIF只有两个结果分支,多条件要写嵌套:
sql复制IIF(amount >= 1000, 'A', IIF(amount >= 500, 'B', 'C'))
这样嵌套三层以上基本没法维护。我自己的习惯是:两个分支用IIF,三个及以上分支果断用CASE WHEN。另外,SQL Server里的IF-ELSE是过程化语句,只能用在存储过程、脚本这样的批处理里,不能直接写在SELECT语句中。很多人误以为IF能当表达式用,实际上一执行就报错。
3.2 MySQL的IF和IFNULL
MySQL里IF函数和SQL Server的IIF用法一样:IF(condition, true_value, false_value)。同样适合简单判断:
sql复制SELECT
order_id,
IF(amount > 0, '有效订单', '无效订单') AS valid_flag
FROM orders;
MySQL还有IFNULL和COALESCE两个处理NULL的函数。IFNULL只有两个参数,IFNULL(expr1, expr2),expr1非NULL就返回expr1,否则返回expr2。COALESCE可以接受多个参数,返回第一个非NULL的值。
条件分支里需要给NULL值设置兜底值时,优先用COALESCE。比如:
sql复制SELECT
customer_id,
COALESCE(phone, '无手机号') AS phone_text
FROM customers;
COALESCE其实是CASE WHEN的一种等价实现,SQL Server、PostgreSQL、Oracle、SQLite都支持,跨库兼容性比IF和IIF更好。
3.3 Oracle的DECODE
Oracle长年提供DECODE函数做等值匹配,它比CASE表达式出现得早,很多老一代Oracle开发习惯用DECODE:
sql复制SELECT
order_id,
DECODE(order_status, 1, '待支付', 2, '已支付', 3, '已发货', '未知')
FROM orders;
DECODE擅长等值匹配,写法简洁。但它的支持范围有限,两个明显问题:
一是不能做范围判断。DECODE(amount, ...)只能判断amount等不等于后面那个值,没法判断amount大于等于某个值。
二是NULL判断行为特殊。DECODE(NULL, NULL, '空值', '非空')在Oracle里可以匹配NULL,因为DECODE内部对NULL做了特殊处理。但换成CASE就要求必须写IS NULL才能匹配。
综合来看,Oracle的新项目我建议用CASE WHEN,跨库迁移时少一个改写成本。老项目里已经用了DECODE的,不涉及迁移也可以保留。
3.4 迁移场景下为什么CASE是安全牌
如果团队有数据库迁移计划,比如从Oracle迁到PostgreSQL,或者从SQL Server迁到MySQL,条件分支的写法优先级应该是:CASE表达式 > COALESCE > 其他厂商函数。
CASE WHEN是SQL标准语法,主流关系型数据库全部支持,写法也几乎一致。IIF、IF、DECODE这些厂家特有函数,换一个数据库就得逐条改写。SQL标准还不止在CASE上,COALESCE、NULLIF这些也都是跨库通用的,条件分支相关的场景能靠标准函数解决就不引入厂商函数。
提示:项目里做跨库开发时,可以约定一条团队规范:条件分支统一用CASE表达式,NULL兜底统一用COALESCE。这条规范能省掉很多迁移时的无效工时。
4. 条件组合的优先级:AND、OR、BETWEEN容易被忽视的边界
4.1 AND比OR优先:一个忘了括号的条件
条件分支放在WHERE里,经常是多个条件组合。SQL里AND的优先级高于OR,也就是说,不加括号的情况下,OR周围的AND会先执行。
看这个经典例子:
sql复制WHERE order_type = 1 OR order_type = 2 AND amount > 100
实际执行是:
sql复制WHERE order_type = 1 OR (order_type = 2 AND amount > 100)
如果你的业务意图是"订单类型为1或2,且金额都大于100",那必须主动加括号:
sql复制WHERE (order_type = 1 OR order_type = 2) AND amount > 100
这两种写法结果可能完全不一样。第一种会把所有type=1的订单捞出来,哪怕金额是0;第二种要求type=1且金额>100,或者type=2且金额>100。
大多数人一眼看过去,第一反应都是第二种逻辑,但没加括号数据库执行的是第一种。所以我的习惯是:OR条件一律加括号,不管优先级是不是多此一举。写代码不是炫技,清晰明确比少打两个括号更重要。
4.2 BETWEEN AND是包含边界的
BETWEEN ... AND ... 判断范围时包含边界值。amount BETWEEN 100 AND 200等价于amount >= 100 AND amount <= 200。
这个特性在条件分支里影响很大。处理金额区间时,如果用BETWEEN做边界,两个相邻区间会重复命中同一条数据:
sql复制CASE
WHEN amount BETWEEN 1 AND 100 THEN '区间A'
WHEN amount BETWEEN 100 AND 200 THEN '区间B'
END
金额是100的订单会同时满足第一个WHEN,因为短路求值,它只返回"区间A"。但如果把两个区间做统计,用CASE分别计数,100这个金额会被重复计入两个区间,统计结果就偏了。
条件分支里的区间边界建议用左闭右开或者左开右闭的写法:
sql复制CASE
WHEN amount >= 1 AND amount < 100 THEN '区间A'
WHEN amount >= 100 AND amount < 200 THEN '区间B'
END
这个写法让数据点在边界上只属于一个区间,统计口径不会出现重叠。
距离说明一下,这就是类似"剪刀差"的边界管理思路。左边一剪刀,右边一剪刀,中间永远只归一边。
4.3 三值逻辑:True、False和Unknown
SQL的WHERE条件求值不是非真即假,而是三值逻辑:TRUE、FALSE、UNKNOWN。只有WHERE条件为TRUE的行才会进入结果集,FALSE和UNKNOWN都会被过滤。
这个机制和条件分支的组合条件有什么关系?一个常见问题是把列值和NULL做不等式判断:
sql复制WHERE amount > 100 OR amount <= 100
直觉上这句把所有行都覆盖了,因为任何数字不是大于100就是小于等于100。但amount为NULL时,NULL > 100是UNKNOWN,NULL <= 100也是UNKNOWN,两个UNKNOWN做OR仍然是UNKNOWN,WHERE返回UNKNOWN,这一行被过滤掉了。
如果你确实想保留NULL,就必须显式加条件:
sql复制WHERE amount > 100 OR amount <= 100 OR amount IS NULL
条件分支里判断组合条件时,心里始终带着三值逻辑这个弦。遇到NULL非空处理,一定主动用IS NULL和IS NOT NULL,而不是试图用"取反"或者"补集"来间接表达。
5. 条件分支在聚合统计里的高频写法:SUM(CASE WHEN)搭配窗口函数
5.1 行转列和条件统计
条件分支在聚合统计里最经典的应用是行列转换,或者叫条件计数。核心写法是:COUNT配合CASE WHEN,每个分支返回1或0,对1计数。
假设有一张订单表orders,字段有customer_id、order_type、order_status、amount,订单状态有'待支付'、'已完成'、'已取消',每个客户想按状态统计订单数量:
sql复制SELECT
customer_id,
COUNT(*) AS total_orders,
SUM(CASE WHEN order_status = '已完成' THEN 1 ELSE 0 END) AS completed_orders,
SUM(CASE WHEN order_status = '待支付' THEN 1 ELSE 0 END) AS pending_orders,
SUM(CASE WHEN order_status = '已取消' THEN 1 ELSE 0 END) AS canceled_orders
FROM orders
GROUP BY customer_id;
这个写法把同一张表里的不同状态聚合到一行展示,在报表里非常实用。先按客户分组,再对各状态分别计数。
同样的思路,也可以做条件求和:
sql复制SUM(CASE WHEN order_type = 'vip' THEN amount ELSE 0 END) AS vip_amount
这段SQL在STDDEV、AVG等聚合函数里也适用。把CASE表达式放在聚合函数内部,是SQL条件分支最实用的一招,可以组合出各种维度的统计指标。
5.2 窗口函数里的条件分支
窗口函数配合条件分支,能实现分组维度的动态计算。
比如每个客户要算累积已完成订单金额,按订单日期排序:
sql复制SELECT
order_id,
customer_id,
order_date,
amount,
SUM(CASE WHEN order_status = '已完成' THEN amount ELSE 0 END)
OVER (PARTITION BY customer_id ORDER BY order_date) AS cum_completed_amount
FROM orders;
这个查询返回每个订单,同时附带该客户在此之前所有已完成订单的累积金额。CASE表达式负责过滤订单状态,SUM OVER负责累积。
窗口函数里加条件分支,几乎可以支持任意维度的聚合统计。按月统计、按类别统计、动态占比,都能在一条SQL里完成。相比先用子查询把明细聚合成一张中间表再来关联,这个写法执行效率更高,代码也更紧凑。
5.3 UPDATE批量修正时CASE的用法与注意
条件分支在UPDATE里最常用的是批量修正数据。假设订单状态表里,状态字段受时间影响需要批量变更:
sql复制UPDATE orders
SET order_status = CASE
WHEN order_status = '待支付' AND order_date < '2024-01-01' THEN '已过期'
WHEN order_status = '待支付' AND order_date >= '2024-01-01' THEN '进行中'
ELSE order_status
END
WHERE order_status IN ('待支付', '已过期', '进行中');
这个例子你可以直接抄。注意两点:第一,CASE里最后一个ELSE保留原值,避免误改;第二,WHERE里也加了限定范围,限制UPDATE影响的行数。
还有一个更险的坑:遗漏ELSE会把非目标行置为NULL,这个前面已经说过了。UPDATE之前先跑一条SELECT,把CASE表达式的结果查出来核对,确认无误再改成UPDATE,这是最稳妥的流程。
实操建议:任何生产环境的批量UPDATE,先复制为SELECT,在SELECT里把CASE的结果列出来,人工核对几条目标数据的预期结果,确认后再执行UPDATE。这一步能挡住九成以上的误更新。
6. 分支能不能放进WHERE、JOIN和HAVING?边界与取舍
6.1 WHERE里用CASE:逻辑对了性能不一定对
有些场景想在WHERE里根据条件动态决定过滤值,比如传入一个参数,当参数是'all'时不限制订单类型,否则按指定类型过滤:
sql复制-- 不推荐:WHERE里直接套CASE,容易影响索引
WHERE CASE WHEN @type = 'all' THEN 1 ELSE order_type END =
CASE WHEN @type = 'all' THEN 1 ELSE @type END;
这个写法在逻辑上是能跑的,但会把order_type列包在CASE表达式里。数据库的索引是针对列值建立的,表达式计算后想走索引,难度极大。大多数情况下这个查询会退化成全表扫描。
更好的写法是把条件拆开,用OR组合:
sql复制WHERE (@type = 'all' OR order_type = @type);
这个写法在@type = 'all'时跳过order_type条件,在@type不是'all'时走order_type索引。执行计划明显更友好。条件分支在WHERE里的原则是:能不用CASE就不用,能用OR、AND和括号表达清楚的条件组合,优先用逻辑运算符表达。
6.2 JOIN条件里放CASE:连接列不确定的风险
JOIN条件里使用CASE,等于告诉数据库"连接列是不确定的,可能按A列连,也可能按B列连"。这种写法想高效利用索引,基本不可能。
比如做了字段合并:
sql复制SELECT *
FROM table_a a
LEFT JOIN table_b b
ON CASE WHEN a.type = 'A' THEN a.ref_id ELSE a.other_ref_id END = b.id;
这种写法每次匹配都要对CASE做计算,而且连接列在不同的CASE分支里可能不同,索引选择变得非常复杂。数据量小的时候看不出问题,数据量一上去,执行时间直接飙升。
换个思路,把CASE分支拆成两个JOIN,用UNION合并,或者用OR条件连接,执行计划往往更稳定。JOIN条件里应该用确定性的等值连接,条件分支逻辑尽量放在WHERE或者SELECT里做。
6.3 HAVING里用CASE做占比过滤
GROUP BY之后,HAVING子句里也可以用CASE,做分组结果的二次筛选。
比如要找出"高价值订单占比超过30%的客户":
sql复制SELECT
customer_id,
COUNT(*) AS total_orders,
SUM(CASE WHEN amount > 1000 THEN 1 ELSE 0 END) AS high_value_orders,
SUM(CASE WHEN amount > 1000 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS high_value_ratio
FROM orders
GROUP BY customer_id
HAVING SUM(CASE WHEN amount > 1000 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) > 0.3;
HAVING里和SELECT里用了同一个CASE表达式。需要注意,HAVING是在GROUP BY分组后执行的,所以HAVING里引用的必须是聚合函数或者GROUP BY的列,不能直接引用原始列。CASE在HAVING里同样要包裹聚合函数才有意义。
占比计算时用* 1.0防止整数除法得到0,这个细节在不同数据库里写法略有差异,MySQL里也可以直接用COUNT(CASE WHEN ... THEN 1 END) / COUNT(*),注意转换。
7. 条件分支SQL结果不对的排查链路:从现象到根因
7.1 先把需求翻译成真值表
排查条件分支SQL结果不对,第一步不是改代码,而是把业务需求翻译成真值表。
比如需求是"统计本月活跃用户中,消费金额在500-1000元,且至少完成一单'已完成'订单的用户数量"。拆成条件:
- 消费金额:500 <= amount <= 1000
- 活跃用户:有登录记录
- 完成状态:order_status = '已完成'
- 时间范围:本月
把每个条件列出来,明确AND和OR关系,明确NULL值的含义,再回头看SQL里的CASE分支是否一一对应。大多数情况下,问题就出在"我以为需求是A,实际SQL写成了B"。
我排查时习惯用纸笔或者文档把真值表画出来。比如一条待支付且金额大于500的订单,预期落到哪个分支?待支付且金额小于等于500呢?已取消的大金额订单呢?NULL金额的订单呢?每个组合都过一遍,和SQL的CASE分支做对照。
7.2 换个角度看:每个分支都拉出来验一遍
条件分支的排查,可以直接把CASE表达式拆开,逐步验证每个分支。
比如这段SQL:
sql复制SELECT
order_id,
amount,
CASE
WHEN amount > 100 THEN '高'
WHEN amount > 50 THEN '中'
ELSE '低'
END AS level
FROM orders;
排查时,可以把每个WHEN条件拉出来单独统计,看数据分布是否符合预期:
sql复制SELECT
CASE
WHEN amount > 100 THEN '高'
WHEN amount > 50 THEN '中'
ELSE '低'
END AS level,
COUNT(*) AS cnt
FROM orders
GROUP BY CASE
WHEN amount > 100 THEN '高'
WHEN amount > 50 THEN '中'
ELSE '低'
END;
如果高、中、低三个级别分布和业务直觉差距很大,多半是WHEN条件优先级或者顺序的问题。再加上NULL值统计,一步步验证。
7.3 用数据说话:先看分布,再看明细
条件分支跑出来结果不对,先查分布,再查明细。
查分布就是上面那个GROUP BY统计,看每个分支的行数。查明细就是拿几条特定的样本数据,跑不带CASE的原始查询,一条条核对。
比如你怀疑金额100的订单被划到'高'档,直接查:
sql复制SELECT *
FROM orders
WHERE amount = 100;
肉眼确认金额100的订单,再对比CASE结果里的level字段。如果确实是'高',那就说明边界值处理不对,可能需要调整条件为amount >= 100或amount > 100。
这种方法和调试程序时打印变量本质一样。数据是SQL最好的调试器,直接拿数据验,比盯着SQL文本空想效率高得多。
7.4 数值和性能问题检查点
如果条件分支SQL结果对,但跑得很慢,重点检查这几个点:
第一,WHERE里是否包了CASE导致索引失效。优先改写为普通条件组合,让索引有机会生效。
第二,CASE表达式是否对列做了隐式类型转换,尤其在CASE返回值类型不统一的情况下。检查一下列的类型,以及THEN返回值的类型是否一致。
第三,JOIN条件里是否放CASE导致连接计算成本高。数据量大时,优先改为确定性等值连接。
第四,聚合函数内部使用CASE时,是否整表扫描导致昂贵计算。如果确实需要整表扫描,考虑加过滤范围减少扫描行数。
针对执行计划,用EXPLAIN或者执行计划工具看关键操作的cost也有帮助。大表上能不能走索引,EXPLAIN一眼就能看出来。
最后再分享一个我自己的习惯:条件分支SQL在提交或者上线前,我一定会在测试环境跑一遍边界值测试。100、1000、边界日期、NULL,这些值逐个验一遍,确认每个边界数据都落在预期分支。这个习惯帮我挡住了无数次线上故障,比事后排查高效得多。
