← Назад к списку
ПрограммированиеBackendMiddle

Даны таблицы orders (id, customer_id, amount, created_at) и customers (id, name). Напишите запрос: топ-5 клиентов по сумме заказов за последние 30 дней, включая число заказов.

Короткий ответ

  • JOIN customers и orders по customer_id
  • Фильтр по дате в WHERE до агрегации
  • GROUP BY по клиенту, SUM и COUNT как агрегаты
  • Сортировка по сумме убыванием, LIMIT 5
  • Условия на агрегаты шли бы в HAVING, не в WHERE
  • Индекс по (customer_id, created_at) ускорит выборку

Классическая связка JOIN + WHERE по дате + GROUP BY + ORDER BY + LIMIT; важно не путать WHERE и HAVING.

Как сказать вслух

пример ответа

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

Подробный ответ

Основной ответ

Задача проверяет базовую механику SQL: порядок логического выполнения (FROM/JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT), понимание, что фильтр по дате — это условие на строки и должен стоять в WHERE, а условия на результаты агрегатов — в HAVING. GROUP BY должен включать все неагрегированные колонки из SELECT (c.id, c.name); группировка по c.id корректна, так как name функционально зависит от первичного ключа. INNER JOIN здесь уместен: клиенты без заказов в топ не попадут по определению; если бы требовалось показать всех клиентов, понадобился бы LEFT JOIN с COALESCE(SUM(amount), 0). Для производительности на большой таблице нужен индекс по (customer_id, created_at) или хотя бы по created_at — иначе фильтр за 30 дней обернётся полным сканом.

Ключевые моменты

  • WHERE vs HAVING. WHERE фильтрует строки до группировки, HAVING — группы после. Фильтр по дате в HAVING формально сработает, но семантически неверен.
  • GROUP BY и SELECT. Каждая колонка SELECT либо агрегат, либо в GROUP BY; группировка по первичному ключу покрывает зависимые колонки.
  • Выбор JOIN. INNER отбрасывает клиентов без заказов — для топа это правильно; на вопрос «а все клиенты с нулями?» ответ — LEFT JOIN.

Практический контекст

Такой запрос — хлеб повседневной работы: отчёты, админки, дашборды. На собеседовании его дают, чтобы за пять минут увидеть, пишет ли кандидат SQL руками или только через ORM. Частые дополнительные вопросы: куда поставить условие на сумму (HAVING), какой индекс поможет, как изменится запрос, если нужны и клиенты без заказов.

Пример кода

SELECT c.id,
       c.name,
       SUM(o.amount)  AS total_amount,
       COUNT(o.id)    AS orders_count
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.created_at >= now() - INTERVAL '30 days'
GROUP BY c.id, c.name
ORDER BY total_amount DESC
LIMIT 5;

Частые ошибки

  • Ставят фильтр по дате в HAVING или условие на SUM в WHERE
  • Забывают включить неагрегированные колонки в GROUP BY
  • Используют LEFT JOIN там, где он не нужен, или не могут объяснить выбор

ИП Кочкин Алексей Сергеевич · ИНН 390509026279 · ОГРНИП 325390000030973 · jiniys2005@yandex.ru