added window functions sql

This commit is contained in:
Dmitry Medvedev
2026-06-12 14:41:23 +05:00
parent 72c9b065b9
commit 3044cecf7a
2 changed files with 187 additions and 1 deletions
-1
View File
@@ -168,7 +168,6 @@ WHERE o.status = 'paid';
/**❓ Ответь на вопросы:**
- что будет, если условие **`status = 'paid'`** написать в **`JOIN`** с **`orders`** (там же, где **`ON`**)? Изменится ли результат сейчас и почему?
В таком случае все задублируется
*/
--task 9
+187
View File
@@ -0,0 +1,187 @@
--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)?
Видим какая именно категория в какой именно день принесла больше выручки
*/