WHERE预过滤必须先于CASE WHEN聚合动态条件,否则导致索引失效、全表扫描;正确做法是将参数判空逻辑置于WHERE中,CASE WHEN仅用于已筛选数据内的分类分流,且聚合后需用COALESCE兜底防NULL。

WHERE预过滤必须先于CASE WHEN聚合
动态条件(比如前端传来的 @region、@status)如果塞进 CASE WHEN 里判断,会导致执行计划退化。数据库无法下推索引,全表扫描变多,哪怕加了 INDEX(region, status) 也白搭。
正确做法是把参数判空逻辑提到 WHERE 子句:
WHERE (@region IS NULL OR region = @region) AND (@status IN ('paid', 'pending') OR @status IS NULL)-
CASE WHEN只负责“已筛出数据内的分类分流”,例如SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) - MySQL 8.0+、PostgreSQL、SQL Server 在这种写法下通常能走索引范围扫描
SUM(CASE WHEN ...) 是标准姿势,别用 COUNT(CASE WHEN ...)
统计金额类指标时,COUNT(CASE WHEN status = 'paid' THEN 1 END) 看似可行,但语义错位:它只数行数,不是求和。一旦字段本身含 NULL(如 amount),还得额外补 COALESCE(amount, 0),徒增复杂度。
统一用 SUM 更安全、更直白:
- 必须显式写
ELSE 0,否则SUM会跳过NULL,导致结果偏小 - 所有
THEN和ELSE返回值类型要一致,避免 MySQL 隐式转成DOUBLE后精度丢失 - 若需统计行数(非金额),仍可用
COUNT(CASE WHEN ... THEN 1 END),但别混用在同一个SUM表达式里
多个离散值条件(如多选地区)不能拼字符串
前端传 ['bj', 'sh', 'gz'],千万别写成 WHERE region IN ('" + user_input.join("','") + "') —— 这是 SQL 注入高危路径,单引号转义挡不住 Unicode 换行符绕过。
按数据库能力选安全方案:
- PostgreSQL:用
WHERE region = ANY($1),绑定参数为ARRAY['bj','sh','gz'] - SQL Server:用
STRING_SPLIT(@regions, ',')(2016+)或表值参数(TVP) - MySQL:用
JSON_CONTAINS(@regions_json, CONCAT('"', region, '"')),前提是@regions_json是合法 JSON 字符串,如'["bj","sh"]'
聚合结果为空时补零要靠 COALESCE,不是靠 CASE WHEN
很多人以为在 CASE WHEN 里写 ELSE 0 就能保证最终结果不为 NULL,其实不对。当整组没匹配到任何行(比如某地区无订单),SUM(...) 本身就返回 NULL,ELSE 0 已经不生效了。
真正兜底要在聚合后处理:
- 写成
COALESCE(SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END), 0) - 别依赖
ISNULL或IFNULL,它们是方言函数,跨库迁移时容易出错 - 如果 GROUP BY 的字段本身可能为
NULL(比如region允许空值),记得用COALESCE(region, 'UNKNOWN')统一归组
实际执行时最容易被忽略的是:**CASE WHEN 的分支顺序决定结果归属,且不可回溯**。比如写 CASE WHEN amount ,那 499 元和 501 元都会进第一支,第二支永远不触发。这种逻辑偏差不会报错,但数据就 quietly 错了。










