Перейти к содержанию
Educora
Средний16 мин7 / 22

GROUP BY и HAVING: итоги по группам

Разбивай строки на группы, вычисляй агрегаты для каждой группы и фильтруй группы с помощью HAVING.

Проверь себя
В этом уроке ты узнаешь
  • Вычислять итоги по каждой группе с помощью GROUP BY
  • Объяснять разницу между WHERE и HAVING
  • Знать логический порядок выполнения частей запроса

Директор спрашивает: «Сколько у нас учеников из каждого города?» Из прошлого урока мы знаем, как посчитать один город с помощью COUNT(*) и WHERE city = 'Bakı'. Но писать семь запросов для семи городов? Нет — GROUP BY отвечает сразу за все города одним запросом.

GROUP BY: разбиваем строки на группы

GROUP BY столбец собирает строки с одинаковым значением в одну группу, а агрегатная функция вычисляется для каждой группы отдельно. В результате получается одна строка на группу.

SQL
SELECT city, COUNT(*) AS students
FROM students
GROUP BY city
ORDER BY students DESC, city;
▸ Ожидаемый результат
city | students
Bakı | 4
Gəncə | 2
Sumqayıt | 2
Lənkəran | 1
Naxçıvan | 1
Quba | 1
Şəki | 1
  1. FROM students берёт 12 строк таблицы.
  2. GROUP BY city складывает строки с одинаковым городом в одну «корзину» — получается 7 корзин.
  3. COUNT(*) считает строки в каждой корзине.
  4. Каждая корзина превращается в одну строку: город и количество.

Для группы можно вычислить и несколько агрегатов. Ниже для каждой категории — количество товаров, средняя цена и общий остаток на складе.

SQL
SELECT category,
       COUNT(*)             AS products,
       ROUND(AVG(price), 2) AS avg_price,
       SUM(stock)           AS total_stock
FROM products
GROUP BY category
ORDER BY category;
▸ Ожидаемый результат
category | products | avg_price | total_stock
Accessories | 2 | 33.5 | 180
Electronics | 4 | 695.12 | 63
Games | 1 | 42 | 18
Home | 1 | 34.99 | 25
Stationery | 2 | 5.55 | 800

Группировка по нескольким столбцам и выражениям

Если в GROUP BY несколько столбцов, группу определяет сочетание значений. Например, посчитаем покупателей для каждой пары «страна — город».

SQL
SELECT country, city, COUNT(*) AS customers
FROM customers
GROUP BY country, city
ORDER BY country, city;
▸ Ожидаемый результат
country | city | customers
Azerbaijan | Bakı | 2
Azerbaijan | Gəncə | 1
Russia | Moscow | 1
Türkiye | Ankara | 1
Türkiye | İstanbul | 1
United Kingdom | London | 1

Группировать можно и по выражению. SUBSTR(order_date, 1, 7) вырезает из даты первые 7 символов — год и месяц (2025-04), — и так мы считаем заказы по месяцам. В SQL Server эта функция называется SUBSTRING.

SQL
SELECT SUBSTR(order_date, 1, 7) AS month,
       COUNT(*) AS orders
FROM orders
GROUP BY SUBSTR(order_date, 1, 7)
ORDER BY month;
▸ Ожидаемый результат
month | orders
2025-01 | 2
2025-02 | 2
2025-03 | 2
2025-04 | 2
2025-05 | 1
2025-06 | 2
2025-07 | 1

HAVING: фильтруем группы

А если нужны только города, где не меньше двух учеников? Написать WHERE COUNT(*) >= 2 нельзя: WHERE смотрит на отдельные строки до образования групп, когда ещё ничего не посчитано. Условия для групп пишут в HAVING — он работает после группировки.

SQL
SELECT city, COUNT(*) AS students
FROM students
GROUP BY city
HAVING COUNT(*) >= 2
ORDER BY city;
▸ Ожидаемый результат
city | students
Bakı | 4
Gəncə | 2
Sumqayıt | 2
SQL
SELECT course_id, ROUND(AVG(score), 1) AS avg_score
FROM enrollments
GROUP BY course_id
HAVING AVG(score) >= 85
ORDER BY avg_score DESC;
▸ Ожидаемый результат
course_id | avg_score
6 | 92.7
5 | 90.5
1 | 87.3
Курсы со средним баллом 85 и выше.
КритерийWHEREHAVING
Что фильтруетотдельные строкигруппы
Когда работаетдо группировкипосле группировки
Можно ли агрегаты?нетда

WHERE и HAVING могут работать в одном запросе вместе. Ниже WHERE сначала отбрасывает товар, которого нет на складе (Monitor), затем оставшиеся делятся на категории, а HAVING оставляет только категории, где больше одного товара.

SQL
SELECT category, COUNT(*) AS in_stock
FROM products
WHERE stock > 0
GROUP BY category
HAVING COUNT(*) > 1
ORDER BY category;
▸ Ожидаемый результат
category | in_stock
Accessories | 2
Electronics | 3
Stationery | 2

Порядок выполнения запроса

Мы пишем запрос в одном порядке, а СУБД логически выполняет его в другом. Знание этого порядка объясняет многие ошибки — например, почему агрегаты и псевдонимы не работают в WHERE, но работают в ORDER BY.

  1. FROM — берутся строки таблицы
  2. WHERE — строки фильтруются
  3. GROUP BY — строки делятся на группы
  4. HAVING — фильтруются группы
  5. SELECT — вычисляются столбцы и псевдонимы
  6. ORDER BY — результат сортируется
  7. LIMIT — берётся нужное число строк
Пример задачи

Какие покупатели купили во всех своих заказах в сумме не меньше 5 штук товара?

Показать решение
1) Группируем заказы по покупателю: GROUP BY customer_id.
2) Суммируем количество в каждой группе: SUM(quantity).
3) Фильтруем группы: HAVING SUM(quantity) >= 5.
Ответ: покупатель № 2 (21 штука) и покупатель № 1 (13 штук).
SQL
SELECT customer_id, SUM(quantity) AS items
FROM orders
GROUP BY customer_id
HAVING SUM(quantity) >= 5
ORDER BY items DESC;
▸ Ожидаемый результат
customer_id | items
2 | 21
1 | 13
Чтобы увидеть и имена покупателей, нужно соединить orders с customers — этому посвящён следующий урок.
Задание

Выведи для каждого предмета (subject) из courses число курсов (courses) и сумму кредитов (credits). Отсортируй по названию предмета.

Задание · SQL
SELECT subject
FROM courses
ORDER BY subject;
▸ Ожидаемый результат
subject | courses | credits
Chemistry | 1 | 3
English | 1 | 2
History | 1 | 2
Informatics | 1 | 3
Math | 2 | 7
Physics | 1 | 4
Задание

Найди учеников, у которых средний балл выше 85: student_id и средний балл, округлённый до 1 знака после запятой (avg_score). Отсортируй по среднему баллу по убыванию, а при равенстве — по student_id.

Задание · SQL
SELECT student_id, ROUND(AVG(score), 1) AS avg_score
FROM enrollments
-- group and filter the groups
▸ Ожидаемый результат
student_id | avg_score
11 | 95.5
3 | 92.5
10 | 91
1 | 90
5 | 89
6 | 88

Главное

  • GROUP BY объединяет строки с одинаковыми значениями; в результате — одна строка на группу.
  • Каждый столбец в SELECT должен быть в GROUP BY или внутри агрегата.
  • WHERE фильтрует строки до группировки, а HAVING — группы после неё.
  • Условия с агрегатами (COUNT(*) >= 2) пишут только в HAVING.
  • Логический порядок: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Что делает GROUP BY city в запросе SELECT city, COUNT(*) FROM students GROUP BY city?