Условие
Таблицы:
Orders(ID, Date, ClientID)— заказ.OrdersState(ID, Date, StateID)— история статусов (0=принят, 2=отмена, 3=в доставке, 4=доставлен, 5=возврат). Текущий статус — последний.Clients(ClientID, FIO).
Задачи:
- Доля заказов со статусом «возврат» и «отмена» от общего числа принятых.
- Среднее количество дней между первым и вторым заказом пользователей. Без
JOIN-ов. - ФИО и количество заказов клиента, у которого их больше всего.
Решение
1. Доля возвратов и отмен
«Текущий статус» = последний по Date. Используем оконку:
WITH last_state AS (
SELECT
ID,
StateID,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Date DESC) AS rn
FROM OrdersState
),
final AS (
SELECT ID, StateID FROM last_state WHERE rn = 1
)
SELECT
100.0 * SUM(CASE WHEN StateID = 5 THEN 1 ELSE 0 END) / COUNT(*) AS pct_return,
100.0 * SUM(CASE WHEN StateID = 2 THEN 1 ELSE 0 END) / COUNT(*) AS pct_cancel
FROM final;2. Средний интервал «первый → второй заказ» без JOIN
SELECT AVG(EXTRACT(EPOCH FROM (second_date - first_date))/86400) AS avg_days
FROM (
SELECT
ClientID,
MIN(CASE WHEN rn = 1 THEN Date END) AS first_date,
MIN(CASE WHEN rn = 2 THEN Date END) AS second_date
FROM (
SELECT
ClientID, Date,
ROW_NUMBER() OVER (PARTITION BY ClientID ORDER BY Date) AS rn
FROM Orders
) t
WHERE rn IN (1, 2)
GROUP BY ClientID
) x
WHERE second_date IS NOT NULL;Альтернатива через LAG:
SELECT AVG(diff) AS avg_days FROM (
SELECT EXTRACT(DAY FROM (Date - LAG(Date) OVER (PARTITION BY ClientID ORDER BY Date))) AS diff,
ROW_NUMBER() OVER (PARTITION BY ClientID ORDER BY Date) AS rn
FROM Orders
) t WHERE rn = 2;3. Самый активный клиент
SELECT c.FIO, cnt
FROM (
SELECT ClientID, COUNT(*) AS cnt
FROM Orders
GROUP BY ClientID
ORDER BY cnt DESC
LIMIT 1
) t JOIN Clients c ON c.ClientID = t.ClientID;С учётом ничьей:
WITH r AS (
SELECT ClientID, COUNT(*) AS cnt,
RANK() OVER (ORDER BY COUNT(*) DESC) AS rk
FROM Orders
GROUP BY ClientID
)
SELECT c.FIO, r.cnt FROM r JOIN Clients c USING(ClientID) WHERE rk = 1;Подводные камни
- «Принятые» в задаче 1 — может быть
всего заказов(любой статус), а не толькоstate=0. Уточнить. - У клиента только один заказ →
second_date IS NULL, не учитываем; иначеAVGбудет сдвинут. - Запрос 2 «без JOIN» — выполнен через подзапрос с
ROW_NUMBER/LAG. - Возвраты и отмены могут пересекаться в истории; финальный статус — единственный валидный.
Эталонный ответ
1) Финальный статус через ROW_NUMBER() ... ORDER BY Date DESC → SUM(CASE)/COUNT. 2) Pivot 1-го и 2-го заказа через ROW_NUMBER. 3) GROUP BY ClientID + LIMIT 1 (или RANK=1).