Задачи на GROUP BY и HAVING
Группировка·middle

Крупные категории: фильтр через HAVING

Задача

Финансовый директор хочет видеть только «локомотивы» каталога: «Не нужен весь список жанров — покажи только категории, которые принесли больше 4000 за всё время».

Верни название категории и её суммарную выручку, но только для категорий с выручкой выше 4000.

Схема данных

Учебная база проката dvdrental, та же цепочка, что и для выручки по жанрам:

  • payment(payment_id, rental_id, amount, ...) — платежи.
  • rental(rental_id, inventory_id, ...) — аренды.
  • inventory(inventory_id, film_id, ...) — копии дисков.
  • film_category(film_id, category_id) — фильм ↔ категория.
  • category(category_id, name) — справочник жанров.

Решение

SELECT cat.name AS category,
       sum(p.amount) AS revenue
FROM payment p
JOIN rental r USING (rental_id)
JOIN inventory i USING (inventory_id)
JOIN film_category fc USING (film_id)
JOIN category cat USING (category_id)
GROUP BY cat.name
HAVING sum(p.amount) > 4000
ORDER BY revenue DESC;

Разбор

Это та же выручка по категориям, что в соседней задаче, но теперь нужно отсечь мелкие жанры. И вот главный вопрос: почему фильтр стоит в HAVING, а не в WHERE?

Потому что мы фильтруем по агрегату — по SUM(p.amount), которого до группировки ещё не существует. Порядок такой: сначала JOIN и WHERE работают со строками, потом GROUP BY схлопывает их в группы, и только затем считаются агрегаты. WHERE выполняется раньше группировки — на этом этапе суммы по жанру ещё нет, и написать WHERE sum(p.amount) > 4000 нельзя, PostgreSQL выдаст ошибку «aggregate functions are not allowed in WHERE». HAVING же работает уже после группировки — по готовым суммам. Это ровно то, ради чего он придуман: фильтровать группы, а не строки. Полный разбор границы — в вопросе чем WHERE отличается от HAVING.

Практическое правило: WHERE — про отдельные строки (например, «только платежи 2007 года»), HAVING — про группы («только жанры с выручкой выше порога»). Они не конкурируют, а работают на разных этапах и часто стоят в одном запросе вместе: сначала WHERE сужает исходные строки, потом GROUP BY их собирает, потом HAVING отсекает слабые группы.

Обрати внимание, что в HAVING мы пишем сам агрегат sum(p.amount), а не алиас revenue: по стандарту SQL в HAVING алиасы из SELECT не видны (в отличие от ORDER BY, где revenue работает). PostgreSQL тут строг, так что повтор выражения — не лишняя перестраховка, а необходимость.

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

category | revenue
--- | ---
Sports | 4892.19
Sci-Fi | 4336.01
Animation | 4245.31
Drama | 4118.46
Comedy | 4002.48

Прорешай такие задачи сам

Читать разбор полезно, а настоящая прокачка — самому писать запросы и получать обратную связь. В симуляторе живая база и проверка запросов, лёгкий режим — бесплатно.

Прорешать в бесплатной песочнице

Смежные задачи

Вопросы по теме