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

Peter
Paul
Mary

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

有帮助吗?

解决方案

如果您使用的是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

其他提示

这个答案可能会返回 意想不到的结果 为了获得一致的结果,请使用其他答案中详细介绍的 FOR XML PATH 方法之一。

使用 COALESCE:

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

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

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

1)无需初始化 @Names 具有空字符串值。

2) 无需在末端剥去多余的分隔符。

  • 如果一行有一个,上面的解决方案将给出不正确的结果 无效的 名称值(如果有 无效的, , 这 无效的 将使 @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

根据您想要的行为(第一个选项只是过滤 无效的退出,第二个选项将它们保留在列表中并带有标记消息[将“N/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 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;

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功能

在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

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

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起,更加轻松

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

在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

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

DECLARE @names VARCHAR(500)

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

建议使用递归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

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

最简单的解决方案

DECLARE @char VARCHAR(MAX);

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

PRINT @char;

从PostgreSQL 9.0开始,这很简单:

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

使用XML帮助我用逗号分隔行。对于额外的逗号,我们可以使用SQL <!>服务器的替换功能。使用AS'data()'代替添加逗号,将使用空格连接行,稍后可以用逗号替换为下面的语法。

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

一个随时可用的解决方案,没有额外的逗号:

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

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

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

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
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 <!>”的示例;表。通过使用此解决方案,无需创建表或添加数据。此示例为数据库中的所有表创建逗号分隔的列名列表。

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 

对于Oracle DB,请参阅此问题:如何在不创建存储过程的情况下将多行连接成一个?

最佳答案似乎是@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文档提供了如何执行此操作的指南。

要避免空值,可以使用CONCAT()

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

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

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

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

这也很有用

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

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

再次,我们有表学生

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]
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top