✅ 面试题:SQL 中如何将 多行合并为一行(合并行数据为列)?
这是面试和实战中非常常见的场景,属于“行列转换”问题之一,常用于报表聚合、分类汇总、透视表生成等。
go专栏:https://duoke360.com/tutorial/path/golang
一、问题描述
假设有如下数据:
name | subject | score |
---|---|---|
张三 | 语文 | 80 |
张三 | 数学 | 90 |
李四 | 语文 | 70 |
李四 | 数学 | 85 |
希望转换成如下结果:
name | 语文 | 数学 |
---|---|---|
张三 | 80 | 90 |
李四 | 70 | 85 |
二、实现方法
✅ 方法1:使用 CASE WHEN + 聚合函数
(最通用,兼容所有数据库)
SELECT
name,
MAX(CASE WHEN subject = '语文' THEN score END) AS 语文,
MAX(CASE WHEN subject = '数学' THEN score END) AS 数学
FROM scores
GROUP BY name;
- 优点:写法清晰,所有数据库都支持(MySQL、PostgreSQL、SQL Server、Oracle)
- 缺点:需要提前知道列名(如“语文”“数学”)
✅ 方法2:MySQL 专用的 GROUP_CONCAT()
实现行合并为一列(文本拼接)
例如合并所有科目为一个字符串:
SELECT name, GROUP_CONCAT(CONCAT(subject, ':', score) ORDER BY subject SEPARATOR ', ')
FROM scores
GROUP BY name;
结果示例:
name | result |
---|---|
张三 | 语文:80, 数学:90 |
李四 | 语文:70, 数学:85 |
✅ 方法3:使用 PIVOT
(仅适用于 SQL Server / Oracle 11g+)
SELECT * FROM (
SELECT name, subject, score FROM scores
) AS source
PIVOT (
MAX(score) FOR subject IN ([语文], [数学])
) AS pvt;
✅ 方法4:PostgreSQL 中使用 FILTER
(语法简洁)
SELECT
name,
MAX(score) FILTER (WHERE subject = '语文') AS 语文,
MAX(score) FILTER (WHERE subject = '数学') AS 数学
FROM scores
GROUP BY name;
三、动态列转换(列不固定)
如果列名(如科目)不固定,必须:
- 在应用层动态拼接 SQL
- 或使用存储过程、动态 SQL 来生成 CASE 表达式
四、总结比较
方法 | 是否支持动态列 | 数据库兼容性 | 特点 |
---|---|---|---|
CASE WHEN |
❌ 手动写列名 | ✅ 所有数据库 | 最通用,稳定 |
GROUP_CONCAT |
❌ | ✅ MySQL 专属 | 多值拼接字符串 |
PIVOT |
❌ | ✅ SQL Server/Oracle | 语法直观,写法简洁 |
FILTER |
❌ | ✅ PostgreSQL | 函数式风格,代码简洁 |