SQL Практика



SQL Практика 2 - задачи

Практика 2 — Агрегации, JOIN, Подзапросы, CTE
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100),
    age INT,
    city VARCHAR(50),
    status VARCHAR(20) DEFAULT 'active',
    created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    category VARCHAR(50),
    price NUMERIC(10, 2),
    stock INT DEFAULT 0
);

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT REFERENCES users(id),
    order_date DATE DEFAULT CURRENT_DATE,
    amount NUMERIC(10, 2),
    status VARCHAR(20) DEFAULT 'pending'
);

CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INT REFERENCES orders(id),
    product_id INT REFERENCES products(id),
    quantity INT,
    unit_price NUMERIC(10, 2)
);

CREATE TABLE departments (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    budget NUMERIC(12, 2)
);

CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    department_id INT REFERENCES departments(id),
    manager_id INT REFERENCES employees(id),
    salary NUMERIC(10, 2),
    hire_date DATE
);

[!tip] Тестовые данные Данные уже загружены через docker-compose up — см. init/02_data.sql
Постарайся решить задачи самостоятельно, не подсматривай в ответы
Агрегатные функции
Задача 1: Посчитать количество пользователей
Задача 2: Найти среднюю цену товаров
Задача 3: Найти минимальную и максимальную цену товаров
Задача 4: Посчитать общую сумму всех заказов
Задача 5: Посчитать количество пользователей в каждом городе
Задача 6: Найти среднюю цену товаров по категориям
Задача 7: Найти категории с более чем 10 товарами
Задача 8: Посчитать сумму заказов по статусам
Задача 9: Посчитать количество заказов по месяцам
Задача 10: Посчитать количество заказов по статусам в столбцы (Pivot)
Задача 11: Медиана зарплат
JOIN
Задача 12: Вывести заказы с именами пользователей
Задача 13: Найти всех пользователей и их заказы (включая тех, у кого нет заказов)
Задача 14: Вывести позиции заказа с названиями товаров
Задача 15: Полная информация о заказе (пользователь + товары)
Задача 16: Вывести все комбинации категорий товаров и городов пользователей (CROSS JOIN)
Нужно получить все возможные пары (категория товара, город пользователя) — даже если таких заказов не было. Это полезно, например, для отчёта: «в каких городах какие категории не продаются».
Задача 17: Сотрудники, которые зарабатывают больше своего менеджера
Задача 18: Вывести для каждого пользователя: количество заказов, общую сумму и средний чек
Включить пользователей без заказов (у них должны быть нули). Отсортировать по общей сумме убыванию.
Задача 19: Найти пользователей, которые покупали товары категории «Электроника»
Задача 20: Статистика продаж: пользователь → количество уникальных товаров
Anti-Join и Semi-Join
Задача 21: Клиенты без заказов (Anti-Join)
Задача 22: Товары, которые заказывали (Semi-Join)
Задача 23: Найти товары, которые никто не заказывал
Задача 24: Пользователи, у которых ВСЕ заказы выполнены
Подзапросы
Задача 25: Найти заказы с суммой выше средней
Задача 26: Найти самый дорогой товар
Задача 27: Найти последний заказ каждого пользователя
Для каждого пользователя вывести его самый свежий заказ (все поля). Если у пользователя несколько заказов в один день — вывести один.
Задача 28: Найти пользователей, сделавших заказ
Задача 29: Найти пары товаров, которые чаще всего покупают в одном заказе
Вывести топ-10 пар товаров, которые встречаются вместе в одном заказе. Для каждой пары показать названия товаров и сколько раз они были в одном заказе. Пара (A, B) и (B, A) — это одно и то же, дубликатов быть не должно.
CTE (Common Table Expressions)
Задача 30: Сотрудники с зарплатой выше средней в своём отделе
Задача 31: Клиенты с суммой заказов выше средней
Задача 32: Замена коррелированного подзапроса на JOIN с CTE
Задача 33: Рекурсивный CTE — все подчинённые менеджера
Задача 34: Иерархия с путём
GROUPING SETS, ROLLUP, CUBE
Задача 35: Отчёт с подитогами по категориям и месяцам
Задача 36: Сводная таблица продаж по городам и статусам
Комплексные задачи
Задача 37: Найти города с более чем 100 пользователями и средним возрастом > 25
Задача 38: Топ-5 категорий по общей стоимости товаров на складе
Задача 39: Найти пользователей, не делавших заказы более 90 дней
Задача 40: Когортный анализ — retention по месяцам
Задача 41: Дубликаты email — оставить первую запись
Задача 42: Статистика заказов по месяцам
Агрегация в массивы и строки
Задача 43: Список товаров в каждом заказе одной строкой
Задача 44: JSON отчёт по категориям
Шпаргалка

Задача

Ключевой приём

Количество/сумма/среднее

COUNT / SUM / AVG

Группировка

GROUP BY + агрегаты

Фильтр групп

HAVING

Соединение

JOIN ON

Все из одной таблицы

LEFT JOIN

Anti-join

NOT EXISTS (безопаснее NOT IN!)

Semi-join

EXISTS или IN

Подзапрос

WHERE ... IN (SELECT ...)

Один на группу

DISTINCT ON или ROW_NUMBER

Подитоги

GROUPING SETS / ROLLUP / CUBE

Иерархия

WITH RECURSIVE

Массив из строк

array_agg(col ORDER BY ...)

Строка из значений

string_agg(col, ', ')

Частые ошибки на собеседованиях
  1. NOT IN с NULL — возвращает 0 строк если подзапрос содержит NULL
  2. COUNT (*) для EXISTS — неэффективно, используйте EXISTS
  3. JOIN создаёт дубликаты — агрегируйте ДО join или используйте COUNT (DISTINCT)
  4. Коррелированные подзапросы — медленные, заменяйте на JOIN с CTE
  5. GROUP BY забыт — все неагрегированные колонки должны быть в GROUP BY