From f8c0af41cae885e75fdf1acae485fd0f1251e717 Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Wed, 27 May 2026 10:37:52 +0300 Subject: [PATCH 1/2] Add homework on SQL CTE --- vberetar/postgresql/sql_cte.sql | 118 ++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 vberetar/postgresql/sql_cte.sql diff --git a/vberetar/postgresql/sql_cte.sql b/vberetar/postgresql/sql_cte.sql new file mode 100644 index 0000000..080d9bc --- /dev/null +++ b/vberetar/postgresql/sql_cte.sql @@ -0,0 +1,118 @@ +-- Задача 1 +SELECT u.name +FROM users u +WHERE id IN ( + SELECT o.user_id + FROM orders o +) +ORDER BY u.name; +-- Ответ на вопросы +-- 1. JOIN склеивает одну таблицу со второй, а подзапрос с IN выполняет запрос и получает список с user_id, +-- далее по этому списку отфильтровывает пользователей в основном запросе. +-- 2. Когда запрос простой и короткий. + + +-- Задача 2 +SELECT u.id, + u.name, + u.email +FROM users u +WHERE NOT EXISTS ( + SELECT 1 + FROM orders o + WHERE o.user_id = u.id +); +-- Ответ на вопросы +-- 1. Предположу что NOT EXISTS более бещопасно работает с null. +-- 2. Отправить таким пользователям рекомендацию, специальный промокод на скидку для первого заказа или рекламу. + + +-- Задача 3 +SELECT u.name, + ( + SELECT COUNT(*) + FROM orders o + WHERE u.id = o.user_id + ) AS orders_count +FROM users u +ORDER BY orders_count DESC, name; +-- Ответ на вопросы +-- 1. Если подзапрос SELECT вернётся две и более строки то будет ошибка. +-- 2. Сложность возрастет и скорее всего нужно будет оптимизировать запрос. + + +-- Задача 4 +WITH total_revenue_cte AS ( + SELECT o.user_id, + SUM(oi.quantity * oi.price_at_purchase) AS total_price + FROM orders o + JOIN order_items oi ON oi.order_id = o.id + GROUP BY o.user_id +) +SELECT u.name, + trc.total_price AS total_revenue +FROM users u +JOIN total_revenue_cte trc ON trc.user_id = u.id +ORDER BY total_revenue DESC; +-- Ответ на вопросы +-- 1. Проще читать запрос "посчитать выручку по пользователям" когда вынесен отдельно. +-- 2. Вроде как в postgres можно в базу данных записывать функции, если я ничего не путаю. +-- И далее вызывать эту функцию при необходимости, так можно избежать дублирования кода. + + +-- Задача 5 +WITH product_price_at_purchase_cte AS ( + SELECT oi.product_id, + SUM(oi.quantity * oi.price_at_purchase) AS product_revenue + FROM order_items oi + GROUP BY oi.product_id +) +SELECT p.name AS product_name, + p.category, + ppps.product_revenue +FROM products p +JOIN product_price_at_purchase_cte ppps ON ppps.product_id = p.id +ORDER BY ppps.product_revenue DESC +LIMIT 3; +-- Ответ на вопросы +-- 1. Возможно потому что products оcновная таблица, сналчала нужно из order_items достать агрегацию по id а потом склеивать с products. +-- 2. Через CTE мы получаем готовую таблицу и далее делаем с ней что хотим, +-- а в гуруппировке через GROUP BY нельзя добавить дополнительные поля name и category. + + +-- Задача 6 +WITH total_revenue_cte AS ( + SELECT o.user_id, + SUM(oi.quantity * oi.price_at_purchase) AS total_revenue + FROM orders o + JOIN order_items oi ON oi.order_id = o.id + GROUP BY o.user_id +) +SELECT u.name, + u.email, + trc.total_revenue +FROM users u +JOIN total_revenue_cte trc ON trc.user_id = u.id +WHERE u.is_active = TRUE AND trc.total_revenue > 1000 +ORDER BY total_revenue DESC; +-- Ответ на вопросы +-- 1. Думаю как то так - user_most_expenses +-- 2. Можно предлагать рекламу товаров, анализировать что пользователи покупали и потом предлагать схожую продукцию. + + +WITH order_revenue_cte AS ( + SELECT order_id, + SUM(quantity * price_at_purchase) AS order_revenue + FROM order_items + GROUP BY order_id +) +SELECT o.id, + o.order_date, + oc.order_revenue +FROM orders o +JOIN order_revenue_cte oc ON oc.order_id = o.id +WHERE o.status = 'paid' AND oc.order_revenue < 100; +-- Ответ на вопросы +-- 1. Какие реальные причины таких заказов, я думаю что просто не все люди богатые что бы заказывать на большие суммы (если я правильно понял статус paid) +-- Чтобы узнать злоупотребления промокодами нужно копаться в других данных, где хранятся данные о введенных промокодах. +-- 2. Я думаю выгоднее фильтровать внутри подзапроса, чтоб подзапрос не создавал большую таблицу и основному запросу не пришлось ее перебирать. From 3428dfeab78e6d392ab8f8b438b1fbb0d1b13ebd Mon Sep 17 00:00:00 2001 From: "valera.beretar" Date: Mon, 1 Jun 2026 17:26:44 +0300 Subject: [PATCH 2/2] Add homework on SQL view --- vberetar/postgresql/sql_view.sql | 69 ++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 vberetar/postgresql/sql_view.sql diff --git a/vberetar/postgresql/sql_view.sql b/vberetar/postgresql/sql_view.sql new file mode 100644 index 0000000..333c660 --- /dev/null +++ b/vberetar/postgresql/sql_view.sql @@ -0,0 +1,69 @@ +-- Задание 1 +CREATE MATERIALIZED VIEW my_category_revenue AS + SELECT + p.category, + SUM(oi.quantity * oi.price_at_purchase) AS total_revenue + FROM orders o + JOIN order_items oi ON oi.order_id = o.id + JOIN products p ON p.id = oi.product_id + WHERE o.status = 'paid' + GROUP BY p.category; + +SELECT * FROM my_category_revenue +ORDER BY total_revenue DESC; +-- Ответ на вопросы +-- 1. Не нужно писать длинные запросы и результат будет уже подсчитан. + + +-- Задание 2 +CREATE MATERIALIZED VIEW mv_user_activity AS + SELECT + u.id AS user_id, + u.name AS user_name, + COUNT(o.user_id) AS orders_count + FROM users u + LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid' + GROUP BY u.id, u.name; + +SELECT * FROM mv_user_activity +WHERE orders_count > 0 +ORDER BY orders_count DESC; +-- Ответ на вопросы +-- 1. Я так понял, что если критерий "самых лояльных" клиентов это колличество оплаченных заказов, +-- то "самые лояльные" это те у кого этих заказов больше всего. +-- 2. Покажет "самых лояльных" клиентов за последный месяц, кто-то из клиетов может сделать много заказов за 1-2 месяца, а кто то за год по чуть чуть заказывает. +-- Возможно еще какие то из клиентов ориентируются на "сезонные" товары и у них будет сплеск продажи за последний месяц, или кто то из клиентов покупал много за год, а в этом месяце перестал делать заказы. + + +-- Задание 3 +CREATE MATERIALIZED VIEW mv_daily_revenue AS + SELECT + o.order_date::date AS sales_date, + SUM(oi.quantity * price_at_purchase) AS daily_revenue + FROM orders o + JOIN order_items oi ON oi.order_id = o.id + WHERE o.status = 'paid' + GROUP BY o.order_date::date; + +SELECT * FROM mv_daily_revenue +ORDER BY sales_date DESC; +-- Ответ на вопросы +-- 1. «Просадки» и «пики» будут видны в колонке daily_revenue +-- 2. Быстрее считывать с готовой витрины mv_daily_revenuе, на лету из сырых таблиц дольше будет обрабатываться запрос. + + +-- Задание 4 +REFRESH MATERIALIZED VIEW mv_daily_revenue; +-- Расписание интернет‑магазина с несколькими заказами в день: +-- Для такого сценария подойдет обновление витрины раз в неделю по ночам. +-- Так как заказов не так уж и много, можно обновлять статискиту чаще. +-- Ночью, потому что заказов будет меньше в то время суток и бд быстрее обработает ззапрос. + +-- Расписание крупного магазина с тысячами заказов в час: +-- Для такого сценария подойдет обновление витрин раз в месяц - раз в квартал по ночам, +-- Так как данных много и частые расчеты будут сильно нагружать базу данных. +-- Возможно, нужно будет получить данные аналитикам раньше указанных сроков. + +-- Ответ на вопросы +-- 1. Данные будут устаревшими, из за того результат работы аналитиков будут не достоверными. +-- 2. Слишком частые обновления витрит будут сильно нагружать БД.