将许多行连接成单个文本string?

考虑一个包含三行的数据库表:

Peter Paul Mary 

有没有简单的方法把它变成Peter, Paul, Mary一串?

当我试图用一对多关系连接两个表时,我遇到了类似的问题。 在SQL 2005中,我发现XML PATH方法可以很容易地处理这些行的连接。

如果有一个名为STUDENTS的表

 SubjectID StudentName ---------- ------------- 1 Mary 1 John 1 Sam 2 Alaina 2 Edward 

结果我预计是:

 SubjectID StudentName ---------- ------------- 1 Mary, John, Sam 2 Alaina, Edward 

我用了下面的T-SQL:

 Select Main.SubjectID, Left(Main.Students,Len(Main.Students)-1) As "Students" From ( Select distinct ST2.SubjectID, ( Select ST1.StudentName + ',' AS [text()] From dbo.Students ST1 Where ST1.SubjectID = ST2.SubjectID ORDER BY ST1.SubjectID For XML PATH ('') ) [Students] From dbo.Students ST2 ) [Main] 

如果您可以在开始时连接逗号,并使用子string跳过第一个string,则可以以更紧凑的方式执行相同的操作,因此不需要执行子查询:

 Select distinct ST2.SubjectID, substring( ( Select ','+ST1.StudentName AS [text()] From dbo.Students ST1 Where ST1.SubjectID = ST2.SubjectID ORDER BY ST1.SubjectID For XML PATH ('') ), 2, 1000) [Students] From dbo.Students ST2 

使用COALESCE

 DECLARE @Names VARCHAR(8000) SELECT @Names = COALESCE(@Names + ', ', '') + Name FROM People 

只是一些解释(因为这个答案似乎得到相对普遍的意见):

  • 合并真的只是一个有用的作弊,完成了两件事情:

1)不需要用空string值初始化@Names

2)最后不需要剥离额外的分离器。

  • 上面的解决scheme将得到不正确的结果,如果一行有一个NULL值(如果有一个NULLNULL将使该行后的@Names NULL ,并且下一行将重新开始作为一个空string。两种解决scheme:
 DECLARE @Names VARCHAR(8000) SELECT @Names = COALESCE(@Names + ', ', '') + Name FROM People WHERE Name IS NOT NULL 

要么:

 DECLARE @Names VARCHAR(8000) SELECT @Names = COALESCE(@Names + ', ', '') + ISNULL(Name, 'N/A') FROM People 

取决于你想要的行为(第一个选项只是过滤NULL ,第二个选项使用标记消息将它们保留在列表中[用适合你的任何东西replaceN / A])。

尚未通过MS SQL Server中的XML data()命令显示的一种方法是:

假设名为NameList的表格带有一个名为FName的列,

 SELECT FName + ', ' AS 'data()' FROM NameList FOR XML PATH('') 

收益:

 "Peter, Paul, Mary, " 

只有额外的逗号必须处理。

编辑:从@ NReilingh的评论通过,您可以使用以下方法删除尾随逗号。 假设有相同的表和列名称:

 STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands 

在SQL Server 2005中 …

 SELECT Stuff( (SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE) .value('text()[1]','nvarchar(max)'),1,2,N'') 

在MySQL中有一个函数GROUP_CONCAT() ,它允许你连接多行的值。 例:

 SELECT 1 AS a, GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ', ') AS people FROM users WHERE id IN (1,2,3) GROUP BY a 

SQL Server 2017和SQL Azure:STRING_AGG

从SQL Server的下一个版本开始,我们可以在行之间进行连接,而不必求助于任何variables或XML巫师。

没有分组

 SELECT STRING_AGG(Name, ', ') AS Departments FROM HumanResources.Department; 

分组:

 SELECT GroupName, STRING_AGG(Name, ', ') AS Departments FROM HumanResources.Department GROUP BY GroupName; 

通过分组和分类sorting

 SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments FROM HumanResources.Department GROUP BY GroupName; 

使用COALESCE – 从这里了解更多

举个例子:

102

103

104

然后在sql server中写下面的代码,

 Declare @Numbers AS Nvarchar(MAX) -- It must not be MAX if you have few numbers SELECT @Numbers = COALESCE(@Numbers + ',', '') + Number FROM TableName where Number IS NOT NULL SELECT @Numbers 

输出将是:

 102,103,104 

Oracle 11g第2版支持LISTAGGfunction。 文档在这里 。

 COLUMN employees FORMAT A50 SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees FROM emp GROUP BY deptno; DEPTNO EMPLOYEES ---------- -------------------------------------------------- 10 CLARK,KING,MILLER 20 ADAMS,FORD,JONES,SCOTT,SMITH 30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD 3 rows selected. 

警告

如果产生的string可能超过4000个字符,请小心执行此function。 它会抛出一个exception。 如果是这种情况,那么你需要处理exception或滚动自己的函数,防止连接的string超过4000个字符。

Postgres数组很棒。 例:

创build一些testing数据:

 postgres=# \c test You are now connected to database "test" as user "hgimenez". test=# create table names (name text); CREATE TABLE test=# insert into names (name) values ('Peter'), ('Paul'), ('Mary'); INSERT 0 3 test=# select * from names; name ------- Peter Paul Mary (3 rows) 

将它们聚合在一个数组中:

 test=# select array_agg(name) from names; array_agg ------------------- {Peter,Paul,Mary} (1 row) 

将数组转换为以逗号分隔的string:

 test=# select array_to_string(array_agg(name), ', ') from names; array_to_string ------------------- Peter, Paul, Mary (1 row) 

DONE

自从PostgreSQL 9.0以来,它变得更加容易 。

在SQL Server 2005和更高版本中,使用下面的查询连接行。

 DECLARE @t table ( Id int, Name varchar(10) ) INSERT INTO @t SELECT 1,'a' UNION ALL SELECT 1,'b' UNION ALL SELECT 2,'c' UNION ALL SELECT 2,'d' SELECT ID, stuff( ( SELECT ','+ [Name] FROM @t WHERE Id = t.Id FOR XML PATH('') ),1,1,'') FROM (SELECT DISTINCT ID FROM @t ) t 

从PostgreSQL 9.0开始,这很简单:

 select string_agg(name, ',') from names; 

在9.0之前的版本中,可以使用hgmnz所示的array_agg array_agg()

我没有在家里访问SQL Server,所以我猜这里的语法,但它是或多或less:

 DECLARE @names VARCHAR(500) SELECT @names = @names + ' ' + Name FROM Names 

build议recursion的CTE解决scheme,但没有提供代码。 下面的代码是一个recursionCTE的例子 – 请注意,虽然结果与问题匹配,但是数据并不完全符合给定的描述,因为我假设你真的想在一组行上做这个,而不是全部表中的行。 将其更改为匹配表中的所有行将作为读者的练习。

 ;with basetable as ( SELECT id, CAST(name as varchar(max))name, ROW_NUMBER() OVER(Partition By id order by seq) rw, COUNT(*) OVER (Partition By id) recs FROM (VALUES (1, 'Johnny', 1), (1,'M', 2), (2,'Bill', 1), (2, 'S.', 4), (2, 'Preston', 5), (2, 'Esq.', 6), (3, 'Ted', 1), (3,'Theodore', 2), (3,'Logan', 3), (4, 'Peter', 1), (4,'Paul', 2), (4,'Mary', 3) )g(id, name, seq) ), rCTE as ( SELECT recs, id, name, rw from basetable where rw=1 UNION ALL SELECT b.recs, r.ID, r.name +', '+ b.name name, r.rw+1 FROM basetable b inner join rCTE r on b.id = r.id and b.rw = r.rw+1 ) SELECT name FROM rCTE WHERE recs = rw and ID=4 

你需要创build一个variables来保存你的最终结果并select它,像这样。

最简单的解决scheme

 DECLARE @char VARCHAR(MAX); SELECT @char = COALESCE(@char + ', ' + [column], [column]) FROM [table]; PRINT @char; 

在SQL Server vNext中,将使用STRING_AGG函数进行内置,请在此处阅读更多内容: https ://msdn.microsoft.com/en-us/library/mt790580.aspx

使用XML帮助我使用逗号分隔行。 对于额外的逗号,我们可以使用SQL Server的replace函数。 使用AS'data()'将不用添加逗号,而是将行与空格连接起来,以后可以用下面的语法replace为逗号。

 REPLACE( (select FName AS 'data()' from NameList for xml path('')) , ' ', ', ') 

即用型解决scheme,无需额外的逗号:

 select substring( (select ', '+Name AS 'data()' from Names for xml path('')) ,3, 255) as "MyList" 

一个空列表将导致NULL值。 通常你会将列表插入到表格列或程序variables中:根据需要调整255最大长度。

(Diwakar和Jens Frandsen提供了很好的答案,但需要改进。)

 DECLARE @Names VARCHAR(8000) SELECT @name = '' SELECT @Names = @Names + ',' + Names FROM People SELECT SUBSTRING(2, @Names, 7998) 

这就让stream浪的逗号开始了。

但是,如果您需要其他列或者CSV子表,则需要将其包含在标量用户定义字段(UDF)中。

您也可以在SELECT子句中使用XMLpath作为相关的子查询(但是我必须等到我重新开始工作,因为Google不在家工作):-)

对于Oracle数据库,请参阅以下问题: 如何在不创build存储过程的情况下将多行连接成Oracle中的一行?

最好的答案似乎是@Emmanuel,使用Oracle 11g第2版及更高版本中提供的内置LISTAGG()函数。

 SELECT question_id, LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id) FROM YOUR_TABLE; GROUP BY question_id 

作为@ user762952指出,并根据Oracle的文档http://www.oracle-base.com/articles/misc/string-aggregation-techniques.php,WM_CONCAT ()函数也是一个选项。 它看起来很稳定,但是Oracle明确build议不要将它用于任何应用程序SQL,所以请自担风险。

除此之外,你将不得不写自己的function; 上面的Oracle文档有关于如何做到这一点的指导。

为了避免空值,你可以使用CONCAT()

 DECLARE @names VARCHAR(500) SELECT @names = CONCAT(@names, ' ', name) FROM Names select @names 

这个答案将需要一些特权在服务器上工作。

assembly对你来说是个不错的select。 有很多网站解释如何创build它。 我认为很好解释的是这一个

如果你愿意,我已经创build了程序集,并且可以在这里下载DLL。

一旦你下载了它,你将需要在你的SQL Server中运行下面的脚本:

 CREATE Assembly concat_assembly AUTHORIZATION dbo FROM '<PATH TO Concat.dll IN SERVER>' WITH PERMISSION_SET = SAFE; GO CREATE AGGREGATE dbo.concat ( @Value NVARCHAR(MAX) , @Delimiter NVARCHAR(4000) ) RETURNS NVARCHAR(MAX) EXTERNAL Name concat_assembly.[Concat.Concat]; GO sp_configure 'clr enabled', 1; RECONFIGURE 

注意到组装的path可以被服务器访问。 由于您已成功完成所有步骤,因此可以使用如下function:

 SELECT dbo.Concat(field1, ',') FROM Table1 

希望它有帮助!

MySQL完成例子:

我们有可以有很多数据的用户,我们希望有一个输出,我们可以在列表中看到所有的用户数据:

结果:

 ___________________________ | id | rowList | |-------------------------| | 0 | 6, 9 | | 1 | 1,2,3,4,5,7,8,1 | |_________________________| 

表设置:

 CREATE TABLE `Data` ( `id` int(11) NOT NULL, `user_id` int(11) NOT NULL ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1; INSERT INTO `Data` (`id`, `user_id`) VALUES (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 0), (7, 1), (8, 1), (9, 0), (10, 1); CREATE TABLE `User` ( `id` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; INSERT INTO `User` (`id`) VALUES (0), (1); 

查询:

 SELECT User.id, GROUP_CONCAT(Data.id ORDER BY Data.id) AS rowList FROM User LEFT JOIN Data ON User.id = Data.user_id GROUP BY User.id 

与其他答案一样,阅读答案的人必须知道特定的领域表,如车辆或学生。 该表必须创build并填充数据来testing解决scheme。

下面是使用SQL Server“Information_Schema.Columns”表的示例。 通过使用此解决scheme,不需要创build表或添加数据。 本示例为数据库中的所有表创build逗号分隔的列名列表。

 SELECT Table_Name ,STUFF(( SELECT ',' + Column_Name FROM INFORMATION_SCHEMA.Columns Columns WHERE Tables.Table_Name = Columns.Table_Name ORDER BY Column_Name FOR XML PATH ('')), 1, 1, '' )Columns FROM INFORMATION_SCHEMA.Columns Tables GROUP BY TABLE_NAME 

我通常使用select来连接SQL Server中的string:

 with lines as ( select row_number() over(order by id) id, -- id is a line id line -- line of text. from source -- line source ), result_lines as ( select id, cast(line as nvarchar(max)) line from lines where id = 1 union all select l.id, cast(r.line + N', ' + l.line as nvarchar(max)) from lines l inner join result_lines r on l.id = r.id + 1 ) select top 1 line from result_lines order by id desc 

这也可以是有用的

 create table #test (id int,name varchar(10)) --use separate inserts on older versions of SQL Server insert into #test values (1,'Peter'), (1,'Paul'), (1,'Mary'), (2,'Alex'), (3,'Jack') DECLARE @t VARCHAR(255) SELECT @t = ISNULL(@t + ',' + name, name) FROM #test WHERE id = 1 select @t drop table #test 

回报

 Peter,Paul,Mary 

我真的很喜欢Dana的回答 。 只是想完成。

 DECLARE @names VARCHAR(MAX) SET @names = '' SELECT @names = @names + ', ' + Name FROM Names -- Deleting last two symbols (', ') SET @sSql = LEFT(@sSql, LEN(@sSql) - 1) 

如果你想处理空值,你可以通过添加一个where子句或者在第一个子句中添加另一个COALESCE来完成。

 DECLARE @Names VARCHAR(8000) SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People 

在Oracle中,它是wm_concat 。 我相信这个function在10g版本和更高版本中可用。

–SQL Server 2005+

 CREATE TABLE dbo.Students ( StudentId INT , Name VARCHAR(50) , CONSTRAINT PK_Students PRIMARY KEY (StudentId) ); CREATE TABLE dbo.Subjects ( SubjectId INT , Name VARCHAR(50) , CONSTRAINT PK_Subjects PRIMARY KEY (SubjectId) ); CREATE TABLE dbo.Schedules ( StudentId INT , SubjectId INT , CONSTRAINT PK__Schedule PRIMARY KEY (StudentId, SubjectId) , CONSTRAINT FK_Schedule_Students FOREIGN KEY (StudentId) REFERENCES dbo.Students (StudentId) , CONSTRAINT FK_Schedule_Subjects FOREIGN KEY (SubjectId) REFERENCES dbo.Subjects (SubjectId) ); INSERT dbo.Students (StudentId, Name) VALUES (1, 'Mary') , (2, 'John') , (3, 'Sam') , (4, 'Alaina') , (5, 'Edward') ; INSERT dbo.Subjects (SubjectId, Name) VALUES (1, 'Physics') , (2, 'Geography') , (3, 'French') , (4, 'Gymnastics') ; INSERT dbo.Schedules (StudentId, SubjectId) VALUES (1, 1) --Mary, Physics , (2, 1) --John, Physics , (3, 1) --Sam, Physics , (4, 2) --Alaina, Geography , (5, 2) --Edward, Geography ; SELECT sub.SubjectId , sub.Name AS [SubjectName] , ISNULL( x.Students, '') AS Students FROM dbo.Subjects sub OUTER APPLY ( SELECT CASE ROW_NUMBER() OVER (ORDER BY stu.Name) WHEN 1 THEN '' ELSE ', ' END + stu.Name FROM dbo.Students stu INNER JOIN dbo.Schedules sch ON stu.StudentId = sch.StudentId WHERE sch.SubjectId = sub.SubjectId ORDER BY stu.Name FOR XML PATH('') ) x (Students) ; 

我并没有对performance进行任何分析,因为我的名单不到10件,但是通过30个奇怪的答案,我感到非常惊讶,我仍然对类似的答案有一个类似于使用COALESCE作为单个名单的类似答案,甚至不必设置我的variables(默认为NULL),并假定我的源数据表中的所有条目都是非空的:

 DECLARE @MyList VARCHAR(1000), @Delimiter CHAR(2) = ', ' SELECT @MyList = CASE WHEN @MyList > '' THEN @MyList + @Delimiter ELSE '' END + FieldToConcatenate FROM MyData 

我相信盟军在内部使用相同的想法。 让我们希望MS不要改变这一点。