sum(case when ...)能替代子查询因其在主表扫描时一次性完成条件聚合,避免每行执行子查询的性能开销;需配合group by、正确使用sum而非count统计金额,并显式写else 0防null干扰。

为什么SUM(CASE WHEN ...)能替代子查询
因为子查询在SELECT里每行都执行一次,性能差;而SUM(CASE WHEN ...)是在主表扫描时一次性完成条件聚合,本质是“一行一算、汇总一次”。它不触发额外的表访问,也不依赖关联逻辑,只要WHERE和GROUP BY合理,就能把N个子查询压成一个扫描。
写法要点:别漏GROUP BY,也别错用COUNT
常见错误是只写SUM(CASE WHEN status='paid' THEN amount END)却忘了GROUP BY user_id——结果整张表被当一行聚合,所有用户数据全混在一起。另一个坑是误用COUNT(CASE WHEN ...)统计金额总和:COUNT只数非NULL行,会丢掉0值或NULL金额,该用SUM就用SUM。
- 聚合字段(如
amount)必须出现在THEN分支里,不能只写1或TRUE -
ELSE 0建议显式写出,避免NULL参与SUM导致结果为NULL - 多个指标并列时,每个
SUM(CASE ...)独立计算,互不影响
对比示例:从3个子查询到1条语句
原写法(低效):
SELECT user_id, (SELECT SUM(amount) FROM orders WHERE user_id = u.id AND status = 'paid') AS paid_sum, (SELECT SUM(amount) FROM orders WHERE user_id = u.id AND status = 'refunded') AS refunded_sum, (SELECT COUNT(*) FROM orders WHERE user_id = u.id AND created_at > '2024-01-01') AS new_order_cnt FROM users u;
改写后(高效):
SELECT u.user_id, SUM(CASE WHEN o.status = 'paid' THEN o.amount ELSE 0 END) AS paid_sum, SUM(CASE WHEN o.status = 'refunded' THEN o.amount ELSE 0 END) AS refunded_sum, COUNT(CASE WHEN o.created_at > '2024-01-01' THEN 1 END) AS new_order_cnt FROM users u LEFT JOIN orders o ON o.user_id = u.user_id GROUP BY u.user_id;
注意:COUNT(CASE ...)这里用THEN 1而非THEN o.id,更安全;LEFT JOIN保证没订单的用户也能出0值行。
性能敏感场景下的边界提醒
当orders表极大且只查少数用户的聚合时,SUM(CASE...)未必比带WHERE的子查询快——因为前者会扫全量关联数据,后者可能走用户ID索引快速定位。这时候得看实际执行计划:EXPLAIN里如果出现Using temporary; Using filesort或扫描行数远超预期,就得考虑是否该拆回子查询,或者加覆盖索引(比如(user_id, status, amount))。











