← Назад к вопросам

В чем разница между HAVING и GROUP BY?

1.0 Junior🔥 181 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между HAVING и GROUP BY

GROUP BY и HAVING — это два разных SQL оператора, которые часто используются вместе, но служат разным целям. Разница может показаться небольшой, но она критична для правильного написания запросов.

Быстрое определение

GROUP BY — используется для группировки строк по одному или нескольким столбцам.

HAVING — используется для фильтрации групп после GROUP BY, обычно используется с агрегирующими функциями.

Где используется каждый

GROUP BY:

  • Появляется перед HAVING
  • Работает на уровне строк ДО группировки
  • Делит данные на группы
  • Нельзя использовать агрегирующие функции в WHERE с GROUP BY

HAVING:

  • Появляется ПОСЛЕ GROUP BY
  • Работает на уровне групп ПОСЛЕ агрегации
  • Фильтрует результаты групп (не отдельные строки)
  • Используется с агрегирующими функциями (SUM, COUNT, AVG, MIN, MAX)

Прямой пример разницы

Пример 1: Найти в каких категориях продуктов больше 3 товаров

Ситуация:

Продукты:
id | category | price
1  | Electronics | 100
2  | Electronics | 200
3  | Electronics | 150
4  | Books | 10
5  | Books | 12

Хотим узнать: в каких категориях БОЛЬШЕ 3 товаров?

Правильный запрос:

SELECT category, COUNT(*) as product_count
FROM products
GROUP BY category    -- Группируем по category
HAVING COUNT(*) > 3  -- Фильтруем ГРУППЫ где COUNT(*) > 3

Результат:

category | product_count
Electronics | 3

Почему не WHERE?

-- ❌ НЕПРАВИЛЬНО:
SELECT category, COUNT(*) as product_count
FROM products
WHERE COUNT(*) > 3      -- ❌ ОШИБКА! COUNT не может быть в WHERE
GROUP BY category

-- Получим ошибку: "aggregate function in WHERE clause"

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

  1. FROM — какую таблицу читаем
  2. WHERE — фильтруем СТРОКИ (работает ДО GROUP BY)
  3. GROUP BY — группируем строки
  4. HAVING — фильтруем ГРУППЫ (работает ПОСЛЕ GROUP BY)
  5. SELECT — выбираем что показать
  6. ORDER BY — сортируем результаты

Пример с этапами:

SELECT 
  user_id,
  SUM(amount) as total_spent
FROM orders
WHERE created_at > '2024-01-01'     -- Этап 2: оставляем только заказы > 2024
GROUP BY user_id                     -- Этап 3: группируем по user
HAVING SUM(amount) > 1000            -- Этап 4: берём только группы с total > 1000

Практический пример

Данные о продажах:

id | user_id | product | amount | date
1  | 10 | Laptop | 1000 | 2024-01-15
2  | 10 | Mouse | 50 | 2024-01-20
3  | 20 | Phone | 800 | 2024-01-10
4  | 20 | Case | 30 | 2024-01-25
5  | 30 | Keyboard | 100 | 2024-01-05

Задача: Найти пользователей, которые потратили больше 500 за последние месяц, но исключить заказы дешевле 40.

SELECT 
  user_id,
  COUNT(*) as order_count,
  SUM(amount) as total_spent
FROM orders
WHERE amount >= 40                    -- Фильтруем СТРОКИ: исключаем заказы < 40
AND created_at >= '2024-01-01'        -- Фильтруем СТРОКИ: только за месяц
GROUP BY user_id                      -- Группируем по user
HAVING SUM(amount) > 500              -- Фильтруем ГРУППЫ: total > 500
ORDER BY total_spent DESC

Этапы выполнения:

1. Читаем все заказы из orders
2. WHERE: оставляем только amount >= 40 AND created_at >= '2024-01-01'
   Результат: заказы 1, 2, 3, 4 (исключён 5 за small amount? нет, date OK)
   Результат: заказы 1, 2, 3, 4
3. GROUP BY user_id: группируем
   user_id=10: [1, 2] (total=1050)
   user_id=20: [3, 4] (total=830)
   user_id=30: нет (заказ 5 отфильтрован)
4. HAVING: берём только group where SUM(amount) > 500
   user_id=10: 1050 > 500 ✅
   user_id=20: 830 > 500 ✅
5. SELECT: выбираем нужные столбцы
6. ORDER BY: сортируем

Результат:

user_id | order_count | total_spent
10 | 2 | 1050
20 | 2 | 830

Ошибка: использование агрегирующей функции в WHERE

-- ❌ НЕПРАВИЛЬНО:
SELECT user_id, COUNT(*) as orders
FROM orders
WHERE COUNT(*) > 5    -- ❌ ОШИБКА!
GROUP BY user_id

-- Почему? COUNT() вычисляется ПОСЛЕ GROUP BY, а WHERE работает ДО

Правильный вариант:

-- ✅ ПРАВИЛЬНО:
SELECT user_id, COUNT(*) as orders
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 5    -- ✅ HAVING работает с агрегирующими функциями

Сложный пример с несколькими условиями

Данные о комментариях:

id | user_id | post_id | likes | created_at
1 | 10 | 100 | 5 | 2024-01-15
2 | 10 | 101 | 2 | 2024-01-16
3 | 20 | 100 | 10 | 2024-01-14
4 | 20 | 102 | 3 | 2024-01-17
5 | 30 | 103 | 1 | 2024-01-10

Задача: Найти авторов, которые:

  • Писали комментарии начиная с 2024-01-15
  • С рейтингом не менее 3
  • Которые получили в сумме больше 10 лайков
SELECT 
  user_id,
  COUNT(*) as comment_count,
  SUM(likes) as total_likes,
  AVG(likes) as avg_likes
FROM comments
WHERE created_at >= '2024-01-15'      -- Фильтруем по дате
AND likes >= 3                          -- Фильтруем по рейтингу
GROUP BY user_id                        -- Группируем авторов
HAVING SUM(likes) > 10                  -- Фильтруем по общему лайкам
ORDER BY total_likes DESC

Результат:

user_id | comment_count | total_likes | avg_likes
10 | 2 | 7 | 3.5      -- НЕ прошёл HAVING (7 < 10)
20 | 2 | 13 | 6.5     -- ПРОШЁЛ (13 > 10)

Факт: только user_id=20 попадает в результат, потому что:

  • 2 комментария начиная с 15-го
  • Оба рейтинг >= 3 (10 и 3)
  • Общо 13 лайков > 10

Таблица сравнения

АспектGROUP BYHAVING
Когда работаетДо агрегацииПосле агрегации
Что фильтруетСтрокиГруппы
Агрегирующие функцииНельзя в WHEREМожно и нужно
ПримерWHERE amount > 10 THEN GROUP BYGROUP BY THEN HAVING SUM(amount) > 100
ORDERРаньшеПозже

Исторический контекст

В старых версиях SQL (SQL-86):

  • WHERE только для фильтрации строк
  • Нужен был новый оператор для фильтрации групп
  • Так появился HAVING

Поэтому сегодня:

  • WHERE = фильтрация строк
  • HAVING = фильтрация групп

Performance совет

Лучше использовать WHERE вместо HAVING когда возможно, потому что:

  • WHERE работает ДО GROUP BY (меньше строк для группировки)
  • HAVING работает ПОСЛЕ GROUP BY (больше вычислений)

Пример:

-- Медленнее (HAVING фильтрует после группировки):
SELECT user_id, SUM(amount)
FROM orders
GROUP BY user_id
HAVING created_at >= '2024-01-01'

-- Быстрее (WHERE фильтрует до группировки):
SELECT user_id, SUM(amount)
FROM orders
WHERE created_at >= '2024-01-01'
GROUP BY user_id

Заключение

Помни простое правило:

  • WHERE — фильтруем СТРОКИ (работает с простыми условиями)
  • HAVING — фильтруем ГРУППЫ (работает с агрегирующими функциями)

Это unterschедение критично для написания правильных и эффективных SQL запросов. На собеседовании это одна из частых вопросов для проверки глубины знания SQL.