Привет! На связи Владимир Лунев, ментор тренинга «Продвинутый SQL». Давайте разберём кейс, где простые приёмы оптимизации могут ускорить выполнение запроса в несколько раз.
Представим, что у нас есть база заказов крупного интернет-магазина с реальными объёмами данных. Индексов нет. Всё выполняется на сырых таблицах. СУБД PostgreSQL.
Задача: посчитать для каждой категории количество заказов, суммарную выручку и средний чек за август 2025 только для завершённых заказов (со статусом completed).
Состав БД:
Таблица orders (5 млн строк) с полями:
➖
id — уникальный идентификатор заказа;➖
product_id — ID продукта;➖
order_date — дата заказа;➖
amount — сумма заказа;➖
status — статус заказа, например, completed.Таблица products (2 тыс. строк):
➖
id — ID продукта;➖
category_id — ID категории;➖
price — цена.Таблица categories (100 строк):
➖
id — ID категории;➖
name — название категории.Исходный запрос (медленный), ожидаемое время выполнения 4-5 минут:
SELECT
c.id, # идентификатор категории
c.name AS category, # название категории
COUNT(o.id) AS orders_count, # количество заказов
SUM(o.amount) / COUNT(o.id) AS avg_check # средний чек (ручное деление)
FROM orders o
JOIN products p ON o.product_id = p.id # соединяем с таблицей продуктов
JOIN categories c ON p.category_id = c.id # соединяем с категориями
WHERE
o.status = 'completed' # фильтруем только завершённые заказы
AND o.order_date >= '2025-08-01' # начало периода (строковый литерал)
AND o.order_date < '2025-09-01' # конец периода
GROUP BY c.id, c.name # группировка по категории (строка и id)
ORDER BY SUM(o.amount) DESC; # сортировка по суммарной выручке
Почему медленно:
➖ JOIN происходит на 5 млн строк без фильтрации.
➖ GROUP BY по c. name — сравнение строк (TEXT) вместо чисел, это дополнительная нагрузка.
➖ AVG считается вручную (SUM / COUNT) — выполняется две агрегации.
➖ Фильтр по дате задан как строка — типы приводятся динамически, что замедляет план выполнения.
Оптимизированный запрос (быстрее в ~10 раз!), ожидаемое время выполнения 25-30 секунд:
WITH filtered_orders AS (
# Сначала фильтруем все нужные заказы в отдельном CTE
SELECT *
FROM orders
WHERE status = 'completed' # только завершённые
AND order_date >= DATE '2025-08-01' # явное указание типа DATE
AND order_date < DATE '2025-09-01'
)
SELECT
c.id, # id категории
c.name AS category, # название категории
COUNT(fo.id) AS orders_count, # количество заказов
SUM(fo.amount) AS total_sales, # суммарная выручка
AVG(fo.amount) AS avg_check # средний чек (встроенная функция)
FROM filtered_orders fo
JOIN products p ON fo.product_id = p.id # соединяем уже отфильтрованные заказы
JOIN categories c ON p.category_id = c.id # соединяем с категориями
GROUP BY c.id # PK, группировка по ID (name подтянется автоматически)
ORDER BY total_sales DESC; # сортировка по суммарной выручке
Что изменилось и почему быстрее:
➖ CTE filtered_orders — с EXPLAIN ANALYZE видно, что количество строк на JOIN сократилось примерно до ~500 тыс. строк вместо 5 млн, т. е. 90%!
➖ GROUP BY c.id — сравнение по числу вместо текста, PostgreSQL подтягивает c.name автоматически.
➖ AVG() вместо SUM/COUNT — встроенная оптимизация движка.
➖ Явное указание типа даты (DATE '2025-08-01') — СУБД не тратит время на приведение типов.
На больших таблицах корректная структура запроса может кардинально влиять на скорость выполнения. В результате правильно построенный запрос может обрабатываться в 10-100 раз быстрее, чем прямой вариант с лишними вычислениями и сортировками.
❗️ Оптимизация SQL — это понимание того, как СУБД выполняет операции и как конкретный юзер может эффективно сэкономить свое время и бонусом ресурсы сервера. Ещё больше кейсов и рекомендаций будет на вебинаре 24 сентября — приходите, расскажу про свои способы оптимизации SQL-запросов.
➡️ Зарегистрироваться на вебинар
📊 Simulative