PARTITION BY SQL: шпаргалка
Зачем знать PARTITION BY
PARTITION BY — ключевая часть оконных функций в SQL. Она делит данные на группы (окна) для вычисления агрегата внутри каждого окна, сохраняя при этом все строки исходной таблицы.
Синтаксис
функция() OVER (PARTITION BY столбец ORDER BY столбец)PARTITION BY— делит строки на группы (окна).ORDER BY— сортирует строки внутри окна.- Оба опциональны, но обычно используются вместе.
Базовый пример
SELECT user_id, order_id, amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS order_num
FROM orders;Результат: каждому заказу пользователя присвоен номер от 1 до N (для него) — в порядке created_at.
PARTITION BY vs GROUP BY
| Критерий | GROUP BY | PARTITION BY |
|---|---|---|
| Сворачивает строки? | ✅ Да | ❌ Нет |
| Количество результатов | = числу групп | = числу строк исходной таблицы |
| Использование | с агрегатами | с оконными функциями |
| Синтаксис | в WHERE/SELECT | в OVER() |
Пример разницы:
-- GROUP BY — одна строка на пользователя
SELECT user_id, SUM(amount) FROM orders GROUP BY user_id;
-- PARTITION BY — все строки + агрегат в каждой
SELECT user_id, order_id, amount,
SUM(amount) OVER (PARTITION BY user_id) AS user_total
FROM orders;Второй вариант удобнее, когда нужен контекст по группе в каждой строке.
Попробовать силы на подобных вопросах проще всего в тренажёре Карьерник — прямо в Telegram, без регистрации через сайт.
Частые паттерны
1. Нарастающий итог по группе
SELECT user_id, order_id, amount,
SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total
FROM orders;Для каждого пользователя — нарастающая сумма по дате.
2. Доля в общей сумме группы
SELECT user_id, order_id, amount,
ROUND(amount * 100.0 / SUM(amount) OVER (PARTITION BY user_id), 2) AS pct
FROM orders;Процент каждого заказа от общей суммы пользователя.
3. Разница с предыдущим в группе
SELECT user_id, order_id, amount,
amount - LAG(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS diff_from_prev
FROM orders;Разница между текущим и предыдущим заказом того же пользователя.
4. Топ-N в группе
WITH ranked AS (
SELECT category, product_id, revenue,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC) AS rn
FROM product_stats
)
SELECT * FROM ranked WHERE rn <= 3;Топ-3 товара в каждой категории.
5. Среднее по группе без агрегации
SELECT user_id, order_id, amount,
amount - AVG(amount) OVER (PARTITION BY user_id) AS deviation_from_avg
FROM orders;Сколько этот заказ отличается от среднего данного пользователя.
Несколько столбцов в PARTITION BY
SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id, category ORDER BY created_at) AS rn
FROM orders JOIN products USING (product_id);Окна создаются по комбинации user_id + category. Например, у одного пользователя может быть 2 окна: «электроника» и «одежда».
PARTITION BY без ORDER BY
SELECT order_id, amount,
SUM(amount) OVER (PARTITION BY user_id) AS user_total
FROM orders;Без ORDER BY агрегат считается на всём окне, не нарастающий. Это именно то, что нужно для «общая сумма по группе в каждой строке».
ORDER BY без PARTITION BY
SELECT created_at, amount,
SUM(amount) OVER (ORDER BY created_at) AS running_total
FROM orders;Одно окно = вся таблица. Нарастающий итог по хронологии.
PARTITION BY в агрегатах
Не только в оконных функциях. Это стандартный приём в OVER():
SELECT city, user_id, orders_count,
MAX(orders_count) OVER (PARTITION BY city) AS city_max,
AVG(orders_count) OVER (PARTITION BY city) AS city_avg,
COUNT(*) OVER (PARTITION BY city) AS city_size
FROM user_stats;Все три агрегата на одном окне (city), без GROUP BY — строки сохраняются.
Пройти 30–50 задач по теме за вечер можно в Telegram-тренажёре. Это то, что отличает «знаю» от «уверенно отвечу на собесе».
Комбинирование с ROWS BETWEEN
SELECT user_id, day, amount,
AVG(amount) OVER (
PARTITION BY user_id ORDER BY day
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS ma_7d
FROM daily_orders;Скользящее среднее за 7 дней для каждого пользователя отдельно.
10 задач на PARTITION BY
1. Номер заказа пользователя
SELECT user_id, order_id,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS n
FROM orders;2. Первый заказ каждого пользователя
WITH t AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS rn
FROM orders
)
SELECT * FROM t WHERE rn = 1;3. Топ-3 в категории
WITH t AS (
SELECT category, product_id, revenue,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC) AS rn
FROM sales
)
SELECT * FROM t WHERE rn <= 3;4. Доля заказа в общей сумме группы
SELECT user_id, amount,
amount / SUM(amount) OVER (PARTITION BY user_id) AS share
FROM orders;5. Max значение в группе
SELECT user_id, amount,
MAX(amount) OVER (PARTITION BY user_id) AS max_in_group
FROM orders;6. Ранг пользователя по выручке в городе
SELECT city, user_id, revenue,
RANK() OVER (PARTITION BY city ORDER BY revenue DESC) AS city_rank
FROM user_stats;7. Предыдущий заказ того же пользователя
SELECT user_id, order_id, amount,
LAG(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS prev_amount
FROM orders;8. Среднее в группе без агрегации
SELECT user_id, amount,
AVG(amount) OVER (PARTITION BY user_id) AS user_avg
FROM orders;9. Нарастающий итог с обнулением каждый месяц
SELECT *,
SUM(amount) OVER (
PARTITION BY DATE_TRUNC('month', created_at)
ORDER BY created_at
) AS monthly_cumulative
FROM orders;10. Процент изменения от предыдущей строки
SELECT *,
(amount - LAG(amount) OVER (PARTITION BY user_id ORDER BY created_at)) * 100.0 /
NULLIF(LAG(amount) OVER (PARTITION BY user_id ORDER BY created_at), 0) AS pct_change
FROM orders;Как тренироваться
PARTITION BY учится вместе с оконными функциями. 90% задач на оконки содержат PARTITION BY.
Совет: на собесе, говоря про оконные функции, чётко проговаривайте «PARTITION BY делит данные на окна, ORDER BY задаёт порядок внутри окна, ROWS BETWEEN — рамку». Это три ключевых элемента, и их смешение — типичная ошибка.
Читайте также
- Оконные функции SQL: шпаргалка
- Задачи на оконные функции
- GROUP BY vs PARTITION BY
- GROUP BY шпаргалка
FAQ
PARTITION BY медленнее GROUP BY?
На бумаге нет — сложность та же. Но оконные запросы обычно возвращают больше строк (не сворачивают), поэтому общий объём результата больше. Если нужна просто агрегация — GROUP BY.
Можно ли несколько PARTITION BY в одном SELECT?
Да, каждая оконная функция может иметь свой OVER:
SELECT *,
SUM(x) OVER (PARTITION BY a) AS total_by_a,
AVG(x) OVER (PARTITION BY b) AS avg_by_b
FROM t;Чем PARTITION BY отличается от sub-partition в таблицах?
Это разные вещи. В оконных функциях PARTITION BY — логическое разбиение для расчёта. В DDL (CREATE TABLE ... PARTITION BY) — физическое разбиение таблицы.
Можно ли PARTITION BY без ORDER BY?
Да. Тогда агрегат считается на всём окне. Полезно для «общая сумма группы в каждой строке».