Диалект — PostgreSQL. JOIN, UNION, SELF JOIN, множественные объединения.
В реальной базе данных данные не хранятся в одной огромной таблице. Вместо этого они разделены на несколько таблиц, связанных между собой. Это называется нормализацией.
Зачем?
Это поле (или комбинация полей), которое уникально идентифицирует каждую строку в таблице.
В нашей таблице employees это id — у каждого сотрудника свой уникальный номер.
CREATE TABLE employees (
id SERIAL PRIMARY KEY, -- автоматически увеличивается
name VARCHAR(100),
department_id INTEGER, -- ссылка на отдел
...
);Это поле, которое ссылается на первичный ключ другой таблицы. Так мы связываем таблицы.
CREATE TABLE employees (
...
department_id INTEGER REFERENCES departments(id)
);department_id в employees — это внешний ключ, который указывает на id в таблице departments.
Давай добавим к employees ещё две таблицы, чтобы было что объединять.
departments| id | name | budget | location |
|---|---|---|---|
| 1 | IT | 5000000 | Москва |
| 2 | Продажи | 3000000 | СПб |
| 3 | Маркетинг | 2000000 | Казань |
| 4 | HR | 1500000 | Москва |
projects| id | name | department_id | start_date |
|---|---|---|---|
| 1 | Сайт | 1 | 2024-01-15 |
| 2 | Мобильное приложение | 1 | 2024-03-01 |
| 3 | Рекламная кампания | 3 | 2024-02-10 |
| 4 | CRM система | NULL | 2024-04-01 |
Обрати внимание: у проекта "CRM система"
department_id = NULL— он пока не привязан к отделу.
employees (теперь с department_id)| id | name | department_id | city | salary | bonus |
|---|---|---|---|---|---|
| 1 | Анна | 1 | Москва | 120000 | 20000 |
| 2 | Иван | 2 | СПб | 80000 | NULL |
| 3 | Мария | 1 | Казань | 130000 | 25000 |
| 4 | Андрей | 3 | Москва | 70000 | NULL |
| 5 | Елена | 2 | СПб | 90000 | 10000 |
| 6 | Павел | 1 | Москва | 110000 | NULL |
| 7 | Дарья | 3 | Казань | 75000 | 5000 |
| 8 | Сергей | 2 | Москва | 85000 | NULL |
Теперь вместо названия отдела у нас department_id — ссылка на departments.id.
Самый частый тип объединения. Берёт только те строки, для которых есть совпадение в обеих таблицах.
SELECT e.name, d.name AS department_name, e.salary
FROM employees e
INNER JOIN departments d ON e.department_id = d.id;→ Анна (IT), Иван (Продажи), Мария (IT), Андрей (Маркетинг), Елена (Продажи), Павел (IT), Дарья (Маркетинг), Сергей (Продажи).
employees.departments строку, где d.id = e.department_id.💡 Псевдонимы таблиц:
eиd— это короткие имена для таблиц. Удобно, чтобы не писать каждый разemployees.department_id.⚠️ Что если бы у сотрудника
department_id = NULL? Он бы не попал в результат, потому что нет совпадения.⚠️ Что если бы в
departmentsбыл отдел без сотрудников (например, HR)? Он тоже не попал бы в результат.
INNER JOIN — это пересечение двух таблиц.
Берёт все строки из левой таблицы (первой в запросе), а из правой — только те, где есть совпадение. Если совпадения нет, подставляет NULL.
SELECT e.name, d.name AS department_name, e.salary
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;Результат такой же, как INNER JOIN, потому что у всех сотрудников есть department_id.
Но если бы у Сергея department_id = NULL:
→ Сергей, NULL, 85000.
SELECT d.name AS department_name, e.name AS employee_name
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id;→ IT (Анна), IT (Мария), IT (Павел), Продажи (Иван), Продажи (Елена), Продажи (Сергей), Маркетинг (Андрей), Маркетинг (Дарья), HR, NULL.
Отдел HR попал в результат, но employee_name = NULL, потому что в этом отделе нет сотрудников.
💡 Когда использовать LEFT JOIN: когда тебе нужны все записи из основной таблицы, даже если нет связанных данных. Например: «покажи всех клиентов, даже если у них не было заказов».
Работает как LEFT JOIN, но наоборот: берёт всё из правой таблицы.
SELECT e.name, d.name AS department_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id;Это то же самое, что:
SELECT e.name, d.name AS department_name
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id;💡 Совет:
RIGHT JOINиспользуется редко. Проще поменять таблицы местами и использоватьLEFT JOIN— так запрос читается естественнее (слева направо).
Берёт все строки из обеих таблиц. Если нет совпадения — подставляет NULL с той стороны, где совпадения нет.
SELECT e.name, d.name AS department_name
FROM employees e
FULL OUTER JOIN departments d ON e.department_id = d.id;→ Все сотрудники (даже если у них нет отдела) + все отделы (даже если в них нет сотрудников).
Если бы у Сергея department_id = NULL, а отдел HR был бы без сотрудников:
→ Сергей, NULL + NULL, HR.
💡 Когда использовать: когда тебе нужно увидеть все данные из обеих таблиц, даже несвязанные.
Объединяет каждую строку из первой таблицы с каждой строкой из второй. Без условия ON.
SELECT e.name, d.name AS department_name
FROM employees e
CROSS JOIN departments d;→ 8 сотрудников × 4 отдела = 32 строки.
💡 Когда использовать: крайне редко. Обычно для генерации комбинаций (например, все возможные пары товаров для анализа).
⚠️ Осторожно: если таблицы большие (1000 × 1000), получишь миллион строк.
Это не JOIN, а другой способ комбинировать данные. UNION объединяет результаты двух запросов в одну таблицу (вертикально, а не горизонтально).
SELECT name AS person, 'employee' AS type
FROM employees
WHERE salary > 100000
UNION
SELECT name, 'department' AS type
FROM departments
WHERE budget > 3000000;→ Анна (employee), Мария (employee), Павел (employee), IT (department).
⚠️ Важно: количество и типы столбцов должны совпадать.
SELECT name FROM employees WHERE city = 'Москва'
UNION ALL
SELECT name FROM employees WHERE salary > 100000;Если Анна подходит под оба условия, она появится дважды.
💡 Когда что использовать:
UNION— когда нужны уникальные значения.UNION ALL— когда дубликаты допустимы (быстрее, не тратит ресурсы на их удаление).
Иногда нужно сравнить строки внутри одной таблицы. Для этого таблицу объединяют саму с собой.
SELECT e1.name AS employee, e1.salary, e2.name AS colleague, e2.salary AS colleague_salary
FROM employees e1
JOIN employees e2 ON e1.department_id = e2.department_id
WHERE e1.salary > e2.salary;→ Мария (130 000) > Анна (120 000), Мария (130 000) > Павел (110 000), Анна (120 000) > Павел (110 000), Елена (90 000) > Иван (80 000) и т.д.
💡 Как это работает: мы создаём две «копии» таблицы
employees(псевдонимыe1иe2) и объединяем их поdepartment_id.
Можно объединять больше двух таблиц.
SELECT e.name AS employee, d.name AS department, p.name AS project
FROM employees e
JOIN departments d ON e.department_id = d.id
LEFT JOIN projects p ON d.id = p.department_id;→ Анна (IT, Сайт), Анна (IT, Мобильное приложение), Мария (IT, Сайт), Мария (IT, Мобильное приложение), Павел (IT, Сайт), Павел (IT, Мобильное приложение), Иван (Продажи, NULL), Андрей (Маркетинг, Рекламная кампания) и т.д.
⚠️ Обрати внимание: Анна, Мария и Павел появились трижды, потому что в отделе IT два проекта. Это нормальное поведение
JOIN— каждая комбинация становится отдельной строкой.💡 Совет: если не хочешь дубликатов, используй
GROUP BYили подзапросы (этап 4).
Попробуй решить самостоятельно, не подглядывая ниже.
LEFT JOIN).SELF JOIN + подзапрос).UNION).-- 1
SELECT e.name, d.name AS department_name
FROM employees e
JOIN departments d ON e.department_id = d.id;
-- 2
SELECT e.name, d.name AS department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id;
-- 3
SELECT d.name, e.name AS employee_name
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id;
-- 4
SELECT d.name, COUNT(e.id) AS employee_count
FROM departments d
JOIN employees e ON d.id = e.department_id
GROUP BY d.name
HAVING COUNT(e.id) > 2;
-- 5
SELECT p.name AS project, d.name AS department
FROM projects p
JOIN departments d ON p.department_id = d.id;
-- 6
SELECT e1.name, e1.salary, AVG(e2.salary) AS dept_avg
FROM employees e1
JOIN employees e2 ON e1.department_id = e2.department_id
GROUP BY e1.name, e1.salary
HAVING e1.salary > AVG(e2.salary);
-- 7
SELECT name AS person, 'employee' AS type
FROM employees
UNION
SELECT name, 'department' AS type
FROM departments;
-- 8
SELECT e1.name AS employee1, e1.salary, e2.name AS employee2, e2.salary
FROM employees e1
JOIN employees e2 ON e1.department_id = e2.department_id
WHERE e1.salary > e2.salary;Ты готова к четвёртому этапу, если можешь без подсказки объяснить:
INNER JOIN отличается от LEFT JOIN;LEFT JOIN нет совпадения в правой таблице;e, d);UNION отличается от UNION ALL;SELF JOIN.