Рассмотрим таблицу базы данных, содержащую имена с тремя строками:
Peter
Paul
Mary
Есть ли простой способ превратить это в одну строку Peter, Paul, Mary?
Для MySQL проверьте Group_Concat из этот ответ
Я бы хотел, чтобы следующая версия SQL Server предлагала новую функцию для элегантного решения конкатенации многострочных строк без глупости FOR XML PATH.
пошаговое руководство для описанных выше ответов: попробуйте эту статью: [sqlmatters.com/Articles/…]
Не SQL, но если это только один раз, вы можете вставить список в этот инструмент в браузере convert.town/column-to-comma-separated-list
В Oracle вы можете использовать LISTAGG (COLUMN_NAME) из 11g r2, до этого существует неподдерживаемая функция WM_CONCAT (COLUMN_NAME), которая делает то же самое.
это решение CLR, которое можно подключить напрямую, похоже на мой sql GROUP_CONCAT, здесь
Вы можете использовать функцию конкатенации строк. Я не могу добавить ответ (так как он заблокирован), поэтому добавляю ответ здесь: DECLARE @big_string varchar(max) = ''; SELECT @big_string += x.s + ',' FROM (VALUES ('string1'), ('string2'), ('string3')) AS x(s);. Теперь покажите результат: SELECT @big_string;. Это так просто.


Один из способов сделать это в SQL Server - это вернуть содержимое таблицы как XML (для необработанного XML), преобразовать результат в строку и затем заменить теги на «,».
В 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
Работает в основном. Две вещи, которые следует учитывать: 1) если ваш столбец не CHAR, вам необходимо преобразовать его, например через GROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',') 2) если у вас много значений, вы должны увеличить group_concat_max_len, как написано в stackoverflow.com/a/1278210/1498405
У меня нет доступа к SQL Server дома, поэтому я предполагаю здесь синтаксис, но он более или менее:
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
Вам нужно будет инициализировать @names во что-то ненулевое, иначе вы получите NULL; вам также нужно будет обработать разделитель (включая ненужный)
единственная проблема с этим подходом (который я использую все время) заключается в том, что вы не можете его встроить
Чтобы избавиться от начального пробела, измените запрос на SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names.
Кроме того, вы должны проверить, что Имя не равно нулю, вы можете сделать это, выполнив: SELECT @names = @names + ISNULL(' ' + Name, '')
This answer may return unexpected results For consistent results, use one of the FOR XML PATH methods detailed in other answers.
Используйте COALESCE:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
Просто некоторое объяснение (поскольку этот ответ, кажется, получает относительно регулярные просмотры):
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
В зависимости от того, какое поведение вы хотите (первый вариант просто отфильтровывает НУЛЕВОЙ, второй вариант сохраняет их в списке с помощью маркерного сообщения [замените «N / A» на то, что вам подходит]).
Чтобы было ясно, coalesce не имеет ничего общего с созданием списка, он просто гарантирует, что значения NULL не включены.
@Graeme Perrow Он не исключает значения NULL (для этого требуется WHERE - это будет потерять результаты, если одно из входных значений NULL), и требуется в этом подходе, потому что: NULL + не-NULL -> NULL и не-NULL + NULL -> NULL; также @Name по умолчанию имеет значение NULL, и, фактически, это свойство используется здесь как неявный дозорный, чтобы определить, следует ли добавлять ',' или нет.
Два способа исправить это, чтобы изящно игнорировать NULL: либо SELECT @Names = @Names + ', ' + Name FROM People WHERE Name IS NOT NULL, либо SELECT @Names = COALESCE(@Names + ', ' + Name, @Names) FROM People.
@krubo Нет, проблема в том, что @Names = @Names + *anything* будет нулевым, потому что @Names имеет нулевое значение при объявлении. COALESCE разрешает и то и другое нулевое значение Nameи начальное нулевое значение @Names.
Это не работает для типов данных varchar и ntext, поскольку они оба несовместимы с оператором добавления.
@XpiritO - вы имеете в виду текст и ntext? Варчар совместим; текст и ntext могут быть преобразованы (если вы используете SQL 2005, преобразуйте их в VARCHAR (MAX) / NVARCHAR (MAX), и вы ничего не потеряете; в противном случае вам все равно придется принять возможность усечения, поскольку вы можете ' t объявить переменную text / ntext).
Обратите внимание, что этот метод конкатенации зависит от того, что SQL Server выполняет запрос с определенным планом. Меня поймали с использованием этого метода (с добавлением ORDER BY). Когда он имел дело с небольшим количеством строк, он работал нормально, но с большим количеством данных SQL Server выбрал другой план, в результате которого был выбран первый элемент без какой-либо конкатенации. См. эта статья Анит Сен.
Этот метод нельзя использовать в качестве подзапроса в списке выбора или в предложении where, поскольку он использует переменную tSQL. В таких случаях вы можете использовать методы, предлагаемые @Ritesh
Это решение не будет работать в представлении, в отличие от решения Ритеша.
Это самый простой способ создания динамического SQL, если вы хотите применить одну и ту же команду ко многим объектам. Кейд Ру использует его для переименовать схемы, а я использую его для переименовать тестовые классы tSQLt. Спасибо, Крис!
Пробовал это, сначала понравилось, потому что это просто и блестяще. Но, как и любой другой повторяющийся процесс concat с varchar(), ввод-вывод и ЦП в конечном итоге привязываются. Для 20 000 GUID потребовалось 2 минуты, чтобы объединить их, тогда как использование for xml path('') заняло менее 1 секунды.
Как добавить к нему Distinct, если я хочу добавить только отдельные значения?
@confusedMind - вот так: DECLARE @Names VARCHAR(8000) SELECT @Names = COALESCE(@Names + ', ', '') + Name FROM ( SELECT DISTINCT Name FROM People )
Это ненадежный метод конкатенации. Он не поддерживается и не должен использоваться (согласно Microsoft, например, support.microsoft.com/en-us/kb/287515, connect.microsoft.com/SQLServer/Feedback/Details/704389). Это может измениться без предупреждения. Используйте технику XML PATH, обсуждаемую в stackoverflow.com/questions/5031204/…, я написал больше здесь: marc.durdin.net/2015/07/…
Основная проблема этого подхода заключается в том, что он усекает все значения, превышающие 8000.
Ваше объяснение на самом деле не объясняет, что это делает. Он полагается на SQL Server, выполняющий выражение для каждой строки. Было бы здорово, если бы на это можно было положиться. Но см. Другие комментарии, указывающие на то, что SQL Server не требуется для этого типа запроса.
Я нашел здесь тот же самый точный пример кода (опубликованный 6 месяцев назад): codeproject.com/Tips/334400/… Я думаю, что ссылка на автора должна быть обязательной.
Штопать. Я поддержал этот ответ, но теперь, когда я знаю, что это неправильный способ выполнения конкатенации, потому что он может вас обмануть, поскольку это неподдерживаемая функция, я больше не могу удалить свой голос :-( Теперь я ввел многих в заблуждение.
Решение работает нормально; однако он обрезает текст до длины 65576
Нельзя использовать ORDER BY для этого, не говоря уже о том, что это ужасный совет по причинам, опубликованным другими!
Спасибо за ваш вклад, это спасло мне день. Пожалуйста, дайте мне знать, сработает ли это, если строк больше сотни или больше? @ Мартин Смит
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)
Это помещает случайную запятую в начало.
Однако, если вам нужны другие столбцы или дочерняя таблица в CSV, вам нужно обернуть это в скалярное определяемое пользователем поле (UDF).
Вы также можете использовать путь XML в качестве коррелированного подзапроса в предложении SELECT (но мне придется подождать, пока я вернусь к работе, потому что Google не выполняет работу дома :-)
Если вы используете 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
Я получаю сообщение об ошибке «Неправильный синтаксис рядом с ключевым словом« Для »» при работе с MS SQL Server 2008 R2
Отличное решение. Следующее может быть полезно, если вам нужно обрабатывать специальные символы, такие как символы HTML: Роб Фарли: обработка специальных символов с помощью FOR XML PATH ('').
Что делать, если нет «ID темы»
По-видимому, это не работает, если имена содержат символы XML, такие как < или &. См. Комментарий @ BenHinman.
Это хорошее решение. Я соединил с ним 20000 GUID менее чем за 1 секунду. for xml path('') работает намного лучше, чем любой подход типа cursor и / или переменной concat.
NB: этот метод основан на недокументированном поведении FOR XML PATH (''). Это означает, что его нельзя считать надежным, поскольку любой патч или обновление может изменить его работу. Он в основном полагается на устаревшую функцию.
@Bacon Bits - Не могли бы вы объяснить это поподробнее? Это невероятно широко используемый фрагмент кода.
@Whelkaholism Это был первый ответ Microsoft на этот запрос. Кажется, я больше не могу найти их исходное утверждение (как вы сказали, оно очень широко используется), но в течение многих лет после Server 2005 команда SQL Server утверждала, что FOR XML PATH ('') в сочетании с безымянными столбцами имеет неопределенное поведение (т. Е. Поведение не в спецификации дизайна). Даже в 2014 году вы не увидите, что FOR XML PATH ('') используется с безымянными столбцами в документации SQL Server. FOR XML PATH с безымянными колонками, да. FOR XML PATH ('') с именованными столбцами, да. Но это дает разные результаты.
@Whelkaholism Суть в том, что FOR XML предназначен для генерации XML, а не для объединения произвольных строк. Вот почему он преобразует &, < и > в коды объектов XML (&, <, >). Я предполагаю, что он также перейдет от " и ' к " и ' в атрибутах. Это нетGROUP_CONCAT(), string_agg(), array_agg(), listagg() и т.д., даже если вы можете заставить его это сделать. Мы, должен, проводим время, требуя от Microsoft реализации надлежащей функции.
Я должен использовать AS 'data ()' вместо AS [text ()].
@BaconBits Этот ответ использует лучший синтаксис, который лучше обрабатывает специальные символы, используя ключевое слово TYPE и извлекая содержимое с помощью функции value XML. Используя этот метод, вы даже можете использовать имена тегов и создавать правильно сформированный XML. Но не стоит бояться колонок без названий. Они официально поддерживается, по крайней мере, еще в SQL Server 2008.
Чтобы удалить начальную запятую, я обычно использую ROW_NUMBER (). Полный образец в ответе ниже. CASE ROW_NUMBER () OVER (ORDER BY stu.Name) WHEN 1 THEN '' ELSE ',' END + stu.Name
Хорошие новости: MS SQL Server добавит string_agg в v.Next. и все это может исчезнуть.
Добавлен улучшенный пример решения проблемы, на которую указывает @Sam, символы <,> и &, возвращаемые как escape-последовательности xml & lt ;, & gt; и & amp ;.
Вы можете использовать STUFF вместо SUBSTRING, чтобы удалить начальную запятую.
Это очень плохой ответ! Он не дает простого, работоспособного примера и не объясняет, какие части необходимы для его работы. Может ли кто-нибудь, кто понимает, сделать это лучше? Может ты, @ P5Coder?
@jpaugh почему я?
Я выбрал вас, потому что вы редактировали его раньше и, вероятно, понимаете это. Однако с тех пор я обнаружил, что Макс ответ выполняет эту роль, и оказывается, что используется тот же трюк SQL, хотя я сначала не распознал его из-за всего шума.
вы можете добавить в свой пример также эту часть. Выберите Main.SubjectID, CASE WHEN Main.Students Like '%,%' THEN Left (Main.Students, Len (Main.Students) -1) ELSE Main.Students END AS [Студенты]
Вам нужно использовать GROUP BY вместо DISTINCT, это должно дать вам идентичные результаты с возможно более высокой производительностью.
используя AS [text()], сделайте поле окруженным тегом <text> ... Я просто удаляю его, и все работает (SQL Server 2008)
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
вы можете использовать ДЛЯ синтаксиса 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.
'"},{"_":"' безопасен, потому что если ваши данные содержат '"},{"_":"',, он будет экранирован в "},{\"_\":\"
Вы можете заменить ', ' любым разделителем строк
Вы можете использовать новый STRING_AGG функция
Хорошее использование функции STUFF для удаления первых двух символов.
Мне больше всего нравится это решение, потому что я могу легко использовать его в списке выбора, добавив 'as <label>'. Я не уверен, как это сделать с помощью решения @Ritesh.
Это лучше, чем принятый ответ, потому что этот параметр также обрабатывает неэкранированные зарезервированные символы XML, такие как <, >, & и т. д., Которые FOR XML PATH('') будет автоматически экранировать.
Один метод, еще не показанный с помощью команды XMLdata() в 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
святое дерьмо, это потрясающе! При выполнении самостоятельно, как в вашем примере, результат форматируется как гиперссылка, при нажатии на которую (в SSMS) открывается новое окно, содержащее данные, но при использовании как части более крупного запроса оно просто отображается как строка. Это строка? или это xml, к которому мне нужно относиться по-другому в приложении, которое будет использовать эти данные?
Этот подход также экранирует символы XML, такие как <и>. Итак, ВЫБОР '<b>' + FName + '</b>' приведет к "& lt; b & gt; John & lt; / b & gt; & lt; b & gt; Paul ..."
Аккуратное решение. Я заметил, что даже когда я не добавляю + ', ', он все равно добавляет один пробел между каждым объединенным элементом.
Аккуратный! Есть идеи, как бороться с запятой в конце?
@Baodad Похоже, это часть сделки. Вы можете обойти эту проблему, заменив добавленный символ токена. Например, это идеальный список с разделителями-запятыми любой длины: SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
Ха-ха, но в этот момент вы можете просто не использовать data () в первую очередь, а просто выполнить один из приведенных выше примеров. Кажется, что data() - это просто сокращение от «разграничить это пространство», и если вам нужно что-то еще, это бесполезно - если только это не сказывается на производительности.
Ничего себе, на самом деле в моем тестировании с использованием data () и замены ПУТЬ более производительно, чем нет. Очень странно.
Если вы замените data () на text (), он, похоже, сгенерирует список без необходимости обрезать пробелы.
Приятно - спасибо. Чтобы убрать запятую, я просто ставлю запятую спереди, а затем беру SUBSTRING( (SELECT ','+FName AS 'data()' FROM NameList FOR XML PATH('')),2,100000), чтобы пропустить начальную.
Как бы вы это сгруппировали? скажем, у меня есть две таблицы people и roles, у человека может быть много ролей. Я хочу получить строку с разделителями-запятыми для каждой роли человека.
Использование XML помогло мне разделить строки запятыми. Для дополнительной запятой мы можем использовать функцию замены SQL Server. Вместо добавления запятой, использование AS 'data ()' объединит строки с пробелами, которые позже могут быть заменены запятыми в соответствии с синтаксисом, описанным ниже.
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
На мой взгляд, это лучший ответ. Использование объявленной переменной не подходит, когда вам нужно присоединиться к другой таблице, и это красиво и коротко. Хорошая работа.
это не работает, если в данных FName уже есть пробелы, например «Мое имя»
На самом деле это работает для меня на ms-sql 2016 Выберите REPLACE ((выберите Name AS 'data ()' from Brand Where Id IN (1,2,3,4) для пути xml ('')), '', ' , ') как allBrands
Как насчет этого:
ISNULL(SUBSTRING(REPLACE((select ',' FName as 'data()' from NameList for xml path('')), ' ,',', '), 2, 300), '') 'MyList'
Где «300» может быть любой ширины, учитывая максимальное количество элементов, которые, по вашему мнению, будут отображаться.
Если вам когда-нибудь придется заранее угадывать, сколько строк будет в ваших результатах, вы делаете это неправильно.
В Oracle это wm_concat. Я считаю, что эта функция доступна в Выпуск 10g и выше.
Обычно я использую такой выбор для объединения строк в 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
В 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
Я считаю, что это не работает, если значения содержат символы XML, такие как < или &.
Если вы хотите иметь дело с нулями, вы можете сделать это, добавив предложение where или добавив еще один COALESCE вокруг первого.
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
Мне очень понравилась элегантность Ответ даны. Просто хотел закончить.
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 + Name +', 'FROM Names'). Таким образом, последние два символа всегда будут ','.
В моем случае мне нужно было избавиться от запятой ведущий, поэтому измените запрос на SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names, тогда вам не придется его усекать впоследствии.
Готовое решение без лишних запятых:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
Пустой список приведет к значению NULL. Обычно вы вставляете список в столбец таблицы или программную переменную: настройте максимальную длину 255 в соответствии с вашими потребностями.
(Дивакар и Йенс Франдсен дали хорошие ответы, но нуждаются в улучшении.)
При использовании этого слова перед запятой ставится пробел :(
Просто замените ', ' на ',', если вам не нужно дополнительное пространство.
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 символов.
Для более старых версий Oracle идеально подходит wm_concat. Его использование объясняется в подарке по ссылке Алексом. Спасибо, Алекс!
LISTAGG работает отлично! Просто прочтите документ, ссылка на который есть здесь. wm_concat удален из версии 12c и новее.
Было предложено рекурсивное решение 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
Для ошеломленных: этот запрос вставляет 12 строк (3 столбца) во временную базовую таблицу, затем создает рекурсивное общее табличное выражение (rCTE), а затем выравнивает столбец name в строку, разделенную запятыми, для 4 группыid. На первый взгляд, мне кажется, что это больше работы, чем то, что делают большинство других решений для SQL Server.
@knb: не уверен, это похвала, осуждение или просто сюрприз. Базовая таблица создана, потому что мне нравится, что мои примеры действительно работают, она не имеет ничего общего с вопросом.
Массивы 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 это даже проще.
Если вам нужно более одного столбца, например, их идентификатор сотрудника в скобках, используйте оператор concat: select array_to_string(array_agg(name||'('||id||')'
Не применимо к sql-сервер, только к MySQL
В оракуле есть несколько способов,
create table name
(first_name varchar2(30));
insert into name values ('Peter');
insert into name values ('Paul');
insert into name values ('Mary');
Решение 1:
select substr(max(sys_connect_by_path (first_name, ',')),2) from (select rownum r, first_name from name ) n start with r=1 connect by prior r+1=r
o/p=> Peter,Paul,Mary
Решение 2:
select rtrim(xmlagg (xmlelement (e, first_name || ',')).extract ('//text()'), ',') first_name from name
o/p=> Peter,Paul,Mary
Для баз данных 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 выше есть руководство о том, как это сделать.
Это тоже может быть полезно
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
К сожалению, это поведение официально не поддерживается. MSDN говорит: «Если на переменную есть ссылка в списке выбора, ей должно быть присвоено скалярное значение, или оператор SELECT должен возвращать только одну строку». И есть люди, которые наблюдали проблемы: sqlmag.com/sql-server/multi-row-variable-assignment-and-orde r
Этот метод применим только к базе данных 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]
С типом ТАБЛИЦА это очень просто. Представьте, что ваша таблица называется Students и имеет столбец name.
declare @rowsCount INT
declare @i INT = 1
declare @names varchar(max) = ''
DECLARE @MyTable TABLE
(
Id int identity,
Name varchar(500)
)
insert into @MyTable select name from Students
set @rowsCount = (select COUNT(Id) from @MyTable)
while @i < @rowsCount
begin
set @names = @names + ', ' + (select name from @MyTable where Id = @i)
set @i = @i + 1
end
select @names
Этот пример протестирован в MS SQL Server 2008 R2.
Чтобы избежать нулевых значений, вы можете использовать CONCAT ()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
Было бы неплохо узнать, что Почему CONCAT работает. Ссылка на MSDN была бы хороша.
declare @phone varchar(max)=''
select @phone=@phone + mobileno +',' from members
select @phone
Почему не +', ' Как OP хотел, и вы не удаляете последний ';'. Я думаю, что этот ответ такой же, как и этот ответ;).
У меня была эта проблема, и я нашел ответ, но я хочу Concatenate с ';' поэтому я вставляю его сюда, последний элемент пуст
Когда вы публикуете здесь свой ответ, он должен быть связан с вопросом, и результатом вашего кода должен быть Null, потому что вы начинаете с @phone IS Null, а добавление к Null будет Null в SQL Server, я думаю, вы забыли что-то вроде добавления = '' после вашего первого линия ;).
Нет, я отправляю ответ после проверки, и результат не был нулевым
Этот ответ потребует некоторых привилегий на сервере для работы.
Сборки - хороший вариант для вас. Есть много сайтов, которые объясняют, как его создать. Я думаю, что это очень хорошо объяснено, это один
Если хотите, я уже создал сборку, и есть возможность скачать 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
Надеюсь, это поможет!!!
Обновлено:
Начиная с MS-SQL 2017 можно использовать функцию STRING_AGG
Ссылка DLL содержит ошибку 404. Использование сборки для этого - излишество. См. лучший ответ для SQL Server.
Полный пример 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
Используйте КОЛЕСЦЕ - Узнайте больше здесь
Например:
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
Это действительно лучшее решение IMO, поскольку оно позволяет избежать проблем с кодировкой, которые представляет FOR XML. Я использовал Declare @Numbers AS Nvarchar(MAX), и он работал нормально. Не могли бы вы объяснить, почему вы не рекомендуете его использовать?
Это решение было опубликовано 8 лет назад! stackoverflow.com/a/194887/986862
Почему этот запрос возвращается ??? символы вместо кириллических? Это просто проблема с выводом?
@EvilDr Вы можете избежать кодирования XML. См .: stackoverflow.com/questions/15643683/…
Что касается других ответов, человек, читающий ответ, должен знать о конкретной таблице доменов, например о транспортном средстве или студенте. Таблица должна быть создана и заполнена данными для тестирования решения.
Ниже приведен пример, в котором используется таблица 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
SELECT PageContent = Stuff(
( SELECT PageContent
FROM dbo.InfoGuide
WHERE CategoryId = @CategoryId
AND SubCategoryId = @SubCategoryId
for xml path(''), type
).value('.[1]','nvarchar(max)'),
1, 1, '')
FROM dbo.InfoGuide info
- 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)
;
Не то, чтобы я проводил какой-либо анализ производительности, поскольку в моем списке было менее 10 пунктов, но я был поражен, просмотрев 30 с лишним ответов, у меня все еще был поворот к аналогичному ответу, который уже был дан, аналогично использованию COALESCE для одного списка групп, и я не сделал этого. Мне даже не нужно устанавливать мою переменную (в любом случае по умолчанию NULL), и предполагается, что все записи в моей таблице исходных данных не пустые:
DECLARE @MyList VARCHAR(1000), @Delimiter CHAR(2) = ', '
SELECT @MyList = CASE WHEN @MyList > '' THEN @MyList + @Delimiter ELSE '' END + FieldToConcatenate FROM MyData
Я уверен, что внутренне COALESCE использует ту же идею. Будем надеяться, что MS не изменит этого на мне.
Вам нужно создать переменную, которая будет содержать ваш окончательный результат, и выбрать его, вот так.
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
Начиная со следующей версии 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;
И, в отличие от решений CLR, вы можете контролировать сортировку.
Похоже, на STRING_AGG есть ограничение на отображение 4000 символов.
Есть ли способ выполнить сортировку в случае отсутствия GROUP BY (например, для примера «Без группировки»)?
Обновление: мне удалось сделать следующее, но есть ли более чистый способ? ВЫБРАТЬ STRING_AGG (Имя, ',') КАК ОТДЕЛЫ ИЗ (ВЫБРАТЬ ВЕРХНИЕ 100000 Имя ИЗ HumanResources. Отдел ORDER BY Name) D;
Мне пришлось применить его к NVarchar (max), чтобы он заработал .. `` SELECT STRING_AGG (CAST (EmpName as NVARCHAR (MAX)), ',') FROM EmpTable as t '' '
Хотя уже поздно, и решений уже много. Вот простое решение для MySQL:
SELECT t1.id,
GROUP_CONCAT(t1.id) ids
FROM table t1 JOIN table t2 ON (t1.id = t2.id)
GROUP BY t1.id
Этот вопрос специфичен для SQL-сервера, поэтому вряд ли тот, кто в нем нуждается. Есть ли вопрос об этом же, связанный с mysql?
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
Определение таблицы
CREATE TABLE "NAMES" ("NAME" VARCHAR2(10 BYTE))) ;
Вставим значения в эту таблицу
INSERT INTO NAMES VALUES('PETER');
INSERT INTO NAMES VALUES('PAUL');
INSERT INTO NAMES VALUES('MARY');
Процедура начинается отсюда
DECLARE
MAXNUM INTEGER;
CNTR INTEGER := 1;
C_NAME NAMES.NAME%TYPE;
NSTR VARCHAR2(50);
BEGIN
SELECT MAX(ROWNUM) INTO MAXNUM FROM NAMES;
LOOP
SELECT NAME INTO C_NAME FROM
(SELECT ROWNUM RW, NAME FROM NAMES ) P WHERE P.RW = CNTR;
NSTR := NSTR ||','||C_NAME;
CNTR := CNTR + 1;
EXIT WHEN CNTR > MAXNUM;
END LOOP;
dbms_output.put_line(SUBSTR(NSTR,2));
END;
Результат
PETER,PAUL,MARY
Вопрос требует ответа, специфичного для SQL Server. Если есть вопрос PL / SQL, вы можете вместо этого ответить там. Однако сначала проверьте wm_concat и посмотрите, является ли это более простым методом.
Вот полное решение для этого:
-- Table Creation
CREATE TABLE Tbl
( CustomerCode VARCHAR(50)
, CustomerName VARCHAR(50)
, Type VARCHAR(50)
,Items VARCHAR(50)
)
insert into Tbl
SELECT 'C0001','Thomas','BREAKFAST','Milk'
union SELECT 'C0001','Thomas','BREAKFAST','Bread'
union SELECT 'C0001','Thomas','BREAKFAST','Egg'
union SELECT 'C0001','Thomas','LUNCH','Rice'
union SELECT 'C0001','Thomas','LUNCH','Fish Curry'
union SELECT 'C0001','Thomas','LUNCH','Lessy'
union SELECT 'C0002','JOSEPH','BREAKFAST','Bread'
union SELECT 'C0002','JOSEPH','BREAKFAST','Jam'
union SELECT 'C0002','JOSEPH','BREAKFAST','Tea'
union SELECT 'C0002','JOSEPH','Supper','Tea'
union SELECT 'C0002','JOSEPH','Brunch','Roti'
-- function creation
GO
CREATE FUNCTION [dbo].[fn_GetItemsByType]
(
@CustomerCode VARCHAR(50)
,@Type VARCHAR(50)
)
RETURNS @ItemType TABLE ( Items VARCHAR(5000) )
AS
BEGIN
INSERT INTO @ItemType(Items)
SELECT STUFF((SELECT distinct ',' + [Items]
FROM Tbl
WHERE CustomerCode = @CustomerCode
AND Type=@Type
FOR XML PATH(''))
,1,1,'') as Items
RETURN
END
GO
-- fianl Query
DECLARE @cols AS NVARCHAR(MAX),
@query AS NVARCHAR(MAX)
select @cols = STUFF((SELECT distinct ',' + QUOTENAME(Type)
from Tbl
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set @query = 'SELECT CustomerCode,CustomerName,' + @cols + '
from
(
select
distinct CustomerCode
,CustomerName
,Type
,F.Items
FROM Tbl T
CROSS APPLY [fn_GetItemsByType] (T.CustomerCode,T.Type) F
) x
pivot
(
max(Items)
for Type in (' + @cols + ')
) p '
execute(@query)
@ User1460901 Можно попробовать примерно так:
WITH cte_base AS (
SELECT CustomerCode, CustomerName,
CASE WHEN Typez = 'Breakfast' THEN Items ELSE NULL END AS 'BREAKFAST'
, CASE WHEN Typez = 'Lunch' THEN Items ELSE NULL END AS 'LUNCH'
FROM #Customer
)
SELECT distinct CustomerCode, CustomerName,
SUBSTRING(
(
SELECT ','+BREAKFAST AS [text()]
FROM cte_base b1
WHERE b1.CustomerCode = b2.CustomerCode AND b1.CustomerName = b2.CustomerName
ORDER BY b1.BREAKFAST
FOR XML PATH('')
), 2, 1000
) [BREAKFAST],
SUBSTRING(
(
SELECT ','+LUNCH AS [text()]
FROM cte_base b1
WHERE b1.CustomerCode = b2.CustomerCode AND b1.CustomerName = b2.CustomerName
ORDER BY b1.LUNCH
FOR XML PATH('')
), 2, 1000
) [LUNCH]
FROM cte_base b2
Вы можете сделать это с помощью рекурсивного запроса:
-- Create example table
CREATE TABLE tmptable (NAME VARCHAR(30)) ;
-- Insert example data
INSERT INTO tmptable VALUES('PETER');
INSERT INTO tmptable VALUES('PAUL');
INSERT INTO tmptable VALUES('MARY');
-- Recurse query
with tblwithrank as (
select * , row_number() over(order by name) rang , count(*) over() NbRow
from tmptable
),
tmpRecursive as (
select *, cast(name as varchar(2000)) as AllName from tblwithrank where rang=1
union all
select f0.*, cast(f0.name + ',' + f1.AllName as varchar(2000)) as AllName
from tblwithrank f0 inner join tmpRecursive f1 on f0.rang=f1.rang +1
)
select AllName from tmpRecursive
where rang=NbRow
Мы можем использовать RECUSRSIVITY, WITH CTE, union ALL следующим образом
declare @mytable as table(id int identity(1,1), str nvarchar(100))
insert into @mytable values('Peter'),('Paul'),('Mary')
declare @myresult as table(id int,str nvarchar(max),ind int, R# int)
;with cte as(select id,cast(str as nvarchar(100)) as str, cast(0 as int) ind from @mytable
union all
select t2.id,cast(t1.str+',' +t2.str as nvarchar(100)) ,t1.ind+1 from cte t1 inner join @mytable t2 on t2.id=t1.id+1)
insert into @myresult select *,row_number() over(order by ind) R# from cte
select top 1 str from @myresult order by R# desc
поверх ответа Криса Шаффера
если ваши данные могут повторяться, например
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
Это сработало для меня (SqlServer 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
Прежде всего, вы должны объявить табличную переменную и заполнить ее данными таблицы, а затем с помощью цикла WHILE выбрать строку одну за другой и добавить ее значение в переменную nvarchar (max).
Go
declare @temp table(
title nvarchar(50)
)
insert into @temp(title)
select p.Title from dbo.person p
--
declare @mainString nvarchar(max)
set @mainString = '';
--
while ((select count(*) from @temp) != 0)
begin
declare @itemTitle nvarchar(50)
set @itemTitle = (select top(1) t.Title from @temp t)
if @mainString = ''
begin
set @mainString = @itemTitle
end
else
begin
set @mainString = concat(@mainString,',',@itemTitle)
end
delete top(1) from @temp
end
print @mainString
Похоже, хороший ответ, но объясните, пожалуйста, как работает этот код.
Кажется, что у него будут границы очень неэффективно - как это будет работать с миллионом строк?
в Postgres - array_agg
SELECT array_to_string(array_agg(DISTINCT rolname), ',') FROM pg_catalog.pg_roles;
ИЛИ STRING_AGG
SELECT STRING_AGG(rolname::text,',') FROM pg_catalog.pg_roles;
Чтобы получить ответы, относящиеся к SQL Server, попробуйте этот вопрос.