You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SQL Server如何将行转列?动态行转列实现求助

动态行转列解决方案(适配ID动态变化场景)

没问题,我帮你搞定这个需求!先理清楚你的场景:你有一张记录字段变更的行式表(假设表名为change_log),结构和示例数据如下:

IDColumnNew ValueModifiedDate
223Num9803/01/2018
223CountryIRE03/01/2018
223UserBob03/01/2018
222UserLily01/01/2018
222Num7201/01/2018
222CountryUK01/01/2018
222UserWendy02/01/2018
222Num2202/01/2018
222CountryUK02/01/2018

需要转成按IDModifiedDate分组的宽表,输出如下:

IDNumCountryUserModifiedDate
22272UKLily01/01/2018
22222UKWendy02/01/2018
22398IREBob03/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 $$;

关键注意事项

  1. 所有方案都会自动识别表中所有不同的Column值,不管ID怎么变化都能适配;
  2. 执行前可以先打印生成的SQL语句(比如SQL Server用PRINT @sql),确认语句正确后再执行,避免踩坑;
  3. 如果New Value列有多种数据类型,建议统一转成字符串(比如用CAST([New Value] AS NVARCHAR(MAX))),避免类型不兼容报错。

内容的提问来源于stack exchange,提问作者Jess8766

火山引擎 最新活动