← Назад к вопросам

В чем разница между LEFT, RIGHT и INNER JOIN?

1.3 Junior🔥 241 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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;

Выводы

  1. INNER JOIN = только совпадения (пересечение)
  2. LEFT JOIN = всё из левой + совпадения (используется чаще всего)
  3. RIGHT JOIN = всё из правой + совпадения (редко, переписывай в LEFT)
  4. Для поиска "БЕЗ" используй LEFT JOIN с WHERE IS NULL
  5. Всегда индексируй поля в условии JOIN
  6. Используй FETCH при LEFT JOIN в JPA чтобы избежать N+1 problem