Proanalytics.tech
ГайдыКак читать чужой SQL запрос: пошаговый разбор

Как читать чужой SQL запрос: пошаговый разбор

10 мин чтения · 8 разделов

Вам достался запрос на триста строк без комментариев, и надо понять, что он считает. Разберём по шагам: где вход и выход, как читать слои и соединения, как найти ошибку и переписать код так, чтобы его понял следующий человек.

С чего начать разбор: вход, выход, фильтры и период

Первый шаг — не читать код сверху вниз, а найти вход и выход. Вход — это таблицы и источники в FROM и JOIN: выпишите их и отметьте, какие из них большие, а какие справочники. Выход — последний SELECT: какие колонки идут в результат и по какой гранулярности — одна строка на заказ, на клиента или на день.

Дальше ищите фильтры и период. Самый важный фильтр часто спрятан в глубоком подзапросе или в условии соединения. Найдите все условия по датам и убедитесь, что период одинаковый у всех частей запроса: типичная ошибка — где-то год фильтруется, а где-то нет.

После этого сформулируйте смысл запроса одной фразой: «считает выручку по клиентам за месяц, кроме возвратов». Если фраза не складывается, вы ещё не поняли запрос, и переписывать рано.

Порядок выполнения не совпадает с порядком написания

SQL пишут в одном порядке, а выполняют в другом. Сервер сначала берёт таблицы (FROM, JOIN), затем фильтрует строки (WHERE), потом группирует (GROUP BY), фильтрует группы (HAVING), считает выражения в SELECT и только потом сортирует (ORDER BY) и ограничивает (LIMIT).

Отсюда практические следствия. Псевдоним из SELECT не работает в WHERE, но работает в ORDER BY. Условие в WHERE отбрасывает строки до агрегации, поэтому фильтр по результату агрегата уходит в HAVING или во внешний запрос.

Оконные функции считаются после WHERE и GROUP BY, но до ORDER BY и LIMIT. Значит, отфильтровать результат ROW_NUMBER() в том же уровне нельзя — нужен внешний запрос.

Слои разбора: CTE, подзапросы и временные таблицы

Разбирайте запрос по слоям. Сначала верхний уровень, потом каждый подзапрос отдельно, как отдельную маленькую задачу. Полезно заменить подзапрос чёрным ящиком: понять, какие колонки он получает и что отдаёт, и не лезть внутрь, пока не понадобится.

Двигаться можно двумя способами. Сверху вниз — от результата к источникам: так быстрее понять, зачем нужен каждый блок. Снизу вверх — от таблиц к результату: так виднее, где теряются или размножаются строки. На практике удобно начать сверху, а спорный блок раскручивать снизу.

CTE (конструкция WITH) читаются легче вложенных подзапросов: у каждого блока есть имя, код идёт линейно, и любой шаг можно запустить отдельно. Но у CTE есть цена: в части баз они материализуются, и тогда запрос работает медленнее, чем с тем же подзапросом внутри. Временные таблицы выручают, когда промежуточный результат нужен несколько раз и он тяжёлый.

Соединения: что с чем и по какому ключу

Для каждого JOIN ответьте на три вопроса: что соединяется, по какому ключу и сколько строк ожидается на выходе. Ключ должен совпадать по смыслу и по гранулярности с обеих сторон. Классическая ошибка — соединить заказы с клиентами не по id клиента, а по городу или email: строк станет больше, а суммы вырастут.

Отдельно проверьте LEFT JOIN. Если по правой таблице в WHERE стоит условие, оно превратит левое соединение во внутреннее: строки без совпадения отбросятся. Такое условие переносят в ON, а если фильтр нужен по результату — считают его во внешнем запросе.

Чтобы поймать размножение строк, сравните количество строк до и после каждого соединения. Если до JOIN было 1000 строк, а после 3400, ключ не уникален с одной из сторон. Быстрая проверка: SELECT COUNT(*), COUNT(DISTINCT ключ) по каждой таблице и сравнение чисел.

Агрегация: где считается метрика и в какой гранулярности

Найдите, где считается метрика, и разложите её на числитель и знаменатель. COUNT(*) считает строки, COUNT(DISTINCT user_id) — уникальных пользователей, SUM(amount) — сумму. Путаница между ними — самая частая причина расхождения с отчётом.

Вторая ловушка — гранулярность. Если соединить заказы с позициями, средний чек начнёт считаться по строкам позиций, а не по заказам: один заказ с тремя товарами попадёт в среднее трижды. Лечится это агрегацией до соединения либо отдельным CTE с уникальными заказами.

Проверьте GROUP BY: все неагрегированные колонки из SELECT должны быть в группировке, иначе результат станет непредсказуемым. Заодно уберите из группировки лишние колонки — они дробят метрику сильнее, чем нужно.

Оконные функции: как читать OVER

OVER (PARTITION BY user_id ORDER BY created_at DESC) читается по частям: PARTITION BY задаёт группы, внутри которых считается функция, ORDER BY — порядок строк внутри группы. Без PARTITION BY окно охватывает весь результат запроса.

ROW_NUMBER() выдаёт уникальные номера 1, 2, 3 даже при одинаковых значениях сортировки. RANK() при равенстве даёт одинаковый номер и пропускает следующий (1, 1, 3), а DENSE_RANK() не пропускает (1, 1, 2). Нужен ровно один заказ на клиента — берите ROW_NUMBER, нужны все заказы с максимальной суммой — RANK.

Отфильтровать результат окна в том же уровне нельзя, поэтому логику кладут во внешний шаг: сначала нумеруем строки, потом оставляем только rn = 1. Ищите этот двухшаговый приём в чужом коде — он часто спрятан в самом низу и меняет смысл всего запроса.

Как найти ошибку методом деления

Метод деления простой: считайте промежуточный итог на каждом шаге и сравнивайте с ожиданием. Сколько строк читается из таблиц, сколько остаётся после фильтра, сколько после каждого соединения, сколько после группировки. Первый шаг, где число расходится с ожиданием, — место ошибки.

Начните с общего числа: SELECT COUNT(*) по базовой таблице за нужный период. Затем добавляйте по одному условию или соединению и смотрите, как меняется счётчик. Так вы за пару минут локализуете блок, а не будете читать триста строк подряд.

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

Читаемее и быстрее: рефакторинг и план запроса

Переписывать стоит не всегда. Если запрос работает и его никто не будет править, добавьте комментарии к шагам и оставьте как есть. Браться за рефакторинг логично, когда логику нельзя объяснить коллеге, когда запрос ломается от любого изменения и когда в нём дублируются большие блоки.

Правила простые: единый стиль (ключевые слова заглавными, отступы, одна колонка на строку), осмысленные псевдонимы — o для orders и c для customers вместо a, b, c, отказ от SELECT * в пользу явных колонок, комментарий к каждому шагу и вынос повторяющейся логики в CTE с именем вроде orders_daily.

Про производительность: посмотрите план запроса через EXPLAIN и поищите полное сканирование большой таблицы, функции вокруг колонок в WHERE, лишние соединения и декартовы произведения. Часто помогает переписать условие диапазоном дат. Предлагайте это без ломки логики и сверяйте результат до и после правок.

Что сделать: короткий чеклист

Где тренировать

Темы из гайда разобраны в тестах портала — каждый вопрос с объяснением ответа:

Разбор чужого запроса — это навык, который тренируется: чем больше запросов вы прочитали по шагам, тем быстрее видите вход, выход и слабое место. Возьмите свои рабочие SQL-запросы и прогоните их по этому методу, а для тренировки найдите задачи по SQL с объяснением каждого ответа.

Проверьте себя

Диагностика из 20 вопросов покажет ваш уровень и темы, которые стоит подтянуть. Вводная тема любого курса открыта бесплатно и без регистрации.

Создать аккаунт — вводная тема бесплатна Пройти демо-тест без регистрации

Частые вопросы

С чего начать, если запрос на пятьсот строк?
С выхода, а не с первой строки. Посмотрите последний SELECT: что попадает в результат и с какой гранулярностью. Затем выпишите таблицы из FROM и JOIN и найдите фильтры по датам. Когда смысл укладывается в одну фразу, переходите к слоям по очереди.
Читать сверху вниз или снизу вверх?
Начните сверху вниз: так быстрее понять, зачем нужен каждый блок. А спорный участок раскручивайте снизу вверх — от таблиц к результату, чтобы увидеть, где теряются или размножаются строки. Оба направления дополняют друг друга.
Почему LEFT JOIN превратился в INNER JOIN?
Из-за условия по правой таблице в WHERE. Условие вида right_table.status = 'active' отбрасывает строки без совпадения, и левое соединение теряет смысл. Перенесите условие в ON, а если фильтр нужен по результату — считайте его во внешнем запросе.
Как понять, что запрос размножает строки?
Сравните число строк до и после каждого соединения и проверьте уникальность ключа: SELECT COUNT(*), COUNT(DISTINCT ключ) по каждой таблице. Если после JOIN строк стало заметно больше, ключ не уникален с одной стороны, и метрики завышены.
Когда чужой запрос лучше не переписывать?
Когда он работает, результат сверен и логика больше не меняется. В таком случае достаточно комментариев к шагам. Переписывайте, если логику трудно объяснить, если запрос ломается от правок или содержит дублирующиеся блоки: тогда переписывание окупается.

Другие гайды

Как подготовиться к собеседованию аналитика: план по шагам и срокам Что делать, если не знаешь ответ на собеседовании С чего начать в аналитике: направления, порядок обучения и план первых недель Вопросы на собеседовании системного аналитика: блоки, примеры и логика ответа Вопросы на собеседовании бизнес-аналитика: блоки, примеры и логика ответов Вопросы на собеседовании продуктового аналитика: блоки, примеры и логика ответов Вопросы на собеседовании аналитика данных: блоки и задачи Собеседование джуна аналитика: чего ждут и как отвечать Собеседование на Мидл-аналитика: блоки вопросов, логика ответов и кейсы Собеседование на Сеньор аналитика: что проверяют и как отвечать SQL задачи на собеседовании: типы, логика решения и типичные ошибки Как посчитать A/B-тест: выборка, длительность и чтение результата Кейсы на собеседовании аналитика: как разбирать и отвечать по шагам Кейсы системного аналитика: 8 рабочих ситуаций с логикой решения Ошибки в отчётах: как найти расхождение и перестать терять доверие к цифрам Первая неделя работы аналитика: спокойный план действий Как подготовиться к тестовому заданию аналитика Метрики продукта: с чего начать Как вести документацию аналитика Собеседование без опыта: план на месяц
Все гайды Курсы и тесты