INNER, LEFT y RIGHT JOIN
Clase práctica. Cómo combinar tablas con cada tipo de JOIN, cuántas filas devuelve cada uno y los errores más comunes al usarlos en reportes de BI.
Antes de empezar
Usamos la basetablas_libro: 5 cursos (uno sin profesor: «SQL avanzado»), 4 profesores (uno sin cursos: Carlos), 6 alumnos y 8 inscripciones. Ejecutá USE tablas_libro; antes de cada ejemplo.1. Resumen de los JOIN en MySQL
| JOIN | Qué devuelve | Filas con cursos ⟶ profesores |
|---|---|---|
INNER JOIN (o JOIN) | Solo las filas que coinciden en ambas tablas | 4 |
LEFT JOIN | Todas las filas de la izquierda + coincidencias (o NULL) | 5 |
RIGHT JOIN | Todas las filas de la derecha + coincidencias (o NULL) | 5 |
CROSS JOIN | Todas las combinaciones posibles (producto cartesiano) | 5 × 4 = 20 |
Sintaxis general:
SELECT columnas
FROM tabla_izquierda AS a
[INNER | LEFT | RIGHT] JOIN tabla_derecha AS b
ON a.columna_clave = b.columna_clave
WHERE ...
ORDER BY ...;Cómo trabaja un JOIN, paso a paso
- MySQL toma una fila de A (por ejemplo, «Programación PHP», con ID_PROFE = 3).
- Busca en B las filas que cumplen el ON: el profesor con ID_PROFE = 3 es Rosa.
- Combina la pareja en una fila de resultado: «Programación PHP – Rosa».
- Repite con cada fila de A. Rosa aparece dos veces porque dicta dos cursos.
- Decide qué hacer con las filas sin pareja («SQL avanzado» y Carlos). Es lo único que cambia entre un tipo de JOIN y otro: INNER las descarta, LEFT conserva las de A y RIGHT conserva las de B.
En los diagramas de Venn de esta clase, A es la tabla del FROM (izquierda), B la del JOIN (derecha), y la zona coloreada es lo que devuelve la consulta.
2. INNER JOIN: solo coincidencias
SELECT c.ID_CURSO, c.TITULO, p.NOMBRE, p.APELLIDOS
FROM cursos AS c
INNER JOIN profesores AS p ON c.ID_PROFE = p.ID_PROFE
ORDER BY c.ID_CURSO;| ID_CURSO | TITULO | NOMBRE | APELLIDOS |
|---|---|---|---|
| 1 | Programación PHP | Rosa | Honrosa Pérez |
| 2 | Modelos abstracto de datos | Rosa | Honrosa Pérez |
| 3 | SQL desde cero | Federico | Gasco Daza |
| 4 | Dibujo técnico | Ana | Saura Trenzo |
Devuelve 4 filas: «SQL avanzado» desaparece porque su ID_PROFE es NULL, y Carlos no aparece porque no dicta cursos.
Tres tablas a la vez: encadená un JOIN por cada relación. Así se ve qué alumno cursa qué curso, a través de la tabla puente:
SELECT a.NOMBRE, a.APELLIDOS, c.TITULO
FROM alumnos AS a
INNER JOIN alumnos_cursos AS ac ON ac.ID_ALUMNO = a.ID_ALUMNO
INNER JOIN cursos AS c ON c.ID_CURSO = ac.ID_CURSO
ORDER BY c.ID_CURSO, a.ID_ALUMNO;
-- 8 filas, una por inscripción. Ej.: Pablo y Sergio en PHP y en SQL desde cero.Atajo USING
Si la columna se llama igual en ambas tablas podés escribirJOIN alumnos_cursos USING (ID_ALUMNO) en lugar de ON .... JOIN a secas equivale a INNER JOIN.3. LEFT JOIN: todo lo de la izquierda
SELECT c.TITULO, p.NOMBRE AS profesor
FROM cursos AS c
LEFT JOIN profesores AS p ON c.ID_PROFE = p.ID_PROFE
ORDER BY c.ID_CURSO;
-- 5 filas: las 4 anteriores + «SQL avanzado» con profesor NULLUso típico en BI: contar incluyendo los ceros. Inscriptos por curso, sin perder el curso vacío:
SELECT c.TITULO, COUNT(ac.ID_ALUMNO) AS inscriptos
FROM cursos AS c
LEFT JOIN alumnos_cursos AS ac ON ac.ID_CURSO = c.ID_CURSO
GROUP BY c.ID_CURSO, c.TITULO
ORDER BY c.ID_CURSO;| TITULO | inscriptos |
|---|---|
| Programación PHP | 3 |
| Modelos abstracto de datos | 1 |
| SQL desde cero | 2 |
| Dibujo técnico | 2 |
| SQL avanzado | 0 |
COUNT(*) vs COUNT(columna)
Con LEFT JOIN,COUNT(*) cuenta también la fila de NULL y le daría 1 inscripto a «SQL avanzado». Usá COUNT(ac.ID_ALUMNO), que ignora los NULL.Anti-join: cursos que todavía no tienen profesor.
SELECT c.TITULO
FROM cursos AS c
LEFT JOIN profesores AS p ON c.ID_PROFE = p.ID_PROFE
WHERE p.ID_PROFE IS NULL;
-- SQL avanzado4. RIGHT JOIN: todo lo de la derecha
Es el espejo del LEFT JOIN. A RIGHT JOIN B da lo mismo que B LEFT JOIN A. Ejemplo: todos los profesores, dicten cursos o no.
SELECT p.NOMBRE, c.TITULO
FROM cursos AS c
RIGHT JOIN profesores AS p ON c.ID_PROFE = p.ID_PROFE
ORDER BY p.ID_PROFE;
-- 5 filas: Federico, Ana, Rosa (2 cursos) y Carlos con TITULO NULL
-- Equivalente con LEFT JOIN (la forma más usada):
SELECT p.NOMBRE, c.TITULO
FROM profesores AS p
LEFT JOIN cursos AS c ON c.ID_PROFE = p.ID_PROFE;Buena práctica
La mayoría de los equipos escribe siempre LEFT JOIN y ordena las tablas para que la «principal» quede a la izquierda. Las consultas se leen más fácil.5. El error más común: filtrar en WHERE después de un LEFT JOIN
-- A) Condición en ON: conserva TODOS los cursos → 5 filas
SELECT c.TITULO, p.NOMBRE
FROM cursos AS c
LEFT JOIN profesores AS p
ON p.ID_PROFE = c.ID_PROFE
AND p.F_NACIMIENTO >= '1975-01-01';
-- Dibujo técnico (Ana nació en 1969) y SQL avanzado quedan con NULL
-- B) Condición en WHERE: descarta las filas NULL → 3 filas
SELECT c.TITULO, p.NOMBRE
FROM cursos AS c
LEFT JOIN profesores AS p ON p.ID_PROFE = c.ID_PROFE
WHERE p.F_NACIMIENTO >= '1975-01-01';En la versión B, NULL >= '1975-01-01' no es verdadero, así que «Dibujo técnico» y «SQL avanzado» desaparecen: el LEFT JOIN se comporta como un INNER JOIN. Si querés filtrar la tabla de la derecha sin perder filas de la izquierda, poné la condición en el ON.
6. La misma tabla dos veces
En eventos cada partido tiene un equipo local y uno visitante, y los dos apuntan a equipos. Para ver los dos nombres se une equipos dos veces con alias distintos (la misma técnica se usa en un self join, cuando una tabla se une consigo misma):
SELECT e.ID_JORNADA, l.EQUIPO AS local, v.EQUIPO AS visitante, e.RESULTADO
FROM eventos AS e
INNER JOIN equipos AS l ON l.ID_EQUIPO = e.LOCAL
INNER JOIN equipos AS v ON v.ID_EQUIPO = e.VISITANTE
WHERE l.EQUIPO = 'Celta' OR v.EQUIPO = 'Celta'
ORDER BY e.ID_JORNADA;
-- 10 partidos del Celta; las jornadas 8, 9 y 10 tienen RESULTADO NULL (sin disputar)7. ¿Y FULL OUTER JOIN?
MySQL no tiene FULL OUTER JOIN. Se emula uniendo un LEFT y un RIGHT con UNION (que elimina duplicados):
SELECT c.TITULO, p.NOMBRE
FROM cursos AS c LEFT JOIN profesores AS p ON p.ID_PROFE = c.ID_PROFE
UNION
SELECT c.TITULO, p.NOMBRE
FROM cursos AS c RIGHT JOIN profesores AS p ON p.ID_PROFE = c.ID_PROFE;
-- 6 filas: 4 parejas + «SQL avanzado | NULL» + «NULL | Carlos»Chuleta visual: los 7 JOIN en MySQL
Referencia rápida con A = cursos y B = profesores. Los resultados son los de tablas_libro.
INNER JOIN
SELECT * FROM cursos c INNER JOIN profesores p ON c.ID_PROFE = p.ID_PROFE;
Solo coincidencias → 4 filas.
LEFT JOIN
SELECT * FROM cursos c LEFT JOIN profesores p ON c.ID_PROFE = p.ID_PROFE;
Todo A + coincidencias → 5 filas.
LEFT JOIN excluyente
SELECT * FROM cursos c LEFT JOIN profesores p ON c.ID_PROFE = p.ID_PROFE WHERE p.ID_PROFE IS NULL;
Solo A sin pareja → 1 fila (SQL avanzado).
RIGHT JOIN
SELECT * FROM cursos c RIGHT JOIN profesores p ON c.ID_PROFE = p.ID_PROFE;
Todo B + coincidencias → 5 filas.
RIGHT JOIN excluyente
SELECT * FROM cursos c RIGHT JOIN profesores p ON c.ID_PROFE = p.ID_PROFE WHERE c.ID_CURSO IS NULL;
Solo B sin pareja → 1 fila (Carlos).
FULL OUTER JOIN (emulado)
SELECT * FROM cursos c LEFT JOIN profesores p ON c.ID_PROFE = p.ID_PROFE UNION SELECT * FROM cursos c RIGHT JOIN profesores p ON c.ID_PROFE = p.ID_PROFE;
Todo A y todo B → 6 filas.
FULL OUTER excluyente (emulado)
SELECT * FROM cursos c LEFT JOIN profesores p ON c.ID_PROFE = p.ID_PROFE WHERE p.ID_PROFE IS NULL UNION ALL SELECT * FROM cursos c RIGHT JOIN profesores p ON c.ID_PROFE = p.ID_PROFE WHERE c.ID_CURSO IS NULL;
Solo filas sin pareja → 2 filas (SQL avanzado y Carlos).
Tip
En las versiones «excluyentes» chequeá conIS NULL una columna NOT NULL de la otra tabla (su clave primaria). Si usás una columna que puede ser NULL por naturaleza, vas a obtener resultados falsos.8. Checklist antes de confiar en un JOIN
- ¿Cuántas filas esperabas? Compará con
SELECT COUNT(*)de la tabla principal. - ¿Uniste por la clave correcta (FK → PK)? Una condición incompleta multiplica filas:
eventos CROSS JOIN equiposda 30 × 6 = 180 filas. - ¿Necesitás conservar filas sin coincidencia? Entonces LEFT JOIN, no INNER.
- ¿Filtraste la tabla de la derecha en WHERE? Revisá si no convertiste el LEFT en INNER.
- Usá alias cortos (
c,p,ac) y calificá todas las columnas.
9. Práctica
- Listá los profesores que no dictan ningún curso.
- Mostrá cuántos alumnos tiene cada profesor (sumando todos sus cursos), incluyendo al que tiene 0.
- Mostrá cada alumno con su cantidad de actuaciones destacadas, incluyendo a los que no tienen ninguna.
-- Soluciones
-- 1) Resultado: Carlos
SELECT p.NOMBRE, p.APELLIDOS
FROM profesores AS p
LEFT JOIN cursos AS c ON c.ID_PROFE = p.ID_PROFE
WHERE c.ID_CURSO IS NULL;
-- 2) Resultado: Rosa 4 · Ana 2 · Federico 2 · Carlos 0
SELECT p.NOMBRE, COUNT(ac.ID_ALUMNO) AS alumnos
FROM profesores AS p
LEFT JOIN cursos AS c ON c.ID_PROFE = p.ID_PROFE
LEFT JOIN alumnos_cursos AS ac ON ac.ID_CURSO = c.ID_CURSO
GROUP BY p.ID_PROFE, p.NOMBRE
ORDER BY alumnos DESC, p.NOMBRE;
-- 3) Resultado: Teresa 2 · Jeremias 1 · Sergio 1 · Pablo, Marta y Carmen 0
SELECT a.NOMBRE, COUNT(ad.ACTUACION) AS destacadas
FROM alumnos AS a
LEFT JOIN actuaciones_destacadas AS ad ON ad.ID_ALUMNO = a.ID_ALUMNO
GROUP BY a.ID_ALUMNO, a.NOMBRE
ORDER BY destacadas DESC;Para seguir practicando online sin instalar nada: SQLBolt – Lección 6 (JOINs) y Lección 7 (OUTER JOINs).
Bibliografía y recursos online
- MySQL 8.0 Reference Manual. JOIN Clause.
- MySQL 8.0 Reference Manual. UNION Clause.
- MySQL Tutorial. INNER JOIN, LEFT JOIN, RIGHT JOIN y Self Join.
- W3Schools. MySQL Joins (con editor «Try it Yourself» para practicar en el navegador).
- SQLBolt. Multi-table queries with JOINs y OUTER JOINs (ejercicios interactivos gratuitos).
- Wikibooks. Structured Query Language (libro libre y gratuito).
- Libro: Silva, R. (2023). MySQL Crash Course: A Hands-on Introduction to Database Development. No Starch Press. Cap. 5: Joining Database Tables; Cap. 6: Performing Complex Joins with Multiple Tables.
- Libro: Grippa, V. M. y Kuzmichev, S. (2021). Learning MySQL (2.ª ed.). O’Reilly Media.
Los recursos en inglés se pueden leer con el traductor del navegador.
Siguiente: Cuestionario del Módulo 3 y luego Módulo 4 · GROUP BY y funciones de agregación.
