SQL Server如何将行转列?动态行转列实现求助
动态行转列解决方案(适配ID动态变化场景)
没问题,我帮你搞定这个需求!先理清楚你的场景:你有一张记录字段变更的行式表(假设表名为change_log),结构和示例数据如下:
| ID | Column | New Value | ModifiedDate |
|---|---|---|---|
| 223 | Num | 98 | 03/01/2018 |
| 223 | Country | IRE | 03/01/2018 |
| 223 | User | Bob | 03/01/2018 |
| 222 | User | Lily | 01/01/2018 |
| 222 | Num | 72 | 01/01/2018 |
| 222 | Country | UK | 01/01/2018 |
| 222 | User | Wendy | 02/01/2018 |
| 222 | Num | 22 | 02/01/2018 |
| 222 | Country | UK | 02/01/2018 |
需要转成按ID和ModifiedDate分组的宽表,输出如下:
| ID | Num | Country | User | ModifiedDate |
|---|---|---|---|---|
| 222 | 72 | UK | Lily | 01/01/2018 |
| 222 | 22 | UK | Wendy | 02/01/2018 |
| 223 | 98 | IRE | Bob | 03/01/2018 |
因为ID数量会动态变化,必须用动态行转列实现,下面针对不同主流数据库给出具体方案:
1. SQL Server 实现
SQL Server自带PIVOT语法,我们用动态SQL自动生成列名:
DECLARE @columns NVARCHAR(MAX), @sql NVARCHAR(MAX); -- 第一步:自动获取所有要转成列的字段名,拼接成SQL识别的格式 SELECT @columns = STRING_AGG(QUOTENAME([Column]), ', ') FROM (SELECT DISTINCT [Column] FROM change_log) AS cols; -- 第二步:拼接完整的动态PIVOT语句 SET @sql = N' SELECT ID, ' + @columns + ', ModifiedDate FROM ( SELECT ID, [Column], [New Value], ModifiedDate FROM change_log ) AS src PIVOT ( MAX([New Value]) FOR [Column] IN (' + @columns + ') ) AS pvt ORDER BY ID, ModifiedDate;'; -- 第三步:执行动态SQL EXEC sp_executesql @sql;
注意:如果你的SQL Server版本低于2017,
STRING_AGG不能用,换成下面的方式拼接列名:SELECT @columns = STUFF((SELECT ', ' + QUOTENAME([Column]) FROM (SELECT DISTINCT [Column] FROM change_log) AS cols FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '');
2. MySQL 实现
MySQL用GROUP_CONCAT拼接列名,再通过预处理语句执行动态SQL:
SET @columns = NULL; -- 获取所有要转成列的字段名,拼接成CASE WHEN语句 SELECT GROUP_CONCAT(DISTINCT CONCAT('MAX(CASE WHEN `Column` = ''', `Column`, ''' THEN `New Value` END) AS `', `Column`, '`')) INTO @columns FROM change_log; -- 拼接完整的动态SQL SET @sql = CONCAT(' SELECT ID, ', @columns, ', ModifiedDate FROM change_log GROUP BY ID, ModifiedDate ORDER BY ID, ModifiedDate;'); -- 执行预处理语句 PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
小提示:如果字段名比较多导致
GROUP_CONCAT结果截断,可以先执行SET group_concat_max_len = 10240;调整最大长度。
3. PostgreSQL 实现
PostgreSQL有两种方式,一种是用crosstab函数(需要扩展),另一种是用JSON转列(无需扩展):
方法一:使用crosstab函数(需安装tablefunc扩展)
-- 先确保扩展已安装 CREATE EXTENSION IF NOT EXISTS tablefunc; -- 动态生成crosstab查询 DO $$ DECLARE columns text; BEGIN -- 获取所有要转成列的字段名 SELECT string_agg(DISTINCT quote_ident("Column"), ', ') INTO columns FROM change_log; -- 执行动态SQL EXECUTE format(' SELECT * FROM crosstab( ''SELECT ID, ModifiedDate, "Column", "New Value" FROM change_log ORDER BY 1,2'', ''SELECT DISTINCT "Column" FROM change_log ORDER BY 1'' ) AS ct(ID int, ModifiedDate date, %s) ORDER BY ID, ModifiedDate;', columns); END $$;
方法二:使用JSON动态转列(无需扩展)
DO $$ DECLARE columns text; BEGIN -- 获取所有要转成列的字段名 SELECT string_agg(DISTINCT quote_ident("Column"), ', ') INTO columns FROM change_log; -- 拼接并执行动态SQL EXECUTE format(' SELECT ID, %s, ModifiedDate FROM ( SELECT ID, ModifiedDate, json_object_agg("Column", "New Value") AS data FROM change_log GROUP BY ID, ModifiedDate ) AS src ORDER BY ID, ModifiedDate;', string_agg(DISTINCT 'data->>' || quote_literal("Column") || ' AS ' || quote_ident("Column"), ', ') ); END $$;
关键注意事项
- 所有方案都会自动识别表中所有不同的
Column值,不管ID怎么变化都能适配; - 执行前可以先打印生成的SQL语句(比如SQL Server用
PRINT @sql),确认语句正确后再执行,避免踩坑; - 如果
New Value列有多种数据类型,建议统一转成字符串(比如用CAST([New Value] AS NVARCHAR(MAX))),避免类型不兼容报错。
内容的提问来源于stack exchange,提问作者Jess8766




