Как декомпозировать большой SQL запрос с множеством джоинов?
Что проверяют
Умеете ли вы разобраться в чужом запросе на двести строк и собрать его заново так, чтобы результат можно было проверить по частям. Для аналитика важна не скорость, а корректность: сколько строк должно быть на выходе и откуда берутся дубли. Следом часто спрашивают, как вы нашли бы ошибку в таком запросе.
Как отвечать
Начните с зерна результата: одна строка на что именно, на заказ, на клиента, на клиента в месяц. От этого зависят все решения дальше.
Дальше порядок работы:
- базовая таблица: берёте таблицу, которая соответствует зерну, и считаете
COUNT(*); - джойны по одному: добавляете соединения по очереди и после каждого сверяете число строк. Если после джойна строк стало больше, чем ожидалось, связь «один ко многим» размножила данные;
- предагрегация: многие строки (платежи, события) сначала сворачиваете
GROUP BYдо нужного зерна и только потом присоединяете. Так уходят раздутые суммы; - CTE через
WITH: каждый логический шаг получает имя, напримерactive_clients,orders_month, и его можно выполнить отдельно. В PostgreSQL с 12-й версии CTE, на который ссылаются один раз, встраивается в запрос, а материализацию включаетMATERIALIZED; - представления и временные таблицы: вьюха для блока, который нужен в нескольких отчётах, временная таблица для отладки или когда промежуточный результат читается много раз;
EXPLAIN ANALYZE: если вопрос всё же про скорость, план покажет, какой шаг дорогой.
Хорошо добавить, что каждый блок стоит описать словами в спецификации: какие условия отбора, откуда поле. Тогда разработчик и заказчик проверяют логику, а не синтаксис.
Где спотыкаются
Лечат дубли через DISTINCT поверх всего запроса. Строки становятся уникальными, но суммы, посчитанные до этого, уже завышены.
Ставят условие на таблицу из LEFT JOIN в WHERE. Строки без пары отфильтровываются, и соединение фактически становится INNER. Условие нужно переносить в ON.
Считают, что разбиение на временные таблицы само по себе ускоряет запрос. Иногда да, но часто оптимизатор справляется с одним запросом лучше, а временная таблица остаётся без статистики и индексов.