T-sql GROUP BY с COUNT, а затем включить MAX из COUNT

Предположим, что у вас была таблица "Автомобили" с сотнями тысяч строк, и вы хотели сделать 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

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

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 из таблицы temp в локальную переменную. Но мне интересно, какой лучший способ сделать это.


UPDATE

Общее выражение таблицы выглядит наиболее элегантно, но похоже на @EBarr, мое ограниченное тестирование указывает на значительно более низкую производительность. Поэтому я не пойду с CTE.

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

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

Немного больше о моем случае использования: возможно, это может оказаться серии других вопросов. Но достаточно сказать, что я загружаю большое подмножество данных в временную таблицу (поэтому подмножество 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

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

Ответ 1

Механически существует несколько способов сделать это. Вы можете использовать переменную temp tables/table. Другой способ - вложенные запросы и/или 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 (читать лишенный) четвертый способ использует ключевое слово 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

Итак, я сгенерировал небольшие данные script и посмотрел на номера профилировщика 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

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

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

После выполнения вышеуказанных запросов я создал индексированное представление в запросе "только группа по" выше. Затем я выполнил запрос в индексированном представлении, который выполнил 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

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

Ответ 2

Здесь один из способов:

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

Ответ 3

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

GROUP BY CarID, CarName WITH ROLLUP