接了个需求:表里把 1 到 3 月的销量存成了 jan、feb、mar 三个字段,报表工具却要长表格式,一行一个月。这就得做 mysql 里的列转行。
先明确要转成什么样
所谓列转行,就是把一行的多个字段值拆成多行:原来一行有三个数值列,转完变成三行,每行一个月份字段加一个数值字段。别和行转列搞反了,行转列是多行聚合成一列,用 group by 加聚合函数,方向正好相反。Excel 里管这叫「逆透视」,理解成把宽表化成窄表就行。
union all:最直白的写法
每个字段拆成一段查询,再用 union all 拼起来:
select id, '1月' as m, jan as v from sales union all select id, '2月' as m, feb as v from sales union all select id, '3月' as m, mar as v from sales
字段多时 SQL 很长,但逻辑清楚,性能也不差,数据量不大时闭眼用这个。
序列表加 case when
字段特别多时可以造个数字序列,和原表关联,再用 case 按序号挑字段:
select s.id, case n.num when 1 then '1月' when 2 then '2月' end as m, case n.num when 1 then s.jan when 2 then s.feb end as v from sales s join seq n on n.num <= 2
seq 可以是现成的数字辅助表,也可以用 union 现造 1 到 N,思路是先造行再填值。
几个容易翻车的点
- union all 各段的字段顺序和类型要对上,不然结果错乱或者报错
- 原字段是 NULL 的行会带出空值,看业务要不要过滤掉
- MySQL 8.0 还能用 JSON 函数把整行打包成数组再展开,写法更短,5.7 用不了
- 转完记得加排序字段,不然月份顺序是乱的
实际选型:字段三五个、一次性报表,union all 最省事;字段几十个或者要做成视图长期用,考虑序列表方案,或者干脆在程序代码里转,别让数据库硬扛。