173 lines
7.4 KiB
SQL
173 lines
7.4 KiB
SQL
--Задача 1. Пользователи с заказами
|
||
--task 1
|
||
|
||
SELECT
|
||
u.name
|
||
FROM users u
|
||
WHERE id IN (
|
||
SELECT o.user_id
|
||
FROM orders o
|
||
)
|
||
ORDER BY u.name;
|
||
|
||
/*
|
||
Чем такой подход отличается от варианта с JOIN + DISTINCT по users?
|
||
Принципиальная разница в том , что в этом варианте сначала выполняется подзапрос,
|
||
который выдает айди, потом уже выполняется внешний селект. И дубликаты не нужно удалять.
|
||
|
||
В каком случае IN (подзапрос) может быть более читаемым, чем JOIN?
|
||
В том случае, если необходимо найти существование записи в таблице или же когда нужна только одна колонка из таблицы.
|
||
*/
|
||
|
||
--task 2
|
||
--Задача 2. Пользователи без заказов (NOT EXISTS)
|
||
SELECT
|
||
u.id,
|
||
u.name,
|
||
u.email
|
||
FROM users u
|
||
WHERE NOT EXISTS (
|
||
SELECT 1
|
||
FROM orders o
|
||
WHERE u.id = o.user_id
|
||
);
|
||
|
||
/*
|
||
Почему для этой задачи NOT EXISTS предпочтительнее, чем NOT IN (SELECT user_id ...)?
|
||
Потому что NOT EXISTS показывает факт существования запиши для определенного условия
|
||
и не перебирает все дублирующие строки, а использует только первую попавшуюся.
|
||
|
||
Как бизнес может использовать список таких пользователей?
|
||
Так как эти пользователи не завершили процесс покупки, менеджерам необходимо будет поработать
|
||
с такими клиентами, предложить скидки, какие-то другие предложения.
|
||
*/
|
||
|
||
--task 3
|
||
--Задача 3. Сколько заказов у каждого пользователя (подзапрос в SELECT)
|
||
SELECT
|
||
name,
|
||
(SELECT COUNT(*)
|
||
FROM orders o
|
||
WHERE o.user_id = u.id
|
||
) AS orders_count
|
||
FROM users u
|
||
ORDER BY orders_count DESC, name DESC;
|
||
|
||
/*
|
||
Почему подзапрос в SELECT не может возвращать несколько строк?
|
||
Потому что таково его свойство. Если же после селекта поставить наименование колонки
|
||
и если будет у пользователя несколько заказов, то выдаст ошибку, поэтому нужно либо
|
||
агрегировать, либо ограничить лимитом 1.
|
||
|
||
Как изменится сложность запроса, если пользователей станет, например, 1 млн?
|
||
В таком случае SQL будет долго обрабатывать данные, потому что нужно будет обработать миллион строк в одном селекте.
|
||
В таких случаях лучше использовать групп бай.
|
||
*/
|
||
|
||
--task 4
|
||
--Задача 4. Сумма выручки по каждому пользователю (подзапрос или CTE)
|
||
WITH revenie_user AS (
|
||
SELECT
|
||
o.user_id,
|
||
SUM(oi.quantity * oi.price_at_purchase) AS total_revenue
|
||
FROM orders o
|
||
LEFT JOIN order_items oi ON o.id = oi.order_id
|
||
GROUP BY o.user_id
|
||
)
|
||
|
||
SELECT
|
||
u.name,
|
||
revenie_user.total_revenue
|
||
FROM users u
|
||
LEFT JOIN revenie_user ON u.id = revenie_user.user_id;
|
||
|
||
/*
|
||
Как проще читать такой запрос: когда шаг «посчитать выручку по пользователям» вынесен отдельно, или когда всё написано в одном SELECT?
|
||
Конечно проще читать, когда делается через СТЕ.
|
||
|
||
Если эту «выручку по пользователям» придётся использовать в другом отчёте, как избежать дублирования кода?
|
||
Может быть завернуть это еще в одну СТЕ?
|
||
*/
|
||
|
||
--task 5
|
||
--Задача 5. Топ‑3 товара по выручке (CTE)
|
||
WITH product_revenue AS (
|
||
SELECT
|
||
product_id,
|
||
SUM(quantity * price_at_purchase) AS revenue_pr
|
||
FROM order_items oi
|
||
GROUP BY product_id
|
||
)
|
||
|
||
SELECT
|
||
p.name AS product_name,
|
||
p.category AS category,
|
||
product_revenue.revenue_pr AS product_revenue
|
||
FROM products p
|
||
LEFT JOIN product_revenue ON p.id = product_revenue.product_id
|
||
ORDER BY product_revenue DESC
|
||
LIMIT 3;
|
||
|
||
/*
|
||
Почему удобно сначала считать агрегаты по товарам, а потом уже подтягивать имена/категории?
|
||
Потому что в категории может быть несколько разных товаров и если мы начнем агрегировать по категориям,
|
||
то можем потерять некоторые товары.
|
||
|
||
В чём отличие подхода “через CTE” от простого GROUP BY в одном SELECT?
|
||
Отличие в том, что здесь мы выполняем два запроса, также СТЕ можно переиспользовать, так как это именованная
|
||
таблица, улучшается читаемость запроса.
|
||
*/
|
||
|
||
--task 6
|
||
--Задача 6. Активные пользователи с высокой суммой заказов
|
||
WITH revenue AS(
|
||
SELECT
|
||
user_id,
|
||
SUM(quantity * price_at_purchase) AS total_revenue
|
||
FROM orders o
|
||
LEFT JOIN order_items oi ON o.id = oi.order_id
|
||
GROUP BY user_id
|
||
)
|
||
|
||
SELECT
|
||
name,
|
||
email,
|
||
revenue.total_revenue
|
||
FROM users u
|
||
LEFT JOIN revenue ON u.id = revenue.user_id
|
||
WHERE is_active IS TRUE AND revenue.total_revenue > 1000;
|
||
|
||
/*
|
||
Как бы ты назвал этот сегмент пользователей на языке продукта (VIP, high‑spenders и т.п.)?
|
||
High-revenue active clients
|
||
|
||
Что можно делать с таким сегментом: какие типы акций/коммуникаций?
|
||
Можно предлагать для них какие-либо скидки на другие товары, потому что сразу становится понятно,
|
||
что они заинтересованы в нащих продуктах.
|
||
*/
|
||
|
||
--task 7
|
||
--Задача 7. «Подозрительно маленькие» оплаченные заказы
|
||
WITH revenue AS(
|
||
SELECT
|
||
order_id,
|
||
SUM(quantity * price_at_purchase) AS order_revenue
|
||
FROM order_items oi
|
||
GROUP BY order_id
|
||
)
|
||
|
||
SELECT
|
||
order_id,
|
||
order_date,
|
||
order_revenue
|
||
FROM orders o
|
||
LEFT JOIN revenue r ON r.order_id = o.id
|
||
WHERE status = 'paid' AND order_revenue < 100;
|
||
|
||
/*
|
||
Какие реальные причины могут быть у таких заказов (ошибки, тесты, злоупотребления промокодами)?
|
||
Все вышеперечисленные, а также мошенничество, взлом системы.
|
||
|
||
На каком шаге выгоднее фильтровать по статусу paid: внутри подзапроса или снаружи, и почему?
|
||
Легче фильтровать внешне, потому что можно создать нескоько внешних запросов, изменив лишь фильтр.
|
||
*/ |