Соединение таблицы самой с собой: менеджеры, пары и одна и та же таблица дважды
Сотрудники и их менеджеры являются сотрудниками, поэтому оба живут в одной таблице. Чтобы отобразить имя рядом с именем менеджера, вам нужно соединить эту таблицу с самой собой, что первые несколько раз кажется неправильным и совершенно обычным.
Что вы делаете: дважды напишите таблицу с двумя псевдонимами и с этого момента обрабатывайте их как две отдельные таблицы. Для восьми сотрудников ниже внутренняя версия возвращает 6 строк, а ЛЕВАЯ версия — 8.
Краткая версия. Самостоятельное соединение — это обычное соединение. Единственная новая вещь заключается в том, что вы должны назвать две копии.
Стол
CREATE TABLE employees(emp_id INT, name TEXT, manager_id INT, dept TEXT, salary INT);
INSERT INTO employees VALUES
(1,'Alvarez',NULL,'Exec',120000),
(2,'Brennan',1,'Sales',72000),
(3,'Cho',1,'Sales',68000),
(4,'Diaz',2,'Sales',54000),
(5,'Ellis',2,'Sales',51000),
(6,'Fowler',3,'Support',49000),
(7,'Grant',NULL,'Support',47000),
(8,'Hale',3,'Support',45000);
manager_id указывает на emp_id в той же таблице. У Альвареса нет менеджера, как и у Гранта, и это имеет значение.
Запрос
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON m.emp_id = e.manager_id
ORDER BY e.emp_id;
| сотрудник | менеджер |
|---|---|
| Альварес | NULL |
| Бреннан | Альварес |
| Давать | Альварес |
| Диас | Бреннан |
| Эллис | Бреннан |
| Фаулер | Давать |
| Грант | NULL |
| Хейл | Давать |
Прочитайте предложение соединения как предложение: копия менеджера emp_id равен копии сотрудника manager_id. Неправильный вариант — это самая распространенная ошибка самосоединения, и она дает результат, который выглядит нормально, пока вы не заметите, что иерархия перевернута.
Внутренний или ЛЕВЫЙ: 6 рядов против 8.
SELECT COUNT(*) FROM employees e JOIN employees m ON m.emp_id = e.manager_id;
-- 6
SELECT COUNT(*) FROM employees e LEFT JOIN employees m ON m.emp_id = e.manager_id;
-- 8
Внутреннее объединение молча теряет Альвареса и Гранта, поскольку ни у одного из них нет подходящей строки менеджера. В отчете о численности это ошибка двух человек из восьми, и она невидима: результат представляет собой совершенно нормальный список.
Правило обобщает. Любое самостоятельное присоединение к иерархии теряет ее вершину, а любое самостоятельное присоединение к иерархии теряет ее нижнюю часть. Решите, какой конец вы готовы отбросить, и если ответ ни тот, ни другой, используйте LEFT JOIN.
Поиск пар в одной таблице
Другое основное применение: строки, которые что-то разделяют. Коллеги в одном отделе, заказы от одного и того же клиента в один и тот же день, дублирующиеся записи.
SELECT a.name, b.name, a.dept
FROM employees a
JOIN employees b ON b.dept = a.dept
WHERE a.emp_id < b.emp_id;
тот WHERE линия выполняет две задачи, и обе они необходимы. Без него каждый человек сопоставляется сам с собой, и каждая подлинная пара появляется дважды в обоих порядках. С a.emp_id < b.emp_id, каждая пара возвращается ровно один раз и не содержит пар строк сама с собой.
В продаже есть Бреннан, Чо, Диас и Эллис, что дает 6 пар. В поддержке есть Фаулер, Грант и Хейл, что дает 3. У Исполнительный директор есть один человек, а он не дает ни одного. Девять строк, а арифметика — проверка: н человек в отделе производят н(n-1)/2 пар.
Еще три формы, которые стоит знать
| Вопрос | Форма |
|---|---|
| Кто зарабатывает больше своего менеджера | Присоединяйтесь сами, тогда WHERE e.salary > m.salary |
| Два уровня вверх | Присоединяйтесь к таблице три раза: сотрудник, менеджер, менеджер менеджера. |
| Вся цепочка, любая глубина | Рекурсивный CTE, WITH RECURSIVE |
По этим данным, Бреннан зарабатывает 72 000, а их менеджер Альварес — 120 000, так что никто не зарабатывает больше своего менеджера. Измените одно число, и запрос найдет его, что делает его полезным аудитом, а не головоломкой.
Три уровня — это практический предел для повторных объединений. Помимо этого или когда глубина меняется, вам нужна рекурсия, и это другая страница.
Ошибки в том порядке, в котором они происходят
- Никаких псевдонимов. База данных не может разрешить
emp_idи вызывает неоднозначную ошибку столбца. Этот громкий, что делает его лучшим из пяти. - Условие соединения изменилось. Никакой ошибки. Иерархия получается перевернутой.
- Внутреннее объединение образует иерархию. Никакой ошибки. Верхушка дерева исчезает.
- Никакого неравенства в парном запросе. Каждая пара дважды, плюс совпадения, поэтому считается двойной.
- Повсюду однобуквенные псевдонимы. Законно и нечитабельно через шесть месяцев. Использовать
empиmgrскорее, чемaиbкогда две копии означают разные вещи.
Как применить это в своей работе
- Найдите в базе данных таблицу, которая указывает на себя: идентификатор менеджера, родительскую категорию, идентификатор заменяющей этой записи. Это встречается чаще, чем люди ожидают.
- Сначала напишите версию LEFT JOIN, посчитайте NULL и спросите, является ли каждый из них настоящей вершиной дерева или отсутствующим значением.
- Каждый раз сравнивайте внутренний и ЛЕВЫЙ счетчики. Разница заключается в количестве строк, которые внутренняя версия могла бы скрыть.
- Назовите свои псевдонимы по роли, а не по алфавиту.
- Прежде чем поверить в любой парный запрос, проверьте счетчик на соответствие н(n-1)/2.
Единственная привычка, которую нужно сохранить
Произнесите вслух предложение соединения как предложение о ролях: идентификатор строки менеджера равен идентификатору менеджера строки сотрудника. Самосоединение не имеет естественных имен таблиц, на которые можно было бы опереться, поэтому предложение — единственное, что сохраняет прямое направление.
В какой таблице вашей базы данных есть столбец, указывающий на собственный ключ?
SQL для аналитиков — это 458 страниц, которые считывают запросы построчно в повседневных словах, поэтому ошибочный результат нужно где-то отслеживать, а не перепечатывать, пока он не станет выглядеть лучше.
SQL для аналитиков, 19 $ →Объяснение SQL JOIN охватывает четыре типа, почему объединение дублирует строки описывает, что происходит, когда ключ не уникален, и SQL-практикум дает вам один работоспособный запрос за раз.
Откройте набор SQL →