В таблице заказов нет имени клиента — только customer_id. Это сделано нарочно: имя хранится один раз в customers, заказы лишь ссылаются на него. Но отчёт «заказ — имя — сумма» требует данных из обеих таблиц сразу. Операция, которая склеивает таблицы по ссылке, называется JOIN — и это самый важный навык после SELECT.

INNER JOIN: только совпавшие

SELECT orders.id, customers.name, orders.amount
FROM orders
JOIN customers ON orders.customer_id = customers.id;

Читается так: возьми заказы, к каждому подбери клиента, у которого customers.id равен orders.customer_id, и выдай строки парами. Условие после ON — сердце соединения: оно говорит, по каким столбцам таблицы связаны (почти всегда это внешний ключ и первичный ключ).

JOIN без уточнения — это INNER JOIN, «внутреннее» соединение: в результат попадают только строки, у которых нашлась пара. Заказ с несуществующим клиентом или клиент без заказов из результата молча выпадут.

Чтобы не писать имена таблиц целиком, им дают короткие алиасы:

SELECT o.id, c.name, o.amount
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'paid';

Всё, что вы знаете про WHERE, ORDER BY и LIMIT, работает и здесь — фильтр применяется уже к склеенным строкам.

LEFT JOIN: сохранить всех слева

А если нужен список всех клиентов, включая тех, кто ещё ничего не заказал? INNER JOIN их выбросит. Нужен LEFT JOIN — «сохрани все строки левой таблицы, даже без пары»:

SELECT c.name, o.id, o.amount
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id;

У Веры заказов нет — она всё равно попадёт в результат, а в столбцах заказа у неё будет пусто (NULL — про него отдельная статья). Этим же приёмом ищут «сирот»: клиентов без заказов —

SELECT c.name FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;

Логика: LEFT JOIN оставил всех клиентов, а у тех, кому пары не нашлось, поля заказа — NULL; фильтр оставляет только их. Это классический запрос для проверок целостности данных.

«Левая» таблица — та, что написана до JOIN. Есть и RIGHT JOIN (сохраняет правую), но на практике все пишут LEFT, просто меняя таблицы местами.

Две ловушки: потери и задвоение

  • Строки пропали. Сделали INNER JOIN — и в отчёте меньше заказов, чем в таблице. Значит, у части заказов не нашлось пары (например, customer_id пустой). INNER JOIN теряет непарные строки молча — если терять нельзя, берите LEFT JOIN.
  • Строки задвоились. JOIN выдаёт строку на каждую пару: у Анны два заказа — она встретится в результате дважды. Это нормально для «списка заказов с именами», но опасно для подсчётов: если после такого соединения просуммировать что-то со стороны клиента, суммы умножатся. Правило: увидели в результате больше строк, чем ожидали, — проверьте, не соединяете ли «один-ко-многим».

Где это применяется

Реальные вопросы к базе почти всегда межтабличные: «покажи заказы клиента с этой почтой», «какие товары лежат в заказе 101», «есть ли платежи без заказа». Одна привычная связка: найти id по-человечески читаемого поля в одной таблице и через JOIN вытащить связанное из другой — заменяет десятки кликов по интерфейсу. А LEFT JOIN с IS NULL — главный инструмент поиска потерянных данных: заказы без позиций, оплаты без заказов, пользователи без профилей.

Где спотыкаются начинающие:

  • Забывают условие ON или пишут его неверно. JOIN без правильного ON склеивает каждую строку с каждой — миллион строк из двух маленьких таблиц и бессмысленный результат.
  • Не замечают, что INNER JOIN выбросил строки. Отчёт «сходится», но в нём молча нет заказов с пустым клиентом. Сомневаетесь — начните с LEFT JOIN и посмотрите на NULL-строки.
  • Считают суммы по задвоенным строкам. После соединения «один-ко-многим» агрегаты со стороны «одного» умножаются на число пар.

Что учить дальше. Следующий шаг — не разглядывать строки, а считать их: COUNT, GROUP BY и HAVING. А что такое NULL, который появился в LEFT JOIN, — в статье про NULL и типы.