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

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

Peter
Paul
Mary

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

30 ответов


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

этот ответ может возвращать неожиданные результаты при наличии предложения ORDER BY. Для получения согласованных результатов используйте один из методов пути for XML, описанных в других ответах.

использовать COALESCE:

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

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

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

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

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

  • решение выше даст неправильные результаты, если строка имеет NULL Значение Имени (если есть NULL, the 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

в зависимости от того, какое поведение вы хотите (первый вариант просто фильтров NULLs out, второй вариант сохраняет их в списке с сообщением маркера [заменить 'N / A' на то, что подходит для вас]).


один метод еще не показан через XML data() команда в MS SQL Server:

предположим, что таблица называется NameList с одним столбцом с именем FName,

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

возвращает:

"Peter, Paul, Mary, "

необходимо иметь дело только с дополнительной запятой.

Edit: как принято из комментария @NReilingh, вы можете использовать следующий метод для удаления конечной запятой. Предполагая те же имена таблиц и столбцов:

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

на 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

можно использовать для синтаксиса 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

the '"},{"_":"' безопасно, потому что если Вы данные содержат '"},{"_":"', он будет сбежали в "},{\"_\":\"

вы можете заменить ', ' с любым разделителем строк


и в SQL Server 2017 база данных SQL Azure

можно использовать функция STRING_AGG


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;

в 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

использовать объединиться - подробнее здесь

для примера:

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

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


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

сделал

начиная с PostgreSQL 9.0 это еще проще.


в 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

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

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

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


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

Простое Решение

DECLARE @char VARCHAR(MAX);

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

PRINT @char;

в SQL Server vNext это будет встроено с помощью функции STRING_AGG, подробнее об этом читайте здесь: https://msdn.microsoft.com/en-us/library/mt790580.aspx


использование XML помогло мне в получении строк, разделенных запятыми. Для дополнительной запятой мы можем использовать функцию replace SQL Server. Вместо добавления запятой, использование 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"

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

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


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

это ставит запятую в начале.

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

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


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

Ниже приведен пример использования SQL Server " Information_Schema.Столбцы " таблица. С помощью этого решения не требуется создавать таблицы или добавлять данные. В этом примере создается разделенный запятыми список имен столбцов для всех таблиц в базе данных.

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 

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

для Oracle DBs см. Этот вопрос:Как можно объединить несколько строк в одну в 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 выше есть руководство о том, как это сделать.


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

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

этот ответ потребует некоторых привилегий на сервере работать.

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

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

после того как вы скачали его, необходимо выполнить следующий скрипт в SQL Сервер:

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

надеюсь, что это помогает!!!


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

Мне очень понравилась элегантность Дане. Просто хотел, чтобы все было закончено.

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

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

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

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

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

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

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

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

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


--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)
;