5 Способов Выполнить Агрегацию Строк В Ms Sql

Иногда возникает необходимость агрегировать строки в SQL-запросе, то есть по следующему набору данных:

Идентификатор группы Элемент
1 ААА
2 ЯВЛЯЕТСЯ
5 МОЙ БОГ
2 ЧТО
2 ТО
1 Этот
получить что-то вроде этого:
Идентификатор группы Список предметов
1 ААА, это
2 ЭТО, ЧТО,
5 МОЙ БОГ
В MySQL, например, есть встроенная функция для таких целей.

ГРУПП_КОНКАТ() :

 SELECT GroupId, GROUP_CONCAT(Item SEPARATOR ",") AS ItemList
 FROM Items
В MS SQL Server такой функции нет, поэтому придется ее подкрутить.

Прежде чем начать, давайте создадим скрипт для создания тестовой таблицы:

 CREATE TABLE Items(GroupId INT, Item NVARCHAR(10))
 
 INSERT INTO Items(GroupId, Item)
 SELECT 1 AS GroupId, 'AAA' AS Item
   UNION ALL
 SELECT 2, 'IS'
   UNION ALL
 SELECT 5, 'OMG'
   UNION ALL
 SELECT 2, 'WHAT'
   UNION ALL
 SELECT 2, 'THE'
   UNION ALL
 SELECT 1, 'This'
Итак, начнем.

Самый тупой и простой способ создать временную таблицу и собрать в нее промежуточные результаты агрегации , пробежав курсором по таблице Items. Этот метод очень медленный, а код страшный.

Восхищаться:

 DECLARE @Aggregated TABLE (GroupId INT, ItemList NVARCHAR(100))
 
 DECLARE ItemsCursor CURSOR READ_ONLY
 FOR SELECT GroupId, Item
     FROM Items
 
 DECLARE @CurrentGroupId INT
 DECLARE @CurrentItem NVARCHAR(10)
 DECLARE @CurrentItemList NVARCHAR(100)
 
 OPEN ItemsCursor
 
 FETCH NEXT FROM ItemsCursor
 INTO @CurrentGroupId, @CurrentItem
 
 WHILE @@FETCH_STATUS = 0
 BEGIN
     SET @CurrentItemList = (SELECT ItemList
                             FROM @Aggregated
                             WHERE GroupId = @CurrentGroupId)
 
     IF @CurrentItemList IS NULL
         INSERT INTO @Aggregated(GroupId, ItemList)
         VALUES(@CurrentGroupId, @CurrentItem)
     ELSE
         UPDATE @Aggregated
         SET ItemList = ItemList + ',' + @CurrentItem
         WHERE GroupId = @CurrentGroupId
 
     FETCH NEXT FROM ItemsCursor
     INTO @CurrentGroupId, @CurrentItem
 END
 
 CLOSE ItemsCursor
 DEALLOCATE ItemsCursor
 
 SELECT GroupId, ItemList
 FROM @Aggregated
Есть более красивый способ, не использующий временные таблицы.

Это основано на трюке ВЫБИРАТЬ вар = вар + ',' + col FROM где .

Да, вы можете сделать это, и это работает:

 CREATE FUNCTION ConcatItems(@GroupId INT)
    RETURNS NVARCHAR(100)
 AS
 BEGIN
     DECLARE @ItemList varchar(8000)
     SET @ItemList = ''
 
     SELECT @ItemList = @ItemList + ',' + Item
     FROM Items
     WHERE GroupId = @GroupId
 
     RETURN SUBSTRING(@ItemList, 2, 100)
 END
 
 GO
 
 SELECT GroupId, dbo.ConcatItems(GroupId) ItemList
 FROM Items
 GROUP BY GroupId
Чуть лучше, но все равно немного херово.

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

 SELECT GroupId,
        CASE Item2 WHEN '' THEN Item1
            ELSE CASE Item3 WHEN '' THEN Item1 + ',' + Item2
            ELSE CASE Item4 WHEN '' THEN Item1 + ',' + Item2 + ',' + Item3
            ELSE Item1 + ',' + Item2 + ',' + Item3 + ',' + Item4
        END END END AS ItemList
 FROM (
   SELECT GroupId,
        MAX(CASE ItemNo WHEN 1 THEN Item ELSE '' END) AS Item1,
        MAX(CASE ItemNo WHEN 2 THEN Item ELSE '' END) AS Item2,
        MAX(CASE ItemNo WHEN 3 THEN Item ELSE '' END) AS Item3,
        MAX(CASE ItemNo WHEN 4 THEN Item ELSE '' END) AS Item4
   FROM (
     SELECT GroupId,
          Item,
          ROW_NUMBER() OVER (PARTITION BY GroupId ORDER BY Item) ItemNo
     FROM Items
   ) AS OrderedItems
   GROUP BY GroupId
 ) AS AlmostAggregated
Да, много кода.

Но в базе данных нет ни одного лишнего объекта — только один чистый выбор.

Иногда это важно.

Однако есть способ обойти ограничение размера группы, оставаясь в рамках того же запроса.

Мы будем собрать все элементы группы в поле XML , который затем преобразуем в строковый тип и заменяем теги между элементами запятыми:

 SELECT GroupId,
        REPLACE(SUBSTRING(ItemListWithTags, 4, LEN(ItemListWithTags)-7),
                '<a>',
                ',') AS ItemList
 FROM (
   SELECT GroupId,
        CAST(XmlItemList AS NVARCHAR(200)) ItemListWithTags
   FROM (
     SELECT GroupId,
          (SELECT Item AS A
         FROM Items ii
         WHERE ii.GroupId = GroupIds.GroupId
         FOR XML PATH('')) AS XmlItemList
     FROM (SELECT DISTINCT GroupId FROM Items) AS GroupIds
   ) AS subq1
 ) AS subq2
В общем, получается не очень быстро, но всегда получается.

И, конечно же, вам нужен SQL Server не ниже 2000. Да, есть еще способ агрегировать строки через Агрегатные функции CLR , но это вообще мрачный ужас, ибо убийственно медленно и несоответственно сложности задачи.

Если на такую статью будет достаточный спрос, я напишу ее позже.

Жду комментариев и критики.

И еще: если кто знает, как сделать подсветку кода, как это сделал я дома - рассказывать.

Я пока не вижу другого способа, кроме вставки скриншотов.

Теги: #sql #ms sql-сервер #агрегация строк #Transact-SQL #извращения #Чулан

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.