Learning Platform
Урок 06.02 · 17 мин
Начальный
Multiple joinsAssociativityAliasesQuery readability

В жизни редко обходится одним JOIN’ом. «Покажи имя клиента, дату заказа, название товара и категорию» — это уже четыре таблицы: customers → orders → order_items → products → categories. Этот урок про то, как такие цепочки правильно писать, как они выполняются, и почему в большинстве случаев СУБД сама разберётся, в каком порядке их выполнить.

Цепочка JOIN’ов: как читать

INNER JOIN слева-направо ассоциативен. A JOIN B JOIN C означает «сначала соедини A и B, потом результат соедини с C» — но из-за коммутативности и ассоциативности equi-join’а результат не зависит от порядка. Любой план, который оптимизатор выберет, даст тот же набор строк (если все JOIN’ы — INNER).

Четыре таблицы в одном запросе: клиент → заказ → позиция → товар → категория

PostgreSQL

Читай это как длинное предложение: «возьми клиентов, добавь к каждому его заказы, к каждому заказу — позиции, к каждой позиции — товар, к каждому товару — категорию». Каждая строка результата — кортеж из пяти таблиц одновременно.

Алиасы — основа читаемости

Без алиасов запрос разваливается:

SELECT customers.full_name, orders.id, order_items.qty, products.name
FROM customers
JOIN orders ON orders.customer_id = customers.id
JOIN order_items ON order_items.order_id = orders.id
JOIN products ON products.id = order_items.product_id;

То же самое с алиасами читается в два раза быстрее. Правила алиасов, которыми пользуется большинство команд:

  1. Один-два символа от имени таблицы: customers c, orders o, products p.
  2. Если есть конфликт (два algорifма с похожим префиксом) — добавляй смысловой суффикс: oi (order_items), pc (parent_category).
  3. Никогда не пиши алиас t1, t2. Через 2 месяца ты сам не поймёшь, что это.
  4. После присвоения алиаса — обращайся только через него. customers c, ..., WHERE customers.id = ... — это ошибка, PostgreSQL отвергает.

Ассоциативность INNER JOIN

(A JOIN B) JOIN C и A JOIN (B JOIN C) дают один и тот же результат, если все JOIN’ы — INNER. Это позволяет оптимизатору переставлять таблицы в любом порядке, ища план с минимальной стоимостью.

Например, если в orders 10 миллионов строк, а в customers 100 — оптимизатору выгодно сначала соединить customers с order_items через orders, выкинув ранние ненужные строки. Семантика гарантирует, что результат не изменится.

INNER JOIN ассоциативен и коммутативен

Любой порядок соединения трёх таблиц даёт идентичный результат. Оптимизатор пользуется этой свободой.

вариант 1(A JOIN B) JOIN C
сначала A+B
вариант 2A JOIN (B JOIN C)
сначала B+C
результатодинаковый
оптимизатор выбирает по cost

Важно: эта ассоциативность работает только для INNER JOIN. Как только в цепочке появляется OUTER — порядок начинает значить. Об этом — следующий раздел.

OUTER JOIN: порядок имеет значение

(A LEFT JOIN B) LEFT JOIN CA LEFT JOIN (B LEFT JOIN C) — потенциально это разные запросы. LEFT JOIN не симметричен и не ассоциативен с другими LEFT’ами в общем случае.

В стандартном SQL без скобок цепочка A LEFT JOIN B LEFT JOIN C интерпретируется слева-направо: (A LEFT JOIN B) LEFT JOIN C. Это почти всегда то, что ты имеешь в виду: «бери A, добивай B, добивай C». Каждый последующий LEFT JOIN добавляет новые строки или NULL, но не выкидывает.

Все клиенты + их заказы (если есть) + платежи (если есть). LEFT-цепочка сохраняет сирот на каждом шаге.

PostgreSQL

В этом запросе мы получаем всех клиентов. Тех, у кого нет заказов — с NULL во второй и третьей таблицах. Тех, у кого есть заказы, но нет платежей — с NULL только в третьей.

Если бы мы написали INNER JOIN payments p вместо LEFT JOIN p, то клиенты без заказов всё равно вернулись бы (благодаря первому LEFT) — но заказы без платежей выпали бы. Тонко.

Когда нужны явные скобки

Скобки в FROM нужны редко, но иногда — необходимы. Типичный пример: смешанные INNER и OUTER JOIN’ы, где ты хочешь сначала жёстко соединить две таблицы, а потом получившийся блок прилепить как OUTER к третьей.

Скобки: сначала INNER (orders + order_items), потом LEFT JOIN к customers

PostgreSQL

Альтернатива — использовать стандартные скобки прямо в FROM: LEFT JOIN (orders o JOIN order_items oi ON ...) ON .... Это менее распространённый стиль; чаще пишут через производную таблицу (subquery в FROM), как выше — её легче читать.

Комбинаторный взрыв: следи за дубликатами

Главная ловушка многотабличных JOIN’ов — неожиданное умножение строк. Если ты соединил orders с order_items (1 → много), а потом ещё с payments (1 → 1, но…), то одна строка с уровня orders превращается в N × M строк.

Считаем заказы клиента — но получаем НЕ количество заказов, а позиций!

PostgreSQL

Аня сделала 3 заказа, но COUNT(*) после JOIN с order_items вернёт ~6 (сумма позиций по её заказам). Если хочешь именно число заказов — пиши COUNT(DISTINCT o.id). К этому вернёмся в модуле про агрегаты.

Правило: каждый раз, когда ты добавляешь JOIN с таблицей, где у одного «родителя» может быть несколько детей, проверяй, не превращается ли твой COUNT/SUM в чушь. Это самая частая ошибка в аналитических запросах.

Проверка знанийKnowledge check
У тебя 3 таблицы: A (10 строк), B (50 строк, у каждой строки A в среднем 5 строк B), C (200 строк, у каждой строки B в среднем 4 строки C). Сколько строк вернёт A JOIN B JOIN C по правильным внешним ключам?
ОтветAnswer
200. После A JOIN B (по FK B → A) получим 50 строк — по одной на каждую строку B (10 × 5). После добавления JOIN C (по FK C → B) получим 50 × 4 = 200 строк. Каждая строка A раздувается в 20 строк результата (5 × 4). Если потом ты сделаешь COUNT(*) GROUP BY A.id — получишь 20, а не 5. Если хочешь «количество B на A» — пиши COUNT(DISTINCT B.id).
Порядок JOIN и почему оптимизатор меняет его без спроса

Чек-лист

  • INNER JOIN ассоциативен и коммутативен — порядок не влияет на результат, оптимизатор сам выбирает план.
  • OUTER JOIN’ы порядок ломают — пиши их слева-направо в логическом порядке наполнения.
  • Алиасы — обязательны для запросов с 3+ таблицами. Короткие, осмысленные, без t1/t2.
  • Явные скобки в FROM нужны при смешении INNER/OUTER. Чаще используют производные таблицы.
  • Главная ловушка: JOIN с дочерней таблицей раздувает строки. COUNT(*) после такого JOIN’а считает не то, что ты думаешь.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Почему `A JOIN B JOIN C` даёт тот же результат при любом порядке таблиц в исполнении (для INNER JOIN)?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7