SUM(CASE WHEN...)能替代PIVOT做行转列,因为标准SQL无PIVOT,而该组合通过条件累加将同一组内不同类别值分别提取并压缩为单行,每列对应一类;必须搭配GROUP BY分组字段且CASE分支需写ELSE 0防NULL干扰。

为什么SUM(CASE WHEN ...)能替代PIVOT做行转列
因为标准SQL里没有PIVOT,但SUM和CASE WHEN组合天然支持“按条件累加”,把同一组内不同类别的值分别提取出来——本质是用聚合把多行压缩成一行,每列对应一个类别。
常见错误是漏写GROUP BY,导致全表只出一行;或者CASE WHEN没写ELSE 0,遇到NULL时SUM结果变NULL。
- 必须搭配
GROUP BY分组字段(比如user_id或date) -
CASE WHEN分支里每个THEN后面只能跟数值型表达式,不能是字符串(除非后续转类型) - 所有
CASE分支都建议显式写ELSE 0,避免NULL干扰求和
怎么写一个带销售品类的月度汇总报表
假设原始表sales有sale_date、category('A'/'B'/'C')、amount三列,要按月统计各品类销售额:
SELECT
DATE_TRUNC('month', sale_date) AS month,
SUM(CASE WHEN category = 'A' THEN amount ELSE 0 END) AS a_sales,
SUM(CASE WHEN category = 'B' THEN amount ELSE 0 END) AS b_sales,
SUM(CASE WHEN category = 'C' THEN amount ELSE 0 END) AS c_sales
FROM sales
GROUP BY DATE_TRUNC('month', sale_date)
ORDER BY month;
注意:DATE_TRUNC在PostgreSQL/Redshift中可用;MySQL要用DATE_FORMAT(sale_date, '%Y-%m-01');SQLite用strftime('%Y-%m-01', sale_date)。函数名差异直接影响能否执行。
遇到NULL值或空类别怎么办
如果某个月某个品类没数据,上面写法会显示0——这是正确行为。但如果原始amount本身可能为NULL,且没写ELSE 0,那整列会变成NULL。
- 用
COALESCE(amount, 0)提前兜底,比依赖ELSE更保险 - 若需显示“无数据”而非0,得用
MAX或STRING_AGG配合CASE,但那就不是数值聚合了 - 新增品类时,必须手动加一列
SUM(CASE...),没法自动扩展——这点不如数据库原生PIVOT
性能上要注意什么
单次扫描就能完成所有列计算,比多次子查询或JOIN快得多,但前提是category值少且固定。如果category有上百种,硬写上百个CASE列不仅难维护,还可能触发某些数据库的表达式长度限制。
- PostgreSQL对单条SQL的表达式树深度有限制,超长CASE链会报
stack depth limit exceeded - MySQL 5.7+在
GROUP BY后允许隐式非聚合字段,但开启sql_mode=only_full_group_by时会报错,务必检查 - 如果品类动态变化,优先考虑应用层拼接,而不是在SQL里硬编码所有CASE分支
真要处理动态列,得靠客户端逻辑或存储过程生成SQL——这一步很容易被忽略,但恰恰是业务扩展时最先卡住的地方。

















