SQL SERVER 动态行转列代码

在实际的项目操作中,数据统计偶尔会用到SQLServer的行转列,

数据表MG_TicketsHistoryData如下:

列名数据类型描述
TicketDatedate
TicketCode

nchar(10)

TicketADJClosedecimal(18,2)

使用SQLServer动态行转列,代码如下:

CREATE proc [dbo].[PIVOT_TicketsHisData]
@start_date varchar(10),@end_date varchar(10)
as

DECLARE @columns NVARCHAR(MAX),@isnullcolumns NVARCHAR(MAX), @sql NVARCHAR(MAX);
SELECT @columns = STUFF((SELECT ',' + QUOTENAME(RTRIM(TicketCode)) 
                    FROM MG_TicketsHistoryData 
                    GROUP BY TicketCode
                    ORDER BY TicketCode
                    FOR XML PATH(''), TYPE
                    ).value('.', 'NVARCHAR(MAX)'), 1, 1, '');
SELECT @isnullcolumns = STUFF((SELECT ',isnull(' + QUOTENAME(RTRIM(TicketCode))+',0) as ' +QUOTENAME(RTRIM(TicketCode))
                    FROM MG_TicketsHistoryData 
                    GROUP BY TicketCode
                    ORDER BY TicketCode
                    FOR XML PATH(''), TYPE
                    ).value('.', 'NVARCHAR(MAX)'), 1, 1, ''); 
SET @sql = 'SELECT TicketDate as date, ' + @isnullcolumns + ' FROM 
            (
                SELECT TicketDate, TicketCode,TicketADJClose
                FROM MG_TicketsHistoryData where TicketDate>='''+@start_date+''' and TicketDate<='''+@end_date+'''
            ) x
            PIVOT 
            (
                SUM(TicketADJClose)
                FOR TicketCode IN (' + @columns + ')
            ) p order by p.TicketDate';

EXEC sp_executesql @sql;

调用存储过程语句:

exec PIVOT_TicketsHisData '2022-01-01','2024-01-01'

行转列前的数据截图如下:

行转列后的数据截图如下:

<think>好的,我现在需要解决的问题是在SQL Server中将行数据转换为列,也就是所谓的行转列操作。用户可能有一个表格,其中的行代表不同的记录,而他们希望将这些行按照某个字段的值转换为列,使得数据以更紧凑的形式展示。常见的应用场景包括报表生成,或者需要将多个行中的不同属性作为列来显示的情况。首先,我应该考虑SQL Server中实现行转列的几种方法。最常见的是使用PIVOT运算符,但有时候可能需要使用CASE语句结合聚合函数来实现,特别是当需要更灵活的控制时。 用户提到的是行转列,所以第一步要确定的是哪些行需要变成列。通常,这需要一个关键的列来决定新列的名称,以及另一个列来提供这些新列的值。例如,假设有一个销售数据表,每个销售记录有产品、月份和销售额,用户可能希望将月份作为列,每个产品在每月的销售额作为行。接下来,我需要回忆PIVOT的语法结构。PIVOT的基本结构包括指定聚合函数和要转换的列。例如,对于上述销售数据,PIVOT可能会对销售额进行SUM聚合,并按月份进行转换。但需要注意的是,PIVOT需要明确列出要转换的列值,这在动态情况下可能需要动态SQL。 另一个方法是使用CASE语句。例如,为每个需要转换的列创建一个CASE表达式,当原列的值等于目标列名时,取对应的值,否则为NULL,然后使用聚合函数如MAX或SUM来汇总。这种方法在列数固定时比较直接,但列数较多时写起来比较麻烦。 如果用户的需求是动态的,即不确定有多少个需要转换为列的字段,那么可能需要使用动态SQL来构建查询语句。这涉及到从数据中获取所有可能的列值,然后构造PIVOT或CASE语句的动态版本。 现在,我需要给出具体的示例代码。假设有一个学生成绩表,结构如下:CREATE TABLE StudentScores ( StudentName VARCHAR(50), Subject VARCHAR(50),Score INT );INSERT INTO StudentScores VALUES ('张三', '数学',90); INSERTINTO StudentScoresVALUES ('张三','语文',85); INSERT INTOStudentScores VALUES('李四','数学',78); INSERT INTOStudentScores VALUES('李四','语文',92); 用户希望将Subject作为列,每个学生的成绩作为行,得到如下结果: StudentName | 数学| 语文张三 |90 |85 李四| 78| 92使用PIVOT的实现方式:SELECT StudentName,[数学], [语文] FROM StudentScores PIVOT (MAX(Score)FOR Subject IN([数学], [语文]) ) ASPivotTable;这里,MAX(Score)作为聚合函数,因为每个学生的每个科目只有一个分数,所以MAX不影响结果。FORSubject IN指定要转换为列的科目名称。需要注意的是,如果存在多个Score记录,可能需要使用SUM或其他聚合函数。另一种方法是使用CASE语句: SELECTStudentName, MAX(CASE WHEN Subject ='数学' THENScore END) AS数学,MAX(CASE WHENSubject = '语文' THEN Score END) AS 语文FROM StudentScores GROUP BYStudentName; 这里,每个CASE语句根据Subject的值提取对应的Score,然后使用MAX聚合(同样因为每个科目只有一个分数),然后按学生姓名分组。 如果科目是动态变化的,比如科目可能增加,这时候需要动态生成列名。这时候需要使用动态SQL,构造IN子句中的列名。例如,首先获取所有不同的Subject值,然后构造PIVOT查询字符串并执行。动态SQL的示例: DECLARE@columns NVARCHAR(MAX), @sql NVARCHAR(MAX); SELECT @columns = STRING_AGG(QUOTENAME(Subject), ', ') WITHIN GROUP (ORDERBY Subject) FROM(SELECT DISTINCTSubject FROM StudentScores) AS Subjects; SET @sql= N' SELECTStudentName, '+ @columns +' FROM StudentScores PIVOT ( MAX(Score)FOR Subject IN ('+ @columns +') ) AS PivotTable;';EXEC sp_executesql @sql; 这里,STRING_AGG用于将不同的Subject值合并成逗号分隔的带方括号的字符串,QUOTENAME处理列名中的特殊字符,并防止SQL注入。然后动态构造SQL语句并执行。 需要提醒用户的是,动态SQL可能存在安全风险,应确保Subject的值是可信的,或者在构造查询时进行适当的验证。 解释部分需要说明每种方法的适用场景:PIVOT适用于已知列名的情况,CASE方法更灵活但列数多时麻烦,动态SQL适用于列名不固定时。接下来,我需要检查可能的错误,例如聚合函数的使用是否正确,列名是否正确处理,以及动态SQL中的字符串拼接是否正确。 最后,总结这些方法,并建议用户根据具体情况选择。例如,列固定时用PIVOT或CASE,列动态时用动态SQL。</think>在SQL Server中实现行转列(PIVOT)有以下三种常见方法,以下是详细说明和示例代码: --- ### **方法1:使用PIVOT运算符(适用于已知列名)** ```sql -- 创建示例表 CREATE TABLE StudentScores ( StudentName VARCHAR(50), Subject VARCHAR(50), Score INT ); -- 插入示例数据 INSERT INTO StudentScores VALUES ('张三', '数学', 90); INSERT INTO StudentScores VALUES ('张三', '语文', 85); INSERT INTO StudentScores VALUES ('李四', '数学', 78); INSERT INTO StudentScores VALUES ('李四', '语文', 92); -- 使用PIVOT行转列 SELECT StudentName, [数学], [语文] FROM StudentScores PIVOT ( MAX(Score) -- 聚合函数 FOR Subject IN -- 指定要转换的列 ([数学], [语文]) -- 目标列名(需显式列出) ) AS PivotTable; ``` **说明**: - `PIVOT` 是SQL Server内置的行转列操作符。 - `MAX(Score)` 是聚合函数,如果数据无重复可确保值唯一。 - `FOR Subject IN ([数学], [语文])` 指定原表的`Subject`列值作为新列名。 --- ### **方法2:使用CASE语句(灵活但需手动列名)** ```sql SELECT StudentName, MAX(CASE WHEN Subject = '数学' THEN Score END) AS 数学, MAX(CASE WHEN Subject = '语文' THEN Score END) AS 语文 FROM StudentScores GROUP BY StudentName; ``` **说明**: - 通过`CASE`语句按条件提取值,`MAX`确保分组后保留非空值。 - 适合列名固定但需要更复杂逻辑的场景。 --- ### **方法3:动态SQL(适用于列名不固定)** ```sql -- 动态获取列名并生成SQL DECLARE @columns NVARCHAR(MAX), @sql NVARCHAR(MAX); -- 获取所有不重复的科目作为列名 SELECT @columns = STRING_AGG(QUOTENAME(Subject), ', ') FROM (SELECT DISTINCT Subject FROM StudentScores) AS Subjects; -- 构造动态SQL SET @sql = N' SELECT StudentName, ' + @columns + ' FROM StudentScores PIVOT ( MAX(Score) FOR Subject IN (' + @columns + ') ) AS PivotTable;'; -- 执行动态SQL EXEC sp_executesql @sql; ``` **说明**: - `STRING_AGG` 合并所有科目名,`QUOTENAME` 处理特殊字符(如空格)。 - 动态SQL通过`sp_executesql`执行,适合列名不确定的场景。 --- ### **输出结果** 所有方法的输出均为: ``` StudentName | 数学 | 语文 ---------------------------- 张三 | 90 | 85 李四 | 78 | 92 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值