最稳的行转列方式是CASE WHEN配合聚合函数,需注意NULL处理、GROUP BY完整性、聚合函数选择及动态列拼接等关键点。

用 CASE WHEN + 聚合函数做条件聚合,是最稳的行转列方式
MySQL 5.7、SQL Server、PostgreSQL 都支持,不依赖版本特性,兼容性最强。核心不是“写对语法”,而是避免因 NULL 或分组粒度错导致结果为空或重复。
关键点:
-
CASE WHEN必须和聚合函数(如SUM、MAX)配对使用,否则会报错或返回多行 - 每个
CASE后必须加ELSE 0或ELSE NULL,否则不匹配的行默认为NULL,可能让整列聚合结果变NULL -
GROUP BY的字段必须覆盖所有非聚合列,漏掉一个就会让行数膨胀或聚合失效 - 若目标列是标识类字段(如用户状态、产品类型),优先用
MAX;若是数值指标(如销售额、数量),用SUM更合理
PIVOT 在 SQL Server 中能省代码,但要求列名完全已知
SQL Server 2005+ 支持 PIVOT,语法更紧凑,但本质仍是条件聚合的封装。它不接受子查询或变量作为列名列表,IN 子句里必须写死值。
典型陷阱:
- IN 列表里的列名必须和源数据中实际值**完全一致**(包括大小写、空格、特殊字符),否则该列直接消失,不报错
-
PIVOT内部强制执行GROUP BY,所以源子查询不能含未聚合的非键字段,否则报错"Msg 8120" - 聚合函数选错会导致语义错误:比如用
AVG处理状态码,结果变成小数,毫无业务意义 - 如果源数据中某列值有重复(如同一用户在同季度有多条记录),
PIVOT会自动聚合,但你未必知道它用的是哪个函数
动态列必须拼 SQL,GROUP_CONCAT 和 EXECUTE 是绕不开的坎
当“要转成列的值”来自业务数据(比如每月新增一个产品型号),就不能硬编码列名,得先查出所有唯一值,再拼成 IN 列表。
MySQL 和 SQL Server 分别要注意:
- MySQL 中用
GROUP_CONCAT拼列名时,默认长度限制是 1024,超长会被截断,需提前设SET SESSION group_concat_max_len = 10000 - SQL Server 用
STRING_AGG(2017+)或 XML PATH(旧版)拼接,拼完必须用EXEC sp_executesql,不能直接 SELECT - 拼出来的列名若含空格或连字符(如
[Q1-2026]),必须用方括号包裹,否则解析失败 - 动态 SQL 执行前建议先
PRINT或SELECT出完整语句,人工验证逻辑,避免运行时报错后难以定位
别忽略 GROUPING SETS / ROLLUP,它们解决的是另一类“多维透视”问题
条件聚合和 PIVOT 解决的是“把某列值展开成多列”,而 GROUPING SETS 解决的是“同时按多个维度组合聚合”,比如既要按地区看销量,又要按产品类别看,还要看总计 —— 这不是行转列,是多粒度汇总。
容易混淆的点:
-
GROUPING SETS ((region), (category), ())输出 3 类行,但每行仍是单列结构,不会把 region 和 category 变成横向并列字段 - 它和
PIVOT不互斥,但目的不同:前者减少多次 UNION 查询,后者改变结果集形状 - 如果误用
ROLLUP替代CASE WHEN,你会发现列没变多,只是多了几行小计,根本不是你要的横表 -
GROUPING()函数返回 1 表示该列为聚合占位符(不是原始 NULL),这点常被忽略,导致前端展示时把小计当真实数据

















