Читать книгу SQL для аналитика. 50 задач с решениями на PostgreSQL - Ар'лан ис'Дрекхэм - Страница 6

Часть 1. Установка. Повторение SQL
Глава 4. Повторяем продвинутый SQL (GROUP BY, JOIN, IN, BETWEEN)

Оглавление

Описание задачи

Продолжим повторение SQL и вспомним запросы с группировкой данных, объединением таблиц, а также IN и BETWEEN. Используется почти в каждой аналитической задаче.

Разберём, как GROUP BY собирает строки в группы, JOIN соединяет данные из разных таблиц, а IN с подзапросом проверяет вхождение в список значений. Узнаем про команду BETWEEN для проверки на диапазон значений.

Основные SQL-конструкции

GROUP BY – создаёт группы из строк с одинаковыми значениями.

Обычно используется вместе с агрегатными функциями:

– COUNT (*) – количество элементов в группе

– MAX (column) – максимальное значение в группе

– MIN (column) – минимальное значение в группе

– AVG (column) – среднее значение в группе

Пример: посчитать количество пользователей в каждой стране.

text

SELECT country, COUNT (*) AS user_count

FROM users

GROUP BY country;


Как это работает

– GROUP BY country – все строки с одинаковым country попадают в одну группу.

– COUNT (*) считает количество строк в каждой группе.


JOIN – объединяет две таблицы по ключу.

Основные виды JOIN:

– INNER JOIN – только строки, где есть совпадение в обеих таблицах

– LEFT JOIN – все строки из левой таблицы, даже если нет совпадения в правой

– RIGHT JOIN – все строки из правой таблицы

– FULL JOIN – все строки из обеих таблиц

Пример: вывести заказы вместе с именами пользователей.

text

SELECT o. order_id, u.user_name, o. order_date

FROM orders o

INNER JOIN users u ON o.user_id = u.user_id;


Как это работает

– o – псевдоним для таблицы orders

– u – псевдоним для таблицы users

– ON o.user_id = u.user_id – условие соединения


Типичная ошибка

Забыть условие JOIN. Если написать FROM orders o, users u без ON, получите декартово произведение (каждая строка из orders соединится с каждой строкой из users). Результат будет огромным и бессмысленным.

IN с подзапросом – проверяет, входит ли значение в результат подзапроса.

Пример: найти пользователей, которые делали заказы.

text

SELECT user_name

FROM users

WHERE user_id IN (SELECT DISTINCT user_id FROM orders);


Как это работает

– Подзапрос (SELECT DISTINCT user_id FROM orders) возвращает список user_id, которые есть в заказах. DISTINCT убирает дубли и возвращает только уникальные значения.

– WHERE user_id IN… оставляет только тех пользователей, чей user_id есть в этом списке.


IN со списком значений – проверяет, входит ли значение в заданный список.

Пример: найти пользователей из нескольких стран.

text

SELECT user_name, country

FROM users

WHERE country IN («Россия», «Беларусь», «Казахстан»);


Как это работает

– IN («Россия», «Беларусь», «Казахстан») – проверяет, совпадает ли country с одним из указанных значений.

– Это то же самое, что WHERE country = «Россия» OR country = «Беларусь» OR country = «Казахстан», но короче и понятнее.


Совет

Для коротких списков (до 5—7 значений) IN удобен и читаем. Если список большой, лучше использовать JOIN с временной таблицей.


BETWEEN – фильтр по интервалу значений, проверяет, входит ли значение в интервал чисел или дат.

Найти товары с ценой от 10000 до 50000 рублей.

text

SELECT product_id, name, price

FROM products

WHERE price BETWEEN 10000 AND 50000

ORDER BY price;


BETWEEN 10000 AND 50000 включает границы. То же самое что price> = 10000 AND price <= 50000.


Найти заказы, сделанные в феврале 2024 года.

text

SELECT order_id, user_id, order_date

FROM orders

WHERE order_date BETWEEN «2024-02-01» AND «2024-02-29» ORDER BY order_date;


Учтите, что в феврале 2024 было 29 дней (високосный год).

SQL-запросы с объяснением

Задача 1. Количество заказов по статусам.

Посчитаем, сколько заказов в каждом статусе.

text

SELECT status, COUNT (*) AS order_count

FROM orders

GROUP BY status

ORDER BY order_count DESC;


Как это работает

– GROUP BY status – группируем заказы по статусу.

– COUNT (*) – считаем количество в каждой группе.

– ORDER BY order_count DESC – сортируем от большего к меньшему.

Пример вывода (фрагмент):

text

’completed’ 79

’pending’ 11

’shipped’ 10

’cancelled’ 2


Задача 2. Сумма выручки по месяцам.

Посчитаем общую выручку по месяцам на основе завершённых заказов.

text

SELECT

DATE_TRUNC (’month’, order_date) AS month,

SUM (oi. quantity * oi.price_per_unit) AS revenue

FROM orders o

INNER JOIN order_items oi ON o. order_id = oi. order_id

WHERE o.status = ’completed’

GROUP BY DATE_TRUNC (’month’, order_date)

ORDER BY month;


Как это работает

– DATE_TRUNC (’month’, order_date) – обрезаем дату до первого дня месяца.

– INNER JOIN – соединяем заказы с их товарами.

– SUM (oi. quantity * oi.price_per_unit) – считаем общую сумму.

– GROUP BY month – группируем по месяцам.


Задача 3. Топ-5 пользователей по сумме покупок.

Найдём пользователей, которые потратили больше всего.

text

SELECT

u.user_id,

u.user_name,

SUM (oi. quantity * oi.price_per_unit) AS total_spent

FROM users u

INNER JOIN orders o ON u.user_id = o.user_id

INNER JOIN order_items oi ON o. order_id = oi. order_id

WHERE o.status = ’completed’

GROUP BY u.user_id, u.user_name

ORDER BY total_spent DESC

LIMIT 5;


Как это работает

– Два INNER JOIN – соединяем users → orders → order_items.

– GROUP BY u.user_id, u.user_name – группируем по пользователю.

– SUM – считаем общую сумму покупок.

– ORDER BY total_spent DESC – сортируем от большего к меньшему.

– LIMIT 5 – оставляем только пять строк.


Задача 4. Пользователи, которые ничего не покупали (через NOT IN).

Найдём пользователей, у которых нет ни одного заказа.

text

SELECT user_id, user_name, country

FROM users

WHERE user_id NOT IN (

SELECT DISTINCT user_id FROM orders WHERE user_id IS NOT NULL

);


Как это работает

– Подзапрос собирает список всех user_id, которые есть в заказах.

– NOT IN оставляет пользователей, которых нет в этом списке.


Типичная ошибка

Если в подзапросе NOT IN встретится NULL, результат будет пустым. Поэтому внутри подзапроса всегда добавляйте WHERE user_id IS NOT NULL.


Задача 5. Пользователи, которые покупали хотя бы раз (через IN).

Найдём пользователей, у которых есть хотя бы один заказ.

text

SELECT user_id, user_name, country

FROM users

WHERE user_id IN (

SELECT DISTINCT user_id FROM orders

);


Как это работает

– Подзапрос собирает список всех user_id, которые есть в заказах.

– IN оставляет пользователей, которые есть в этом списке.


Задача 6. Пользователи из выбранных стран (IN со списком).

Найдём пользователей из России, Беларуси и Казахстана.

text

SELECT user_id, user_name, country

FROM users

WHERE country IN («Россия», «Беларусь», «Казахстан»)

ORDER BY country, user_name;


Как это работает

– IN («Россия», «Беларусь», «Казахстан») – оставляет только пользователей из этих трёх стран.

– ORDER BY country, user_name – сортируем сначала по стране, потом по имени.


Пример вывода (фрагмент):

text

user_id user_name country

4 ’dmitry_kozlov’ «Беларусь».

11 ’sergey_volkov’ «Беларусь».

12 ’aidar_kz’ «Казахстан».


Вопросы и ответы

В: Что будет, если в GROUP BY указать не все колонки из SELECT?

О: PostgreSQL выдаст ошибку. Все колонки, которые не обёрнуты в агрегатную функцию (SUM, COUNT, AVG), должны быть перечислены в GROUP BY.


В: Чем INNER JOIN отличается от LEFT JOIN?

О: INNER JOIN оставляет только строки с совпадением в обеих таблицах. LEFT JOIN оставляет все строки из левой таблицы, даже если в правой нет совпадения (тогда поля из правой таблицы будут NULL).


В: Чем IN с подзапросом отличается от JOIN?

О: Оба могут дать одинаковый результат, но работают по-разному. IN удобен, когда нужно просто проверить вхождение. JOIN даёт больше гибкости (можно вывести поля из обеих таблиц).


В: Почему в подзапросе для NOT IN нужно убирать NULL?

О: Потому что NOT IN с NULL в списке всегда возвращает пустой результат. Это особенность SQL.

Что в итоге

Мы научились:

– Группировать строки с GROUP BY и считать агрегаты (COUNT, SUM)

– Соединять таблицы с помощью INNER JOIN и LEFT JOIN

– Использовать IN и NOT IN с подзапросами и со списками значений

– Использовать BETWEEN для фильтрации по интервалу

– Комбинировать WHERE, GROUP BY и ORDER BY в одном запросе

Эти навыки – основа для следующих глав, где мы будем решать реальные аналитические задачи.

* Задание со звёздочкой

Напишите запрос, который выводит общую сумму выручки по каждому пользователю (user_id, user_name) и название страны, но только для пользователей из России и Казахстана. Используйте JOIN и WHERE с IN.

Результат отсортируйте по сумме от большей к меньшей.

SQL для аналитика. 50 задач с решениями на PostgreSQL

Подняться наверх