考虑一个包含三个行的名称的数据库表:
Peter
Paul
Mary
有没有一种简单的方法可以将其转换为单个字符串Peter, Paul, Mary
?
1> 小智..: 如果您使用的是SQL Server 2017或Azure,请参阅Mathieu Renda的答案.
当我尝试使用一对多关系加入两个表时,我遇到了类似的问题.在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]
如果你可以在开头连接逗号并使用substring
跳过第一个逗号,那么你可以以更紧凑的方式做同样的事情,这样你就不需要做一个子查询:
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
@Whelkaholism底线是"FOR XML"旨在生成XML,而不是连接任意字符串.这就是为什么它将`&`,`<`和`>`转换为XML实体代码(`&`,`<`,`>`).我假设它也会在属性中将```和`'`转换为``和`'``.它不是*`GROUP_CONCAT()`,`string_agg()`,`array_agg()`, `listagg()`等等,即使你可以做到这一点.我们*应该*花时间要求微软实现正确的功能.
注意:此方法依赖于`FOR XML PATH('')`的未记录行为.这意味着它不应被视为可靠,因为任何补丁或更新都可能改变其功能.它基本上依赖于已弃用的功能.
好的解决方案 如果您需要处理HTML中的特殊字符,以下内容可能会有所帮助:[Rob Farley:使用FOR XML PATH('')处理特殊字符](http://sqlblog.com/blogs/rob_farley/archive/2010/ 4月15日/处理 - 特殊字符 - 用换XML-path.aspx).
好消息:[MS SQL Server将在v.Next中添加`string_agg`.](http://stackoverflow.com/a/42778050),所有这些都可以消失.
如果名称包含诸如`<`或`&`之类的XML字符,显然这不起作用.见@ BenHinman的评论.
2> Chris Shaffe..:
当存在ORDER BY子句时,此答案可能会返回意外结果.要获得一致的结果,请使用其他答案中详述的FOR XML PATH方法之一.
用途COALESCE
:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
只是一些解释(因为这个答案似乎得到相对规律的观点):
Coalesce真的只是一个有用的骗子,可以完成两件事:
1)无需@Names
使用空字符串值进行初始化.
2)最后无需剥去额外的分离器.
如果行具有NULL Name值,则上面的解决方案将给出不正确的结果(如果存在NULL ,则NULL 将在该行之后@Names
变为NULL ,并且下一行将再次作为空字符串重新开始.使用两个中的一个轻松修复解决方案:
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 s,第二个选项将它们保留在列表中,并带有标记消息[用适合您的任何内容替换'N/A']).
要清楚,coalesce与创建列表无关,它只是确保不包含NULL值.
请注意,这种连接方法依赖于SQL Server使用特定计划执行查询.我被发现使用这种方法(添加了ORDER BY).当它处理少量行时它运行正常但是有了更多数据,SQL Server选择了一个不同的计划,导致选择第一个没有连接的项目.见Anith Sen的[本文](http://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/#_Toc205129492).
@Graeme Perrow它不排除NULL值(需要WHERE - 如果其中一个输入值为NULL,*将丢失结果*),并且**在此方法中是必需的*因为:NULL + non NULL - > NULL和非NULL + NULL - > NULL; 默认情况下,@ Name也是NULL,实际上,该属性在此处用作隐式标记,以确定是否应添加",".
此方法不能用作选择列表或where子句中的子查询,因为它使用tSQL变量.在这种情况下,您可以使用@Ritesh提供的方法
这不是一种可靠的连接方法.它不受支持且不应使用(根据Microsoft,例如https://support.microsoft.com/en-us/kb/287515,https://connect.microsoft.com/SQLServer/Feedback/Details/704389).它可以在没有任何警告的情 使用http://stackoverflow.com/questions/5031204/does-t-sql-have-an-aggregate-function-to-concatenate-strings/5031297#5031297中讨论的XML PATH技术我在这里写了更多:http:/ /marc.durdin.net/2015/07/concatenating-strings-in-sql-server-or-undefined-behaviour-by-design/
@krubo不,问题是`@Names = @Names +*nothing*`将为null,因为@Names在声明时为null.COALESCE解析**null`Name`值**和**初始null` @ Names`值.
@XpiritO-您是指text和ntext吗?Varchar是兼容的;可以将text和ntext转换(如果您使用的是SQL 2005,则将它们转换为VARCHAR(MAX)/ NVARCHAR(MAX),并且不会丢失任何内容;否则,无论如何,您都必须接受截断的可能性,因为可以这样) t声明一个text / ntext变量)。
这种解决方案在某种观点上是行不通的,但Ritesh的观点是可行的。
尝试过,一开始就喜欢它,因为它是如此的简单和辉煌。但是,就像对varchar()的任何其他重复concat进程一样,I / O和CPU最终会固定在一起。对于20,000个GUID,连接它们花费了2分钟,而使用`for xml path('')`则花费了不到1秒的时间。
@confusedMind-像这样:`DECLARE @Names VARCHAR(8000)SELECT @Names = COALESCE(@Names +',','')+ Name FROM(SELECT DISTINCT Name FROM People)`
这种方法的主要问题是它将截断所有超过8000的值。
您的解释并不能真正解释其作用。它依赖于SQL Server为每一行执行表达式。如果可以依靠的话那真是太好了。但是请参阅其他注释,指出对于这种类型的查询,不需要SQL Server。
真是 我对这个答案进行了投票,但是现在我知道这不是连接的正确方法,因为它是一种不受支持的功能,它会使您烦恼,我无法删除我的投票了:-(现在我误导了很多。
3> Mathieu Rend..: SQL Server 2017+和SQL Azure:STRING_AGG
从下一版本的SQL Server开始,我们最终可以跨行连接,而不必诉诸任何变量或XML witchery.
STRING_AGG(Transact-SQL)
没有分组
SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;
分组:
SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
通过分组和子排序
SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
这在Azure SQL中也适用于我.辉煌!
4> 小智..: 通过XML
data()
MS SQL Server中的命令尚未显示的一种方法是:
假设名为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
圣洁的,太神奇了!当单独执行时,如在您的示例中,结果被格式化为超链接,单击时(在SSMS中)打开一个包含数据的新窗口,但当作为较大查询的一部分使用时,它只显示为字符串.它是一个字符串?或者是我需要在将使用此数据的应用程序中区别对待的xml?
这种方法也可以XML转义像<和>这样的字符.因此,选择'
'+ FName +' b>'会产生"< b> John</b>< b> Paul ......" 整洁的解决方案.我注意到,即使我不添加"+","它仍然在每个连接元素之间添加一个空格. @Baodad这似乎是交易的一部分.您可以通过替换添加的令牌字符来解决此问题.例如,这为任何长度都有一个完美的逗号分隔列表:`SELECT STUFF(REPLACE((SELECT'#!'+ city AS'data()'FROM #cityzip FOR XML PATH('')),'#! ',','),1,2,'')` 5> Steven Chong..: 在SQL Server 2005中
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
在SQL Server 2016中
您可以使用FOR JSON语法
即
SELECT per.ID,
Emails = JSON_VALUE(
REPLACE(
(SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
,'"},{"_":"',', '),'$[0]._'
)
FROM Person per
结果将成为
Id Emails
1 abc@gmail.com
2 NULL
3 def@gmail.com, xyz@gmail.com
这甚至会使您的数据包含无效的XML字符
这'"},{"_":"'
是安全的,因为如果您包含数据'"},{"_":"',
,它将被转义为"},{\"_\":\"
您可以替换', '
任何字符串分隔符
在SQL Server 2017中,Azure SQL数据库
您可以使用新的STRING_AGG功能
这比接受的答案要好,因为这个选项还可以处理未转义的XML保存字符,例如```,`>`,`&`等,`FOR XML PATH('')`会自动转义.
很好地利用STUFF函数来修改前两个字符.
我最喜欢这个解决方案,因为我可以通过附加'as
'轻松地在选择列表中使用它.我不知道如何使用@Ritesh解决方案. 6> Darryl Hein..: 在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
7> pedram..: 使用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
此解决方案已于8年前发布!http://stackoverflow.com/a/194887/986862 这确实是IMO的最佳解决方案,因为它避免了FOR XML带来的编码问题.我使用`Declare @Numbers AS Nvarchar(MAX)`并且它工作正常.你能解释为什么你建议不要使用它吗? 8> hgmnz..: Postgres数组非常棒.例:
创建一些测试数据:
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)
将数组转换为逗号分隔的字符串:
test=# select array_to_string(array_agg(name), ', ') from names;
array_to_string
-------------------
Peter, Paul, Mary
(1 row)
DONE
从PostgreSQL 9.0开始,它就更容易了.
9> Alex..: Oracle 11g第2版支持LISTAGG功能.文档在这里.
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.
警告
如果结果字符串可能超过4000个字符,请小心实现此功能.它会引发异常.如果是这种情况,那么您需要处理异常或滚动自己的函数,以防止连接的字符串超过4000个字符.
10> 小智..: 在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
我相信当值包含诸如`<`或`&`之类的XML符号时,这会失败. 11> Dana..: 我在家里无法访问SQL Server,所以我猜这里的语法,但它或多或少:
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
你需要将@names初始化为非null值,否则你将得到NULL; 你还需要处理分隔符(包括不必要的分隔符) 这种方法(我一直使用)的唯一问题是你无法嵌入它 12> jmoreno..: 建议使用递归CTE解决方案,但不提供代码.下面的代码是一个递归CTE的例子 - 请注意,尽管结果与问题匹配,但数据与给定的描述并不完全 匹配,因为我假设你真的想要在行组上执行此操作,而不是全部表中的行.更改它以匹配表中的所有行留给读者练习.
;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
13> Tigerjz32..: 您需要创建一个变量来保存最终结果并选择进入它,就像这样.
最简单的解决方案
DECLARE @char VARCHAR(MAX);
SELECT @char = COALESCE(@char + ', ' + [column], [column])
FROM [table];
PRINT @char;
14> a_horse_with..: 从PostgreSQL 9.0开始,这非常简单:
select string_agg(name, ',')
from names;
在9.0之前的版本array_agg()
中可以使用如hgmnz所示
15> Henrik Frans..: 在SQL Server vNext中,这将使用STRING_AGG函数构建,请在此处阅读更多相关信息:https://msdn.microsoft.com/en-us/library/mt790580.aspx
16> 小智..: 使用XML帮助我用逗号分隔行.对于额外的逗号,我们可以使用SQL Server的替换功能.使用AS'data()'代替添加逗号将使用空格连接行,稍后可以用逗号替换下面的语法.
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
如果FName数据已经有空格,那就不行了,例如"我的名字" 这是我的观点中最好的答案.当你需要加入另一个表时,使用declare变量并不好,这很好而且简短.干得好. 17> Daniel Reis..: 一个随时可用的解决方案,没有额外的逗号:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
空列表将导致NULL值.通常,您将列表插入表列或程序变量:根据需要调整255最大长度.
(Diwakar和Jens Frandsen提供了很好的答案,但需要改进.)
18> Max Szczurek..: SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')
这是一个示例:
DECLARE @t TABLE (name VARCHAR(10))
INSERT INTO @t VALUES ('Peter'), ('Paul'), ('Mary')
SELECT STUFF((SELECT ', ' + name FROM @t FOR XML PATH('')), 1, 2, '')
--Peter, Paul, Mary
19> gbn..: DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)
这使得流浪逗号开头.
但是,如果您需要其他列,或者要CSV子表,则需要将其包装在标量用户定义字段(UDF)中.
您也可以在SELECT子句中使用XML路径作为相关子查询(但是我必须等到我重新开始工作,因为Google不在家做工作:-)
20> Mike Barlow ..: 对于其他答案,阅读答案的人必须知道特定的域表,例如车辆或学生.必须创建该表并使用数据填充该表以测试解决方案.
下面是一个使用SQL Server"Information_Schema.Columns"表的示例.通过使用此解决方案,无需创建表或添加数据.此示例为数据库中的所有表创建逗号分隔的列名列表.
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
21> Oleg Sakharo..: 我真的很喜欢Dana的回答.只是想让它完整.
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
22> ZeroK..: 对于Oracle DB,请参阅以下问题:如何在不创建存储过程的情况下将多行连接到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明确建议不要将它用于任何应用程序SQL,因此使用风险自负.
除此之外,你必须编写自己的函数; 上面的Oracle文档提供了如何执行此操作的指南.
23> Rapunzo..: 要避免空值,可以使用CONCAT()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
24> Nizam..: 这个答案需要服务器中的某些特权才能工作.
装配是一个很好的选择.有很多网站可以解释如何创建它.我认为很好解释的是这一个
如果你愿意,我已经创建了程序集,可以在这里下载DLL .
下载后,您需要在SQL Server中运行以下脚本:
CREATE Assembly concat_assembly
AUTHORIZATION dbo
FROM ''
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
注意服务器可以访问程序集的路径.由于您已成功完成所有步骤,因此可以使用以下功能:
SELECT dbo.Concat(field1, ',')
FROM Table1
希望能帮助到你!!!
25> Vladimir Nes..: 我通常使用select这样来连接SQL Server中的字符串:
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
26> 小智..: 如果要处理空值,可以通过添加where子句或在第一个周围添加另一个COALESCE来实现.
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
27> user1767754..: 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