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

Что такое HAVING?

1.3 Junior🔥 241 комментариев
#SQL и базы данных

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

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

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

Что такое HAVING в SQL: полное руководство

HAVING — это один из самых неправильно понимаемых clauses в SQL. Многие путают его с WHERE. На самом деле, это разные инструменты с разными целями.

Основное определение

HAVING — это clause SQL, который фильтрует группы данных (результаты GROUP BY), а не отдельные строки. WHERE фильтрует строки перед группировкой, HAVING фильтрует группы после.

WHERE vs HAVING: ключевая разница

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

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

Как запомнить: WHERE это фильтр на входе (строки), HAVING это фильтр на выходе (группы).

Пример 1: WHERE vs HAVING

-- WHERE: фильтруем ДО группировки
SELECT 
    department,
    AVG(salary) as avg_salary,
    COUNT(*) as employee_count
FROM employees
WHERE hire_date >= '2020-01-01'  -- Только нанятые после 2020
GROUP BY department;

Здесь WHERE отсекает сотрудников, нанятых до 2020. Потом считаем по отделам.

-- HAVING: фильтруем ПОСЛЕ группировки
SELECT 
    department,
    AVG(salary) as avg_salary,
    COUNT(*) as employee_count
FROM employees
GROUP BY department
HAVING COUNT(*) >= 10;  -- Только отделы с 10+ сотрудниками

Здесь HAVING отсекает отделы, в которых меньше 10 человек.

Пример 2: объединяем WHERE и HAVING

Это частый паттерн: WHERE отсекает ненужные строки, HAVING отсекает ненужные группы.

SELECT 
    customer_id,
    COUNT(*) as order_count,
    SUM(total_amount) as total_spent,
    AVG(total_amount) as avg_order
FROM orders
WHERE order_date >= '2024-01-01'  -- Только заказы этого года (WHERE)
GROUP BY customer_id
HAVING 
    COUNT(*) >= 5                   -- Минимум 5 заказов (HAVING)
    AND SUM(total_amount) > 10000;  -- Потратил больше 10K (HAVING)

Здесь:

  • WHERE отсекает заказы прошлых лет
  • GROUP BY группирует по клиентам
  • HAVING оставляет только клиентов с 5+ заказами И потратившими >10K

Пример 3: агрегатные функции в HAVING

В HAVING используются агрегатные функции (COUNT, SUM, AVG, MAX, MIN):

-- Найти категории товаров, где среднее количество товара в заказе > 100
SELECT 
    category,
    COUNT(DISTINCT product_id) as product_count,
    AVG(quantity) as avg_quantity_per_order
FROM order_items oi
JOIN products p ON oi.product_id = p.id
GROUP BY category
HAVING AVG(quantity) > 100;  -- Только категории со средним > 100

Практический пример: анализ активности пользователей

-- Найти пользователей, которые:
-- 1. Зарегистрировались до 2023 года
-- 2. Имеют 10+ чекинов
-- 3. Их средний rating > 4.5
-- 4. Чекинов в последний месяц >= 2

SELECT 
    u.user_id,
    u.username,
    COUNT(*) as total_checkins,
    AVG(c.rating) as avg_rating,
    COUNT(CASE WHEN c.checkin_date >= NOW() - INTERVAL '1 month' THEN 1 END) as recent_checkins
FROM users u
JOIN checkins c ON u.user_id = c.user_id
WHERE u.signup_date < '2023-01-01'  -- WHERE: фильтруем пользователей
GROUP BY u.user_id, u.username
HAVING 
    COUNT(*) >= 10                                          -- Минимум 10 чекинов
    AND AVG(c.rating) > 4.5                                 -- Рейтинг выше 4.5
    AND COUNT(CASE WHEN c.checkin_date >= NOW() - INTERVAL '1 month' THEN 1 END) >= 2
ORDER BY total_checkins DESC;

Почему нельзя использовать WHERE вместо HAVING

-- НЕПРАВИЛЬНО: WHERE не знает про COUNT(*)
SELECT department, COUNT(*) as emp_count
FROM employees
WHERE COUNT(*) > 5  -- ОШИБКА!
GROUP BY department;
-- Error: aggregate functions not allowed in WHERE clause

Почему? Когда выполняется WHERE, GROUP BY еще не произошел, поэтому COUNT(*) еще не вычислен.

-- ПРАВИЛЬНО: HAVING видит результаты COUNT(*)
SELECT department, COUNT(*) as emp_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;  -- Работает!

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

-- Найти продукты с высоким спросом, но низкой прибылью
SELECT 
    p.product_id,
    p.product_name,
    COUNT(oi.id) as times_ordered,
    SUM(oi.quantity) as total_quantity,
    AVG(p.margin_percent) as avg_margin,
    SUM(oi.quantity * p.price) as total_revenue
FROM products p
JOIN order_items oi ON p.product_id = oi.product_id
WHERE p.is_active = true  -- Только активные товары (WHERE)
GROUP BY p.product_id, p.product_name
HAVING 
    COUNT(oi.id) > 100              -- Заказан >100 раз
    AND AVG(p.margin_percent) < 10  -- Но маржа < 10%
    AND SUM(oi.quantity * p.price) > 100000  -- Выручка > 100K
ORDER BY times_ordered DESC;

Производительность: WHERE быстрее, чем HAVING

Это критично при работе с большими данными:

-- МЕДЛЕННО: HAVING фильтрует ПОСЛЕ группировки
SELECT user_id, COUNT(*) as events
FROM events
GROUP BY user_id
HAVING user_id NOT IN (SELECT user_id FROM banned_users);  -- Фильтруем 1M групп
-- БЫСТРО: WHERE фильтрует ДО группировки
SELECT user_id, COUNT(*) as events
FROM events
WHERE user_id NOT IN (SELECT user_id FROM banned_users)  -- Фильтруем 100M строк
GROUP BY user_id;

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

Чек-лист: когда использовать HAVING

Используй HAVING, если:

  • Нужно фильтровать по агрегатной функции (COUNT, SUM, AVG, MAX, MIN)
  • Нужно фильтровать результаты группировки
  • Условие зависит от других строк в группе

НЕ используй HAVING, если:

  • Фильтруешь исходные данные (используй WHERE)
  • Не используешь GROUP BY
  • Условие не зависит от группировки

Ошибка: HAVING без GROUP BY

-- НЕПРАВИЛЬНО: HAVING без GROUP BY — что это значит?
SELECT user_id, created_at
FROM users
HAVING created_at > '2024-01-01';  -- Не имеет смысла

В этом случае используй WHERE:

-- ПРАВИЛЬНО:
SELECT user_id, created_at
FROM users
WHERE created_at > '2024-01-01';

Реальный пример: аналитический отчет

-- Отчет: продажи по категориям за последний квартал
SELECT 
    DATE_TRUNC('month', o.order_date) as month,
    p.category,
    COUNT(DISTINCT o.order_id) as order_count,
    COUNT(oi.id) as item_count,
    SUM(oi.quantity * oi.price) as total_sales,
    AVG(oi.price) as avg_item_price,
    STDDEV(oi.quantity) as qty_stddev
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.order_date >= DATE_TRUNC('quarter', NOW()) - INTERVAL '3 months'
  AND o.status = 'completed'
GROUP BY DATE_TRUNC('month', o.order_date), p.category
HAVING 
    COUNT(DISTINCT o.order_id) >= 50        -- Минимум 50 заказов в категории
    AND SUM(oi.quantity * oi.price) > 50000  -- Выручка > 50K
    AND AVG(oi.price) > 100                   -- Средняя цена > 100
ORDER BY month DESC, total_sales DESC;

Итоговая шпаргалка

AspectWHEREHAVING
Когда выполняетсяДо GROUP BYПосле GROUP BY
Что фильтруетСтрокиГруппы
АгрегатыНетДа
ПроизводительностьЧасто быстрееМедленнее для больших данных
Требует GROUP BYНетДа (обычно)

Главный вывод

HAVING — это инструмент для фильтрации агрегированных данных. Используй его, когда нужно выбрать только те группы, которые соответствуют определённым условиям по агрегатным функциям. Для фильтрации исходных строк всегда используй WHERE — это и быстрее, и понятнее.