t-sql GROUP BY с COUNT, а затем включить MAX из COUNT - PullRequest
12 голосов
/ 09 февраля 2012

Предположим, у вас есть таблица "Cars" с сотнями тысяч строк, и вы хотите выполнить GROUP BY:

SELECT   CarID
         , CarName
         , COUNT(*) AS Total
FROM     dbo.tbl_Cars
GROUP BY CarID
         , CarName

Группировка оставляет результат сродни:

CarID       CarName    Total
1872        Olds       202,121   
547841      BMW        175,298
9877        Ford        10,241

Все хорошо и хорошо.Мой вопрос, однако, заключается в том, каков наилучший способ объединить итоговое значение и итоговое значение MAX в одну таблицу с точки зрения производительности и чистого кодирования, поэтому вы получите такой результат, как:

CarID       CarName    Total      Max Total
1872        Olds       202,121    202,121
547841      BMW        175,298    202,121
9877        Ford        10,241    202,121 

Один подход будетчтобы поместить результат GROUP в временную таблицу, а затем получить MAX из временной таблицы в локальную переменную.Но мне интересно, как лучше всего это сделать.


ОБНОВЛЕНИЕ

Выражение Common Table кажется наиболее элегантным для написания, но, как и в случае @EBarr, мое ограниченное тестирование указывает на значительно более низкую производительность.Так что я не пойду с CTE.

Поскольку ссылка @EBarr для опции COMPUTE указывает на то, что функция устарела, это тоже не самый лучший маршрут.

Вариант локальной переменной для значения MAX и использование временной таблицы, скорее всего, будет тем маршрутом, по которому я иду, поскольку я не знаю о проблемах производительности с ним.

Немного подробнее о моем случае использования: возможно, он окажется серией других вопросов SO.Но достаточно сказать, что я загружаю большое подмножество данных во временную таблицу (поэтому подмножество tbl_Cars собирается в #tbl_Cars, и даже #tbl_Cars может быть дополнительно отфильтровано и на нем выполнены агрегации), потому что я долженвыполнить несколько запросов на фильтрацию и агрегирование в одном хранимом процессе, который возвращает несколько наборов результатов.


ОБНОВЛЕНИЕ 2

@ EBarr использует оконную функцию приятно и коротко.Примечание для себя: если вы используете RIGHT JOIN для внешней справочной таблицы, функция COUNT() должна выбирать столбец из tbl_Cars, а не '*'.

SELECT       M.MachineID
             , M.MachineType
             , COUNT(C.CarID) AS Total
             , MAX(COUNT(C.CarID)) OVER() as MaxTotal
FROM         dbo.tbl_Cars C
RIGHT JOIN   dbo.tbl_Machines M
      ON     C.CarID = M.CarID
GROUP BY     M.MachineID
             , M.MachineType

С точки зрения скорости это выглядит нормально, но в какой момент вам нужно беспокоиться о количестве операций чтения?

Ответы [ 3 ]

13 голосов
/ 09 февраля 2012

Механически есть несколько способов сделать это.Вы можете использовать временные таблицы / переменную таблицы.Другой способ - с вложенными запросами и / или CTE, как показало @Aaron_Bertrand.Третий способ - использовать WINDOWED FUNCTIONS, такие как ...

SELECT    CarName,
          COUNT(*) as theCount,
          MAX(Count(*)) OVER(PARTITION BY 'foo') as MaxPerGroup
FROM      dbo.tbl_Cars
GROUP BY CarName

A DISFAVORED (readricric). Четвертым способом является использование ключевого слова COMPUTE как такового ...

SELECT   CarID, CarName, Count(*)
FROM     dbo.tbl_Cars
GROUP BY CarID, CarName 
COMPUTE MAX(Count(*))   

Ключевое слово COMPUTE генерирует итоги, которые отображаются в виде дополнительных сводных столбцов в конце набора результатов ( см. ).В приведенном выше запросе вы на самом деле увидите два набора записей.

Самый быстрый

Теперь следующая проблема - «лучший / самый быстрый / самый простой».Я сразу думаю о indexed view.Как мягко напомнил мне @Aaron, индексированные представления имеют всевозможные ограничения.Однако вышеприведенная стратегия позволяет вам создать индексированное представление для SELECT ... FROM..GROUP BY.Затем, выбрав в индексированном представлении, примените предложение WINDOWED FUNCTION.

Однако, не зная больше о вашем дизайне, любому будет сложно сказать, что лучше.Вы получите освещение быстрых запросов из индексированного представления.Эта производительность имеет свою цену, хотя.Цена это расходы на техническое обслуживание.Если базовая таблица является целью большого количества операций вставки / обновления / удаления, обслуживание индексированного представления снизит производительность в других областях.

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


MICRO PERFORMANCE TEST

Итак, я сгенерировал небольшой скрипт данных и посмотрел на номера профайлера sql для производительности CTE по сравнению с оконными функциями.Это микротест, поэтому попробуйте некоторые действительные числа в вашей системе при реальной нагрузке .

Генерация данных:

Create table Cars ( CarID int identity (1,1) primary key, 
                    CarName varchar(20), 
                    value int)
GO
insert into Cars (CarName, value)
values  ('Buick', 100),
        ('Ford', 10),
        ('Buick', 300),     
        ('Buick', 100),
        ('Pontiac', 300),       
        ('Bmw', 100),
        ('Mecedes', 300),       
        ('Chevy', 300),     
        ('Buick', 100),
        ('Ford', 200);
GO 1000

ЭтоСкрипт генерирует 10000 строк.Затем я запускал каждый из четырех следующих запросов несколько раз:

--just group by
select  CarName,COUNT(*) countThis
FROM    Cars
GROUP BY CarName        

--group by with compute (BAD BAD DEVELOPER!)
select  CarName,COUNT(*) countThis
FROM    Cars
GROUP BY CarName        
COMPUTE  MAX(Count(*));

-- windowed aggregates...
SELECT  CarName,
        COUNT(*) as theCount,
        MAX(Count(*)) OVER(PARTITION BY 'foo') as MaxInAnyGroup
FROM Cars
GROUP BY CarName        

--CTE version
;WITH x AS (
  SELECT   CarName,
           COUNT(*) AS Total
  FROM     Cars
  GROUP BY CarName
)
SELECT x.CarName, x.Total, x2.[Max Total]
FROM x CROSS JOIN (
  SELECT [Max Total] = MAX(Total) FROM x
) AS x2;

После выполнения вышеупомянутых запросов я создал индексированное представление для запроса "just group by" выше.Затем я выполнил запрос в индексированном представлении, который выполнил MAX(Count(*)) OVER(PARTITION BY 'foo'.

СРЕДНИЕ РЕЗУЛЬТАТЫ

Query                      CPU       Reads     Duration   
--------------------------------------------------------
Group By                   15        31        7 ms  
Group & Compute            15        31        7 ms
Windowed Functions         14        56        8 ms 
Common Table Exp.          16        62       15 ms
Windowed on Indexed View    0        24        0 ms

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

8 голосов
/ 09 февраля 2012

Вот один из способов:

;WITH x AS
(
  SELECT   CarID
         , CarName
         , COUNT(*) AS Total
  FROM     dbo.tbl_Cars
  GROUP BY CarID, CarName
)
SELECT x.CarID, x.CarName, x.Total, x2.[Max Total]
FROM x CROSS JOIN
(
  SELECT [Max Total] = MAX(Total) FROM x
) AS x2;
0 голосов
/ 11 апреля 2014

SQL Server 2008 R2 и более новые версии, вы можете использовать:

GROUP BY CarID, CarName WITH ROLLUP
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...