Как использовать GROUP BY для объединения строк в SQL Server? - PullRequest
339 голосов
/ 07 ноября 2008

Как мне получить:

id       Name       Value
1          A          4
1          B          8
2          C          9

до

id          Column
1          A:4, B:8
2          C:9

Ответы [ 16 ]

7 голосов
/ 07 ноября 2008

Этот тип вопросов задается здесь очень часто, и решение будет во многом зависеть от базовых требований:

https://stackoverflow.com/search?q=sql+pivot

и

https://stackoverflow.com/search?q=sql+concatenate

Как правило, нет единственного способа сделать это без SQL, без динамического sql, пользовательской функции или курсора.

6 голосов
/ 09 мая 2011

Это всего лишь дополнение к посту Кевина Фэйрчайлда (кстати, очень умный). Я бы добавил это как комментарий, но у меня пока недостаточно очков:)

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

Еще раз спасибо за классный обходной путь, Кевин!

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 
5 голосов
/ 07 ноября 2008

Курсору не нужен ... цикла 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
4 голосов
/ 31 марта 2015

Давайте разберемся очень просто:

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

По вашему запросу.

3 голосов
/ 15 марта 2017

не видел перекрестных ответов, также не нужно извлекать 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
2 голосов
/ 23 июня 2015

Вы можете значительно повысить производительность следующим образом, если группа по содержит в основном один элемент:

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