如何在 SQL Server 中将多行中的文本连接成一个文本字符串

How to concatenate text from multiple rows into a single text string in SQL Server

提问人:JohnnyM 提问时间:10/12/2008 最后编辑:Peter MortensenJohnnyM 更新时间:11/7/2023 访问量:3224502

问:

考虑一个包含名称的数据库表,该表包含三行:

Peter
Paul
Mary

有没有一种简单的方法可以将其转换为单个字符串?Peter, Paul, Mary

sql sql-server csv 字符串串联 组 - concat

评论

29赞 Matt Hamilton 10/12/2008
有关特定于 SQL Server 的答案,请尝试此问题
22赞 Pykler 5/7/2011
对于MySQL,请从此答案中查看Group_Concat
31赞 Pete Alvin 10/2/2014
我希望 SQL Server 的下一个版本能够提供一项新功能,以优雅地解决多行字符串连接问题,而不会出现 FOR XML PATH 的愚蠢之处。
4赞 Stack Man 5/27/2015
不是 SQL,但如果这是一次性的,您可以将列表粘贴到这个浏览器内工具 convert.town/column-to-comma-separated-list
4赞 Richard 7/6/2017
在 Oracle 中,您可以使用 11g r2 中的 LISTAGG(COLUMN_NAME),在此之前,有一个名为 WM_CONCAT(COLUMN_NAME) 的不受支持的函数可以执行相同的操作。

答:

3赞 Manu 10/12/2008 #1

在 SQL Server 中执行此操作的一种方法是将表内容作为 XML(对于 XML 原始)返回,将结果转换为字符串,然后将标记替换为“,”。

163赞 Darryl Hein 10/12/2008 #2

在 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

评论

0赞 hardmooth 2/14/2018
基本有效。需要考虑两件事:1)如果你的列不是,你需要强制转换它,例如通过 2)如果你有很多值,你应该增加 stackoverflow.com/a/1278210/1498405CHARGROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',')group_concat_max_len
0赞 Wilfred Almeida 3/23/2022
截至 2022 年 3 月,这对我有用。我在行中有 url,并希望它们作为一列,这奏效了。谢谢!
3赞 GoldBishop 3/27/2022
OP 是关于 [MS] SQL Server 的
37赞 Dana 10/12/2008 #3

我在家里无法访问 SQL Server,所以我猜测这里的语法,但它或多或少是:

DECLARE @names VARCHAR(500)

SELECT @names = @names + ' ' + Name
FROM Names

评论

13赞 Marc Gravell 10/12/2008
您需要将 @names 初始化为非 null 的内容,否则您将始终获得 NULL;您还需要处理分隔符(包括不必要的分隔符)
5赞 ekkis 11/24/2012
这种方法(我一直在使用)的唯一问题是你不能嵌入它
3赞 Tian van Heerden 3/4/2016
若要删除前导空格,请将查询更改为SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
0赞 Vita1ij 3/18/2016
此外,您必须检查 Name 是否不为 null,您可以通过执行以下操作来完成:SELECT @names = @names + ISNULL(' ' + Name, '')
1148赞 Chris Shaffer 10/12/2008 #4

此答案可能会返回意外结果 为了获得一致的结果,请使用其他答案中详述的 FOR XML PATH 方法之一。

用:COALESCE

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

只是一些解释(因为这个答案似乎得到了相对常规的观点):

  • Coalesce 实际上只是一个有用的作弊工具,可以完成两件事:

1) 无需使用空字符串值进行初始化。@Names

2)无需在最后剥离额外的分离器。

  • 如果一行具有 NULL Name 值,则上述解决方案将给出不正确的结果(如果存在 NULL则 NULL 将在该行之后变为 NULL,下一行将再次作为空字符串重新开始。使用以下两种解决方案之一轻松修复:@Names
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,第二个选项将它们保留在列表中,并带有标记消息 [将“N/A”替换为适合您的任何内容])。

评论

84赞 Graeme Perrow 2/13/2009
需要明确的是,合并与创建列表无关,它只是确保不包括 NULL 值。
23赞 8/16/2010
@Graeme Perrow 它不排除 NULL 值(需要 WHERE,如果其中一个输入值为 NULL,这将丢失结果),并且此方法中需要它,因为:NULL + non-NULL -> NULL 和非 NULL + NULL -> NULL;此外,默认情况下@Name为 NULL,实际上,该属性在此处用作隐式哨兵,以确定是否应添加 ', '。
72赞 fbarber 4/26/2012
请注意,此串联方法依赖于 SQL Server 使用特定计划执行查询。我被发现使用这种方法(添加了 ORDER BY)。当它处理少量行时,它工作正常,但对于更多数据,SQL Server 选择了不同的计划,这导致选择没有任何串联的第一项。请参阅Anith Sen的这篇文章
20赞 R. Schreurs 8/2/2013
此方法不能用作选择列表或 where 子句中的子查询,因为它使用 tSQL 变量。在这种情况下,您可以使用@Ritesh提供的方法
17赞 Marc Durdin 7/15/2015
这不是一种可靠的串联方法。它不受支持,不应使用(根据Microsoft,例如 support.microsoft.com/en-us/kb/287515connect.microsoft.com/SQLServer/Feedback/Details/704389)。它可以在没有警告的情况下更改。使用 stackoverflow.com/questions/5031204/...我在这里写了更多:marc.durdin.net/2015/07/......
8赞 gbn 10/14/2008 #5
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)

这会将杂散逗号放在开头。

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

您也可以在 SELECT 子句中使用 XML 路径作为相关子查询(但我必须等到我回去工作,因为 Google 不会在家做工作:-)

1651赞 Ritesh 2/13/2009 #6

如果使用的是 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 (''), TYPE
            ).value('text()[1]','nvarchar(max)') [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 (''), TYPE
        ).value('text()[1]','nvarchar(max)'), 2, 1000) [Students]
FROM dbo.Students ST2

评论

18赞 4/17/2013
很棒的解决方案。如果您需要处理 HTML 中的特殊字符,以下内容可能会有所帮助: Rob Farley:使用 FOR XML PATH('') 处理特殊字符
14赞 Sam 8/13/2013
显然,如果名称包含 XML 字符(如 或 )。请参阅@BenHinman的评论。<&
30赞 Bacon Bits 11/14/2014
注意:此方法依赖于 的未记录行为。这意味着它不应被视为可靠,因为任何补丁或更新都可能改变其功能。它基本上依赖于已弃用的功能。FOR XML PATH ('')
34赞 Bacon Bits 3/23/2015
@Whelkaholism 底线是旨在生成 XML,而不是连接任意字符串。这就是为什么它转义 和 XML 实体代码 (, , ) 的原因。我假设它也会转义,并且也会在属性中转义。它不是 、 、 、 等,即使你可以让它这样做。我们应该花时间要求Microsoft实现适当的功能。FOR XML&<>&amp;&lt;&gt;"'&quot;&apos;GROUP_CONCAT()string_agg()array_agg()listagg()
27赞 Jason C 4/6/2017
好消息:MS SQL Server 将在 v.Next 中添加string_agg所有这些都可以消失。
358赞 9 revs, 7 users 70%Steven Chong #7

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   [email protected]
2   NULL
3   [email protected], [email protected]

即使您的数据包含无效的 XML 字符,这也将起作用

是安全的,因为如果您的数据包含它将被转义为'"},{"_":"''"},{"_":"',"},{\"_\":\"

您可以替换为任何字符串分隔符', '


在 SQL Server 2017 中,Azure SQL 数据库

您可以使用新的STRING_AGG功能

评论

4赞 David 8/12/2011
很好地使用 STUFF 函数来删除前两个字符。
4赞 R. Schreurs 8/2/2013
我最喜欢这个解决方案,因为我可以通过附加“as <label> 轻松地在选择列表中使用它。我不确定如何使用@Ritesh的解决方案来做到这一点。
18赞 BateTech 4/8/2014
这比公认的答案要好,因为此选项还处理未转义的 XML 保留字符,如 、 、 等,这些字符将自动转义。<>&FOR XML PATH('')
421赞 jens frandsen 4/6/2011 #8

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

假设有一个名为 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

评论

17赞 Ben 9/7/2012
天哪,太棒了!当单独执行时,如示例中所示,结果的格式为超链接,当单击该超链接(在 SSMS 中)时,将打开一个包含数据的新窗口,但当用作较大查询的一部分时,它仅显示为字符串。是字符串吗?或者,我需要在将使用此数据的应用程序中以不同的方式处理 XML?
10赞 Lukáš Lánský 2/27/2014
这种方法还可以对 < 和 > 等字符进行 XML 转义。因此,选择 '<b>' + FName + '</b>' 会导致“<b>约翰<b>保罗......”
8赞 Baodad 10/4/2014
简洁的解决方案。我注意到,即使我不添加,它仍然在每个连接的元素之间添加一个空格。+ ', '
9赞 NReilingh 3/1/2016
@Baodad 这似乎是交易的一部分。您可以通过替换添加的标记字符来解决此问题。例如,对于任何长度,这都可以创建一个完美的逗号分隔列表:SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
1赞 NReilingh 3/1/2016
哇,实际上在我使用 data() 和替换的测试中,性能比没有要高得多。 超级奇怪。
19赞 Diwakar 4/7/2011 #9

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

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

评论

2赞 David Roussel 6/3/2011
在我看来,这是最好的答案。当您需要加入另一个表时,使用 declare 变量是不好的,这很好而且很短。干得好。
8赞 binball 6/8/2011
如果 FName 数据已经有空格,例如“我的名字”,那就不好用了
0赞 Rejwanul Reja 4/28/2017
真的,它在 ms-sql 2016 上对我有用 选择 REPLACE( ( (select Name AS 'data()' from Brand Where Id IN (1,2,3,4) for xml path('')) , ' ', ', ') as allBrands
1赞 Hans Bluh 4/29/2011 #10

使用这个:

ISNULL(SUBSTRING(REPLACE((select ',' FName as 'data()' from NameList for xml path('')), ' ,',', '), 2, 300), '') 'MyList'

其中“300”可以是任何宽度,同时考虑到您认为将显示的最大项目数。

评论

1赞 Geoff Griswald 1/10/2020
如果您发现自己必须提前猜测结果中将有多少行,那么您就做错了。
6赞 user762952 6/4/2011 #11

在 Oracle 中,它是 .我相信此功能在 10g 及更高版本中可用。wm_concat

6赞 Vladimir Nesterovsky 7/6/2011 #12

我通常使用这样的 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
39赞 Yogesh Bhadauirya 7/6/2011 #13

在 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

评论

2赞 Sam 8/13/2013
我相信当值包含 XML 符号(例如 或 )时,这会失败。<&
0赞 Stritof 9/20/2021
如提供的示例所示,效果很好。我没有使用临时表或变量,而是使用了 CTE learn.microsoft.com/en-us/sql/t-sql/queries/......
6赞 Pramod 7/28/2011 #14

如果要处理空值,可以通过添加 where 子句或在第一个子句周围添加另一个 COALESCE 来实现。

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
8赞 4 revs, 4 users 71%Oleg Sakharov #15

我真的很喜欢 Dana 回答的优雅,只是想让它完整。

DECLARE @names VARCHAR(MAX)
SET @names = ''

SELECT @names = @names + ', ' + Name FROM Names

-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)

评论

0赞 JT_ 12/18/2015
如果要删除最后两个符号 ', ',则需要在 Name ('SELECT \@names = \@names + Name + ', ' FROM Names') 后添加 ', '这样,最后两个字符将始终是 ', '。
0赞 Tian van Heerden 3/4/2016
就我而言,我需要去掉前导逗号,所以将查询更改为这样你就不必在之后截断它了。SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names
22赞 Daniel Reis 2/3/2012 #16

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

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

空列表将导致 NULL 值。 通常,您会将列表插入到表列或程序变量中:根据需要调整最大长度 255。

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

评论

0赞 slayernoah 11/19/2015
使用此:(时,逗号前有一个空格
2赞 Daniel Reis 11/19/2015
如果您不想要额外的空间,只需替换即可。', '','
50赞 Alex 3/9/2012 #17

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 个字符。

评论

2赞 toscanelli 7/20/2015
对于旧版本的 Oracle,wm_concat 是完美的。它的用法在亚历克斯的链接礼物中进行了解释。谢谢亚历克斯!
1赞 asgs 6/23/2016
LISTAGG效果完美!只需阅读此处链接的文档即可。 从版本 12c 开始删除。wm_concat
39赞 jmoreno 8/10/2012 #18

提出了递归 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
OPTION (MAXRECURSION 101)

评论

5赞 knb 7/24/2017
对于大吃一惊的人:此查询将 12 行(3 列)插入到临时基表中,然后创建一个递归公用表表达式 (rCTE),然后将该列展平为 4 的逗号分隔字符串。乍一看,我认为这比大多数其他 SQL Server 解决方案所做的工作要多。nameid
5赞 jmoreno 7/25/2017
@knb:不知道这是赞美、谴责,还是只是惊讶。基表是因为我喜欢我的例子真正起作用,它与问题没有任何关系。
1赞 RET 11/1/2022
我真的很喜欢这个解决方案(迭代是人类的,递归是神圣的!),但是如果有 100 个或更多条目要连接,它会自燃,因为 SQL Server 中的递归限制。对于粗心的人来说,这是一个地雷......
1赞 jmoreno 11/1/2022
@RET:好点,它是可调节的,但应该提到。
67赞 hgmnz 8/10/2012 #19

PostgreSQL 数组很棒。例:

创建一些测试数据:

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)

从 PostgreSQL 9.0 开始,它甚至更容易,引用“没有名字的马”的已删除答案:

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

评论

2赞 ProbablePrime 2/27/2015
如果需要多个列,例如括号中的员工 ID,请使用 concat 运算符:select array_to_string(array_agg(name||'('||id||')'
4赞 GoldBishop 5/4/2017
不适用于 sql-server,仅适用于 mysql
1赞 Priti Getkewar Joshi 9/29/2012 #20

Oracle 中有几种方法:

    create table name
    (first_name varchar2(30));

    insert into name values ('Peter');
    insert into name values ('Paul');
    insert into name values ('Mary');

解决方案是 1:

    select substr(max(sys_connect_by_path (first_name, ',')),2) from (select rownum r, first_name from name ) n start with r=1 connect by prior r+1=r
    o/p=> Peter,Paul,Mary

解决方案是 2:

    select  rtrim(xmlagg (xmlelement (e, first_name || ',')).extract ('//text()'), ',') first_name from name
    o/p=> Peter,Paul,Mary
6赞 ZeroK 5/14/2013 #21

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

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

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 文档提供了有关如何执行此操作的指南。

6赞 endo64 10/25/2013 #22

这也很有用

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

评论

5赞 blueling 12/5/2013
不幸的是,这种行为似乎没有得到官方支持。MSDN 说:“如果在选择列表中引用了某个变量,则应为其分配一个标量值,或者 SELECT 语句应仅返回一行。有些人观察到了问题:sqlmag.com/sql-server/multi-row-variable-assignment-and-order
0赞 endo64 1/6/2022
别忘了看 stackoverflow.com/a/42778050/333153 在较新版本的 SQL Server 上,我们现在可以使用STRING_AGG函数。
4赞 topchef 11/16/2013 #23

此方法仅适用于 Teradata Aster 数据库,因为它使用其 NPATH 函数。

同样,我们有表 Students

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

然后使用 NPATH,它只是单个 SELECT:

SELECT * FROM npath(
  ON Students
  PARTITION BY SubjectID
  ORDER BY StudentName
  MODE(nonoverlapping)
  PATTERN('A*')
  SYMBOLS(
    'true' as A
  )
  RESULT(
    FIRST(SubjectID of A) as SubjectID,
    ACCUMULATE(StudentName of A) as StudentName
  )
);

结果:

SubjectID       StudentName
----------      -------------
1               [John, Mary, Sam]
2               [Alaina, Edward]
1赞 Max Tkachenko 12/2/2013 #24

使用“TABLE”类型,这非常容易。让我们想象一下,你的表被调用,它有列。Studentsname

declare @rowsCount INT
declare @i INT = 1
declare @names varchar(max) = ''

DECLARE @MyTable TABLE
(
  Id int identity,
  Name varchar(500)
)
insert into @MyTable select name from Students
set @rowsCount = (select COUNT(Id) from @MyTable)

while @i < @rowsCount
begin
 set @names = @names + ', ' + (select name from @MyTable where Id = @i)
 set @i = @i + 1
end
select @names

此示例已使用 SQL Server 2008 R2 进行了测试。

8赞 Rapunzo 2/12/2015 #25

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

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

评论

0赞 Reversed Engineer 9/20/2016
很高兴知道为什么CONCAT有效。链接到MSDN会很好。
-5赞 Hamid Bahmanabady 4/20/2015 #26
   declare @phone varchar(max)='' 
   select @phone=@phone + mobileno +',' from  members
   select @phone

评论

0赞 shA.t 4/20/2015
为什么不按照 OP 的意愿,而且你也不删除最后一个 ';'。我认为这个答案是一样的,这个答案;)。+', '
0赞 Hamid Bahmanabady 4/20/2015
我遇到了这个问题,我找到了答案,但我想要用“;”连接,所以我把它粘贴到这里,最后一个元素是空的
4赞 shA.t 4/20/2015
当您在此处发布答案时,它应该与问题相关,并且您的代码的结果应该是,因为您以 和 add 开头,而 add 将在 SQL Server 中,我想您忘记了在第一行 ;)之后添加之类的东西。Null@phone IS NullNullNull= ''
0赞 Hamid Bahmanabady 4/21/2015
不,我在检查后发布答案,结果不为空
7赞 Nizam 5/8/2015 #27

此答案需要服务器上的某些权限才能工作。

程序集对您来说是一个不错的选择。有很多网站解释了如何创建它。我认为解释得很好的就是这个。

如果你愿意,我已经创建了程序集,可以在这里下载DLL文件。

下载后,需要在 SQL Server 中运行以下脚本:

EXEC sp_configure 'show advanced options', 1
RECONFIGURE;
EXEC sp_configure 'clr strict security', 1;
RECONFIGURE;

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

请注意,服务器可以访问程序集的路径。由于您已成功完成所有步骤,因此可以使用以下功能:

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

SQL Server 2017 开始,可以使用 STRING_AGG 函数。

评论

1赞 Protiguous 2/19/2020
DLL 链接是 404 错误。为此使用程序集是矫枉过正的。请参阅 SQL Server 的最佳答案
10赞 user1767754 7/22/2015 #28

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

评论

0赞 Josh 12/10/2021
谢谢你!我可能会建议进行编辑以指出GROUP BY
73赞 Pedram 4/5/2016 #29

使用 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

评论

4赞 EvilDr 8/3/2016
这确实是IMO的最佳解决方案,因为它避免了FOR XML带来的编码问题。我用过,效果很好。你能解释一下为什么你建议不要使用它吗?Declare @Numbers AS Nvarchar(MAX)
12赞 Andre Figueiredo 5/4/2017
这个解决方案已经在 8 年前发布了!stackoverflow.com/a/194887/986862
0赞 Akmal Salikhov 12/7/2017
为什么此查询返回???符号而不是西里尔字母?这只是输出问题吗?
0赞 Developer Webs 3/11/2021
@EvilDr 您可以避免使用 XML 编码。请参阅:stackoverflow.com/questions/15643683/...
0赞 Peter Mortensen 8/21/2021
为什么不使用问题中的例子呢?
12赞 Mike Barlow - BarDev 5/5/2016 #30

对于其他答案,阅读答案的人必须知道特定的域表,例如车辆或学生。必须创建该表并使用数据填充该表才能测试解决方案。

下面是使用 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 
1赞 Muhammad Bilal 5/26/2016 #31
SELECT PageContent = Stuff(
    (   SELECT PageContent
        FROM dbo.InfoGuide
        WHERE CategoryId = @CategoryId
          AND SubCategoryId = @SubCategoryId
        for xml path(''), type
    ).value('.[1]','nvarchar(max)'),
    1, 1, '')
FROM dbo.InfoGuide info

评论

0赞 Peter Mortensen 8/21/2021
解释是有序的。请通过编辑(更改)您的答案来回复,而不是在评论中(没有“编辑:”,“更新:”或类似内容 - 答案应该看起来像今天写的一样)。
5赞 Graeme 6/2/2016 #32

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)
;
3赞 Glen 6/10/2016 #33

并不是说我对性能进行了任何分析,因为我的列表少于 10 个项目,但在浏览了 30 个奇怪的答案后,我感到很惊讶,我仍然对已经给出的类似答案有所了解,类似于将 COALESCE 用于单个组列表,甚至不必设置我的变量(无论如何默认为 NULL),并且它假设我的源数据表中的所有条目都为非空白:

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

我相信 COALESCE 内部也使用同样的想法。 让我们希望Microsoft不要改变我。

29赞 Tigerjz32 11/16/2016 #34

你需要创建一个变量来保存你的最终结果,并在其中进行选择,就像这样。

最简单的解决方案

DECLARE @char VARCHAR(MAX);

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

PRINT @char;
24赞 Henrik Fransas 11/21/2016 #35

在 SQL Server vNext 中,这将与 STRING_AGG 函数一起内置。有关详细信息,请参阅STRING_AGG (Transact-SQL)。

894赞 Mathieu Renda 3/14/2017 #36

SQL Server 2017+ 和 SQL Azure:STRING_AGG

从下一版本的 SQL Server 开始,我们终于可以跨行连接,而不必求助于任何变量或 XML 巫术。

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;

评论

5赞 canon 7/11/2017
而且,与 CLR 解决方案不同的是,您可以控制排序。
1赞 RuudvK 5/10/2020
在没有 GROUP BY 的情况下,有没有办法进行排序(所以对于“没有分组”示例)?
1赞 RuudvK 5/10/2020
更新:我设法做到了以下几点,但是有没有更干净的方法?SELECT STRING_AGG(Name, ', ') AS Departments FROM ( SELECT TOP 100000 Name FROM HumanResources.Department ORDER BY Name) D;
2赞 Varun 9/26/2020
我必须将其转换为NVarchar(max)才能使其工作。''' SELECT STRING_AGG(CAST(EmpName as NVARCHAR(MAX)), ',') FROM EmpTable as t '''
2赞 Yahya 7/5/2023
我错过了我的一生。这应该是公认的答案!STRING_AGG
1赞 Shahbaz 12/8/2017 #37

虽然为时已晚,而且已经有很多解决方案。这是MySQL的简单解决方案:

SELECT t1.id,
        GROUP_CONCAT(t1.id) ids
 FROM table t1 JOIN table t2 ON (t1.id = t2.id)
 GROUP BY t1.id

评论

1赞 jpaugh 3/19/2018
这个问题特定于 SQL Server,因此需要它的人不太可能找到这个答案。关于同一件事,是否有特定于 mysql 的问题?
0赞 Peter Mortensen 8/21/2021
@jpaugh:不是借口,但第二天发布的第 4 个答案(如果删除的答案不计算在内,则为 2 个),有 140 个赞成票,也是关于 MySQL 的:Darryl Hein 的答案。其他答案适用于 Oracle 和 PostgreSQL。最好的方法是什么?将它们标记为“不是答案”?还是别的什么?
18赞 Max Szczurek 1/25/2018 #38
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
3赞 Pooja Bhat 2/15/2018 #39

下面是一个简单的 PL/SQL 过程,使用“基本循环”和“rownum”实现给定的场景

表定义

CREATE TABLE "NAMES" ("NAME" VARCHAR2(10 BYTE))) ;

让我们将值插入到此表中

INSERT INTO NAMES VALUES('PETER');
INSERT INTO NAMES VALUES('PAUL');
INSERT INTO NAMES VALUES('MARY');

程序从这里开始

DECLARE 

MAXNUM INTEGER;
CNTR INTEGER := 1;
C_NAME NAMES.NAME%TYPE;
NSTR VARCHAR2(50);

BEGIN

SELECT MAX(ROWNUM) INTO MAXNUM FROM NAMES;

LOOP

SELECT NAME INTO  C_NAME FROM 
(SELECT ROWNUM RW, NAME FROM NAMES ) P WHERE P.RW = CNTR;

NSTR := NSTR ||','||C_NAME;
CNTR := CNTR + 1;
EXIT WHEN CNTR > MAXNUM;

END LOOP;

dbms_output.put_line(SUBSTR(NSTR,2));

END;

结果

PETER,PAUL,MARY

评论

0赞 jpaugh 3/19/2018
该问题要求特定于 SQL Server 的答案。如果有 PL/SQL 问题,您可以在那里回答。但是,请先查看wm_concat,看看这是否是一种更简单的方法。
4赞 Ravi Pipaliya 4/2/2018 #40

以下是实现此目的的完整解决方案:

-- Table Creation
CREATE TABLE Tbl
( CustomerCode    VARCHAR(50)
, CustomerName    VARCHAR(50)
, Type VARCHAR(50)
,Items    VARCHAR(50)
)

insert into Tbl
SELECT 'C0001','Thomas','BREAKFAST','Milk'
union SELECT 'C0001','Thomas','BREAKFAST','Bread'
union SELECT 'C0001','Thomas','BREAKFAST','Egg'
union SELECT 'C0001','Thomas','LUNCH','Rice'
union SELECT 'C0001','Thomas','LUNCH','Fish Curry'
union SELECT 'C0001','Thomas','LUNCH','Lessy'
union SELECT 'C0002','JOSEPH','BREAKFAST','Bread'
union SELECT 'C0002','JOSEPH','BREAKFAST','Jam'
union SELECT 'C0002','JOSEPH','BREAKFAST','Tea'
union SELECT 'C0002','JOSEPH','Supper','Tea'
union SELECT 'C0002','JOSEPH','Brunch','Roti'

-- function creation
GO
CREATE  FUNCTION [dbo].[fn_GetItemsByType]
(   
    @CustomerCode VARCHAR(50)
    ,@Type VARCHAR(50)
)
RETURNS @ItemType TABLE  ( Items VARCHAR(5000) )
AS
BEGIN

        INSERT INTO @ItemType(Items)
    SELECT  STUFF((SELECT distinct ',' + [Items]
         FROM Tbl 
         WHERE CustomerCode = @CustomerCode
            AND Type=@Type
            FOR XML PATH(''))
        ,1,1,'') as  Items



    RETURN 
END

GO

-- fianl Query
DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT distinct ',' + QUOTENAME(Type) 
                    from Tbl
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT CustomerCode,CustomerName,' + @cols + '
             from 
             (
                select  
                    distinct CustomerCode
                    ,CustomerName
                    ,Type
                    ,F.Items
                    FROM Tbl T
                    CROSS APPLY [fn_GetItemsByType] (T.CustomerCode,T.Type) F
            ) x
            pivot 
            (
                max(Items)
                for Type in (' + @cols + ')
            ) p '

execute(@query) 

评论

0赞 Peter Mortensen 8/21/2021
解释是有序的。例如,要点/想法是什么?您还应该修复缩进。请通过编辑(更改)您的答案来回复,而不是在评论中(没有“编辑:”,“更新:”或类似内容 - 答案应该看起来像今天写的一样)。
1赞 Esperento57 7/31/2018 #41

使用递归查询,您可以做到这一点:

-- Create example table
CREATE TABLE tmptable (NAME VARCHAR(30)) ;

-- Insert example data
INSERT INTO tmptable VALUES('PETER');
INSERT INTO tmptable VALUES('PAUL');
INSERT INTO tmptable VALUES('MARY');

-- Recurse query
with tblwithrank as (
select * , row_number() over(order by name) rang , count(*) over() NbRow
from tmptable
),
tmpRecursive as (
select *, cast(name as varchar(2000)) as AllName from tblwithrank  where rang=1
union all
select f0.*,  cast(f0.name + ',' + f1.AllName as varchar(2000)) as AllName 
from tblwithrank f0 inner join tmpRecursive f1 on f0.rang=f1.rang +1 
)
select AllName from tmpRecursive
where rang=NbRow
0赞 Kemal AL GAZZAH 4/24/2019 #42

我们可以按如下方式使用 RECUSRSIVITY、CTE、union ALL

declare @mytable as table(id int identity(1,1), str nvarchar(100))
insert into @mytable values('Peter'),('Paul'),('Mary')

declare @myresult as table(id int,str nvarchar(max),ind int, R# int)

;with cte as(select id,cast(str as nvarchar(100)) as str, cast(0 as int) ind from @mytable
union all
select t2.id,cast(t1.str+',' +t2.str as nvarchar(100)) ,t1.ind+1 from cte t1 inner join @mytable t2 on t2.id=t1.id+1)
insert into @myresult select *,row_number() over(order by ind) R# from cte

select top 1 str from @myresult order by R# desc
11赞 asmgx 8/16/2019 #43

在Chris Shaffer的回答之上:

如果您的数据可能会重复,例如

Tom
Ali
John
Ali
Tom
Mike

而不是拥有Tom,Ali,John,Ali,Tom,Mike

您可以使用 DISTINCT 来避免重复并获得:Tom,Ali,John,Mike

DECLARE @Names VARCHAR(8000)
SELECT DISTINCT @Names = COALESCE(@Names + ',', '') + Name
FROM People
WHERE Name IS NOT NULL
SELECT @Names
24赞 Arash.Zandi 9/16/2019 #44

这对我有用(SQL Server 2016):

SELECT CarNamesString = STUFF((
         SELECT ',' + [Name]
            FROM tbl_cars
            FOR XML PATH('')
         ), 1, 1, '')

这是来源:https://www.mytecbits.com/

对于较新的 SQL 版本(最终实现)

SELECT STRING_AGG(Name, ', ') AS CarNames
FROM tbl_TypeCar;

以及MySQL的解决方案(因为此页面显示在Google中,用于MySQL):

SELECT [Name],
       GROUP_CONCAT(DISTINCT [Name]  SEPARATOR ',')
       FROM tbl_cars

来自 MySQL 文档

0赞 Amirreza mohammadi 11/13/2020 #45

首先,您应该声明一个表变量并用表数据填充它,然后使用 WHILE 循环,逐个选择行并将其值添加到 nvarchar(max) 变量中。

    Go
    declare @temp table(
        title nvarchar(50)
    )
    insert into @temp(title)
    select p.Title from dbo.person p
    --
    declare @mainString nvarchar(max)
    set @mainString = '';
    --
    while ((select count(*) from @temp) != 0)
    begin
        declare @itemTitle nvarchar(50)
        set @itemTitle = (select top(1) t.Title from @temp t)
    
        if @mainString = ''
        begin
            set @mainString = @itemTitle
        end
        else
        begin
            set @mainString = concat(@mainString,',',@itemTitle)
        end
    
        delete top(1) from @temp
    
    end
    print @mainString

评论

1赞 MeanGreen 11/13/2020
看起来是一个很好的答案,但请解释一下这段代码是如何工作的。
1赞 user2864740 1/31/2021
这似乎具有非常低效的边界 - 这在一百万行中如何工作?
0赞 Peter Mortensen 8/21/2021
while 循环?这不是unSQLish吗?
2赞 panser 1/14/2021 #46

在 PostgreSQL 中 - array_agg

SELECT array_to_string(array_agg(DISTINCT rolname), ',') FROM pg_catalog.pg_roles;

或者STRING_AGG

SELECT STRING_AGG(rolname::text,',') FROM pg_catalog.pg_roles;

评论

0赞 Dale K 8/19/2022
但是问题指定了 SQL Server,而不是 PostgresSQL
36赞 sameer Ahmed 4/5/2021 #47

在 SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 201 STRING_AGG 7SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2 请看下面的一个例子。

SELECT
    VendorId, STRING_AGG(FirstName,',') UsersName
FROM Users
WHERE VendorId != 9
GROUP BY VendorId

Enter image description here

评论

0赞 whytheq 6/24/2022
比旧的XML解决方案简单得多 - 感谢分享!
0赞 Ammad Amir 9/12/2023 #48

在 SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 201 STRING_AGG 7SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017SQL Server 2017下面是如何实现此目的的示例:

假设您有一个名为“Names”的表,其中“Name”列在单独的行中包含值 Peter、Paul 和 Mary,则可以使用以下 SQL 查询:

SELECT STRING_AGG(Name, ', ') AS ConcatenatedNames
FROM Names;

此查询将返回一个字符串,其名称连接起来并用逗号分隔:

ConcatenatedNames
-----------------
Peter, Paul, Mary