## ПОЛНОЕ РУКОВОДСТВО ПО JOIN В SQL --- ### 1. Что такое JOIN? **JOIN** — это операция, которая **объединяет строки из двух (или более) таблиц** на основе общего поля (ключа). Представьте, что у вас есть две таблицы: **Таблица "Клиенты" (customers):** | customer_id | name | |-------------|------| | 1 | Иван | | 2 | Петр | | 3 | Анна | **Таблица "Заказы" (orders):** | order_id | customer_id | product | |----------|-------------|---------| | 101 | 1 | Ноутбук | | 102 | 1 | Телефон | | 103 | 3 | Планшет | Теперь мы будем соединять их разными способами. --- ### 2. Диаграмма Венна — что это? **Диаграмма Венна** — это графическое представление множеств и их пересечений. В контексте SQL она показывает, **какие строки попадут в результат** при разных типах JOIN. Представьте два круга: - **Левый круг** — все строки из таблицы A (Customers) - **Правый круг** — все строки из таблицы B (Orders) - **Пересечение** — строки, которые есть в обеих таблицах (совпадение по ключу) --- ### 3. ВСЕ ТИПЫ JOIN (ПОДРОБНО) --- #### 3.1 INNER JOIN — Только пересечение **Диаграмма:** ``` ╭──────────╮ ╱ A ╲ │ ╭──────╮ │ │ ╱ ✅ ╲ │ ← Только пересечение (совпадающие) │ │ A∩B │ │ │ ╲ ╱ │ │ ╰──────╯ │ ╲ ╱ ╰──────────╯ ``` **Что делает:** Возвращает **только те строки**, где есть совпадение в ОБЕИХ таблицах. **Пример 3.1.1 — INNER JOIN:** ```sql -- Клиенты, у которых есть заказы SELECT c.name, o.product FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id; ``` **Результат:** | name | product | |------|---------| | Иван | Ноутбук | | Иван | Телефон | | Анна | Планшет | **Почему нет Петра?** У него нет заказов, поэтому он не попал в пересечение. --- #### 3.2 LEFT JOIN — Левая + пересечение **Диаграмма:** ``` ╭──────────╮ ╱ A ╲ │ ╭──────╮ │ │ ╱ ✅ ╲ │ ← Все строки из A + пересечение │ │ A∩B │ │ │ ╲ ╱ │ │ ╰──────╯ │ ╲ ✅ ╱ ╰──────────╯ ``` **Что делает:** Возвращает **ВСЕ строки из левой таблицы (A)** + совпадающие из правой (B). Если совпадений нет — в колонках из правой таблицы будет **NULL**. **Пример 3.2.1 — LEFT JOIN:** ```sql -- Все клиенты и их заказы (даже без заказов) SELECT c.name, o.product FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id; ``` **Результат:** | name | product | |------|---------| | Иван | Ноутбук | | Иван | Телефон | | Петр | NULL | | Анна | Планшет | **Почему Петр есть?** LEFT JOIN включает все строки из левой таблицы (customers), даже если нет совпадений. --- #### 3.3 RIGHT JOIN — Правая + пересечение **Диаграмма:** ``` ╭──────────╮ ╱ B ╲ │ ╭──────╮ │ │ ╱ ✅ ╲ │ ← Все строки из B + пересечение │ │ A∩B │ │ │ ╲ ╱ │ │ ╰──────╯ │ ╲ ✅ ╱ ╰──────────╯ ``` **Что делает:** Возвращает **ВСЕ строки из правой таблицы (B)** + совпадающие из левой (A). Если совпадений нет — в колонках из левой таблицы будет **NULL**. **Пример 3.3.1 — RIGHT JOIN:** ```sql -- Все заказы и их клиенты (даже если клиент удален) SELECT c.name, o.product FROM customers c RIGHT JOIN orders o ON c.customer_id = o.customer_id; ``` **Результат:** | name | product | |------|---------| | Иван | Ноутбук | | Иван | Телефон | | Анна | Планшет | **Зачем нужен RIGHT JOIN?** Обычно его используют, когда правая таблица — "главная" (например, мы хотим увидеть ВСЕ заказы, даже если клиент был удален из базы). > **Важно:** На практике `RIGHT JOIN` используется редко. Его всегда можно заменить на `LEFT JOIN`, просто поменяв таблицы местами. --- #### 3.4 FULL OUTER JOIN — Все строки из обеих таблиц **Диаграмма:** ``` ╭──────────╮ ╱ A ╱ ╲ │ ╭──────╮ │ │ ╱ ✅ ╲ │ ← ВСЕ строки из A И из B │ │ A∪B │ │ │ ╲ ╱ │ │ ╰──────╯ │ ╲ ╱ B ╱ ╰──────────╯ ``` **Что делает:** Возвращает **ВСЕ строки из обеих таблиц**. Если совпадения нет — недостающие колонки заполняются NULL. **Пример 3.4.1 — FULL OUTER JOIN:** ```sql -- Все клиенты и все заказы (полное объединение) SELECT c.name, o.product FROM customers c FULL OUTER JOIN orders o ON c.customer_id = o.customer_id; ``` **Результат:** | name | product | |------|---------| | Иван | Ноутбук | | Иван | Телефон | | Петр | NULL | | Анна | Планшет | (В данном примере результат похож на LEFT JOIN, потому что у всех заказов есть клиенты. Если бы был заказ без клиента — он бы тоже появился.) --- #### 3.5 CROSS JOIN — Декартово произведение **Диаграмма:** ``` ╭──────────╮ ╱ A ╲ │ ╭──────╮ │ │ ╱ ✅ ╲ │ ← КАЖДАЯ строка из A │ │ A × B │ │ соединяется с КАЖДОЙ строкой из B │ ╲ ╱ │ │ ╰──────╯ │ ╲ B ╱ ╰──────────╯ ``` **Что такое Декартово произведение?** Это **все возможные комбинации** строк из таблицы A и таблицы B. **Формула:** ``` Количество строк результата = Количество строк в A × Количество строк в B ``` **Пример 3.5.1 — CROSS JOIN:** ```sql -- Все комбинации клиентов и товаров (без учета реальных заказов) SELECT c.name, p.product_name FROM customers c CROSS JOIN products p; ``` **Результат (если у нас 3 клиента и 100 товаров):** | name | product_name | |------|--------------| | Иван | Ноутбук | | Иван | Телефон | | Иван | Планшет | | Петр | Ноутбук | | Петр | Телефон | | Петр | Планшет | | Анна | Ноутбук | | Анна | Телефон | | Анна | Планшет | **3 × 3 = 9 строк** (в реальности 3 × 100 = 300 строк). **Где применяется CROSS JOIN?** - Для генерации тестовых данных - Для создания "календарных сеток" (например, все комбинации магазин × день) - Для построения матриц (например, все возможные пары товаров для анализа корзины) > **Внимание:** CROSS JOIN может создать **огромную** таблицу! Если в A 1000 строк, а в B 1000 — результат будет 1 000 000 строк. --- ### 4. СРАВНИТЕЛЬНАЯ ТАБЛИЦА ВСЕХ JOIN | Тип JOIN | Какие строки попадают | Когда использовать | | :--- | :--- | :--- | | **INNER JOIN** | Только пересечение | Когда нужны только данные, которые есть в обеих таблицах | | **LEFT JOIN** | Вся левая + пересечение | Когда нужно показать ВСЕ записи из основной таблицы, даже без данных в связанной | | **RIGHT JOIN** | Вся правая + пересечение | Когда нужно показать ВСЕ записи из связанной таблицы (редко) | | **FULL OUTER JOIN** | Все строки из обеих | Когда нужно ничего не потерять из обеих таблиц | | **CROSS JOIN** | Каждая строка с каждой | Для генерации комбинаций / матриц | --- ### 5. ВИЗУАЛЬНЫЙ ПРИМЕР (НА ДАННЫХ) #### Таблицы: **employees:** | employee_id | name | |-------------|------| | 1 | Иван | | 2 | Петр | | 3 | Анна | **departments:** | dept_id | dept_name | employee_id | |---------|-----------|-------------| | 101 | Продажи | 1 | | 102 | Маркетинг | 1 | | 103 | IT | 3 | | 104 | Бухгалтерия | NULL | --- #### 5.1 INNER JOIN ```sql SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.employee_id = d.employee_id; ``` **Результат:** Иван-Продажи, Иван-Маркетинг, Анна-IT. (Петр потерялся, Бухгалтерия потерялась) --- #### 5.2 LEFT JOIN ```sql SELECT e.name, d.dept_name FROM employees e LEFT JOIN departments d ON e.employee_id = d.employee_id; ``` **Результат:** Иван-Продажи, Иван-Маркетинг, Анна-IT, **Петр-NULL**. (Все сотрудники есть, даже без отдела) --- #### 5.3 RIGHT JOIN ```sql SELECT e.name, d.dept_name FROM employees e RIGHT JOIN departments d ON e.employee_id = d.employee_id; ``` **Результат:** Иван-Продажи, Иван-Маркетинг, Анна-IT, **NULL-Бухгалтерия**. (Все отделы есть, даже без сотрудника) --- #### 5.4 FULL OUTER JOIN ```sql SELECT e.name, d.dept_name FROM employees e FULL OUTER JOIN departments d ON e.employee_id = d.employee_id; ``` **Результат:** Иван-Продажи, Иван-Маркетинг, Анна-IT, **Петр-NULL**, **NULL-Бухгалтерия**. (Ничего не потеряно) --- #### 5.5 CROSS JOIN ```sql SELECT e.name, d.dept_name FROM employees e CROSS JOIN departments d; ``` **Результат:** 3 сотрудника × 4 отдела = 12 строк. (Иван-Продажи, Иван-Маркетинг, Иван-IT, Иван-Бухгалтерия, Петр-Продажи, ...) --- ### 6. ПОЧЕМУ ВАЖНО ПОНИМАТЬ JOIN? | Сценарий | Какой JOIN использовать | | :--- | :--- | | Отчет по продажам (только клиенты с покупками) | `INNER JOIN` | | Список всех клиентов (даже тех, кто ничего не купил) | `LEFT JOIN` | | Анализ: какие товары НИКОГДА не продавались | `LEFT JOIN` + `WHERE right.id IS NULL` | | Поиск заказов без клиентов (ошибки в БД) | `RIGHT JOIN` или `FULL OUTER JOIN` | | Построение матрицы "клиент × товар" для анализа | `CROSS JOIN` | | Сравнение двух таблиц (что есть в одной, но нет в другой) | `FULL OUTER JOIN` + `WHERE` | --- ### 7. ШПАРГАЛКА (ЗАПОМНИТЬ НАВСЕГДА) ``` INNER JOIN = Только совпадения LEFT JOIN = Все из левой + совпадения RIGHT JOIN = Все из правой + совпадения FULL JOIN = Все из левой И все из правой CROSS JOIN = Каждый с каждым (Декартово произведение) ``` **Мнемоническое правило:** - `INNER` = "внутри" (только то, что внутри пересечения) - `LEFT` = "слева все" (левая таблица важнее) - `RIGHT` = "справа все" (правая таблица важнее) - `FULL` = "все полностью" (обе таблицы важны) - `CROSS` = "крест-накрест" (каждый с каждым) --- #### 8. ОШИБКИ НОВИЧКОВ ##### Ошибка 1: Использовать INNER JOIN, когда нужен LEFT **Плохо:** ```sql -- Потеряем клиентов без заказов SELECT c.name, SUM(o.amount) FROM customers c JOIN orders o ON c.id = o.customer_id GROUP BY c.name; ``` **Хорошо:** ```sql -- Все клиенты (даже без заказов покажут 0) SELECT c.name, COALESCE(SUM(o.amount), 0) FROM customers c LEFT JOIN orders o ON c.id = o.customer_id GROUP BY c.name; ``` #### Ошибка 2: Забыть про NULL в LEFT JOIN ```sql -- Неправильно (потеряет клиентов без заказов) SELECT * FROM customers c LEFT JOIN orders o ON c.id = o.customer_id WHERE o.amount > 1000; ``` **Правильно:** ```sql -- Фильтруем после JOIN или используем INNER SELECT * FROM customers c LEFT JOIN orders o ON c.id = o.customer_id WHERE o.amount > 1000 OR o.amount IS NULL; ``` #### Ошибка 3: Слишком много CROSS JOIN ```sql -- Если в таблицах по 10 000 строк -> 100 000 000 строк! SELECT * FROM big_table1 CROSS JOIN big_table2; ``` --- ### 9. ПРАКТИЧЕСКОЕ ЗАДАНИЕ ДЛЯ СТУДЕНТА На основе базы Northwind напишите: 1. **INNER JOIN** — список заказов с именем сотрудника (orders → employees) 2. **LEFT JOIN** — все товары и количество их продаж (products → order_details) (товары без продаж должны показать NULL) 3. **LEFT JOIN + IS NULL** — товары, которые никогда не продавались 4. **FULL OUTER JOIN** — сравнение: какие клиенты есть в customers, но не в orders, и наоборот 5. **CROSS JOIN** — все комбинации товаров и категорий (для проверки: сколько строк получится?) **Решение (для проверки):** ```sql -- 1. INNER JOIN SELECT o.order_id, e.first_name || ' ' || e.last_name AS employee_name FROM orders o INNER JOIN employees e ON o.employee_id = e.employee_id; -- 2. LEFT JOIN (все товары + продажи) SELECT p.product_name, SUM(od.quantity) AS total_sold FROM products p LEFT JOIN order_details od ON p.product_id = od.product_id GROUP BY p.product_id, p.product_name; -- 3. Товары, которые никогда не продавались SELECT p.product_name FROM products p LEFT JOIN order_details od ON p.product_id = od.product_id WHERE od.product_id IS NULL; -- 4. FULL OUTER JOIN (клиенты без заказов + заказы без клиентов) SELECT c.customer_id AS customer_id, o.order_id AS order_id FROM customers c FULL OUTER JOIN orders o ON c.customer_id = o.customer_id WHERE c.customer_id IS NULL OR o.order_id IS NULL; -- 5. CROSS JOIN (количество строк = количество товаров × количество категорий) SELECT p.product_name, c.category_name FROM products p CROSS JOIN categories c; ``` --- Теперь студент полностью понимает **все типы JOIN**, их диаграммы и практическое применение. Это знание проверяют на **каждом** собеседовании! 🎯