Ya sabes cómo unir tablas usando JOIN. Pero en la vida real, a veces no basta con que coincidan las claves. Muchas veces necesitas unir datos solo si cumplen un criterio extra — por ejemplo, solo los registros activos, solo datos del año actual o solo pedidos completados.
Y aquí es donde entra en juego la extensión de la cláusula ON usando AND.
Las condiciones adicionales en JOIN ... ON te permiten controlar exactamente qué filas participan en la unión, incluso antes de que SQL empiece a construir el resultado. Esto hace que la consulta sea:
- Más rápida (menos filas pasan por el
JOIN), - Más precisa (el filtrado ocurre en la etapa de unión),
- Más predecible cuando usas
LEFT JOIN(a diferencia del filtrado enWHERE).
Ejemplo: Solo registros activos en los cursos
Supón que la tabla enrollments tiene el estado de participación del estudiante: active, dropped, pending.
Tabla students:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
Actualizamos la tabla enrollments:
| student_id | course_id | status |
|---|---|---|
| 1 | 101 | active |
| 1 | 103 | active |
| 2 | 102 | dropped |
| 3 | 101 | active |
Tabla courses:
| id | name |
|---|---|
| 101 | Mathematics |
| 102 | Physics |
| 103 | Computer Science |
Ahora queremos obtener solo los estudiantes que tienen cursos activos:
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 |
Aquí hemos añadido AND enrollments.status = 'active' dentro de ON, para que la unión ocurra solo con los registros activos, y no se filtre después de unir.
¿Por qué no WHERE?
Podrías escribirlo así:
...
WHERE enrollments.status = 'active'
Pero esto se comporta diferente con LEFT JOIN. El filtrado en WHERE elimina las filas donde no hay coincidencias (NULL), y así convierte el LEFT JOIN en un INNER JOIN.
En cambio, la condición AND enrollments.status = 'active' dentro de ON limita directamente las filas que se unen — controla qué filas entran en la unión, no solo filtra el resultado después.
Este enfoque es especialmente importante si quieres mantener filas de una tabla aunque en la otra no haya valores coincidentes (algo muy común en informes y analítica).
Más ejemplos de uso de ON ... AND ...
Tabla students:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
Tabla 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 |
Tabla courses:
| id | name |
|---|---|
| 101 | Math |
| 102 | Physics |
| 103 | CS |
Ejemplo: solo cursos del año actual
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;
Aquí solo unimos los registros que pertenecen al año actual.
| name | name | enrolled_at |
|---|---|---|
| Otto Song | Math | 2025-02-01 |
| Otto Song | CS | 2025-03-05 |
| Alex Lin | Math | 2025-03-12 |
Ejemplo: exclusión por valor
JOIN enrollments
ON students.id = enrollments.student_id
AND enrollments.status != 'dropped'
Excluimos a los estudiantes que se dieron de baja en la etapa de unión, no después de unir.
| name | name |
|---|---|
| Otto Song | Math |
| Otto Song | CS |
| Alex Lin | Math |
Cuando la condición está dentro de ON, PostgreSQL puede optimizar el plan de unión y procesar menos filas. Esto es súper importante cuando tienes muchos datos. El filtrado interno es más eficiente que filtrar después del JOIN.
JOIN ON — no es solo para claves
Mucha gente piensa que ON es solo id = id. Pero en realidad puedes poner ahí:
- Operadores lógicos:
AND,OR,NOT - Comparaciones:
>,<,<>,BETWEEN,IN - Expresiones:
EXTRACT,DATE_TRUNC,COALESCE,NULLIF
Combinando todo junto
Tabla students:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
Tabla faculties:
| id | name | |
|---|---|---|
| 10 | Engineering | |
| 20 | Natural Sciences | |
| 30 | ← sin nombre (NULL) |
Tabla 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 |
Tabla 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;
Aquí filtramos a la vez por:
- Registros activos,
- Cursos que no sean "PE",
- Facultades que tengan nombre.
Resultado de la consulta:
| student_name | course_name | faculty_name |
|---|---|---|
| Otto Song | Math | Engineering |
| Otto Song | CS | Engineering |
| Alex Lin | Math | Engineering |
Espero que te haya molado esta lección. Vas a usar un montón de JOINs con filtros en tus consultas. ¡Casi siempre! :)
GO TO FULL VERSION