ON ... AND ...
Você já sabe como juntar tabelas usando JOIN. Mas na vida real, só bater as chaves pode não ser suficiente. Muitas vezes rola a necessidade de juntar dados só se eles baterem em algum critério extra — tipo, só registros ativos, só dados do ano atual ou só pedidos finalizados.
É aí que entra a extensão do ON usando AND.
Condições extras no JOIN ... ON te deixam controlar exatamente quais linhas vão entrar na junção, antes mesmo do SQL começar a montar o resultado. Isso faz sua query ficar:
- Mais rápida (menos linhas passam pelo
JOIN), - Mais precisa (o filtro já rola na hora da junção),
- Mais previsível quando você usa
LEFT JOIN(diferente do filtro noWHERE).
Exemplo: Só registros ativos em cursos
Imagina que a tabela enrollments tem o status da participação do estudante: active, dropped, pending.
Tabela students:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
Atualizando a 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 |
Agora a gente quer pegar só os estudantes que têm cursos ativos:
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;
Resultado:
| student_name | course_name |
|---|---|
| Otto Song | Mathematics |
| Otto Song | Computer Science |
| Alex Lin | Mathematics |
Aqui a gente colocou AND enrollments.status = 'active' dentro do ON, pra junção rolar só com os registros ativos, e não filtrar depois de juntar tudo.
Por que não WHERE?
Dava pra escrever assim também:
...
WHERE enrollments.status = 'active'
Mas isso tem outro comportamento com LEFT JOIN. O filtro no WHERE remove linhas que não têm correspondência (NULL), e aí o LEFT JOIN vira um INNER JOIN.
Já a condição AND enrollments.status = 'active' dentro do ON já limita as linhas que vão ser juntadas — controla o que entra na junção, não só filtra depois.
Esse jeito é importante principalmente quando você quer manter linhas de uma tabela mesmo se não tiver valor correspondente na outra (bem comum em relatórios e análises).
Mais exemplos de 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 |
Exemplo: só cursos do ano atual
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;
Aqui a gente só junta os registros que são do ano atual.
| name | name | enrolled_at |
|---|---|---|
| Otto Song | Math | 2025-02-01 |
| Otto Song | CS | 2025-03-05 |
| Alex Lin | Math | 2025-03-12 |
Exemplo: excluindo por valor
JOIN enrollments
ON students.id = enrollments.student_id
AND enrollments.status != 'dropped'
Aqui a gente exclui estudantes que saíram do curso na hora da junção, não depois.
| name | name |
|---|---|
| Otto Song | Math |
| Otto Song | CS |
| Alex Lin | Math |
Quando a condição tá dentro do ON, o PostgreSQL pode otimizar o plano de junção e processar menos linhas. Isso faz muita diferença quando tem muito dado. Filtrar dentro é mais eficiente do que "cortar" depois do JOIN.
JOIN ON — não é só chave, não!
Muita gente acha que ON é só id = id. Mas na real, dá pra colocar:
- Operadores lógicos:
AND,OR,NOT - Comparações:
>,<,<>,BETWEEN,IN - Expressões:
EXTRACT,DATE_TRUNC,COALESCE,NULLIF
Juntando tudo
Tabela students:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
Tabela faculties:
| id | name | |
|---|---|---|
| 10 | Engineering | |
| 20 | Natural Sciences | |
| 30 | ← sem nome (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 != 'PE'
JOIN faculties f
ON c.faculty_id = f.id
AND f.name IS NOT NULL;
Aqui a gente filtra ao mesmo tempo por:
- Registros ativos,
- Cursos, menos "PE",
- Faculdades que têm nome.
Resultado da query:
| student_name | course_name | faculty_name |
|---|---|---|
| Otto Song | Math | Engineering |
| Otto Song | CS | Engineering |
| Alex Lin | Math | Engineering |
Espero que você tenha curtido essa aula. Você vai usar vários JOINs com filtros nas suas queries o tempo todo. Quase sempre :)
GO TO FULL VERSION