Условие
Дана таблица orders(cityname, date, clientid, orderid, sales) — заказы за начало 2022 г. в нескольких городах. Решите:
- Суммарная выручка по городам и дням с разбивкой и накопительным итогом за период.
- Для каждого клиента — порядковый номер его заказа (1-й, 2-й, ...).
- AOV (средний чек) по городам и доля «первого заказа» в выручке.
Решение
1. Выручка по дням с накоплением
SELECT
cityname,
date,
SUM(sales) AS day_sales,
SUM(SUM(sales)) OVER (
PARTITION BY cityname
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cum_sales
FROM orders
GROUP BY cityname, date
ORDER BY cityname, date;2. Порядковый номер заказа
SELECT
clientid,
orderid,
date,
ROW_NUMBER() OVER (PARTITION BY clientid ORDER BY date, orderid) AS order_no
FROM orders;3. AOV и доля первого заказа
WITH numbered AS (
SELECT
cityname, clientid, orderid, sales, date,
ROW_NUMBER() OVER (PARTITION BY clientid ORDER BY date, orderid) AS rn
FROM orders
)
SELECT
cityname,
AVG(sales) AS aov,
SUM(CASE WHEN rn = 1 THEN sales ELSE 0 END) AS first_order_revenue,
SUM(sales) AS total_revenue,
ROUND(100.0 * SUM(CASE WHEN rn = 1 THEN sales ELSE 0 END)
/ NULLIF(SUM(sales), 0), 2) AS first_order_share_pct
FROM numbered
GROUP BY cityname
ORDER BY total_revenue DESC;Подводные камни
SUM() OVER (...)сORDER BYбезROWS BETWEENпо умолчанию — этоRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, что в большинстве СУБД эквивалентно. Но в Postgres это может усложнить план — фиксируйтеROWS BETWEEN.- Если клиент впервые в этой выгрузке, но был ранее (вне периода) —
rn = 1ложно идентифицирует «первого». Нужна более ранняя история. - AOV считается как
SUM/COUNT(orders)илиAVG(sales)— отличаются, если есть отрицательные / нулевые суммы. - В одной строке может быть
sales = NULL— суммирование пропустит, средние смещают.
Эталонный ответ
Window-функции: SUM() OVER ORDER BY date для накопленной выручки; ROW_NUMBER() OVER (PARTITION BY clientid ORDER BY date) для порядка заказа клиента.