Группировка в Transact SQL сложна тем, что из исходной таблицы она делает новую таблицу. На вход поступает исходная таблица. Операции группировки группируют ее объекты по какому-то принципу, и на выходе получаем список групп.
Пример: есть готовая БД с книгами разных тематик (type). Сгруппируем их по типу:
SELECT Type
FROM titles
GROUP BY Type
При этом можно группировать, используя агрегатные функции: Count, Min, Max, Avg:
SELECT Type, Count(*), AVG(price), Min(price), Max(price)
FROM titles
GROUP BY Type
Т.е. из примера выше получается новая таблица, в которую можем добавлять новые строки (какие захотим).
Другой пример: есть БД интернет-магазина, в которой колонка дата заказа (order_date). Узнаем, сколько было заказов по годам:
SELECT Year(order_date), Count(*)
FROM orders
GROUP BY Year(order_date)
Теперь узнаем год, в котором наибольшее число заказов:
SELECT TOP(1) Year(order_date)
FROM orders
GROUP BY Year(order_date)
ORDER BY Count(*) DESC
Другой пример: есть БД с книгами разных категорий. Узнаем, в какой категории самая дорогая книга:
SELECT TOP(1)
FROM titles
GROUP BY Type
ORDER BY Max(price) DESC
-- можно без группировки
SELECT TOP(1) Type
FROM titles
ORDER BY price DESC
Группировать можно по нескольким полям:
SELECT price, title
FROM titles
GROUP BY price, title
Фильтрация сгруппированных таблиц
Для группировки выборки в исходной таблице используется конструкция WHERE. Но в новой таблице после группировки это не работает. Вместо WHERE применяют оператор HAVING:
SELECT Type, Count(*)
FROM titles
GROUP BY Type
HAVING Count(*)>1
В идеале фильтрацию лучше делать до группировки — в исходной таблице, т.е. попытаться отбросить побольше лишнего. А применение HAVING, т.е. фильтрация после группировки, тормозит запросы.
Вывод: суть работы группировок несложен. Берется исходная таблица. Отфильтровываем нужные данные из нее. Далее группироем эти данные в новую таблицу. И работаем уже с новой таблицей, к ней применяем функции агрегации и группировки (HAVING).