Ранее мы рассматривали примеры, в которых соединялись две разные таблицы. Но иногда нужно выполнить соединение таблицы с самой собой.
В этом уроке мы научимся решать такие задачи с помощью соединений с условием: INNER JOIN
, LEFT JOIN
, RIGHT JOIN
или FULL JOIN
. Такой прием называется SELF JOIN, но это именно название приема, а не инструкция языка SQL.
Применяем SELF JOIN при работе с графами
Допустим, в базе данных хранится информация о сотрудниках. Почти у каждого сотрудника есть его непосредственный руководитель. Получается иерархическая структура:
Эта структура — дерево, частный случай графа. Узлами являются сотрудники, а ребра указывают на то, кто кому подчиняется.
Информация о сотрудниках хранится в таблице employees
:
employees
employee_id | employee_name | position | manager_id |
---|---|---|---|
100 | Иванов А.Б. | Технический директор | NULL |
101 | Петрова В.Г. | Руководитель отдела разработки | 100 |
102 | Сидоров Д.Е. | Руководитель отдела тестирования | 100 |
103 | Орлова Ж.З. | Программист | 101 |
104 | Соколов И.К. | Программист | 101 |
105 | Лебедева Л.М. | Программист | 101 |
106 | Кузнецов Н.О. | Тестировщик | 102 |
View on DB Fiddle
У каждого сотрудника в поле manager_id
хранится ссылка на employee_id
его непосредственного начальника. Такой способ используется для хранения иерархии между сотрудниками.
Наша задача — построить отчет, в котором для каждого сотрудника будет указан его непосредственный начальник.
Предположим, что у нас есть полная копия таблицы employees
— таблица employeesCopy
. У них одинаковые структура и данные. Тогда мы можем решить эту задачу с помощью соединения таблиц:
SELECT
empl.employee_name,
mngr.employee_name AS manager_name
FROM employees AS empl
LEFT JOIN employeescopy AS mngr ON
empl.manager_id = mngr.employee_id
Разберем этот запрос:
- Из таблицы
employees
мы загружаем сотрудников, поэтому дали ей псевдонимempl
. - Из таблицы
employeesCopy
мы загружаем менеджеров, поэтому дали ей псевдонимmngr
. - Поскольку нам нужны все сотрудники, мы используем
LEFT JOIN
. - Условие соединения гарантирует нам, что менеджер из
mngr
является непосредственным начальником сотрудника изempl
:empl.manager_id = mngr.employee_id
.
Результат выполнения запроса:
employee_name | manager_name |
---|---|
Иванов А.Б. | NULL |
Петрова В.Г. | Иванов А.Б. |
Сидоров Д.Е. | Иванов А.Б. |
Орлова Ж.З. | Петрова В.Г. |
Соколов И.К. | Петрова В.Г. |
Лебедева Л.М. | Петрова В.Г. |
Кузнецов Н.О. | Сидоров Д.Е. |
Мы решили задачу, предположив, что у нас есть полная копия таблицы employees
. Но в этом нет необходимости. Вместо таблицы-копии мы можем использовать саму таблицу employees
:
SELECT
empl.employee_name,
mngr.employee_name AS manager_name
FROM employees AS empl
LEFT JOIN employees AS mngr ON
empl.manager_id = mngr.employee_id
View on DB Fiddle
В этом запросе таблица employees
соединяется сама с собой. Этот прием называется SELF JOIN
. Логически этот запрос работает так же, как если бы мы соединяли таблицу со своей полной копией.
В SELF JOIN
запросах одна и та же таблица выполняет разные роли. Поэтому необходимо использовать псевдонимы для таблиц, чтобы явно разграничить эти роли.
В нашем запросе используется LEFT JOIN
, но это не принципиально. Другие виды соединений также поддерживают соединение таблицы с самой собой.
Применяем SELF JOIN
для поиска «дубликатов»
Допустим, в базе данных хранится информация об аккаунтах пользователей: имя аккаунта и адрес электронной почты. Мы хотим определить все случаи, когда на один и тот же адрес электронной почты зарегистрировано больше одного аккаунта.
Информация хранится в таблице users
:
users
user_id | nickname | |
---|---|---|
1 | RedFox | ivanova@email.com |
2 | BlackCat | petrov@email.com |
4 | Spider | sidorov@email.com |
3 | Hawk | petrov@email.com |
Переформулируем задачу в терминах, близких к базе данных: в таблице users
необходимо найти разные строки с одинаковыми значениями поля email
. Решим эту задачу с помощью INNER JOIN
:
SELECT
u1.nickname AS first_nickname,
u2.nickname AS second_nickname,
u1.email
FROM users AS u1
INNER JOIN users AS u2 ON
u1.email = u2.email
AND u1.user_id != u2.user_id
В этом запросе таблица users
соединяется сама с собой. Условием соединения является выражение: u1.email = u2.email AND u1.user_id != u2.user_id
. Благодаря ему в результат попадут только такие пары строк, у которых одинаковое значение email
, но разные user_id
.
Результат выполнения запроса:
first_nickname | second_nickname | |
---|---|---|
BlackCat | Hawk | petrov@email.com |
Hawk | BlackCat | petrov@email.com |
View on DB Fiddle
Логически можно представить, что СУБД для каждой строки попыталась найти подходящую по условию соединения пару. Поэтому в результате две строки.
Выводы
SELF JOIN
— это прием, когда выполняется соединение таблицы с самой собой. СУБД обрабатывает такие запросы так, будто мы соединяем две разные таблицы с одинаковой структурой и данными.
У SELF JOIN
есть две популярные области применения:
- Когда строки внутри одной таблицы ссылаются друг на друга, то есть образуют граф
- Когда нужно выполнить поиск разных строк, у которых совпадает значение одного из полей
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.