Как объединить текст из нескольких строк в одну текстовую строку на сервере SQL? - PullRequest
1709 голосов
/ 12 октября 2008

Рассмотрим таблицу базы данных, содержащую имена, с тремя строками:

Peter
Paul
Mary

Есть ли простой способ превратить это в одну строку Peter, Paul, Mary?

Ответы [ 45 ]

1261 голосов
/ 13 февраля 2009

Если вы используете SQL Server 2017 или Azure, см. Матье Ренда, ответ .

У меня была похожая проблема, когда я пытался соединить две таблицы с отношениями один-ко-многим. В 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
952 голосов
/ 12 октября 2008

Этот ответ может возвращать неожиданные результаты Для получения согласованных результатов используйте один из методов FOR XML PATH, подробно описанных в других ответах.

Использование COALESCE:

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

Просто некоторое объяснение (поскольку этот ответ, кажется, получает относительно регулярные взгляды):

  • Coalesce на самом деле просто полезный чит, который выполняет две вещи:

1) Нет необходимости инициализировать @Names пустым строковым значением.

2) Нет необходимости снимать дополнительный разделитель в конце.

  • Приведенное выше решение даст неверные результаты, если строка имеет NULL Имя значения (если есть 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' тем, что подходит вам ]).

336 голосов
/ 06 апреля 2011

Один метод, еще не показанный с помощью команды 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
293 голосов
/ 14 марта 2017

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;
270 голосов
/ 09 сентября 2010

В 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 база данных SQL Azure

Вы можете использовать новую функцию STRING_AGG

111 голосов
/ 12 октября 2008

В 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
54 голосов
/ 05 апреля 2016

Использование COALESCE - Подробнее здесь

Например:

102

103

104

Затем напишите приведенный ниже код на сервере sql,

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
47 голосов
/ 10 августа 2012

Массивы 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, еще проще .

45 голосов
/ 08 марта 2012

Oracle 11g Release 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 символов.

33 голосов
/ 06 июля 2011

В 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
...