Как я могу получить:
id Name Value
1 A 4
1 B 8
2 C 9
к
id Column
1 A:4, B:8
2 C:9
Каждый, у кого есть учетная запись Microsoft, должен проголосовать за более простое решение для подключения: connect.microsoft.com/SQLServer/feedback/details/427987/…
Вы можете использовать агрегаты SQLCLR, найденные здесь, в качестве замены, пока T-SQL не будет улучшен: groupconcat.codeplex.com
Дубликат stackoverflow.com/questions/194852/…


Здесь очень часто задают такой вопрос, и решение во многом будет зависеть от основных требований:
https://stackoverflow.com/search?q=sql+pivot
и
https://stackoverflow.com/search?q=sql+concatenate
Как правило, это невозможно сделать с использованием только SQL без динамического sql, пользовательской функции или курсора.
Не правда. Решение cyberkiwi, использующее cte: s, представляет собой чистый sql без каких-либо хакерских действий, специфичных для производителя.
Во время вопроса и ответа я бы не считал рекурсивные CTE ужасно переносимыми, но теперь они поддерживаются Oracle. Лучшее решение будет зависеть от платформы. Для SQL Server это, скорее всего, метод FOR XML или агрегат CLR клиента.
окончательный ответ на все вопросы? stackoverflow.com/search?q=[без того вопрос]
Просто чтобы добавить к тому, что сказал Кейд, это, как правило, интерфейсный дисплей, и поэтому с ним нужно работать. Я знаю, что иногда проще написать что-то на 100% на SQL для таких вещей, как экспорт файлов или другие решения «только SQL», но в большинстве случаев эту конкатенацию следует обрабатывать на уровне отображения.
Теперь группировка - это внешний вид дисплея? Существует множество допустимых сценариев объединения одного столбца в сгруппированный набор результатов.
Не нужен курсор ... достаточно цикла while.
------------------------------
-- Setup
------------------------------
DECLARE @Source TABLE
(
id int,
Name varchar(30),
Value int
)
DECLARE @Target TABLE
(
id int,
Result varchar(max)
)
INSERT INTO @Source(id, Name, Value) SELECT 1, 'A', 4
INSERT INTO @Source(id, Name, Value) SELECT 1, 'B', 8
INSERT INTO @Source(id, Name, Value) SELECT 2, 'C', 9
------------------------------
-- Technique
------------------------------
INSERT INTO @Target (id)
SELECT id
FROM @Source
GROUP BY id
DECLARE @id int, @Result varchar(max)
SET @id = (SELECT MIN(id) FROM @Target)
WHILE @id is not null
BEGIN
SET @Result = null
SELECT @Result =
CASE
WHEN @Result is null
THEN ''
ELSE @Result + ', '
END + s.Name + ':' + convert(varchar(30),s.Value)
FROM @Source s
WHERE id = @id
UPDATE @Target
SET Result = @Result
WHERE id = @id
SET @id = (SELECT MIN(id) FROM @Target WHERE @id < id)
END
SELECT *
FROM @Target
См .: Плохие привычки, которые нельзя выбросить: думать о цикле WHILE - это не КУРСОР
@marc_s, возможно, лучшая критика заключается в том, что PRIMARY KEY должен быть объявлен в переменных таблицы.
@marc_s При дальнейшем рассмотрении эта статья является фикцией, как и почти все обсуждения производительности без измерения ввода-вывода. Я действительно узнал о LAG - так что спасибо за это.
Не требуется CURSOR, WHILE loop или User-Defined Function..
Просто нужно проявить творческий подход к FOR XML и PATH.
[Примечание: это решение работает только с SQL 2005 и новее. В исходном вопросе не указана используемая версия.]
CREATE TABLE #YourTable ([ID] INT, [Name] CHAR(1), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'A',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
почему нельзя заблокировать временную таблицу?
Это самая крутая вещь SQL, которую я видел в своей жизни. Есть идеи, «быстро» ли это для больших наборов данных? Он не начинает ползать, как курсор или что-то в этом роде, не так ли? Я хочу, чтобы больше людей проголосовали за это безумие.
@mbrierst, этот пример создавался не с расчетом на эффективность. В основном просто хотел показать альтернативное решение. Когда вы спрашиваете, насколько быстро это работает для больших наборов данных, как вы определяете «большой» и «быстрый»? Я уверен, что есть более элегантный способ переписать его с большей эффективностью.
Очень круто. В плане запроса указано, что выполняется только два сканирования таблицы и затем вложенный цикл. udf, вероятно, нельзя оптимизировать аналогичным образом. Обычно FOR XML не используют часто, мне нужно больше узнать о его хитростях.
Я совсем не уверен, что есть способ написать его более эффективно, если кто-нибудь знает его, пожалуйста, сделайте шаг вперед. И это не менее элегантно, чем отвратительные сводные операторы, добавленные в язык. Мне это так понравилось, что я нашел некоторые из ваших ответов и проголосовал за те, которые мне понравились.
Эх. Я просто ненавижу его стиль подзапросов. JOINS намного приятнее. Только не думайте, что я смогу использовать это в этом решении. В любом случае, я рад видеть, что здесь есть и другие SQL-придурки, кроме меня, которым нравится изучать подобные вещи. Престижность всем вам :)
Я тоже ненавижу подзапросы. Хорошая точка зрения. Но безумие FOR XML PATH закрыло мне глаза на все остальное в вашем запросе. Во всяком случае, до конца моей жизни. И я бы хотел, чтобы хоть один SQL-придурок, кроме меня, хоть раз поработал в моей компании.
Немного более чистый способ обработки строк: STUFF ((SELECT ',' + [Name] + ':' + CAST ([Value] AS VARCHAR (MAX)) FROM #YourTable WHERE (ID = Results.ID) FOR XML ПУТЬ ('')), 1,2, '') AS NameValues
Замечательно и безумно :-) Я использовал немного другую версию для получения всех таблиц в базе данных: SELECT STUFF ((SELECT ',' + [Name] FROM sys.objects WHERE [type] = 'U' FOR XML PATH ('' )), 1, 2, '') AS [Таблицы] FROM sys.objects WHERE [type] = 'U' GROUP BY [type]
Просто чтобы отметить то, что я нашел. Даже в среде, нечувствительной к регистру, часть запроса .value НЕОБХОДИМО быть в нижнем регистре. Я предполагаю, что это потому, что это XML, который чувствителен к регистру
Мне неловко для SQLServer, что это единственное решение этой проблемы без использования переменных.
Помните: включите в подзапрос WHERE все столбцы в GROUP BY, которые необходимы для одинаковой степени уникальности. Например, если у вас GROUP BY ID, ORDERNO, то вам может понадобиться WHERE (ID = Results.ID) AND (ORDERNO = Results.ORDERNO)
На самом деле это скорее обходной путь, чем ответ. Что делать, если у вас очень большой запрос без столбца простого идентификатора и много + объединений + подзапросов + групп по условиям в представлении? Скопируйте и вставьте весь запрос в предложение «FOR XML» (для каждого столбца, к которому вы хотите присоединиться), действительно ли это лучшее решение, которое может предложить SQL Server? Я думаю, что реальный ответ заключается в том, что до тех пор, пока конкатенация строк 2017 г. не была изначально поддержана SQL Server. Очень огорчающе :(
@DavidRogers, возможно, ваши запросы нужно еще упростить, прежде чем использовать эту технику. В любом случае, я был рад видеть, что STRING_AGG WITHIN GROUP был добавлен в SQL 2017, и настоятельно рекомендую его людям, использующим эту версию или позже. Поскольку эта функция не была доступна в то время, когда был дан ответ на этот вопрос, а SQL 2017 все еще находится в предварительной версии и не используется большинством пользователей, пытающихся найти решение этой проблемы на SQL Server до 2017 года, я поддерживаю это как «ответ», а не «обходной путь». :)
SQL Server 2005 и более поздние версии позволяют создавать свои собственные настраиваемые агрегатные функции, в том числе для таких вещей, как конкатенация - см. Образец в нижней части связанной статьи.
К сожалению, это требует (?) Использования сборок CLR .. это еще одна проблема, с которой нужно иметь дело: - /
Только в этом примере CLR используется для фактической реализации конкатенации, но это не требуется. Вы можете заставить агрегатную функцию конкатенации использовать FOR XML, так что, по крайней мере, будет удобнее вызывать ее в будущем!
Другой вариант с использованием Sql Server 2005 и выше
---- test data
declare @t table (OUTPUTID int, SCHME varchar(10), DESCR varchar(10))
insert @t select 1125439 ,'CKT','Approved'
insert @t select 1125439 ,'RENO','Approved'
insert @t select 1134691 ,'CKT','Approved'
insert @t select 1134691 ,'RENO','Approved'
insert @t select 1134691 ,'pn','Approved'
---- actual query
;with cte(outputid,combined,rn)
as
(
select outputid, SCHME + ' ('+DESCR+')', rn=ROW_NUMBER() over (PARTITION by outputid order by schme, descr)
from @t
)
,cte2(outputid,finalstatus,rn)
as
(
select OUTPUTID, convert(varchar(max),combined), 1 from cte where rn=1
union all
select cte2.outputid, convert(varchar(max),cte2.finalstatus+', '+cte.combined), cte2.rn+1
from cte2
inner join cte on cte.OUTPUTID = cte2.outputid and cte.rn=cte2.rn+1
)
select outputid, MAX(finalstatus) from cte2 group by outputid
Спасибо за ввод, я всегда предпочитаю использовать CTE и рекурсивные CTE для решения проблем на сервере SQL. Это сработало, у меня отлично работает!
можно ли использовать его в запросе с внешним применением?
Это просто дополнение к сообщению Кевина Фэйрчайлда (кстати, очень умного). Я бы добавил это как комментарий, но мне пока не хватает очков :)
Я использовал эту идею для представления, над которым работал, однако элементы, которые я объединял, содержали пробелы. Поэтому я немного изменил код, чтобы не использовать пробелы в качестве разделителей.
Еще раз спасибо за крутой обходной путь, Кевин!
CREATE TABLE #YourTable ( [ID] INT, [Name] CHAR(1), [Value] INT )
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (1, 'A', 4)
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (1, 'B', 8)
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (2, 'C', 9)
SELECT [ID],
REPLACE(REPLACE(REPLACE(
(SELECT [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) as A
FROM #YourTable
WHERE ( ID = Results.ID )
FOR XML PATH (''))
, '</A><A>', ', ')
,'<A>','')
,'</A>','') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
Я столкнулся с парой проблем, когда попытался преобразовать предложение Кевина Фэйрчайлда для работы со строками, содержащими пробелы и специальные символы XML (&, <, >), которые были закодированы.
Окончательная версия моего кода (которая не отвечает на исходный вопрос, но может быть кому-то полезна) выглядит так:
CREATE TABLE #YourTable ([ID] INT, [Name] VARCHAR(MAX), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'Oranges & Lemons',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'1 < 2',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT [ID],
STUFF((
SELECT ', ' + CAST([Name] AS VARCHAR(MAX))
FROM #YourTable WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE
/* Use .value to uncomment XML entities e.g. > < etc*/
).value('.','VARCHAR(MAX)')
,1,2,'') as NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
Вместо того, чтобы использовать пробел в качестве разделителя и заменять все пробелы запятыми, он просто добавляет запятую и пробел к каждому значению, а затем использует STUFF для удаления первых двух символов.
Кодирование XML выполняется автоматически с помощью директивы ТИП.
использование пути XML не будет идеально сцепляться, как вы могли ожидать ... он заменит "&" на "& amp;" а также будет связываться с <" and ">
... может быть, еще кое-что, не уверен ... но вы можете попробовать это
Я нашел обходной путь для этого ... вам нужно заменить:
FOR XML PATH('')
)
с:
FOR XML PATH(''),TYPE
).value('(./text())[1]','VARCHAR(MAX)')
... или NVARCHAR(MAX), если это то, что вы используете.
какого черта у SQL нет агрегатной функции конкатенации? это ПИТА.
Я обыскал сеть в поисках лучшего способа НЕ кодировать вывод. Большое спасибо! Это окончательный ответ - до тех пор, пока MS не добавит для этого надлежащую поддержку, например агрегатную функцию CONCAT (). Что я делаю, так это бросаю это во внешнее приложение, которое возвращает мое объединенное поле. Я не поклонник добавления вложенных выборок в мои операторы выбора.
Я согласился, что без использования Value мы можем столкнуться с проблемами, когда текст представляет собой символ в кодировке XML. В моем блоге описаны сценарии групповой конкатенации на сервере SQL. blog.vcillusion.co.in/…
В Oracle вы можете использовать агрегатную функцию LISTAGG.
Оригинальные записи
name type
------------
name1 type1
name2 type2
name2 type3
Sql
SELECT name, LISTAGG(type, '; ') WITHIN GROUP(ORDER BY name)
FROM table
GROUP BY name
Результат в
name type
------------
name1 type1
name2 type2; type3
Выглядит неплохо, но вопросы конкретно не об Oracle.
Я понимаю. Но я искал то же самое для Oracle, поэтому я подумал, что выложу это здесь для других людей, таких как я :)
@MichalB. Разве вы не упускаете из виду внутренний синтаксис? например: listagg (type, ',') внутри группы (по имени)?
@gregory: Я отредактировал свой ответ. Я думаю, что в свое время мое старое решение работало. Текущая форма, которую вы предложили, наверняка будет работать, спасибо.
для будущих людей - вы можете написать новый вопрос со своим собственным ответом для значительного различия, например, разные платформы
Получим очень просто:
SELECT stuff(
(
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
FOR XML PATH('')
)
, 1, 2, '')
Замените эту строку:
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
С вашим запросом.
Вы можете значительно улучшить производительность следующим образом, если group by содержит в основном один элемент:
SELECT
[ID],
CASE WHEN MAX( [Name]) = MIN( [Name]) THEN
MAX( [Name]) NameValues
ELSE
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
END
FROM #YourTable Results
GROUP BY ID
Предполагая, что вам не нужны повторяющиеся имена в списке, что может быть, а может и нет.
Установите агрегаты SQLCLR из http://groupconcat.codeplex.com
Затем вы можете написать такой код, чтобы получить желаемый результат:
CREATE TABLE foo
(
id INT,
name CHAR(1),
Value CHAR(1)
);
INSERT INTO dbo.foo
(id, name, Value)
VALUES (1, 'A', '4'),
(1, 'B', '8'),
(2, 'C', '9');
SELECT id,
dbo.GROUP_CONCAT(name + ':' + Value) AS [Column]
FROM dbo.foo
GROUP BY id;
Я использовал его несколько лет назад, синтаксис намного чище, чем все уловки "XML Path", и он работает очень хорошо. Я настоятельно рекомендую это делать, когда можно использовать функции SQL CLR.
Восемь лет спустя ... Microsoft SQL Server vNext Database Engine наконец-то усовершенствовал Transact-SQL для прямой поддержки конкатенации сгруппированных строк. Версия 1.0 Community Technical Preview добавила функцию STRING_AGG, а CTP 1.1 добавил предложение WITHIN GROUP для функции STRING_AGG.
Ссылка: https://msdn.microsoft.com/en-us/library/mt775028.aspx
не видел никаких перекрестных ответов, также нет необходимости в извлечении xml. Вот немного другая версия того, что написал Кевин Фэирчайлд. Это быстрее и проще использовать в более сложных запросах:
select T.ID
,MAX(X.cl) NameValues
from #YourTable T
CROSS APPLY
(select STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = T.ID)
FOR XML PATH(''))
,1,2,'') [cl]) X
GROUP BY T.ID
Без использования Value мы можем столкнуться с проблемами, когда текст представляет собой символ в кодировке XML.
Если это SQL Server 2017 или SQL Server Vnext, SQL Azure, вы можете использовать string_agg, как показано ниже:
select id, string_agg(concat(name, ':', [value]), ', ')
from #YourTable
group by id
Работает безупречно!
Использование функции замены и FOR JSON PATH
SELECT T3.DEPT, REPLACE(REPLACE(T3.ENAME,'{"ENAME":"',''),'"}','') AS ENAME_LIST
FROM (
SELECT DEPT, (SELECT ENAME AS [ENAME]
FROM EMPLOYEE T2
WHERE T2.DEPT=T1.DEPT
FOR JSON PATH,WITHOUT_ARRAY_WRAPPER) ENAME
FROM EMPLOYEE T1
GROUP BY DEPT) T3
Для образцов данных и других способов кликните сюда
Если у вас включен clr, вы можете использовать библиотеку Group_Concat из GitHub
Другой пример без мусора: ", TYPE) .value ('(./ text ()) [1]', 'VARCHAR (MAX)')"
WITH t AS (
SELECT 1 n, 1 g, 1 v
UNION ALL
SELECT 2 n, 1 g, 2 v
UNION ALL
SELECT 3 n, 2 g, 3 v
)
SELECT g
, STUFF (
(
SELECT ', ' + CAST(v AS VARCHAR(MAX))
FROM t sub_t
WHERE sub_t.g = main_t.g
FOR XML PATH('')
)
, 1, 2, ''
) cg
FROM t main_t
GROUP BY g
Ввод-вывод есть
************************* -> *********************
* n * g * v * * g * cg *
* - * - * - * * - * - *
* 1 * 1 * 1 * * 1 * 1, 2 *
* 2 * 1 * 2 * * 2 * 3 *
* 3 * 2 * 3 * *********************
*************************
Использование оператора Stuff and for xml path для объединения строк в строку: Group By two columns ->
CREATE TABLE #YourTable ([ID] INT, [Name] CHAR(1), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'A',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',5)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
-- retrieve each unique id and name columns and concatonate the values into one column
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) -- CONCATONATES EACH APPLICATION : VALUE SET
FROM #YourTable
WHERE (ID = Results.ID and Name = results.[name] )
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
SELECT
[ID],[Name] , --these are acting as the group by clause
STUFF((
SELECT ', '+ CAST([Value] AS VARCHAR(MAX)) -- CONCATONATES THE VALUES FOR EACH ID NAME COMBINATION
FROM #YourTable
WHERE (ID = Results.ID and Name = results.[name] )
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID, name
DROP TABLE #YourTable
Я использовал этот подход, который, возможно, легче понять. Получите корневой элемент, затем объедините его, чтобы выбрать любой элемент с тем же идентификатором, но не с `` официальным '' именем
Declare @IdxList as Table(id int, choices varchar(max),AisName varchar(255))
Insert into @IdxLIst(id,choices,AisName)
Select IdxId,''''+Max(Title)+'''',Max(Title) From [dbo].[dta_Alias]
where IdxId is not null group by IdxId
Update @IdxLIst
set choices=choices +','''+Title+''''
From @IdxLIst JOIN [dta_Alias] ON id=IdxId And Title <> AisName
where IdxId is not null
Select * from @IdxList where choices like '%,%'
Этот тип проблемы легко решается в MySQL с его агрегатной функцией
GROUP_CONCAT(), но решить ее на Microsoft SQL Server сложнее. См. Следующий вопрос SO для справки: "Как получить несколько записей против одной записи на основе отношения?"