В этой статье мы разберём ещё одну фундаментальную тему в SQL — оператор JOIN.
Умение правильно объединять таблицы — это не просто технический навык, это краеугольный камень всей реляционной аналитики. Давайте разберёмся, что это такое и как избежать распространённых ошибок.
Что такое JOIN и зачем он нужен
Основная идея реляционных баз данных — нормализация: хранение данных без избыточности в разных, но логически связанных таблицах.
Например, у нас есть в базе данных есть 4 таблицы: все сотрудники employees, все отделы departments, таблица с заказами orders и справочник продуктов products. Если бы мы хранили всю информацию в одной гигантской таблице, это привело бы к хаосу.
JOIN — это оператор, который позволяет нам в рамках одного запроса комбинировать строки из нескольких таблиц на основе логической связи между ними. Например, столбец department_ccode в таблице employees смотрит на столбец ccode в таблице departments.
Без JOIN наша аналитика была бы разорвана на десятки отдельных запросов, которые потом пришлось бы вручную сводить в Excel через ВПР. JOIN автоматизирует этот процесс, позволяя базе данных сделать всю тяжелую работу.
Внутреннее соединение (INNER JOIN)
Это самый распространённый тип соединения. Его логика проста: «Покажи мне записи, которые существуют в обеих таблицах».
Представьте себе две окружности (таблицы). INNER JOIN возвращает только область их пересечения.
Например, нам нужно посмотреть сотрудников, которые закреплены за отделами:
SELECT
e. employee_uk,
e. employee_name,
d.department_name
FROM employees e
INNER JOIN departments d ON e.department_ccode = d.ccode;
В запросе выше мы берём таблицу employees (псевдоним e) и соединяем её с таблицей departments (псевдоним d). Условие ON e.department_ccode = d.ccode — это и есть та самая логическая связь. В результате мы получим список всех сотрудников, у которых указан отдел, и рядом с их именами — название этого отдела.
Важный нюанс: если в таблице c сотрудниками есть запись, у которой
department_ccodeравенNULLили указано несуществующее значение, он не попадет в результат.INNER JOINстрог и возвращает только парные записи.
Зачастую в скриптах можно увидеть сокращённое написание — JOIN (приставка INNER не обязательна).
Внешние соединения (OUTER JOIN)
Внешние соединения нужны, когда нам важны все записи из одной или обеих таблиц, даже если для них нет парных записей. Выделяют три вида внешних джоинов: левый (LEFT JOIN), правый (RIGHT JOIN) и полный (FULL JOIN).
LEFT JOIN (Левый джоин)
Логика: «Выбери все записи из левой таблицы и только соответствующие им записи из правой. Если значения не соответствуют, то оставь их пустыми».
Левая таблица — это та, что указана после FROM, правая — после LEFT JOIN.
Например, мы хотим получить список всех моделей самолётов, которые вылетали из аэропорта, даже если самолёт не указан.
SELECT
a.airport_id,
a.airport_name,
p.plant_model
FROM airports a
LEFT JOIN plants p ON o.model_ccode = p.ccode;
В этом случае мы увидим все аэропорты и самолёты, название которых есть в правой таблице. У тех записей, у которых наименования самолета нет (или указан неверный ccode), в этой колонке будет NULL. Это критически важно для анализа «сирот» — записей, которые потеряли связь.
Стоит отметить, что у одного аэропорта может быть несколько самолётов, поэтому аэропорты будут не уникальны.
RIGHT JOIN (Правый джоин)
Правый джоин — полная противоположность LEFT JOIN. Во время запроса он говорит базе данных: «Верни мне все записи из правой таблицы и только соответствующие им записи из левой. Если соответствия нет, заполни поля из левой таблицы значениями NULL».
Например, мы хотим получить список всех самолётов и всех аэропортов, из которых они вылетали, включая и абсолютно пустые отделы.
SELECT
p.plant_model,
a.airport_id,
a.airport_name
FROM airports a
RIGHT JOIN plants p ON o.model_ccode = p.ccode;
Здесь в результате будут имена всех самолётов и аэропорты, из которых они вылетали. Стоит отметить, что у одного самолета может быть несколько аэропортов, поэтому самолеты будут не уникальны.
Практический совет: RIGHT JOIN используется на порядок реже, чем LEFT JOIN — по моему опыту, почти не используется. Поэтому использование левого джоина делает код более стандартизированным и читаемым.
FULL JOIN (Полное внешнее соединение)
Логика: «Верни мне всё со всех сторон». FULL JOIN возвращает все записи и из левой, и из правой таблицы. Он объединяет в себе логику LEFT и RIGHT JOIN.
Например, мы хотим увидеть все аэропорты (даже без вылетов самолета) и все самолёты (даже пустые).
SELECT
p.plant_model,
a.airport_id,
a.airport_name
FROM airports a
FULL JOIN plants p ON o.model_ccode = p.ccode;
Этот запрос даст нам полную картину связей и всех аномалий в данных. В результате мы получим все аэропорты и все самолёты, даже если поля левой или правой таблицы окажутся пустыми.
CROSS JOIN (Перекрёстное соединение)
Логика: «Соедини каждую строку левой таблицы с каждой строкой правой». Это декартово произведение двух множеств.
Например, мы делаем отчёт по распределению задач аналитиков. У нас есть статичное значение — 2 часа в день на инфраструктурные проекты, и нам нужно добавить это значение в каждый день. Для этого необходимо «замножить» его на количество дней.
SELECT c.day, 120 as timespent_minutes
FROM dual
CROSS JOIN calendar c
Where c.day between date’2025-11-01’ and date’2025-11-05’;
В ходе такого запроса мы получим следующее:
| DAY | TIMESPENT_MINUTES |
|---|---|
| 01.11.2025 | 120 |
| 02.11.2025 | 120 |
| 03.11.2025 | 120 |
| 04.11.2025 | 120 |
| 05.11.2025 | 120 |
CROSS JOIN без условия ON может породить гигантские объёмы данных (миллионы строк из двух небольших таблиц) и «положить» базу данных, если использовать его бездумно. Он применяется в специфических сценариях: генерации всех возможных вариантов, планировании и т. д.
USING
До сих пор мы в джоинах использовали оператор ON для указания условия соединения. Но что если столбцы, по которым мы соединяем таблицы, называются абсолютно одинаково? Именно для этого идеально подходит USING.
Например, у нас есть таблица с заказами orders и таблица с продуктами products, где есть столбец product_id:
SELECT
o.order_id,
p.roduct_name,
p.product_price
FROM orders o
LEFT JOIN products p USING (product_id);
Что здесь происходит? Мы говорим базе данных: «Соедини таблицы orders и products по столбцу с именем product_id, который есть в обеих таблицах». База данных сама понимает, что нужно использовать o.product_id = p.product_id.
Используя USING, вы делаете код более лаконичным и чистым. Запрос становится короче и читабельнее, особенно когда условие соединения простое и очевидное. Исчезает необходимость вводить псевдонимы только для того, чтобы устранить неоднозначность столбца.
Встраивание USING в ваш арсенал — это признак того, что вы не просто механически соединяете таблицы, а задумываетесь о качестве, читаемости и эффективности своего SQL-кода. Это ценится как коллегами, которые будут читать ваш код, так и системой, которая будет его выполнять.
Как выбрать правильный тип JOIN? Практический алгоритм
Вот пошаговая инструкция, которую я даю своим младшим аналитикам и стажёрам.
- Сформулируйте вопрос на естественном языке.
- «Покажи мне заказы и имена клиентов, которые их сделали» → Нужны только парные записи →
INNER JOIN. - «Покажи мне всех клиентов и их заказы, даже если заказов нет» → Главное — клиенты, заказы — опционально →
LEFT JOIN(клиенты слева, заказы справа). - «Проведи ревизию: покажи всех сотрудников и все отделы, чтобы найти несоответствия» → Нужно всё →
FULL JOIN.
- Определите главную (ведущую) таблицу.
Часто бизнес-вопрос звучит так: «Для каждой сущности A покажи информацию из B». Таблица A — главная. Если вам важны все записи из A, даже без B, ваш выбор — LEFT JOIN.
- Спросите себя: «Что важнее — точное соответствие или полнота данных?»
- Точность:
INNER JOIN. Вы жертвуете записями без пар, чтобы получить чистое, непротиворечивое множество данных. Идеально для отчетов по конкретным операциям. - Полнота:
LEFT/RIGHT/FULL JOIN. Вы жертвуете «чистотой» данных (появляютсяNULL), чтобы убедиться, что ни одна сущность не потеряна. Идеально для аудита, поиска пропусков и анализа охвата.
- Проверяйте количество строк.
После написания JOIN всегда проверяйте, не увеличилось ли число строк в результате запроса. Резкий рост может указывать на «многие ко многим», когда одной записи в левой таблице соответствуют несколько в правой. В этом случае нужно тщательно продумывать агрегацию (DISTINCT, GROUP BY) или условия фильтрации в WHERE.
Распространённая ошибка: неявное соединение (запятая в FROM)
Старый синтаксис, который до сих пор можно встретить:
SELECT e.emloyee_name, d.department_name
FROM employees e, departments d
WHERE e.department_ccode = d.ccode;
Это эквивалент INNER JOIN. Не используйте его. Он устарел и опасен. Если вы забудете условие в WHERE, вы получите CROSS JOIN (декартово произведение), что является грубой ошибкой и приводит к неверным результатам. Явное использование ключевого слова JOIN делает ваш код гораздо более читаемым и защищённым от ошибок.
JOIN — это мост между островами ваших данных. Правильный выбор типа соединения — это не про синтаксис, это про семантику вашего бизнес-запроса.
INNER JOINдля точных, проверенных связей.LEFT JOINдля сохранения полноты главной сущности.FULL JOINдля полного аудита данных с двух сторон.CROSS JOIN— мощное и опасное оружие, применяйте с пониманием последствий.
Практикуйтесь. Ставьте себе задачи. Пытайтесь написать один и тот же запрос с разными JOIN и смотрите, как меняется результат. Именно так вы разовьёте интуицию и сможете не просто писать запросы, а конструировать их, предвидя результат. Это и есть путь от джуна к сеньору.
