Гид по технологиям

Соединения (JOIN) в MySQL: руководство и примеры

6 min read Базы данных Обновлено 07 Apr 2026
JOIN в MySQL: типы, примеры и оптимизация
JOIN в MySQL: типы, примеры и оптимизация

Визуализация соединения таблиц в MySQL

Одна из главных выгод реляционных баз данных, таких как MySQL — способность хранить связанные данные в разных таблицах и объединять их в запросах. Правильно составленные JOIN-ы позволяют получить ровно те данные, которые нужны, не дублируя и не копируя информацию вручную.

Инициализация примерной базы данных

Этот шаг не обязателен, но если вы хотите повторить примеры локально, инициализируйте тестовую базу командами в терминале:

git clone https://github.com/mdizak/sample-select-db.git  
cd sample-select-db  
sudo mysql < store.sql  
sudo mysql sampledb  
mysql> SELECT COUNT(*) FROM customers;

Вы должны увидеть результат, указывающий, что в таблице customers 2000 строк.

INNER JOIN — соединение по умолчанию

INNER JOIN — самый распространённый тип соединения. Он возвращает только те записи, для которых есть совпадающие строки в обеих таблицах. Все остальные строки отбрасываются.

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

  
SELECT  
c.id, c.first_name, c.last_name, o.amount, o.created_at  
FROM  
customers c, orders o  
WHERE  
o.customer_id = c.id AND o.amount >= 1000;  

Несколько пояснений к запросу выше:

  • Мы выбираем пять столбцов: три из таблицы customers и два из orders.
  • В части FROM две таблицы названы с псевдонимами «c» и «o». Это просто сокращения, которые упрощают чтение запросов.
  • Выражение o.customer_id = c.id — это условие соединения, обеспечивающее соответствие заказов и клиентов.

Технически корректнее и читабельнее записать то же самое с явным INNER JOIN:

  
SELECT  
c.id, c.first_name, c.last_name, o.amount, o.created_at  
FROM  
customers c INNER JOIN orders o  
ON  
customer_id = c.id  
WHERE  
o.amount >= 1000;  

Визуально такой формат сразу показывает, какие таблицы и по каким столбцам связаны.

LEFT JOIN

LEFT JOIN возвращает все строки из левой таблицы и совпадающие строки из правой. Если совпадение не найдено — значения из правой таблицы будут NULL.

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

  
SELECT  
p.name, sum(item.amount) AS tamount  
FROM  
orders_items item LEFT JOIN products p  
ON  
item.product_id = p.id  
GROUP BY item.product_id ORDER BY tamount DESC  

Этот запрос пройдётся по таблице orders_items и для каждого product_id попытается найти запись в products. Если продукт в заказах встречается — он появится с суммой продаж.

RIGHT JOIN

RIGHT JOIN — зеркальная версия LEFT JOIN: он возвращает все строки из правой таблицы и совпадающие из левой. В примере выше LEFT JOIN вернул 19 строк, тогда как в базе 22 продукта — это потому, что некоторые продукты никогда не появлялись в orders_items.

Если нужно получить список всех продуктов с суммами продаж, включая те, которые не заказывали, используйте RIGHT JOIN:

  
SELECT  
p.name, sum(item.amount) AS tamount  
FROM  
orders_items item RIGHT JOIN products p  
ON  
item.product_id = p.id  
GROUP BY p.id ORDER BY tamount DESC  

В результате вернётся 22 продукта, у трёх из них сумма будет null — это нормальное поведение для неприсутствующих значений.

Соединение нескольких таблиц

Иногда нужно объединить три и более таблиц. Пример: найти всех клиентов, которые купили микроволновку (product_id = 1) с указанием имени и даты заказа. Это можно сделать двумя JOIN-ами:

  
SELECT  
c.first_name, c.last_name, o.amount, o.created_at  
FROM  
customers c INNER JOIN orders o  
ON  
c.id = o.customer_id INNER JOIN orders_items item  
ON  
item.order_id = o.id  
WHERE  
item.product_id = 1 ORDER BY o.created_at;  

Запрос сначала сопоставляет клиентов и их заказы, затем фильтрует эти заказы по товарам, которые содержат микроволновку.

Не используйте подзапросы с IN

Подзапросы с IN часто работают медленно и потребляют много ресурсов. Вместо них лучше использовать JOIN или EXISTS.

Плохой пример (избегайте):

SELECT first_name,last_name FROM customers WHERE id IN (SELECT customer_id FROM orders WHERE status = 'approved' AND amount < 100);

Эквивалент через JOIN (рекомендация):

SELECT c.first_name, c.last_name FROM customers c LEFT JOIN orders o ON o.customer_id = c.id WHERE o.status = 'approved' AND o.amount < 100;

LEFT JOIN здесь гарантирует, что мы начинаем с таблицы customers и при желании можем увидеть клиентов без заказов (если условия WHERE скорректировать). Если нужно только тех, у кого есть заказ — используйте INNER JOIN.

Когда JOIN дают неверные или неожиданные результаты

  • Пересечения по неправильным колонкам. Убедитесь, что в ON вы используете корректные столбцы (частая ошибка — забыть префиксы таблиц).
  • Картезианское произведение. Если забыть условие ON или WHERE — получите умножение строк (очень большой результат).
  • Дубликаты. JOIN может продублировать строки, если в связанной таблице есть несколько совпадающих записей. Для агрегатов используйте GROUP BY или DISTINCT при необходимости.
  • NULL в столбцах. LEFT/RIGHT JOIN возвращают NULL, и агрегации без COALESCE могут вести себя неожиданно.
  • Отсутствие индексов. JOIN по неиндексированным колонкам приводит к полным сканированиям таблиц и значительному падению производительности.

Альтернативы и приёмы

  • EXISTS вместо IN: часто быстрее и семантически точнее при проверке существования строк.
  • Подзапросы в FROM (derived tables) — удобны, когда нужно предварительно агрегировать данные.
  • Оконные функции (WINDOW) для аналитических задач вместо сложных JOIN-ов.
  • Материализованные представления (materialized views) или регулярная денормализация для очень горячих отчётов.

Пример использования EXISTS:

SELECT c.first_name, c.last_name FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id AND o.status = 'approved');

Оптимизация и лучшие практики

  • Индексируйте колонки, по которым производится JOIN (особенно внешние ключи).
  • Используйте EXPLAIN для анализа плана выполнения и поиска узких мест.
  • Избегайте SELECT * — перечисляйте только нужные столбцы.
  • Фильтры в WHERE ставьте до JOIN, если это логически корректно, но помните о семантике LEFT/RIGHT JOIN.
  • Ограничивайте размер результирующего набора с помощью LIMIT и пагинации при отладке.
  • Для больших таблиц проверяйте cardinality и выбирайте оптимальный порядок объединения (MySQL оптимизатор учитывает это, но подсказки могут помочь).

Шпаргалка: быстрые правила

Тип JOINЧто возвращаетКогда использовать
INNER JOINТолько совпадающие строки из обеих таблицКогда нужны только коррелированные данные
LEFT JOINВсе строки из левой таблицы + совпадения справаКогда нужно сохранить все строки левой таблицы
RIGHT JOINВсе строки из правой таблицы + совпадения слеваРедко; зеркально LEFT JOIN

Короткие советы:

  • Если хотите все продукты и их продажи — начните с products и LEFT JOIN к orders_items.
  • Для отчётов по транзакциям обычно удобен INNER JOIN между orders и orders_items.
  • Если видите много NULL-ов — подумайте, откуда они появляются и нужны ли они.

Контрольные списки по ролям

Для разработчика:

  • Указать явные псевдонимы таблиц.
  • Тестировать запрос на небольшом наборе данных.
  • Использовать EXPLAIN при подозрении на медленный запрос.

Для аналитика:

  • Проверять, что агрегаты учитывают NULL (COALESCE).
  • Использовать GROUP BY после JOIN только при необходимости.

Для DBA:

  • Следить за индексами на полях JOIN.
  • Мониторить медленные запросы и предлагать денормализацию, если нужно.

Критерии приёмки

  • Запрос возвращает ожидаемые строки для контрольных тестовых данных.
  • Время выполнения укладывается в приемлемый SLA для задачи (например, <2 с для интерактивного отчёта).
  • План выполнения не содержит полных сканирований больших таблиц без индексов.

Краткий глоссарий

  • JOIN — операция объединения таблиц по условию.
  • INNER JOIN — возвращает только совпавшие строки.
  • LEFT JOIN — сохраняет все строки левой таблицы.
  • RIGHT JOIN — сохраняет все строки правой таблицы.
  • Псевдоним (alias) — короткое имя таблицы, упрощающее запрос.

Заключение

JOIN-ы — мощный инструмент реляционных баз данных. Они экономят время и позволяют строить точные запросы без дублирования данных. В этой статье вы узнали о трёх основных типах JOIN, научились читать и писать запросы с несколькими соединениями, узнали, почему стоит избегать подзапросов с IN и получили рекомендации по оптимизации.

Важно: всегда тестируйте запросы на реальных объёмах данных, смотрите EXPLAIN и следите за индексами. С хорошо продуманными JOIN-ами вы будете быстрее получать корректные отчёты и уменьшите нагрузку на базу.

Замечания:

  • Если в результате видите неожиданные NULL-значения — проверьте направление JOIN и условие ON.
  • Для очень больших отчётов рассмотрите денормализацию или предварительную агрегацию.
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Несколько аккаунтов Skype: Multi Skype Launcher
Программное обеспечение

Несколько аккаунтов Skype: Multi Skype Launcher

Журнал для работы: повысить продуктивность
Productivity

Журнал для работы: повысить продуктивность

Персональные звуки уведомлений на Android
Android.

Персональные звуки уведомлений на Android

Скачивание шоу Hulu для офлайн‑просмотра
Стриминг

Скачивание шоу Hulu для офлайн‑просмотра

Microsoft Start: персонализированная новостная лента
Новости

Microsoft Start: персонализированная новостная лента

Как изменить имя в Epic Games быстро
Гайды

Как изменить имя в Epic Games быстро