Рассмотрим таблицу базы данных, содержащую имена с тремя строками:
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 (''), TYPE
).value('text()[1]','nvarchar(max)') [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 (''), TYPE
).value('text()[1]','nvarchar(max)'), 2, 1000) [Students]
FROM dbo.Students ST2
<
или &
. См. Комментарий @ BenHinman.
FOR XML PATH ('')
. Это означает, что его нельзя считать надежным, поскольку любой патч или обновление может изменить его работу. Он в основном полагается на устаревшую функцию.
FOR XML
предназначен для генерации XML, а не для объединения произвольных строк. Вот почему он экранирует &
, <
и >
в коды объектов XML (&
, <
, >
). Я предполагаю, что он также будет экранировать "
и '
в "
и '
в атрибутах. Это не GROUP_CONCAT()
, string_agg()
, array_agg()
, listagg()
и т. Д., Даже если вы можете заставить его это сделать. Мы должны тратить время на то, чтобы требовать от Microsoft реализации надлежащей функции.
string_agg
в v.Next., и все это может исчезнуть.
Этот ответ может вернуть неожиданные результаты для согласованных результатов используйте один из методов 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» на то, что вам подходит]) .
SQL Server 2017+ и SQL Azure: STRING_AGG
Начиная со следующей версии SQL Server, мы, наконец, можем объединять строки без необходимости прибегать к какой-либо переменной или XML-колдовству.
Без группировки
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;
Используйте 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
Declare @Numbers AS Nvarchar(MAX)
, и он работал нормально. Не могли бы вы объяснить, почему вы не рекомендуете его использовать?
Вам нужно создать переменную, которая будет содержать ваш окончательный результат, и выбрать его, вот так.
Самое простое решение
DECLARE @char VARCHAR(MAX);
SELECT @char = COALESCE(@char + ', ' + [column], [column])
FROM [table];
PRINT @char;
В SQL Server vNext это будет встроено с функцией STRING_AGG. Подробнее об этом читайте в STRING_AGG (Transact-SQL) .
Массивы PostgreSQL - это круто. Пример:
Создайте некоторые тестовые данные:
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 это стало еще проще, цитируя удаленный ответ «лошадь без имени»:
select string_agg(name, ',')
from names;
select array_to_string(array_agg(name||'('||id||')'
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 символов.
LISTAGG
отлично работает! Просто прочтите документ, ссылка на который есть здесь. wm_concat
удален из версии 12c и новее.
У меня нет доступа к SQL Server дома, поэтому я предполагаю здесь синтаксис, но он более или менее:
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
SELECT @names = @names + ISNULL(' ' + Name, '')
В 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
CHAR
, вам необходимо преобразовать его, например через GROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',')
2) если у вас много значений, вам следует увеличить group_concat_max_len
, как написано в stackoverflow.com / a / 1278210/1498405
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
В 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 2017 или более поздних версиях вы можете использовать функцию STRING_AGG() для создания значения, разделенные запятыми. Пожалуйста, взгляните ниже на один пример.
SELECT
VendorId, STRING_AGG(FirstName,',') UsersName
FROM Users
WHERE VendorId != 9
GROUP BY VendorId
Для баз данных 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 выше есть руководство о том, как это сделать.
Один метод, еще не показанный с помощью команды XML
data()
в 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
+ ', '
, он все равно добавляет один пробел между каждым сцепленным элементом.
SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
В 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 а>
<
, >
, &
и т. Д., Которые FOR XML PATH('')
автоматически экранируются.
Было предложено рекурсивное 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
name
в строку, разделенную запятыми для 4 группы из id
s. На первый взгляд, мне кажется, что это больше работы, чем то, что делают большинство других решений для SQL Server.
Использование XML помогло мне разделить строки запятыми. Для дополнительной запятой мы можем использовать функцию замены 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"
Пустой список приведет к значению NULL. Обычно вы вставляете список в столбец таблицы или программную переменную: настройте максимальную длину 255 в соответствии с вашими потребностями.
(Дивакар и Йенс Франдсен дали хорошие ответы, но нуждаются в улучшении.)
', '
на ','
, если вам не нужно дополнительное пространство.
Что касается других ответов, человек, читающий ответ, должен знать о конкретной таблице доменов, например о транспортном средстве или студенте. Таблица должна быть создана и заполнена данными для тестирования решения.
Ниже приведен пример, в котором используется таблица 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
Это сработало для меня ( SQL Server 2016 ):
SELECT CarNamesString = STUFF((
SELECT ',' + [Name]
FROM tbl_cars
FOR XML PATH('')
), 1, 1, '')
Вот источник: https://www. mytecbits.com/
И решение для MySQL (поскольку эта страница отображается в Google для MySQL):
SELECT [Name],
GROUP_CONCAT(DISTINCT [Name] SEPARATOR ',')
FROM tbl_cars
Чтобы избежать нулевых значений, вы можете использовать CONCAT ()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
Полный пример 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
GROUP BY
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)
Это помещает случайную запятую в начало.
Однако, если вам нужны другие столбцы или дочерняя таблица в CSV, вам нужно обернуть это в скалярное определяемое пользователем поле (UDF).
Вы также можете использовать путь XML в качестве коррелированного подзапроса в предложении SELECT (но мне придется подождать, пока я вернусь к работе, потому что Google не выполняет работу дома :-)
Мне очень понравилась элегантность ответа Даны и просто хотел закончить.
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names
, тогда вам не нужно будет усекать его впоследствии.
Этот ответ потребует некоторых привилегий на сервере для работы.
Сборки - хороший вариант для вас. Есть много сайтов, которые объясняют, как его создать. Я думаю, что это очень хорошо объяснено, это .
Если хотите, я уже создал сборку, и можно загрузить файл DLL здесь.
После того, как вы скачали его, вам нужно будет запустить следующий скрипт на вашем SQL Server:
EXEC sp_configure 'show advanced options', 1
RECONFIGURE;
EXEC sp_configure 'clr strict security', 1;
RECONFIGURE;
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
Начиная с SQL Server 2017, можно использовать STRING_AGG.
Обычно я использую такой выбор для объединения строк в 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
В верхней части Ответ Криса Шаффера:
Если ваши данные могут повторяться, например
Tom
Ali
John
Ali
Tom
Mike
Вместо того, чтобы иметь Tom,Ali,John,Ali,Tom,Mike
Вы можете использовать DISTINCT, чтобы избежать дублирования и получить Tom,Ali,John,Mike
:
DECLARE @Names VARCHAR(8000)
SELECT DISTINCT @Names = COALESCE(@Names + ',', '') + Name
FROM People
WHERE Name IS NOT NULL
SELECT @Names
В Oracle это wm_concat
. Я считаю, что эта функция доступна в версии 10g и выше.
Похожие вопросы
Новые вопросы
sql
Язык структурированных запросов (SQL) - это язык запросов к базам данных. Вопросы должны включать примеры кода, структуру таблицы, примеры данных и тег для используемой реализации СУБД (например, MySQL, PostgreSQL, Oracle, MS SQL Server, IBM DB2 и т. Д.). Если ваш вопрос относится исключительно к конкретной СУБД (использует определенные расширения / функции), используйте вместо этого тег этой СУБД. Ответы на вопросы, помеченные SQL, должны использовать стандарт ISO / IEC SQL.