Собесов

Delivery: SQL — выручка по городам и клиентам с порядковым номером заказа

SQLОконные функцииСредняяMiddle

Условие

Дана таблица orders(cityname, date, clientid, orderid, sales) — заказы за начало 2022 г. в нескольких городах. Решите:

  1. Суммарная выручка по городам и дням с разбивкой и накопительным итогом за период.
  2. Для каждого клиента — порядковый номер его заказа (1-й, 2-й, ...).
  3. 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;

Подводные камни

  1. SUM() OVER (...) с ORDER BY без ROWS BETWEEN по умолчанию — это RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, что в большинстве СУБД эквивалентно. Но в Postgres это может усложнить план — фиксируйте ROWS BETWEEN.
  2. Если клиент впервые в этой выгрузке, но был ранее (вне периода) — rn = 1 ложно идентифицирует «первого». Нужна более ранняя история.
  3. AOV считается как SUM/COUNT(orders) или AVG(sales) — отличаются, если есть отрицательные / нулевые суммы.
  4. В одной строке может быть sales = NULL — суммирование пропустит, средние смещают.

Эталонный ответ

Window-функции: SUM() OVER ORDER BY date для накопленной выручки; ROW_NUMBER() OVER (PARTITION BY clientid ORDER BY date) для порядка заказа клиента.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти