Już wiesz, jak łączyć tabele za pomocą JOIN. Ale w prawdziwym życiu zwykłe dopasowanie po kluczach często nie wystarcza. Często pojawia się zadanie: łączyć dane tylko jeśli spełniają dodatkowe kryterium — na przykład tylko aktywne rekordy, tylko dane z bieżącego roku albo tylko zakończone zamówienia.
I tu właśnie wchodzi rozszerzenie konstrukcji ON z użyciem AND.
Dodatkowe warunki w JOIN ... ON pozwalają dokładnie kontrolować, które wiersze biorą udział w łączeniu, zanim SQL zacznie budować wynik. Dzięki temu zapytanie jest:
- Szybsze (mniej wierszy przechodzi przez
JOIN), - Dokładniejsze (filtrowanie odbywa się już na etapie łączenia),
- Bardziej przewidywalne przy pracy z
LEFT JOIN(w przeciwieństwie do filtrowania wWHERE).
Przykład: Tylko aktywne zapisy na kursy
Załóżmy, że tabela enrollments zawiera status uczestnictwa studenta: active, dropped, pending.
Tabela students:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
Zaktualizowana tabela enrollments:
| student_id | course_id | status |
|---|---|---|
| 1 | 101 | active |
| 1 | 103 | active |
| 2 | 102 | dropped |
| 3 | 101 | active |
Tabela courses:
| id | name |
|---|---|
| 101 | Mathematics |
| 102 | Physics |
| 103 | Computer Science |
Teraz chcemy uzyskać tylko tych studentów, którzy mają aktywne kursy:
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;
Wynik:
| student_name | course_name |
|---|---|
| Otto Song | Mathematics |
| Otto Song | Computer Science |
| Alex Lin | Mathematics |
Tutaj dodaliśmy AND enrollments.status = 'active' do środka ON, żeby łączenie odbywało się tylko po aktywnych rekordach, a nie filtrowało się po połączeniu.
Dlaczego nie WHERE?
Można by napisać tak:
...
WHERE enrollments.status = 'active'
Ale to ma inne zachowanie przy LEFT JOIN. Filtrowanie w WHERE usuwa wiersze, gdzie nie ma dopasowań (NULL), przez co zamienia LEFT JOIN w INNER JOIN.
A warunek AND enrollments.status = 'active' wewnątrz ON od razu ogranicza łączone wiersze — steruje tym, które wiersze w ogóle trafią do połączenia, a nie tylko filtruje wynik po fakcie.
To podejście jest szczególnie ważne, jeśli chcesz zachować wiersze z jednej tabeli, nawet jeśli w drugiej nie ma pasujących wartości (co często się zdarza w raportach i analizie).
Kolejne przykłady użycia ON ... AND ...
Tabela students:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
Tabela 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 |
Tabela courses:
| id | name |
|---|---|
| 101 | Math |
| 102 | Physics |
| 103 | CS |
Przykład: tylko kursy z bieżącego roku
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;
Tutaj łączymy tylko te rekordy, które dotyczą bieżącego roku.
| name | name | enrolled_at |
|---|---|---|
| Otto Song | Math | 2025-02-01 |
| Otto Song | CS | 2025-03-05 |
| Alex Lin | Math | 2025-03-12 |
Przykład: wykluczenie po wartości
JOIN enrollments
ON students.id = enrollments.student_id
AND enrollments.status != 'dropped'
Wykluczamy studentów, którzy się wypisali, już na etapie łączenia, a nie filtrujemy po fakcie.
| name | name |
|---|---|
| Otto Song | Math |
| Otto Song | CS |
| Alex Lin | Math |
Kiedy warunek jest wewnątrz ON, PostgreSQL może optymalizować plan łączenia i przetwarzać mniej wierszy. To szczególnie ważne przy dużych ilościach danych. Wewnętrzne filtrowanie jest wydajniejsze niż "odsiew" po JOIN.
JOIN ON — to nie tylko klucze
Wielu myśli, że ON to po prostu id = id. Tak naprawdę można tam wrzucać:
- Operatory logiczne:
AND,OR,NOT - Porównania:
>,<,<>,BETWEEN,IN - Wyrażenia:
EXTRACT,DATE_TRUNC,COALESCE,NULLIF
Łączymy wszystko razem
Tabela students:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
Tabela faculties:
| id | name | |
|---|---|---|
| 10 | Engineering | |
| 20 | Natural Sciences | |
| 30 | ← bez nazwy (NULL) |
Tabela 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 |
Tabela 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 != 'PhysicalEducation'
JOIN faculties f
ON c.faculty_id = f.id
AND f.name IS NOT NULL;
Tutaj jednocześnie filtrujemy po:
- Aktywnych rekordach,
- Kursach innych niż "PhysicalEducation",
- Wydziałach, które mają nazwę.
Wynik zapytania:
| student_name | course_name | faculty_name |
|---|---|---|
| Otto Song | Math | Engineering |
| Otto Song | CS | Engineering |
| Alex Lin | Math | Engineering |
Mam nadzieję, że spodobał ci się ten wykład. Bardzo często będziesz używać w zapytaniach kilku JOIN z filtrami. Prawie zawsze :)
GO TO FULL VERSION