Menu

SQLite Self Join: соединяем таблицу саму с собой

Разбираем self join в SQLite: как через алиасы соединить таблицу саму с собой, примеры с сотрудниками и руководителями, иерархические данные.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Self join - это обычный JOIN, просто с алиасами

В self join нет ничего магического. Это самый обычный JOIN, у которого по обе стороны оказывается одна и та же таблица. Вся хитрость в том, что SQLite нужно как-то различать две её копии - поэтому каждой даём свой алиас.

Соединение таблицы самой с собой пригождается, когда строка ссылается на другую строку в той же таблице. Хрестоматийный пример - таблица employees, где у каждого сотрудника есть manager_id, указывающий на его руководителя:

У Ады руководителя нет. Борис и Клео подчиняются Аде. Диего и Эсме - Борису. Вся эта иерархия живёт в одной-единственной таблице, и именно тут self join по-настоящему показывает свою силу.

Базовый синтаксис self join

Чтобы вытащить каждого сотрудника вместе с именем его руководителя, соединим таблицу employees саму с собой. Одна копия будет играть роль «сотрудника», вторая - «руководителя»:

Думайте об этом как о двух таблицах, которые просто хранятся в одном месте. e - это строка сотрудника, m - строка руководителя. Условие соединения e.manager_id = m.id сшивает их вместе: для каждого сотрудника ищется строка из m, у которой id совпадает с manager_id этого сотрудника.

Обратите внимание, что Ада не попала в результат. У неё manager_id равен NULL, а INNER JOIN молча выбрасывает строки, для которых не нашлось пары.

Сохраняем строки без пары: LEFT JOIN

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

Теперь в результате появляется и Ada - с NULL в колонке руководителя. Механика self join та же самая, просто LEFT JOIN делает то, что и должен: сохраняет все строки левой таблицы и подставляет пустоту там, где совпадения нет.

Именно такой вариант обычно и нужен, когда вы выводите список сотрудников. «Нет руководителя» - это тоже информация, и терять такую строку не стоит.

Без алиасов никак

Попробуйте написать тот же запрос без алиасов - и SQLite просто не поймёт, чего вы от него хотите:

SELECT name, manager_id FROM employees JOIN employees ON manager_id = id;
-- Ошибка: неоднозначное имя столбца: name

Каждый столбец появляется дважды - по одному из каждой копии таблицы - и SQLite не может выбрать нужный. Алиасы решают эту проблему, давая каждому экземпляру собственное имя. Подбирайте алиасы так, чтобы они отражали роль строки, а не имя таблицы:

  • e и m - для сотрудника и руководителя.
  • parent и child - для иерархий.
  • a и b - когда сравниваете произвольные пары.

Именно благодаря алиасу self join читается легко.

Поиск пар внутри одной таблицы

Self join - это не только про иерархии. Шаблон подходит везде, где нужно сравнить строки в пределах одной таблицы. Допустим, есть список товаров, и мы хотим найти все пары с одинаковой ценой:

Здесь стоит обратить внимание на две вещи. Во-первых, a.price = b.price - это собственно условие совпадения. Во-вторых, a.id < b.id нужен для того, чтобы запрос не возвращал каждую пару по два раза (сначала (Кружка, Блокнот), потом (Блокнот, Кружка)) и не сводил строку саму с собой. Этот приём с < стоит запомнить - он пригодится всякий раз, когда нужно перечислить пары.

Поднимаемся на два уровня выше

Один self join покрывает один шаг по иерархии. А если хочется узнать «руководителя руководителя» для каждого сотрудника? Тогда соединяем таблицу саму с собой трижды:

Каждый новый алиас - это ещё один шаг вверх по дереву. Для двух-трёх уровней такой подход работает нормально, но дальше всё разваливается: нужно заранее знать глубину иерархии и добавлять по join'у на каждый уровень. Именно эту стену в своё время и пробили рекурсивные CTE.

Когда self join - не лучший выбор

Self join уместен, когда в результат нужны столбцы с обеих сторон отношения. Если же задача просто отфильтровать - например, найти всех сотрудников, у которых руководитель Ада, - подзапрос обычно читается приятнее:

Никакой возни с алиасами, и намерение читается с первого взгляда. Простое правило: нужны ли в результате данные сразу из обеих строк? Тогда self join. А если требуется лишь значение для сравнения - берите подзапрос.

Когда же речь о иерархиях произвольной глубины (оргструктуры, файловые деревья, ветки комментариев), ни тот ни другой подход уже не тянет. Тут на сцену выходят рекурсивные CTE.

Дальше: подзапросы

Self join и подзапросы решают пересекающиеся задачи, и понимание, где какой инструмент уместен, избавит вас от долгих раздумий над SQL в будущем. На следующей странице подробно разберём подзапросы - скалярные, коррелированные и форму с IN - и покажем, в каких ситуациях каждый из них раскрывается лучше всего.

Часто задаваемые вопросы

Что такое self join в SQLite?

Self join - это обычный JOIN, в котором таблица соединяется сама с собой. Вы задаёте ей два разных алиаса, чтобы SQLite воспринимал их как два отдельных источника строк, и сопоставляете записи по столбцу, который связывает одну строку с другой. Чаще всего так оформляют связь «родитель–потомок», например сотрудник и его руководитель.

Зачем в self join обязательно нужны алиасы?

Без алиасов SQLite просто не поймёт, к какой именно «копии» таблицы относится столбец, который вы пишете. Когда у каждого экземпляра свой алиас (скажем, e для сотрудника и m для руководителя), вы спокойно пишете e.manager_id = m.id без всякой двусмысленности. Это не вопрос стиля - без алиасов запрос просто не разберётся парсером.

Когда выбрать self join, а когда подзапрос?

Self join берут, когда в результате нужны столбцы сразу из обеих строк - например, имя сотрудника и имя его руководителя в одной строке выдачи. Подзапрос подойдёт, если нужно только отфильтровать данные или подтянуть одно значение. А вот для глубоко вложенных иерархий ни то, ни другое толком не работает - там в дело идёт рекурсивный CTE.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ