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

SQL запрос с GROUP BY и HAVING

2.2 Middle🔥 121 комментариев
#Базы данных и SQL

Условие

Дана таблица orders с колонками: id, customer_id, amount, order_date. Напишите SQL запрос, который находит всех клиентов, сделавших более 5 заказов на общую сумму более 1000.

Ожидаемый результат

customer_id, количество заказов, общая сумма.

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

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

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

Решение: SQL GROUP BY и HAVING

Описание задачи

Нужно найти клиентов, которые:

  • Сделали более 5 заказов (count > 5)
  • На общую сумму более 1000 (sum > 1000)

Это классическая задача агрегирования данных в SQL. Используем GROUP BY для группировки по customer_id и HAVING для фильтрации агрегатных функций.

Базовое решение

SELECT 
    customer_id,
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 5 AND SUM(amount) > 1000
ORDER BY total_amount DESC;

Объяснение:

  • GROUP BY customer_id — группируем заказы по каждому клиенту
  • COUNT(*) — считаем количество заказов в группе
  • SUM(amount) — суммируем сумму заказов в группе
  • HAVING COUNT(*) > 5 AND SUM(amount) > 1000 — фильтруем группы
  • ORDER BY total_amount DESC — сортируем по общей сумме (опционально)

Различие между WHERE и HAVING

WHERE: фильтрует строки ПЕРЕД группировкой HAVING: фильтрует ГРУППЫ ПОСЛЕ агрегирования

-- НЕПРАВИЛЬНО: WHERE не работает с COUNT
SELECT customer_id, COUNT(*) as cnt
FROM orders
WHERE COUNT(*) > 5  -- ❌ ОШИБКА
GROUP BY customer_id;

-- ПРАВИЛЬНО: используем HAVING
SELECT customer_id, COUNT(*) as cnt
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > 5;  -- ✓ Правильно

Оптимизированное решение

SELECT 
    customer_id,
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount
FROM orders
WHERE amount > 0  -- Фильтруем невалидные суммы
GROUP BY customer_id
HAVING COUNT(*) > 5 AND SUM(amount) > 1000
ORDER BY total_amount DESC
LIMIT 100;  -- Ограничиваем результаты

Полное решение с дополнительной информацией

SELECT 
    c.customer_id,
    c.customer_name,
    COUNT(o.id) AS order_count,
    SUM(o.amount) AS total_amount,
    AVG(o.amount) AS avg_amount,
    MIN(o.order_date) AS first_order,
    MAX(o.order_date) AS last_order
FROM customers c
INNER JOIN orders o ON c.id = o.customer_id
GROUP BY c.customer_id, c.customer_name
HAVING COUNT(o.id) > 5 AND SUM(o.amount) > 1000
ORDER BY total_amount DESC;

Альтернатива с подзапросом

SELECT 
    customer_id,
    order_count,
    total_amount
FROM (
    SELECT 
        customer_id,
        COUNT(*) AS order_count,
        SUM(amount) AS total_amount
    FROM orders
    GROUP BY customer_id
) aggregated
WHERE order_count > 5 AND total_amount > 1000
ORDER BY total_amount DESC;

Шаг за шагом выполнение

Входные данные:

orders таблица:
id | customer_id | amount | order_date
1  | 100         | 150    | 2024-01-01
2  | 100         | 200    | 2024-01-02
3  | 100         | 180    | 2024-01-03
4  | 100         | 220    | 2024-01-04
5  | 100         | 190    | 2024-01-05
6  | 100         | 160    | 2024-01-06
7  | 100         | 175    | 2024-01-07
8  | 200         | 100    | 2024-01-01
...

Шаг 1: GROUP BY создаёт группы

customer_id | orders
100         | [150, 200, 180, 220, 190, 160, 175, ...]
200         | [100, ...]
...

Шаг 2: Агрегирование

customer_id | COUNT(*) | SUM(amount)
100         | 8        | 1435
200         | 2        | 150
...

Шаг 3: HAVING фильтрация

Отбираем только где COUNT(*) > 5 И SUM > 1000:
customer_id | COUNT(*) | SUM(amount)
100         | 8        | 1435
...

Типичные ошибки

  1. Забыли HAVING: используешь WHERE вместо HAVING
-- ❌ НЕПРАВИЛЬНО
SELECT customer_id, COUNT(*) cnt
FROM orders
WHERE COUNT(*) > 5  -- Ошибка!
GROUP BY customer_id;
  1. Не указали GROUP BY: агрегирование без группировки
-- ❌ НЕПРАВИЛЬНО
SELECT customer_id, COUNT(*)
FROM orders;  -- Должен быть GROUP BY!
  1. Неправильный синтаксис: используешь колонки вне GROUP BY
-- ❌ НЕПРАВИЛЬНО (в PostgreSQL)
SELECT customer_id, order_date, COUNT(*)
FROM orders
GROUP BY customer_id;  -- order_date не в GROUP BY!

Оптимизация для больших таблиц

-- Индекс для быстрого поиска
CREATE INDEX idx_orders_customer ON orders(customer_id, amount);

-- Для очень больших таблиц — партиционирование
CREATE TABLE orders (
    id INT,
    customer_id INT,
    amount DECIMAL,
    order_date DATE
) PARTITION BY RANGE YEAR(order_date) (
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p2025 VALUES LESS THAN (2026)
);

Практическое применение в QA Automation

  • Анализ поведения пользователей: найти VIP клиентов для тестирования
  • Валидация бизнес-логики: проверить корректность подсчётов
  • Поиск аномалий: найти клиентов с необычным поведением покупок
  • Загрузочное тестирование: готовить тестовые данные с определёнными критериями
  • Регрессионное тестирование: проверить что агрегирование работает правильно

Запрос для проверки в тестах

-- Проверим результат
SELECT * FROM (
    SELECT 
        customer_id,
        COUNT(*) AS order_count,
        SUM(amount) AS total_amount
    FROM orders
    GROUP BY customer_id
    HAVING COUNT(*) > 5 AND SUM(amount) > 1000
) result
WHERE order_count > 5  -- Двойная проверка
AND total_amount > 1000;
SQL запрос с GROUP BY и HAVING | PrepBro