diff --git a/dmedvedev/joins.sql b/dmedvedev/joins.sql index 561fbb1..dfeb3bc 100644 --- a/dmedvedev/joins.sql +++ b/dmedvedev/joins.sql @@ -168,7 +168,6 @@ WHERE o.status = 'paid'; /**❓ Ответь на вопросы:** - что будет, если условие **`status = 'paid'`** написать в **`JOIN`** с **`orders`** (там же, где **`ON`**)? Изменится ли результат сейчас и почему? -В таком случае все задублируется */ --task 9 diff --git a/dmedvedev/window functions.sql b/dmedvedev/window functions.sql new file mode 100644 index 0000000..ad78441 --- /dev/null +++ b/dmedvedev/window functions.sql @@ -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)? +Видим какая именно категория в какой именно день принесла больше выручки +*/ \ No newline at end of file