⛓️🗄️ Соединения в SQL. Часть 1. Типы соединений и простые примеры

Соединения — это способ получения данных из нескольких таблиц с помощью SQL. В первой части статьи мы расскажем о типах соединений и рассмотрим несколько базовых примеров.

Данная статья является переводом. Ссылка на оригинал.

Типы соединений SQL

Тип соединения Описание
INNER JOIN (ВНУТРЕННЕЕ СОЕДИНЕНИЕ) Возвращает те строки, для которых в обеих таблицах выполняется условие соединения.
LEFT JOIN (ЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ или просто ЛЕВОЕ СОЕДИНЕНИЕ) Возвращает строки, содержащие данные из левой таблицы (указанной слева от ключевого слова JOIN), даже если в правой таблице нет совпадающих строк. В строках, где нет совпадения справа, все столбцы справа будут заполнены значением NULL.
RIGHT JOIN(ПРАВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ или просто ПРАВОЕ СОЕДИНЕНИЕ) Возвращает строки, содержащие данные из правой таблицы (указанной справа от ключевого слова JOIN), даже если в левой таблице нет совпадающих строк. В строках, где нет совпадения слева, все столбцы слева будут заполнены значением NULL.
FULL JOIN (ПОЛНОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ или просто ПОЛНОЕ СОЕДИНЕНИЕ) Возвращает строки, содержащие данные из обеих таблиц. В строках, где нет совпадения слева или справа, все столбцы без совпадения будут заполнены значением NULL.
CROSS JOIN (ПЕРЕКРЕСТНОЕ СОЕДИНЕНИЕ) Возвращает строки, содержащие все возможные комбинации левой и правой частей соединения. Такое соединение называют «декартовым произведением» (прим. перев.)

Есть еще несколько терминов, которые используются для операций соединения:

Термин Описание
Self-join (самосоединение) Соединение данных в таблице с данными в этой же таблице (с самой собой)
Natural join (естественное соединение) Неявное соединение двух таблиц за счет совпадения имен столбцов в этих таблицах
Equi-join (равнозначное соединение) Соединение двух таблиц, в условии которого используется только операция равенства (=)

Синтаксис соединений

Внутреннее соединение (inner join) может быть описано с помощью предложений FROM или WHERE в операторе SELECT или других операторах DML (data manipulation language, языка манипулирования данными).

Чтобы создать соединение с помощью предложения FROM, нужно поступить следующим образом:

SELECT *
FROM Table1 <JoinType> Table2 [ON (JoinCondition)]

Здесь конструкция <JoinType> указывает тип соединения, которое мы хотим получить, а JoinCondition – это логическое выражение (предикат), которое должно быть вычислено для каждой пары соединяемых строк.

Чтобы создать соединение с помощью предложения WHERE, напишите так:

SELECT *
FROM Table1, Table2 [WHERE (JoinCondition)]

В этом случае JoinCondition – это тоже логическое выражение, которое должно быть вычислено для каждой пары соединяемых строк.

Любая часть предложения, заключенная в квадратные скобки [ ], является необязательной.

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека программиста»

Таблицы данных для наших примеров

Для демонстрации примеров соединений мы будем использовать две таблицы с данными:

  • Таблицу PetTypes
+-------------+-----------+
| PetTypeId   | PetType   |
|-------------+-----------|
| 1           | Bird      |
| 2           | Cat       |
| 3           | Dog       |
| 4           | Rabbit    |
+-------------+-----------+
(4 rows affected)
  • Таблицу Pets
+---------+-------------+-----------+-----------+------------+
| PetId   | PetTypeId   | OwnerId   | PetName   | DOB        |
|---------+-------------+-----------+-----------+------------|
| 1       | 2           | 3         | Fluffy    | 2020-11-20 |
| 2       | 3           | 3         | Fetch     | 2019-08-16 |
| 3       | 2           | 2         | Scratch   | 2018-10-01 |
| 4       | 3           | 3         | Wag       | 2020-03-15 |
| 5       | 1           | 1         | Tweet     | 2020-11-28 |
| 6       | 3           | 4         | Fluffy    | 2020-09-17 |
| 7       | 3           | 2         | Bark      | NULL       |
| 8       | 2           | 4         | Meow      | NULL       |
+---------+-------------+-----------+-----------+------------+
(8 rows affected)

Внутреннее соединение (INNER JOIN)

Внутреннее соединение (INNER JOIN)

Внутреннее соединение в SQL возвращает строки, для которых в обеих таблицах выполняется условие соединения:

SELECT
	Pets.PetName,
	PetTypes.PetType
FROM Pets
INNER JOIN PetTypes
ON Pets.PetTypeId = PetTypes.PetTypeId;

Результат:

----------+-----------+
| PetName | PetType   |
|---------+-----------|
| Fluffy  | Cat       |
| Fetch   | Dog       |
| Scratch | Cat       |
| Wag     | Dog       |
| Tweet   | Bird      |
| Fluffy  | Dog       |
| Bark    | Dog       |
| Meow    | Cat       |
+---------+-----------+
(8 rows affected)

Чтобы указать внутренние соединения в предложении FROM, нужно использовать ключевые слова INNER JOIN. Далее после слова ON мы должны описать предикат, соединяющий части строк из наших двух таблиц.

При любом типе соединения нам желательно указывать имена столбцов вместе с именами таблиц, в которых они содержатся. Такой способ записи позволяет избежать неоднозначностей из-за того, что в разных таблицах могут быть столбцы с одинаковыми именами (как в нашем примере). СУБД просто не поймет, из какой таблицы нужно взять столбец. Использование имени таблицы как префикса решит эту проблему. В нашем примере в обеих таблицах есть столбец PetTypeId. В таблице Pets этот столбец используется в качестве внешнего ключа, а в таблице PetTypes – в качестве первичного.

В результате мы получили не все виды питомцев, так как в таблице Pets у нас нет кроликов (rabbit), хотя в таблице PetTypes они есть. Причина в том, что внутреннее соединение возвращает только соединенные строки, для которых условие соединения выполняется в обеих таблицах.

Обратите внимание, что для внутреннего соединения слово INNER часто не является обязательным. Многие (но не все) СУБД позволяют сокращать тип соединения до слова JOIN, что предполагает именно внутреннее соединение таблиц.

Поэтому наш пример мог бы быть записан короче:

SELECT
	Pets.PetName,
	PetTypes.PetType
FROM Pets
JOIN PetTypes
ON Pets.PetTypeId = PetTypes.PetTypeId;

Для простоты всё предложение FROM можно записать в одну строку:

SELECT
Pets.PetName,
PetTypes.PetType
FROM Pets JOIN PetTypes ON Pets.PetTypeId = PetTypes.PetTypeId;

Псевдонимы

Часто для сокращения записи соединений используют псевдонимы таблиц. Такую запись становится проще читать.

Например, можно переписать предыдущий запрос так:

SELECT
	p.PetName,
	pt.PetType
FROM Pets p INNER JOIN PetTypes pt
ON p.PetTypeId = pt.PetTypeId;

Результат:

+----------+-----------+
| PetName  | PetType   |
|----------+-----------|
| Fluffy   | Cat       |
| Fetch    | Dog       |
| Scratch  | Cat       |
| Wag      | Dog       |
| Tweet    | Bird      |
| Fluffy   | Dog       |
| Bark     | Dog       |
| Meow     | Cat       |
+----------+-----------+
(8 rows affected)

Равнозначное соединение

Соединение, показанное выше, называется еще равнозначным соединением. Такое соединение содержит внутри предиката только операции «равно» (=).

Есть и другой способ описать такое соединение:

SELECT
	p.PetName,
	pt.PetType
FROM
Pets p,
PetTypes pt
WHERE p.PetTypeId = pt.PetTypeId;

Результат:

+-----------+-----------+
| PetName   | PetType   |
|-----------+-----------|
| Fluffy    | Cat       |
| Fetch     | Dog       |
| Scratch   | Cat       |
| Wag       | Dog       |
| Tweet     | Bird      |
| Fluffy    | Dog       |
| Bark      | Dog       |
| Meow      | Cat       |
+-----------+-----------+

Это пример описания внутреннего соединения с помощью предложения WHERE. Мы указали список таблиц, просто перечислив их через запятую, а условие соединения перенесли в предложение WHERE. Если бы мы не указали такое условие, то мы получили бы перекрестное соединение (cross join).

Многие начинающие считают такой способ записи внутреннего соединения более простым, чем с использованием слов INNER JOIN. Вы вправе выбрать любой из двух вариантов на свой вкус, но учтите, что способ записи с использованием INNER JOIN считается более профессиональным.

Правое соединение (RIGHT JOIN)

Правое соединение (RIGHT JOIN)

Правое соединение, которое еще называют правым внешним соединением, возвращает строки, в которых всегда есть правая часть (полученная из таблицы, стоящей справа от слова JOIN). Строка будет получена, даже если в левой части не нашлось значений, которые описаны предикатом.

SELECT
	p.PetName,
	pt.PetType
FROM Pets p
RIGHT JOIN PetTypes pt
ON p.PetTypeId = pt.PetTypeId;

Результат:

+-----------+-----------+
| PetName   | PetType   |
|-----------+-----------|
| Tweet     | Bird      |
| Fluffy    | Cat       |
| Scratch   | Cat       |
| Meow      | Cat       |
| Fetch     | Dog       |
| Wag       | Dog       |
| Fluffy    | Dog       |
| Bark      | Dog       |
| NULL      | Rabbit    |
+-----------+-----------+
(9 rows affected)

В этом результате мы видим строки с видом «кролик» (rabbit), которых не было в выборке после внутреннего соединения. Напротив слова «Rabbit» в колонке PetName (имя питомца) стоит значение NULL.

Левое соединение (LEFT JOIN)

Левое соединение (LEFT JOIN)

Как и правое соединение, левое соединение называют левым внешним соединением. Оно возвращает строки, в которых всегда есть левая часть (полученная из таблицы, стоящей слева от слова JOIN). Строка будет получена, даже если в правой части не нашлось значений, которые описаны предикатом. Левое соединение по своему смыслу противоположно правому соединению.

Если немного изменить предыдущий пример с использованием левого соединения, то он будет выглядеть так:

SELECT
	p.PetName,
	pt.PetType
FROM Pets p
LEFT JOIN PetTypes pt
ON p.PetTypeId = pt.PetTypeId;

Результат:

+-----------+-----------+
| PetName   | PetType   |
|-----------+-----------|
| Fluffy    | Cat       |
| Fetch     | Dog       |
| Scratch   | Cat       |
| Wag       | Dog       |
| Tweet     | Bird      |
| Fluffy    | Dog       |
| Bark      | Dog       |
| Meow      | Cat       |
+-----------+-----------+
(8 rows affected)

В этом конкретном случае наш результат совпадает с результатом внутреннего соединения. Однако если мы поменяем местами таблицы в предложении FROM, тогда мы получим такую же выборку, как и при правом соединении.

SELECT
	p.PetName,
	pt.PetType
FROM PetTypes pt
LEFT JOIN Pets p
ON p.PetTypeId = pt.PetTypeId;

Результат:

+-----------+-----------+
| PetName   | PetType   |
|-----------+-----------|
| Tweet     | Bird      |
| Fluffy    | Cat       |
| Scratch   | Cat       |
| Meow      | Cat       |
| Fetch     | Dog       |
| Wag       | Dog       |
| Fluffy    | Dog       |
| Bark      | Dog       |
| NULL      | Rabbit    |
+-----------+-----------+
(9 rows affected)

Как видите, разница в результатах между левым и правым соединениями зависит исключительно от того, как вы располагаете таблицы в предложении FROM.

Источники

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

eFusion
08 января 2020

11 типов современных баз данных: краткие описания, схемы и примеры БД

Любые данные где-то хранятся. Будь это интернет вещей или пароли в *nix. По...
admin
23 февраля 2017

SQL за 20 минут

Предлагаем вашему вниманию статью с кричащим названием "SQL за 20 минут". К...