Вы уже знаете, как объединять таблицы с помощью 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 с фильтрами. Почти всегда :)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ