如何将多行中的文本连接到SQL Server中的单个文本字符串中?

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

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

1
2
3
Peter
Paul
Mary

有没有一种简单的方法可以把它变成一个单串的Peter, Paul, Mary


如果您使用的是SQL Server 2017或Azure,请参阅Mathieu Renda答案。

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

如果有一个名为STUDENTS的表

1
2
3
4
5
6
7
SubjectID       StudentName
----------      -------------
1               Mary
1               John
1               Sam
2               Alaina
2               Edward

我期望的结果是:

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

我用了以下的T-SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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跳过第一个逗号,则可以以更紧凑的方式执行相同的操作,这样就不需要执行子查询:

1
2
3
4
5
6
7
8
9
10
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


This answer may return unexpected results when an ORDER BY clause is present. For consistent results, use one of the FOR XML PATH methods detailed in other answers.

使用COALESCE

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

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

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

1)不需要用空字符串值初始化@Names

2)无需在末端剥离额外的分离器。

  • 如果一行的名称值为空(如果有空,则该空将使该行之后的@Names为空,下一行将重新开始为空字符串),则上述解决方案将给出错误的结果。两种解决方案中的一种很容易修复:
1
2
3
4
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL

或:

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

根据您想要的行为(第一个选项只是过滤掉空值,第二个选项使用标记消息[将"n/a"替换为适合您的内容]将它们保留在列表中)。


One method not yet shown via the XML data() command in MS SQL Server is:

Assume table called NameList with one column called FName,

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

返回:

1
"Peter, Paul, Mary,"

只能处理多余的逗号。

编辑:根据@nreligh的注释,可以使用以下方法删除尾随逗号。假定表名和列名相同:

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


SQL Server 2017+和SQL Azure:string_agg

从下一个版本的SQL Server开始,我们最终可以跨行连接,而无需借助任何变量或XML开关。

字符串标记(Transact-SQL)

不分组

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

分组:

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

分组和子排序

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


在SQL Server 2005中

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

在SQL Server 2016中

可以使用for json语法

1
2
3
4
5
6
7
8
9
SELECT per.ID,
Emails = JSON_VALUE(
   REPLACE(
     (SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
    ,'
<div class="suo-content">[collapse title=""]<ul><li>很好地使用了stuff函数来消除前两个字符。</li><li>我最喜欢这个解决方案,因为我可以通过附加"as<label>"在选择列表中很容易地使用它。我不知道如何用@ritesh的解决方案来实现这一点。</li><li>这比公认的答案更好,因为此选项还处理不转义的XML保留字符,如<wyn><</wyn>、<wyn>></wyn>、<wyn>&</wyn>等,<wyn>FOR XML PATH('
')</wyn>将自动转义。</li><li>这是一个很棒的回答,因为它解决了这个问题,并提供了在不同版本的SQL中进行操作的最佳方式,现在我希望我可以使用2017/Azure</li></ul>[/collapse]</div><hr><P>在MySQL中有一个函数group_concat(),它允许您连接多行中的值。例子:</P>[cc lang="sql"]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


使用Coalesce-从这里了解更多信息

举个例子:

102

103

104

然后在SQL Server中编写以下代码,

1
2
3
4
5
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

输出将是:

1
102,103,104


Postgres数组太棒了。例子:

创建一些测试数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

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

1
2
3
4
5
test=# SELECT array_agg(name) FROM names;
 array_agg    
-------------------
 {Peter,Paul,Mary}
(1 ROW)

将数组转换为逗号分隔的字符串:

1
2
3
4
5
test=# SELECT array_to_string(array_agg(name), ', ') FROM names;
 array_to_string
-------------------
 Peter, Paul, Mary
(1 ROW)

完成

因为PostgreSQL 9.0更简单。


Oracle11g版本2支持listagg函数。此处为文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
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个字符。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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


我在家里没有访问SQL Server的权限,所以我猜这里的语法是这样的,但或多或少是这样的:

1
2
3
4
DECLARE @names VARCHAR(500)

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


您需要创建一个变量来保存您的最终结果并选择它,就像这样。

最简单的解决方案

1
2
3
4
5
6
DECLARE @CHAR VARCHAR(MAX);

SELECT @CHAR = COALESCE(@CHAR + ', ' + [COLUMN], [COLUMN])
FROM [TABLE];

PRINT @CHAR;

建议使用递归CTE解决方案,但未提供代码。下面的代码是递归CTE的一个例子——请注意,尽管结果与问题匹配,但数据与给定的描述并不完全匹配,因为我假定您确实希望在一组行上执行此操作,而不是在表中的所有行上执行此操作。将其更改为与表中的所有行匹配,留给读者作为练习。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
;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


从PostgreSQL 9.0开始,这非常简单:

1
2
SELECT string_agg(name, ',')
FROM names;

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


在SQL Server VNext中,这将与string_agg函数一起内置,请阅读以下内容:https://msdn.microsoft.com/en-us/library/mt790580.aspx


一个现成的解决方案,没有额外的逗号:

1
2
3
SELECT SUBSTRING(
        (SELECT ', '+Name AS 'data()' FROM Names FOR xml path(''))
       ,3, 255) AS"MyList"

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

(迪瓦卡和詹斯弗兰森提供了很好的答案,但需要改进。)


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

1
2
3
REPLACE(
        (SELECT FName AS 'data()'  FROM NameList  FOR xml path(''))
         , ' ', ', ')


1
SELECT STUFF((SELECT ', ' + name FROM [TABLE] FOR XML PATH('')), 1, 2, '')

这是一个示例:

1
2
3
4
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


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

这会在开头加上离群的逗号。

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

在select子句中,也可以使用xml路径作为相关的子查询(但我必须等到回到工作岗位,因为Google在家里不做工作:—)


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

下面是一个使用SQL Server"Information_schema.columns"表的示例。通过使用此解决方案,不需要创建表或添加数据。此示例为数据库中的所有表创建一个以逗号分隔的列名列表。

1
2
3
4
5
6
7
8
9
10
11
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
2
3
4
5
6
7
DECLARE @names VARCHAR(MAX)
SET @names = ''

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

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


对于Oracle DBS,请参见以下问题:在Oracle中,如何将多行连接到一行而不创建存储过程?

最好的答案似乎是由@emmanuel使用内置的listagg()函数提供的,该函数在Oracle11g版本2及更高版本中提供。

1
2
3
4
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-technologies.php,wm_concat()函数也是一个选项。它看起来很稳定,但Oracle明确建议不要将它用于任何应用程序SQL,因此请自行承担风险。

除此之外,您还必须编写自己的函数;上面的Oracle文档提供了如何编写函数的指南。


这个答案需要服务器中的一些特权才能工作。

装配是一个很好的选择。有很多网站可以解释如何创建它。我认为解释得很清楚的是这个

如果需要,我已经创建了程序集,可以在此处下载dll。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

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

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

希望它有帮助!!!!


为了避免空值,可以使用concat()。

1
2
3
4
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
SELECT @names


MySQL完整示例:

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

结果:

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

表设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);

查询:

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

我通常使用如下的select来连接SQL Server中的字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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

如果您想处理空值,可以通过添加一个WHERE子句来实现,或者在第一个子句周围添加另一个合并。

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

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


这也很有用

1
2
3
4
5
6
7
8
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

收益率

1
Peter,Paul,Mary


--SQL Server 2005+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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)
;

以下是实现这一目标的完整解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
-- 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)

我没有做过任何性能分析,因为我的列表少于10个项目,但是我在浏览了30个奇怪的答案后感到惊讶,我仍然对已经给出的类似答案有一个扭曲,类似于对单个组列表使用coalesce,甚至不必设置我的变量(默认为空),它假定我的源数据中的所有条目。ATA表非空:

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

我确信内部合并使用相同的思想。希望女士不要在我身上改变这一点。


这种方法只适用于Teradata aster数据库,因为它使用了它的npath函数。

我们还有桌上的学生

1
2
3
4
5
6
7
SubjectID       StudentName
----------      -------------
1               Mary
1               John
1               Sam
2               Alaina
2               Edward

然后,对于npath,它只是一个选择:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
  )
);

结果:

1
2
3
4
SubjectID       StudentName
----------      -------------
1               [John, Mary, Sam]
2               [Alaina, Edward]

这个怎么样?

1
   ISNULL(SUBSTRING(REPLACE((SELECT ',' FName AS 'data()' FROM NameList FOR xml path('')), ' ,',', '), 2, 300), '') 'MyList'

其中"300"可以是任意宽度,考虑到您认为将显示的最大项目数。


在SQL Server中可以这样做的一种方法是将表内容返回为XML(对于XML raw),将结果转换为字符串,然后用","替换标记。


通过递归查询,可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 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

在Oracle中还有两种方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    CREATE TABLE name
    (first_name varchar2(30));

    INSERT INTO name VALUES ('Peter');
    INSERT INTO name VALUES ('Paul');
    INSERT INTO name VALUES ('Mary');

    Solution 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

    Soution 2:
    SELECT  rtrim(xmlagg (xmlelement (e, first_name || ',')).extract ('//text()'), ',') first_name FROM name
    o/p=> Peter,Paul,Mary

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

1
2
3
4
SELECT t1.id,
        GROUP_CONCAT(t1.id) ids
 FROM TABLE t1 JOIN TABLE t2 ON (t1.id = t2.id)
 GROUP BY t1.id


下面是使用"basic loop"和"rownum"实现给定场景的简单pl/sql过程。

表定义

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

让我们在这个表中插入值

1
2
3
INSERT INTO NAMES VALUES('PETER');
INSERT INTO NAMES VALUES('PAUL');
INSERT INTO NAMES VALUES('MARY');

程序从这里开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;

结果

1
PETER,PAUL,MARY


桌上型非常简单。假设您的表名为Students,它有列name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

此示例在MS SQL Server 2008 R2中测试


@用户1460901可以尝试如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
WITH cte_base AS (
    SELECT CustomerCode, CustomerName,
    CASE WHEN Typez = 'Breakfast' THEN Items ELSE NULL END AS 'BREAKFAST'
    , CASE WHEN Typez = 'Lunch' THEN Items ELSE NULL END AS 'LUNCH'
    FROM #Customer
    )
    SELECT DISTINCT CustomerCode, CustomerName,
    SUBSTRING(
    (  
        SELECT ','+BREAKFAST AS [text()]
        FROM cte_base b1
        WHERE b1.CustomerCode = b2.CustomerCode AND b1.CustomerName = b2.CustomerName
        ORDER BY b1.BREAKFAST
        FOR XML PATH('')
        ), 2, 1000
    ) [BREAKFAST],
    SUBSTRING(
    (  
        SELECT ','+LUNCH AS [text()]
        FROM cte_base b1
        WHERE b1.CustomerCode = b2.CustomerCode AND b1.CustomerName = b2.CustomerName
        ORDER BY b1.LUNCH
        FOR XML PATH('')
        ), 2, 1000
    ) [LUNCH]
    FROM cte_base b2

1
2
3
4
5
6
7
8
9
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

取决于您的数据库供应商。mysql有concat-ws。MS SQL Server希望您在客户端应用程序中执行此操作。

更新:您也可以在一个外部过程或UDF中完成,可能是通过使用一个光标或调用CLR代码。


1
2
3
   DECLARE @phone VARCHAR(MAX)=''
   SELECT @phone=@phone + mobileno +',' FROM  members
   SELECT @phone