В чем разница между LEFT, RIGHT и INNER JOIN?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
LEFT, RIGHT и INNER JOIN: полное руководство
JOIN — это одна из самых важных операций в SQL. Давайте разберемся в каждом типе и когда его использовать.
Основная концепция
JOIN объединяет данные из разных таблиц по условию (обычно по иностранному ключу):
Табля users: Таблица orders:
id | name id | user_id | total
1 | Alice 1 | 1 | 100
2 | Bob 2 | 1 | 200
3 | Charlie 3 | 2 | 150
(нет заказов от Charlie)
1. INNER JOIN (пересечение)
Возвращает ТОЛЬКО строки, которые есть в ОБЕИХ таблицах:
SELECT users.id, users.name, orders.id, orders.total
FROM users
INNER JOIN orders ON users.id = orders.user_id;
Результат:
user_id | name | order_id | total
1 | Alice | 1 | 100
1 | Alice | 2 | 200
2 | Bob | 3 | 150
❌ Charlie НЕ появляется (у нее нет заказов)
Диаграмма Венна:
users orders
│ ∩ │
└───┘ ← только пересечение
Когда использовать:
- Хочу видеть ТОЛЬКО пользователей, у которых есть заказы
- Нужны только корректные данные без пропусков
// В JPA/Spring Data
@Query("""
SELECT new com.example.UserOrderDTO(u.id, u.name, o.id, o.total)
FROM User u
INNER JOIN Order o ON u.id = o.userId
""")
List<UserOrderDTO> getAllUsersWithOrders();
2. LEFT JOIN (левая таблица плюс совпадения)
Возвращает ВСЕ строки из левой таблицы + совпадающие из правой. Если совпадения нет → NULL:
SELECT users.id, users.name, orders.id, orders.total
FROM users
LEFT JOIN orders ON users.id = orders.user_id;
Результат:
user_id | name | order_id | total
1 | Alice | 1 | 100
1 | Alice | 2 | 200
2 | Bob | 3 | 150
3 | Charlie | NULL | NULL ← Charlie БЕЗ заказов!
Диаграмма Венна:
users orders
███│ │ ← всё из users + совпадения
└─┴───┘
Когда использовать:
- Нужны ВСЕ пользователи, даже если нет заказов
- Нужно найти пользователей БЕЗ заказов
// Получить всех пользователей с количеством заказов
@Query("""
SELECT new com.example.UserStatsDTO(
u.id, u.name, COUNT(o.id)
)
FROM User u
LEFT JOIN Order o ON u.id = o.userId
GROUP BY u.id, u.name
""")
List<UserStatsDTO> getAllUsersWithOrderCount();
// Результат:
// Alice: 2 заказа
// Bob: 1 заказ
// Charlie: 0 заказов ← видна несмотря на отсутствие заказов!
Найти пользователей БЕЗ заказов:
SELECT u.id, u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL; -- только без совпадений
Результат:
id | name
3 | Charlie
3. RIGHT JOIN (правая таблица плюс совпадения)
Обратная LEFT JOIN — возвращает ВСЕ строки из ПРАВОЙ таблицы + совпадающие из левой:
SELECT users.id, users.name, orders.id, orders.total
FROM users
RIGHT JOIN orders ON users.id = orders.user_id;
Результат:
user_id | name | order_id | total
1 | Alice | 1 | 100
1 | Alice | 2 | 200
2 | Bob | 3 | 150
❌ Строк для NULL users не будет (нет заказов без пользователя)
Диаграмма Венна:
users orders
│ ███ ← всё из orders + совпадения
└─┴───┘
Важно: RIGHT JOIN используется редко! Часто переписывают как LEFT JOIN (просто меняют таблицы местами):
-- Вместо этого (RIGHT JOIN)
SELECT u.*, o.*
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
-- Пишут так (LEFT JOIN, проще читать)
SELECT u.*, o.*
FROM orders o
LEFT JOIN users u ON o.user_id = u.id;
Сравнительная таблица
┌──────────────┬─────────────────────────────┬──────────┐
│ JOIN │ Возвращает │ NULL │
├──────────────┼─────────────────────────────┼──────────┤
│ INNER JOIN │ Только совпадения │ Нет │
│ │ (пересечение) │ │
├──────────────┼─────────────────────────────┼──────────┤
│ LEFT JOIN │ ВСЕ из левой + совпадения │ Да │
│ │ (левая таблица целиком) │ (правая) │
├──────────────┼─────────────────────────────┼──────────┤
│ RIGHT JOIN │ ВСЕ из правой + совпадения │ Да │
│ │ (правая таблица целиком) │ (левая) │
├──────────────┼─────────────────────────────┼──────────┤
│ FULL OUTER │ ВСЕ из обеих таблиц │ Да │
│ JOIN │ (объединение) │ (обе) │
└──────────────┴─────────────────────────────┴──────────┘
Практические примеры
Пример 1: Найти все заказы с информацией о пользователе
SELECT o.id, o.total, u.name
FROM orders o
INNER JOIN users u ON o.user_id = u.id;
-- Только заказы, у которых есть пользователь ✓
-- Alice: 2 заказа
-- Bob: 1 заказ
Пример 2: Найти информацию о продажах по категориям
SELECT
c.name as category,
COUNT(o.id) as order_count,
SUM(o.total) as total_revenue
FROM categories c
LEFT JOIN products p ON c.id = p.category_id
LEFT JOIN orders o ON p.id = o.product_id
GROUP BY c.id, c.name;
-- Видны ВСЕХ категории, даже без продуктов или заказов
-- Категория "Electronics": 5 заказов
-- Категория "Books": 0 заказов (но видна!) ✓
Пример 3: Сравнить две таблицы (найти различия)
-- Записи в table_a, которых нет в table_b
SELECT a.*
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
WHERE b.id IS NULL;
-- Записи в table_b, которых нет в table_a
SELECT b.*
FROM table_a a
RIGHT JOIN table_b b ON a.id = b.id
WHERE a.id IS NULL;
На Java с Spring Data JPA
@Entity
public class User {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Order> orders = new ArrayList<>();
}
@Entity
public class Order {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
private BigDecimal total;
}
// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// LEFT JOIN (fetch = FetchType.LEFT automatically)
@Query("""
SELECT u FROM User u
LEFT JOIN FETCH u.orders
""")
List<User> findAllUsersWithOrders();
// INNER JOIN
@Query("""
SELECT u FROM User u
INNER JOIN u.orders o
""")
List<User> findUsersWithAtLeastOneOrder();
}
Производительность
INNER JOIN: быстро (меньше строк)
LEFT JOIN: медленнее (больше строк с NULL)
RIGHT JOIN: не используй, переработай в LEFT
Оптимизация:
-- Плохо: N+1 problem (без JOIN)
SELECT * FROM users;
-- потом в Java цикле: for each user → SELECT * FROM orders WHERE user_id = ?
-- Хорошо: одно с JOIN
SELECT u.*, o.* FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
Выводы
- INNER JOIN = только совпадения (пересечение)
- LEFT JOIN = всё из левой + совпадения (используется чаще всего)
- RIGHT JOIN = всё из правой + совпадения (редко, переписывай в LEFT)
- Для поиска "БЕЗ" используй LEFT JOIN с WHERE IS NULL
- Всегда индексируй поля в условии JOIN
- Используй FETCH при LEFT JOIN в JPA чтобы избежать N+1 problem