问题
我需要一种将多行汇总为一行和一列的方法。我知道我可以使用 Pivot将多行汇总为一行 ,但是我需要将所有数据串联到一行中的一列中。在本文中,我们将介绍一种简单的方法来完成此任务。
解
为了说明需要什么,下面是表中的数据示例:
这是我们希望最终结果看起来像的一个示例:
如何在SQL Server中将多行汇总为单行
将数据从多行汇总到单行对于连接数据,报告,在系统之间交换数据等等可能是必需的。这可以通过以下方式完成:
- 本技巧文章中提出的解决方案探讨了两个SQL Server命令,这些命令可以帮助我们获得预期的结果。使用的SQL Server T-SQL命令是 STUFF和 FOR XML。
- T-SQL STUFF命令用于将结果连接在一起。在此示例中,结果用分号分隔。
- SELECT命令的FOR XML选项具有四个选项(即RAW,AUTO,EXPLICIT或PATH)以返回结果。在此示例中,PATH参数用于以XML字符串的形式检索结果。
请查看下面的示例,以遍历代码示例和最终解决方案,以将多行汇总到SQL Server中的单行中。
准备样品数据
在开始之前,我们将创建一些表和示例数据,以下脚本将为我们完成这些工作。
CREATE TABLE SALES_SECTORS( SEC_ID INT, SEC_NAME VARCHAR(30)) GO CREATE TABLE USRS( USR_ID INT, USR_NAME VARCHAR(30), SEC_ID INT ) GO CREATE TABLE ADV_CAMPAIGN( ADV_ID INT, ADV_NAME VARCHAR(30) ) GO CREATE TABLE USR_ADV_CAMPAIGN( USR_ID INT, ADV_ID INT ) GO CREATE TABLE SEC_ADV_CAMPAIGN( SEC_ID INT, ADV_ID INT ) GO INSERT INTO SALES_SECTORS( SEC_ID, SEC_NAME ) VALUES ( 1, 'ENTERTAINMENT' ) INSERT INTO SALES_SECTORS( SEC_ID, SEC_NAME ) VALUES ( 2, 'CLOTHES' ) GO INSERT INTO USRS( USR_ID, USR_NAME, SEC_ID ) VALUES ( 1, 'ANDERSON', 1 ) INSERT INTO USRS( USR_ID, USR_NAME, SEC_ID ) VALUES ( 2, 'CHARLES', 1 ) INSERT INTO USRS( USR_ID, USR_NAME, SEC_ID ) VALUES ( 3, 'DANNY', 1 ) INSERT INTO USRS( USR_ID, USR_NAME, SEC_ID ) VALUES ( 4, 'LUCAS', 1 ) INSERT INTO USRS( USR_ID, USR_NAME, SEC_ID ) VALUES ( 5, 'KEITH', 2 ) INSERT INTO USRS( USR_ID, USR_NAME, SEC_ID ) VALUES ( 6, 'STEFAN', 2 ) INSERT INTO USRS( USR_ID, USR_NAME, SEC_ID ) VALUES ( 7, 'EDUARD', 2 ) INSERT INTO USRS( USR_ID, USR_NAME, SEC_ID ) VALUES ( 8, 'BRAD', 2 ) GO INSERT INTO ADV_CAMPAIGN( ADV_ID, ADV_NAME ) VALUES ( 1, 'SONY ENTERTAINMENT' ) INSERT INTO ADV_CAMPAIGN( ADV_ID, ADV_NAME ) VALUES ( 2, 'BEATS SOUNDS' ) INSERT INTO ADV_CAMPAIGN( ADV_ID, ADV_NAME ) VALUES ( 3, 'BOOSE' ) INSERT INTO ADV_CAMPAIGN( ADV_ID, ADV_NAME ) VALUES ( 4, 'POLO RALPH LAUREN' ) INSERT INTO ADV_CAMPAIGN( ADV_ID, ADV_NAME ) VALUES ( 5, 'LACOSTE' ) GO INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 1, 1 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 1, 2 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 2, 2 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 2, 3 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 3, 3 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 4, 2 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 5, 4 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 6, 5 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 7, 4 ) INSERT INTO USR_ADV_CAMPAIGN( USR_ID, ADV_ID ) VALUES ( 8, 5 ) GO INSERT INTO SEC_ADV_CAMPAIGN( SEC_ID, ADV_ID ) VALUES ( 1, 1 ) INSERT INTO SEC_ADV_CAMPAIGN( SEC_ID, ADV_ID ) VALUES ( 1, 2 ) INSERT INTO SEC_ADV_CAMPAIGN( SEC_ID, ADV_ID ) VALUES ( 1, 3 ) INSERT INTO SEC_ADV_CAMPAIGN( SEC_ID, ADV_ID ) VALUES ( 2, 4 ) INSERT INTO SEC_ADV_CAMPAIGN( SEC_ID, ADV_ID ) VALUES ( 2, 5 ) GO
SQL Server STUFF()函数
在转到示例之前,我们需要了解上述命令的工作原理。STUFF()函数从一个初始位置将一个字符串放入另一个字符串中。这样,我们可以插入,替换或删除一个或多个字符。
此语法为STUFF(character_expression,开始,长度,replaceWith_expression):
- character_expression:要操作的字符串
- 开始:开始的初始位置
- 长度:要操纵的字符数
- replaceWith_expression:要使用的字符
这是如何使用STUFF命令的示例。
对于我们的示例,我们有一个如下所示的字符串:
;KEITH;STEFAN;EDUARD;BRAD
我们要删除第一个; 从列表中,所以我们最终得到以下输出:
KEITH;STEFAN;EDUARD;BRAD
为此,我们可以使用STUFF命令来替换第一个;在带有空字符串的字符串中。
SELECT STUFF(';KEITH;STEFAN;EDUARD;BRAD', 1, 1, '')
这将返回以下输出:
KEITH;STEFAN;EDUARD;BRAD
SQL Server SELECT语句的FOR XML子句
FOR XML子句将以XML形式返回SQL查询的结果。FOR XML有四种模式,分别是RAW,AUTO,EXPLICIT或PATH。我们将使用PATH选项,该选项为返回的每一行生成单个元素。
如果我们使用如下常规查询,它将返回如下所示的结果集。
SELECT SS.SEC_NAME, US.USR_NAME FROM SALES_SECTORS SS INNER JOIN USRS US ON US.SEC_ID = SS.SEC_ID ORDER BY 1, 2
如果更进一步,我们可以使用FOR XML PATH选项以XML字符串的形式返回结果,该结果会将所有数据放入一行和一列。
SELECT SS.SEC_NAME, US.USR_NAME FROM SALES_SECTORS SS INNER JOIN USRS US ON US.SEC_ID = SS.SEC_ID ORDER BY 1, 2 FOR XML PATH('')
SQL Server Example to Rolling up Multiple Rows into a Single Row
Example 1
SELECT SS.SEC_NAME, (SELECT '; ' + US.USR_NAME FROM USRS US WHERE US.SEC_ID = SS.SEC_ID FOR XML PATH('')) [SECTORS/USERS] FROM SALES_SECTORS SS GROUP BY SS.SEC_ID, SS.SEC_NAME ORDER BY 1
SELECT SS.SEC_NAME, STUFF((SELECT '; ' + US.USR_NAME FROM USRS US WHERE US.SEC_ID = SS.SEC_ID FOR XML PATH('')), 1, 1, '') [SECTORS/USERS] FROM SALES_SECTORS SS GROUP BY SS.SEC_ID, SS.SEC_NAME ORDER BY 1
SELECT SS.SEC_NAME, STUFF((SELECT '; ' + US.USR_NAME FROM USRS US WHERE US.SEC_ID = SS.SEC_ID ORDER BY USR_NAME FOR XML PATH('')), 1, 1, '') [SECTORS/USERS] FROM SALES_SECTORS SS GROUP BY SS.SEC_ID, SS.SEC_NAME ORDER BY 1
Example 2
SELECT SS.SEC_NAME + ': ' + STUFF((SELECT '; ' + US.USR_NAME FROM USRS US WHERE US.SEC_ID = SS.SEC_ID FOR XML PATH('')), 1, 1, '') [SECTORS/USERS] FROM SALES_SECTORS SS GROUP BY SS.SEC_ID, SS.SEC_NAME ORDER BY 1
Example 3
SELECT SS.SEC_ID, SS.SEC_NAME, STUFF((SELECT '; ' + AC.ADV_NAME + ' (' + STUFF((SELECT ',' + US.USR_NAME FROM USR_ADV_CAMPAIGN UAC INNER JOIN USRS US ON US.USR_ID = UAC.USR_ID WHERE UAC.ADV_ID = SAC.ADV_ID FOR XML PATH('')), 1, 1, '') + ')' FROM ADV_CAMPAIGN AC INNER JOIN SEC_ADV_CAMPAIGN SAC ON SAC.ADV_ID = AC.ADV_ID AND SAC.SEC_ID = SS.SEC_ID ORDER BY AC.ADV_NAME FOR XML PATH('')), 1, 1, '') [CAMPAIGNS/USERS PER SECTOR] FROM SALES_SECTORS SS GROUP BY SS.SEC_ID, SS.SEC_NAME
Example Rolling Up Index Columns into One Row
SELECT SCHEMA_NAME(ss.SCHEMA_id) AS SchemaName, ss.name as TableName, ss2.name as IndexName, ss2.index_id, ss2.type_desc, STUFF((SELECT ', ' + name from sys.index_columns a inner join sys.all_columns b on a.object_id = b.object_id and a.column_id = b.column_id and a.object_id = ss.object_id and a.index_id = ss2.index_id and is_included_column = 0 order by a.key_ordinal FOR XML PATH('')), 1, 2, '') IndexColumns, STUFF((SELECT ', ' + name from sys.index_columns a inner join sys.all_columns b on a.object_id = b.object_id and a.column_id = b.column_id and a.object_id = ss.object_id and a.index_id = ss2.index_id and is_included_column = 1 FOR XML PATH('')), 1, 2, '') IncludedColumns FROM sys.objects SS INNER JOIN SYS.INDEXES ss2 ON ss.OBJECT_ID = ss2.OBJECT_ID WHERE ss.type = 'U' ORDER BY 1, 2, 3