JavaRush /Курсы /SQL SELF /Дополнительные условия в JOIN: ON .....

Дополнительные условия в JOIN: ON ... AND ...

SQL SELF
12 уровень , 2 лекция
Открыта

Вы уже знаете, как объединять таблицы с помощью JOIN. Но в реальной жизни простого совпадения по ключам может быть недостаточно. Часто возникает задача: объединять данные, только если они соответствуют дополнительному критерию — например, только активные записи, только данные за текущий год или только завершённые заказы.

И вот тут в игру вступает расширение конструкции ON с помощью AND.

Дополнительные условия в JOIN ... ON позволяют точно контролировать, какие строки участвуют в соединении, ещё до того, как SQL начнёт строить результат. Это делает запрос:

  • Быстрее (меньше строк проходит через JOIN),
  • Точнее (фильтрация происходит на этапе соединения),
  • Предсказуемее при работе с LEFT JOIN (в отличие от фильтрации в WHERE).

Пример: Только активные записи на курсы

Допустим, таблица enrollments содержит статус участия студента: active, dropped, pending.

Таблица students:

id name
1 Otto Song
2 Maria Chi
3 Alex Lin

Обновим таблицу enrollments:

student_id course_id status
1 101 active
1 103 active
2 102 dropped
3 101 active

Таблица courses:

id name
101 Mathematics
102 Physics
103 Computer Science

Теперь мы хотим получить только тех студентов, у которых активные курсы:

SELECT 
    students.name AS student_name,
    courses.name AS course_name
FROM students 
INNER JOIN enrollments 
    ON students.id = enrollments.student_id
    AND enrollments.status = 'active'
INNER JOIN courses 
    ON enrollments.course_id = courses.id;

Результат:

student_name course_name
Otto Song Mathematics
Otto Song Computer Science
Alex Lin Mathematics

Здесь мы добавили AND enrollments.status = 'active' внутрь ON, чтобы соединение происходило только по активным записям, а не фильтровалось после объединения.

Почему не WHERE?

Можно было бы написать так:

...
WHERE enrollments.status = 'active'

Но это имеет другое поведение при LEFT JOIN. Фильтрация в WHERE удаляет строки, где нет совпадений (NULL), и тем самым превращает LEFT JOIN в INNER JOIN.

А вот условие AND enrollments.status = 'active' внутри ON сразу ограничивает сами соединяемые строки — управляет тем, какие строки вообще попадут в соединение, а не просто фильтрует результат после.

Такой подход особенно важен, если вы хотите сохранить строки из одной таблицы, даже если в другой таблице нет подходящих значений (что часто бывает в отчётах и аналитике).

Ещё примеры использования ON ... AND ...

Таблица students:

id name
1 Otto Song
2 Maria Chi
3 Alex Lin

Таблица enrollments:

student_id course_id status enrolled_at
1 101 active 2025-02-01
1 103 active 2025-03-05
2 102 dropped 2024-05-15
3 101 active 2025-03-12

Таблица courses:

id name
101 Math
102 Physics
103 CS

Пример: только курсы текущего года

SELECT
    students.name,
    courses.name,
    enrollments.enrolled_at
FROM students
JOIN enrollments
    ON students.id = enrollments.student_id
    AND EXTRACT(YEAR FROM enrollments.enrolled_at) = EXTRACT(YEAR FROM CURRENT_DATE)
JOIN courses
    ON enrollments.course_id = courses.id;

Здесь мы объединяем только те записи, которые относятся к текущему году.

name name enrolled_at
Otto Song Math 2025-02-01
Otto Song CS 2025-03-05
Alex Lin Math 2025-03-12

Пример: исключение по значению

JOIN enrollments
    ON students.id = enrollments.student_id
    AND enrollments.status != 'dropped'

Исключаем студентов, которые отчислились, на этапе соединения, а не фильтруем после.

name name
Otto Song Math
Otto Song CS
Alex Lin Math

Когда условие размещено внутри ON, PostgreSQL может оптимизировать план соединения и обработать меньше строк. Это особенно критично на больших объёмах данных. Внутренняя фильтрация эффективнее, чем "отсев" после JOIN.

JOIN ON — это не только ключи

Многие думают, что ON — это просто id = id. На самом деле туда можно вставлять:

  • Логические операторы: AND, OR, NOT
  • Сравнения: >, <, <>, BETWEEN, IN
  • Выражения: EXTRACT, DATE_TRUNC, COALESCE, NULLIF

Комбинируем всё вместе

Таблица students:

id name
1 Otto Song
2 Maria Chi
3 Alex Lin

Таблица faculties:

id name
10 Engineering
20 Natural Sciences
30 ← без имени (NULL)

Таблица courses:

id name teacher faculty_id
101 Math Liam Park 10
102 Physics Chloe Zhang 20
103 CS Noah Kim 10
104 PE Ava Chen 30

Таблица enrollments:

student_id course_id status
1 101 active
1 103 active
2 102 dropped
3 101 active
3 104 active
SELECT
    s.name AS student_name,
    c.name AS course_name,
    f.name AS faculty_name
FROM students s
JOIN enrollments e
    ON s.id = e.student_id
    AND e.status = 'active'
JOIN courses c
    ON e.course_id = c.id
    AND c.name != 'Физкультура'
JOIN faculties f
    ON c.faculty_id = f.id
    AND f.name IS NOT NULL;

Здесь мы одновременно фильтруем по:

  • Активным записям,
  • Курсам, кроме "Физкультуры",
  • Факультетам, у которых есть имя.

Результат выполнения запроса:

student_name course_name faculty_name
Otto Song Math Engineering
Otto Song CS Engineering
Alex Lin Math Engineering

Надеюсь вам понравилась эта лекция. Вы будете очень часто использовать в запросах несколько JOIN с фильтрами. Почти всегда :)

2
Задача
SQL SELF, 12 уровень, 2 лекция
Недоступна
Выбор студентов с активными курсами
Выбор студентов с активными курсами
2
Задача
SQL SELF, 12 уровень, 2 лекция
Недоступна
Выбор записей за текущий год
Выбор записей за текущий год
Комментарии (5)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Сергей Третяк Уровень 14
28 октября 2025
задачи, созданные для 2023 года до сих пор не скорректированы, чтобы увидеть результат решения задачи требуется менять даты в таблице БД, иначе фильтровать результат с функцией 'current_date' не имеет смысла.
Anemon Уровень 13 Expert
12 сентября 2025
🤓
Slevin Уровень 1
5 сентября 2025
Почему не WHERE? Можно было бы написать так: Потому что пример дан для INNER JOIN, а рассказывается тема про LEFT JOIN.... 😶
Anonymous #3080756 Уровень 45
19 июля 2025
На 19.07.2025 в плагине IDE задача "Выбор записей за текущий год" почему-то требует запятую перед AND, и только так выводит результат, иначе выдает синтаксическую ошибку. Но такое решение неверно и, соответственно, не проходит валидацию. В браузере решение не требует этой лишней запятой и решение хорошо проходит валидацию
25 июня 2025
На самом деле в PostgreSQL для обычного inner join все равно где находиться условие фильтрации - в теле ON или в теле WHERE, результат всегда будет одинаковый, и время выполнения и план выполнения тоже будут одинаковыми, это можно посмотреть в плане запроса. PostgreSQL сначала отфильтровывает записи (если это условие фильтрации по одной таблице), а уже потом выполняет inner join Поэтому с точки зрения планировщика, все ниже описанные запросы одинаковые

SELECT * FROM attachment_users au
JOIN attachment_users_info aui ON au.attachment_id = aui.attachment_id
WHERE aui.name = 'name61779';

SELECT * FROM attachment_users au
JOIN attachment_users_info aui ON au.attachment_id = aui.attachment_id AND aui.name = 'name61779';

SELECT * FROM attachment_users au
JOIN attachment_users_info aui ON TRUE
WHERE au.attachment_id = aui.attachment_id
AND aui.name = 'name61779';

SELECT * FROM attachment_users au
CROSS JOIN attachment_users_info aui
WHERE au.attachment_id = aui.attachment_id
AND aui.name = 'name61779'
Поэтому условия объединения 2 таблиц лучше писать в теле ON, а условия отбора по таблицам в теле WHERE, это имеет удобночитаемый и понятный вид, и на оптимизацию запроса никак не влияет Но это все не касается LEFT, RIGHT и FULL JOIN, потому что здесь сначала выполняется объединение по ON а потом уже идет фильтрация по WHERE