← все задачи
SQL · задача 2 из 10
Топ-3 товара в каждой категории
Средний
15 минут
оконные функцииPARTITION BYROW_NUMBER
Условие
Таблица products (id, category_id, name, sales). Верните по три самых продаваемых товара в каждой категории.
Что требуется
- Ровно три товара на категорию (или все, если их меньше)
- Порядок внутри категории — по убыванию продаж
- Одним запросом, без циклов на стороне приложения
Пример
category name sales Ноутбуки A 100 Ноутбуки B 90 Ноутбуки C 80 Ноутбуки D 70 <- не попадает Телефоны E 50
Сначала уточните
Вопросы до кода — половина оценки. Молча начать печатать хуже, чем задать два вопроса.
- Что делать при равных продажах на границе: взять всех или ровно три?
- Нужны ли категории, в которых товаров нет вовсе?
- Какая версия СУБД — есть ли оконные функции (MySQL до 8.0 их не знает)?
Показать решение Скрыть решение
Решение
WITH ranked AS (
SELECT
p.id,
p.category_id,
p.name,
p.sales,
ROW_NUMBER() OVER (
PARTITION BY p.category_id
ORDER BY p.sales DESC, p.id
) AS position
FROM products p
)
SELECT category_id, name, sales
FROM ranked
WHERE position <= 3
ORDER BY category_id, position;
Почему так
Почему нельзя просто GROUP BY
- GROUP BY схлопывает группу в одну строку: он даёт максимум продаж, но не сам товар и тем более не три товара
- Оконная функция считает по группе, но не схлопывает строки — каждая строка остаётся и получает свой номер
- Это ключевое различие, которое и проверяют задачей
Почему фильтр в отдельном уровне
- В WHERE оконную функцию использовать нельзя: WHERE отрабатывает до окон
- Поэтому нумерацию делают в CTE или подзапросе, а фильтр — уровнем выше
- Порядок выполнения FROM → WHERE → GROUP BY → окна → SELECT → ORDER BY стоит уметь проговорить
Почему в ORDER BY окна есть p.id
- При равных продажах порядок между строками не определён, и результат может меняться от запуска к запуску
- Уникальное поле вторым ключом делает нумерацию детерминированной
- Если по условию нужны все при ничьей — вместо ROW_NUMBER берут RANK
Что спросят дальше
- Спросят, как это сделать без оконных функций: коррелированный подзапрос со счётчиком «сколько товаров лучше этого»
- Спросят про производительность: индекс (category_id, sales DESC) позволяет брать верхушку каждой группы без полной сортировки
- Спросят про LATERAL JOIN — в Postgres это часто самый быстрый способ взять топ-N по группе
Следующая задача
Найти и удалить дубликаты — Данные приехали дважды: нужно найти повторы и удалить лишние, оставив по одной записи.