行转列这事儿,搞数据库的人迟早都会碰上。说白了,就是把一堆竖着排的数据横着铺开,变成一张宽表。比如你有个订单表,每个订单对应多条记录,现在想把它变成“一行一个订单”,其他字段都横着展开。听起来简单,但真做起来,不少人会卡住。我今天就掰扯三种最实用的方法:写代码、用函数、甚至手动调整,总有一款适合你。

第一种方法,用 CASE WHEN 配合聚合函数。这是最老派但也最稳妥的方式。假设你有一张销售表,里面有商品、月份和销售额,你想把每个月的销售额变成一列。核心逻辑是:对每个月写一个 CASE WHEN,月份匹配就取销售额,否则取 0,然后用 SUM 或 MAX 把同一行数据聚合成一个值。比如这招在 MySQL、SQL Server、Oracle 都能跑,只是代码量大一点——如果有 12 个月,就得写 12 个 CASE WHEN。不过胜在直观,刚入门的同学也能看懂。
第二种方法,用 PIVOT 函数。这是 SQL Server 和 Oracle 内置的语法糖,专门为行转列设计,写法更简洁:拿刚才的销售表举例,直接写系统会自动把月份值变成列名。但有个坑:PIVOT 要求列值必须明确写在 IN 里,如果月份是动态变化的,就得先拼 SQL 字符串再执行。另外,MySQL 8.0 以下不支持 PIVOT,只能用 CASE WHEN 或后面要讲的方法。PIVOT 写起来快,但灵活性稍差,适合列值固定的场景。
第三种方法,用 GROUPCONCAT 或 STRINGAGG 配合 JSON。这招适合列值不确定,或想保留原始数据的情况。比如把某个客户的所有订单号拼成一行,用 MySQL 的 GROUPCONCAT:如果需要结构化数据,可以拼成 JSON:在 SQL Server 用 STRINGAGG,PostgreSQL 用 arrayagg。这方法不改变行数,只是把多行合并成一个字符串或数组,适合报表预览或导出前的预处理。
实际工作中,我见过很多人被行转列搞得怀疑人生。一次帮一个电商团队处理数据,他们想按季度展示每个商品的销量,但季度列是动态的——每个季度结束后才会出现新列。用 CASE WHEN 必须提前写好所有分支,季度一多代码就膨胀。用 PIVOT 又得动态拼接 SQL,性能还受影响。最后我建议他们先用 GROUPCONCAT 把数据拼成 JSON,再在应用层解析。虽然多了一步,但维护成本低,新增季度也不用改 SQL。记住:没有银弹,关键看数据量、列数变化频率以及团队的技术栈。
还有个隐藏技巧:如果列数非常多,比如要转置 100 列,别硬写 CASE WHEN,也别指望 PIVOT 能自动适配。这时可以先生成动态 SQL 字符串,用系统表或信息模式查出所有列值,然后拼成完整的 PIVOT 语句。比如在 SQL Server 里:这种方法可以自动适应列变化,但要注意 SQL 注入和性能问题——如果列值有特殊字符,记得用 QUOTENAME 或双引号包裹。
说个常见误区:很多人以为行转列只能靠数据库函数,其实有时候手动调整更快。比如你从 Excel 导出的数据本身是竖排的,想转成宽表,不如直接在 Excel 里用透视表,几秒钟搞定。如果数据量超过百万行,再考虑用数据库方法。另外,有些数据库如 ClickHouse、Doris 支持更高级的语法,例如 ClickHouse 的 arrayJoin 和 groupArray,配合 Lambda 表达式可以非常优雅地实现行转列。但不管用什么工具,核心逻辑都是:找到分组键、找到转置列、找到聚合值。
总结一下:CASE WHEN 适合小规模、列数固定、需要跨数据库兼容的场景;PIVOT 适合列值已知、追求简洁代码的 SQL Server 或 Oracle 用户;GROUP_CONCAT 和 JSON 适合列值动态,或想保留原始数据结构的场景。这三种方法都不难,关键是理解背后的逻辑——行转列本质上是数据重组,把一对多关系变成一对一关系。下次遇到类似需求,先问自己三个问题:列值固定吗?数据量大吗?团队用什么数据库?答案明确后,方法自然就有了。


