187 lines
7.0 KiB
SQL
187 lines
7.0 KiB
SQL
--task 1
|
|
--Задача 1. Нумерация заказов пользователя
|
|
|
|
SELECT
|
|
o.user_id AS user_id,
|
|
u.name AS user_name,
|
|
o.id AS order_id,
|
|
o.order_date AS order_date,
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY o.user_id
|
|
ORDER BY o.order_date
|
|
) AS order_number_for_user
|
|
FROM orders o
|
|
JOIN users u ON o.user_id = u.id;
|
|
|
|
/*
|
|
В чём отличие такой нумерации от простой сортировки ORDER BY order_date без оконной функции?
|
|
|
|
Здесь функция работает внутри определенных строк, в данном случае имени.
|
|
*/
|
|
|
|
|
|
--task 2
|
|
--Задача 2. Накопительная выручка по пользователю
|
|
SELECT
|
|
o.user_id AS user_id,
|
|
u.name AS user_name,
|
|
o.id AS order_id,
|
|
o.order_date,
|
|
oi.quantity * oi.price_at_purchase AS order_revenue,
|
|
SUM(oi.quantity * oi.price_at_purchase) OVER (
|
|
PARTITION BY o.user_id
|
|
ORDER BY o.order_date
|
|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
) AS running_revenue_for_user
|
|
FROM orders o
|
|
JOIN order_items oi ON oi.order_id = o.id
|
|
JOIN users u ON u.id = o.user_id;
|
|
|
|
|
|
/*
|
|
Почему для накопительной суммы важно задавать рамку окна (от «самого первого» до «текущего»), а не оставлять значения по умолчанию?
|
|
Если не задавать рамку окна, то не будет накопительного эффекта, будет просто считаться сумма по строке. И то на самом деле я до конца не понял как она считается, как-будто сумма заказа транслируется на строку с частью заказа.
|
|
|
|
Как по такой таблице понять, что пользователь начал тратить больше?
|
|
Можно дополнительно сделать столбец разности текущего значения от предыдущего и будет видно разницу или же сделать столбец по отклонению от среднего. Правильно мыслю?
|
|
*/
|
|
|
|
--task 3
|
|
--Задача 3. Рейтинг пользователей по выручке
|
|
WITH total AS (
|
|
SELECT
|
|
o.user_id,
|
|
SUM(quantity * price_at_purchase) AS total_revenue
|
|
FROM order_items oi
|
|
JOIN orders o ON o.id = oi.order_id
|
|
GROUP BY o.user_id
|
|
)
|
|
|
|
SELECT
|
|
u.name AS user_name,
|
|
total_revenue,
|
|
RANK() OVER (
|
|
ORDER BY total_revenue
|
|
) AS revenue_rank,
|
|
DENSE_RANK() OVER (
|
|
ORDER BY total_revenue
|
|
) AS dense_revenue_rank
|
|
FROM users u
|
|
JOIN total t ON u.id = t.user_id;
|
|
/*
|
|
Чем RANK отличается от DENSE_RANK в случае, когда у двух пользователей одинаковая выручка?
|
|
|
|
При RANK будет одно одинаковое место, при dense будут разные ранги.
|
|
*/
|
|
|
|
--task 4
|
|
--Задача 4. Первый и последний заказ пользователя
|
|
--task 4
|
|
--Задача 4. Первый и последний заказ пользователя
|
|
|
|
SELECT
|
|
u.name AS user_name,
|
|
o.id AS order_id,
|
|
o.order_date AS order_date,
|
|
first_value(o.order_date) OVER (
|
|
PARTITION BY o.user_id
|
|
ORDER BY o.order_date
|
|
) AS first_order_date,
|
|
last_value(o.order_date) OVER (
|
|
PARTITION BY o.user_id
|
|
ORDER BY o.order_date
|
|
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
|
|
) AS last_order_date
|
|
FROM orders o
|
|
JOIN users u ON u.id = o.user_id;
|
|
|
|
/*
|
|
Как можно использовать поля «первый заказ» и «последний заказ» в сегментации клиентов?
|
|
Можно понять единый ли это заказ или нет. Если единый, то соответственно, других заказов не было и сравнить с текущей датой и понять давно ли вообще была активность.
|
|
|
|
Почему для правильного LAST_VALUE важно задать рамку окна так, чтобы смотреть на всю историю пользователя, а не только до текущей строки?
|
|
Потому что если не задавать рамку, то функция выдаст предыдущее значение от текущей строки.
|
|
*/
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
--task 5
|
|
--Задача 5. Заказы и количество заказов пользователя (без GROUP BY)
|
|
SELECT
|
|
u.name AS user_name,
|
|
o.id AS order_id,
|
|
count(*) OVER (
|
|
PARTITION BY u.name
|
|
) AS orders_per_user
|
|
FROM users u
|
|
JOIN orders o ON o.user_id = u.id;
|
|
|
|
/*
|
|
Чем такой результат отличается от классического отчёта «по пользователям» с GROUP BY user_id?
|
|
GROUP BY уменьшил бы количество строк.
|
|
*/
|
|
|
|
--task 6
|
|
--Задача 6. Сравнение суммы заказа с предыдущим
|
|
WITH revenue AS (
|
|
SELECT
|
|
o.id AS order_id,
|
|
o.user_id AS user_id,
|
|
o.order_date AS order_date,
|
|
SUM(oi.quantity * oi.price_at_purchase) AS order_revenue
|
|
FROM orders o
|
|
JOIN order_items oi ON oi.order_id = o.id
|
|
GROUP BY o.id, o.user_id, o.order_date
|
|
)
|
|
|
|
SELECT
|
|
u.name,
|
|
order_id,
|
|
order_date,
|
|
order_revenue,
|
|
LAG(order_revenue) OVER (PARTITION BY user_id ORDER BY order_date) AS prev_order_revenue
|
|
FROM revenue r
|
|
JOIN users u ON r.user_id = u.id;
|
|
|
|
/*
|
|
Как по такому результату понять, увеличивается или уменьшается средний чек с течением времени?
|
|
Можно дополнительно создать еще один столбец в котором будем получать разность текущего и предыдущего заказов, а на основе этого уже делать выводы.
|
|
А по такому запросы - думаю наглядно видно в таблице какая сумма больше, а какая меньше.
|
|
*/
|
|
|
|
--task 7
|
|
--Задача 7. Категории с накопительной выручкой по времени
|
|
WITH category_date_revenue AS (
|
|
SELECT
|
|
p.category,
|
|
o.order_date::date AS order_date,
|
|
SUM(oi.price_at_purchase * oi.quantity) AS daily_category_revenue
|
|
FROM products p
|
|
JOIN order_items oi
|
|
ON p.id = oi.product_id
|
|
JOIN orders o
|
|
ON oi.order_id = o.id
|
|
|
|
GROUP BY
|
|
p.category,
|
|
o.order_date::date
|
|
)
|
|
|
|
SELECT
|
|
category AS category,
|
|
order_date::date AS order_date,
|
|
daily_category_revenue,
|
|
SUM(daily_category_revenue) OVER (
|
|
PARTITION BY category
|
|
ORDER BY order_date
|
|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
|
) AS running_category_revenue
|
|
FROM category_date_revenue;
|
|
|
|
|
|
/*
|
|
Чем такой «накопительный» график по категории отличается по смыслу от обычного ежедневного (без SUM OVER)?
|
|
Видим какая именно категория в какой именно день принесла больше выручки
|
|
*/ |