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

StackOverflow https://stackoverflow.com/questions/194852

Вопрос

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

Peter
Paul
Mary

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

Это было полезно?

Решение

Если вы используете 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

Другие советы

Этот ответ может вернуться неожиданные результаты Для получения согласованных результатов используйте один из методов 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

В зависимости от того, какое поведение вы хотите (первый вариант просто фильтрует НУЛЕВОЙЕсли нет, второй вариант сохраняет их в списке с маркерным сообщением [замените «Н/Д» на то, что вам подходит]).

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

В SQL-сервер 2005

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

В SQL-сервере 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   abc@gmail.com
2   NULL
3   def@gmail.com, xyz@gmail.com

Это будет работать, даже если ваши данные содержат недопустимые символы XML.

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

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


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

Вы можете использовать новый Функция 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

Использовать ОБЪЕДИНЯТЬСЯ - Узнайте больше здесь

Например:

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

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

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

В 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 & nbsp; Вместо добавления запятой использование 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 в соответствии с вашими потребностями.

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

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

Вы также можете использовать путь XML в качестве коррелированного подзапроса в предложении SELECT (но мне придется подождать, пока я не вернусь к работе, потому что 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, посмотрите этот вопрос: Как можно объединить несколько строк в одну в 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

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

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

Если вы хотите иметь дело с пустыми значениями, вы можете сделать это, добавив предложение 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

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

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

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