MySQL无PIVOT函数,需用CASE WHEN+GROUP BY模拟行转列:数值型用SUM(CASE)并ELSE 0,字符串型用GROUP_CONCAT或MAX(CASE);动态列须应用层拼SQL,超20列应改用应用层组装。

MySQL没有PIVOT函数,得用CASE WHEN + GROUP BY硬写
MySQL原生不支持PIVOT语法,不像SQL Server或Oracle可以直接调用。想把行数据按某个字段值“摊开”成列(比如把不同产品线的销售额变成横向并列的字段),必须手动用CASE WHEN配合GROUP BY聚合来模拟。
常见错误是漏掉GROUP BY,或者CASE WHEN里没加ELSE 0导致NULL参与求和——结果整列变NULL;另一个坑是字段名重复或含空格,后续程序取值时出错。
- 每个要转成列的值(如产品类别)对应一个
CASE WHEN product = 'A' THEN sales ELSE 0 END - 必须套一层
SUM()或MAX()等聚合函数,否则只返回首行匹配值 -
GROUP BY的字段应是你要“保留为行”的维度(如月份、地区),不能漏 - 如果类别值不确定(比如动态新增产品),就得先查出所有类别,再拼SQL——没法纯SQL自动完成
用SUM(CASE WHEN ...)做数值型行转列最稳妥
统计类报表(销售额、订单数、用户量)基本都走这条路。用SUM比MAX或MIN更直观:每个CASE只对匹配行输出数值,其余为0,加总后就是该类别的总量。
例如按月份统计各产品销售额:
SELECT month, SUM(CASE WHEN product = 'iPhone' THEN amount ELSE 0 END) AS iPhone, SUM(CASE WHEN product = 'Mac' THEN amount ELSE 0 END) AS Mac, SUM(CASE WHEN product = 'iPad' THEN amount ELSE 0 END) AS iPad FROM sales GROUP BY month;
注意:ELSE 0不能省——若写成ELSE NULL,SUM会忽略NULL,但人眼难判断是否真没数据还是逻辑漏了;统一用0语义清晰,也方便前端直接渲染。
字符串类型行转列要用GROUP_CONCAT或MAX(CASE)
如果要转的是非数字字段(如用户标签、状态描述),SUM就不适用了。两种主流做法:
- 用
GROUP_CONCAT(DISTINCT ...)合并同类值,适合多对一场景(如一个用户有多个标签) - 用
MAX(CASE WHEN ... THEN field END)取单值,适合一对一(如每个订单只有一种支付方式)
示例:按地区汇总用户使用的浏览器类型(可能一人多选):
SELECT region, GROUP_CONCAT(DISTINCT CASE WHEN browser = 'Chrome' THEN 'Chrome' END) AS Chrome, GROUP_CONCAT(DISTINCT CASE WHEN browser = 'Safari' THEN 'Safari' END) AS Safari FROM users GROUP BY region;
这里DISTINCT防重复,CASE后不写ELSE——让不匹配的返回NULL,GROUP_CONCAT会自动跳过NULL,比硬写ELSE ''更干净。
动态行转列只能靠应用层拼SQL或存储过程
当列名(如产品名、部门名)来自数据表且经常变动,纯SQL无法自动适配。MySQL不支持动态列名,所以必须由应用代码(Python/Java)先查出所有可能的值,再拼出带N个CASE的SQL执行。
存储过程虽能拼接SQL字符串并PREPARE/EXECUTE,但调试困难、权限要求高、不易缓存,线上报表系统一般避开这条路。
真正容易被忽略的是性能边界:10个以内固定列,手写CASE没问题;超过20个,查询计划变复杂,EXPLAIN会看到临时表+filesort;这时该考虑在应用层做两次查询(一次取维度,一次取明细),内存里组装——别硬扛在SQL里。


















