Как конкатенировать текст из нескольких строк в одну текстовую строку на SQL-сервере?

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

Peter
Paul
Mary

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

Ответ 1

Если вы находитесь на 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

Ответ 2

Этот ответ может возвращать неожиданные результаты. Для получения согласованных результатов используйте один из методов 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, вторая опция сохраняет их в списке с сообщением-маркером [замените 'N/A' тем, что вам подходит]).

Ответ 3

Один метод, еще не показанный с помощью команды 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

Ответ 4

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;

Ответ 5

В 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

Ответ 6

В 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

Используйте 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

Ответ 8

Массивы 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

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 символов.

Ответ 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

Ответ 11

У меня нет доступа к SQL Server дома, поэтому я думаю о синтаксисе здесь, но это более или менее:

DECLARE @names VARCHAR(500)

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

Ответ 12

Было предложено рекурсивное решение 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

Вам нужно создать переменную, которая будет содержать конечный результат и выбрать его, например.

Самое простое решение

DECLARE @char VARCHAR(MAX);

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

PRINT @char;

Ответ 14

Начиная с PostgreSQL 9.0, это довольно просто:

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

В версиях до 9.0 array_agg() можно использовать, как показано hgmnz

Ответ 16

Использование XML помогло мне получить строки, разделенные запятыми. Для дополнительной запятой мы можем использовать функцию замены SQL Server. Вместо добавления запятой использование AS 'data()' будет конкатенировать строки с пробелами, которые позже могут быть заменены запятыми в качестве синтаксиса, написанного ниже.

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

Ответ 17

Готовое к использованию решение без дополнительных запятых:

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

Пустой список приведет к значению NULL. Обычно вы вставляете список в столбец таблицы или программную переменную: отрегулируйте максимальную длину 255 до ваших потребностей.

(Дивакар и Йенс Франдсен дали хорошие ответы, но нуждаются в улучшении.)

Ответ 18

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

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

Вначале ставится паразитная запятая.

Однако, если вам нужны другие столбцы или CSV для дочерней таблицы, вам необходимо обернуть это в поле скалярного пользователя (UDF).

Вы также можете использовать XML-путь как коррелированный подзапрос в предложении SELECT (но мне придется подождать, пока я вернусь на работу, потому что Google не делает работу дома: -)

Ответ 20

С другими ответами человек, читающий ответ, должен знать определенную таблицу домена, такую ​​как автомобиль или ученик. Таблица должна быть создана и заполнена данными для проверки решения.

Ниже приведен пример использования таблицы 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

Мне действительно понравилась элегантность ответа Даны. Просто хотел сделать это.

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

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

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

Ответ 22

Для Oracle DB см. этот вопрос: Как несколько строк могут быть объединены в один в Oracle без создания хранимой процедуры?

Лучший ответ, по-видимому, принадлежит @Emmanuel, используя встроенную функцию LISTAGG(), доступную в Oracle 11g Release 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 выше содержит руководство о том, как это сделать.

Ответ 23

Чтобы избежать нулевых значений, вы можете использовать CONCAT()

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

Ответ 24

Этот ответ потребует некоторой привилегии на работе сервера.

Ассемблирование - хороший вариант для вас. Есть много сайтов, которые объясняют, как его создать. Я думаю, это очень хорошо объясняется, это один

Если вы хотите, я уже создал сборку, и здесь можно загрузить DLL .

После его загрузки вам нужно будет запустить следующий script на вашем SQL Server:

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

Надеюсь, это поможет!!!

Ответ 25

Обычно я использую 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

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

Ответ 28

В Oracle это wm_concat. Я считаю, что эта функция доступна в 10g release и выше.

Ответ 29

Это тоже может быть полезно

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

Ответ 30

Этот метод применяется к базе данных 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]