首页/数据/SQL 速查/用 SUM(CASE) 做行转列透视
聚合与窗口函数54 条

用 SUM(CASE) 做行转列透视

不依赖专用函数实现行列转换:把要展开的维度值写成 CASE 分支,外层 SUM/COUNT 得到透视表;维度值需提前确定。

SQL 速查数据更新于 2026-10-13

用条件聚合做行转列

把行变成列的标准手法是条件聚合:为每个目标列写一个 SUM(CASE WHEN 维度 = 值 THEN 度量 ELSE 0 END),一次扫描就能同时算出多列结果。MySQL 8 与 PostgreSQL 都支持这种写法;MySQL 还可以用 IF() 简写,PostgreSQL 只能用 CASE 或 FILTER 子句。相比把多个指标拆成多条查询再由应用层拼装,条件聚合只扫一遍数据,与 GROUP BY 天然配合,是报表与看板最常用的透视实现方式。

基本信息

适用平台MySQL 8.0 / PostgreSQL / SQL Server / Oracle
通用写法SUM(CASE WHEN 条件 THEN 度量 ELSE 0 END)
MySQL 简写SUM(IF(条件, 度量, 0)),PostgreSQL 没有 IF()
NULL 处理SUM 忽略 NULL;无匹配行时 SUM 返回 NULL,可用 COALESCE 兜底
列数限制SQL 的列必须在写语句时确定,维度值动态变化时要靠应用层拼 SQL 或改用 JSON 聚合

典型使用场景

  • 订单状态透视:一行同时展示已支付、已退款、待发货等各状态数量
  • 月份宽表:把 1 月到 12 月的金额铺成 12 列,便于导出与图表
  • 渠道拆解:同一行内比较各渠道的销售额与订单量
  • 看板一次查询返回全部指标,减少往返次数
  • 与总量列配合算占比:既看绝对值也看结构

MySQL 写法

MySQL 写法
SELECT DATE(created_at) AS d,
       SUM(CASE WHEN status = 'paid'     THEN 1 ELSE 0 END) AS paid_cnt,
       SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) AS refund_cnt,
       SUM(amount)                                          AS total_amount,
       SUM(IF(channel = 'app', amount, 0))                  AS app_amount   # MySQL 可用 IF 简写
FROM orders
WHERE created_at >= '2026-09-01'
GROUP BY DATE(created_at)
ORDER BY d;

PostgreSQL 写法

PostgreSQL 写法
-- PostgreSQL 没有 IF(),可以用 CASE,也可以用 SQL 标准的 FILTER
SELECT date_trunc('month', created_at)::date AS m,
       COUNT(*) FILTER (WHERE status = 'paid')     AS paid_cnt,
       COUNT(*) FILTER (WHERE status = 'refunded') AS refund_cnt,
       COALESCE(SUM(amount) FILTER (WHERE status = 'paid'), 0) AS paid_amount
FROM orders
GROUP BY 1
ORDER BY 1;

几种写法对照

写法支持的库备注
SUM(CASE WHEN … THEN 1 ELSE 0 END)通用跨库一致,最稳的选择
SUM(IF(cond, val, 0))MySQL更短,PostgreSQL 不支持
COUNT(*) FILTER (WHERE …)PostgreSQL 等可读性好;COUNT 无匹配时返回 0
SUM(…) FILTER (WHERE …)PostgreSQL 等无匹配行时返回 NULL,需要 COALESCE
原生 PIVOT 语法Oracle / SQL ServerMySQL 与 PostgreSQL 未提供
条件聚合的列数是写死的:如果维度值来自数据(渠道列表经常变),要么由应用层根据维度凑 SQL,要么先聚合成 JSON 交给前端做透视。
透视的筛选条件必须放在 CASE 分支里:WHERE 在聚合前就执行,把状态条件写进 WHERE 会让其他状态整列消失,只剩一个非零分支。
各分支列相加应等于总量列:这个等式是最实用的口径自检,一旦对不上就说明 CASE 条件与业务口径不一致,或存在未覆盖的状态取值。
别把 COUNT(*) 与 COUNT(CASE WHEN … THEN 1 END) 混用:前者统计所有行,后者只统计满足条件的行;写成 COUNT(*) 做条件计数会得到全量行数,指标静默失真。

易错点

  • 用 COUNT(*) 代替条件计数,算出来的是整组行数而不是满足条件的行数
  • CASE 分支的 ELSE 写成 1(应为 0),把不匹配的行也计入指标
  • 对可能无匹配行的 SUM 忘记 COALESCE,报表里出现 NULL 导致前端展示异常
  • 维度值硬编码在 SQL 里,业务新增渠道后报表静默漏数据

数据来源与参考

  • MySQL 8.0 Reference Manual · CASE
  • PostgreSQL Documentation · CASE
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌