Aprende SQL · lección gratuita
El INNER JOIN descarta las filas sin pareja, pero muchas veces eso es justo lo que NO quieres: necesitas ver al empleado aunque no haya hecho pedidos, o el producto aunque nunca se haya vendido. Para eso existen los OUTER JOIN: conservan todas las filas de un lado y rellenan con NULL las columnas del otro lado cuando no hay coincidencia.
LEFT JOIN (o LEFT OUTER JOIN): conserva todas las filas de la tabla izquierda. Si no hay pareja a la derecha, sus columnas salen como NULL.RIGHT JOIN: conserva todas las filas de la tabla derecha (es el espejo de LEFT). SQLite moderno lo soporta.LEFT JOIN ... WHERE columna_derecha IS NULL devuelve justo las filas sin pareja (empleados sin pedidos, productos sin ventas).ON ≠ WHERE: en un LEFT JOIN, una condición sobre la tabla derecha puesta en WHERE elimina las filas con NULL y lo convierte de hecho en un INNER JOIN. Si quieres conservar los NULL, esa condición va en el ON.LEFT es preferible a RIGHT: cualquier RIGHT JOIN se puede reescribir como LEFT JOIN invirtiendo las tablas, y se lee mejor.La tabla "izquierda" es la que aparece en el FROM; la "derecha" es la del JOIN. a LEFT JOIN b = todas las de a + las coincidencias de b. a RIGHT JOIN b = todas las de b + las coincidencias de a. Por tanto a RIGHT JOIN b es idéntico a b LEFT JOIN a.
El detalle más importante y mal entendido es dónde poner los filtros. Compara:
... WHERE p.estado = 'Completado' filtra después de unir: descarta las filas donde p es NULL, perdiendo a los empleados sin pedidos.... ON p.empleado_id = e.id AND p.estado = 'Completado' filtra durante la unión: el empleado se conserva aunque no tenga pedidos completados (con NULL en las columnas de p).Cuidado al encadenar varios LEFT JOIN (por ejemplo categorias → productos → detalle_pedidos): para conservar las filas hasta el final, todos los joins de la cadena deben ser LEFT. Basta un solo INNER JOIN en medio para que se descarten silenciosamente las filas sin pareja y se pierda la conservación que buscabas.
SELECT p.id, c.empresa AS cliente, e.nombre AS vendedor, p.estado FROM pedidos p JOIN clientes c ON p.cliente_id = c.id JOIN empleados e ON p.empleado_id = e.id LIMIT 5
-- TODOS los empleados; cuántos pedidos gestionó cada uno (0 si ninguno)
SELECT e.nombre, e.cargo, COUNT(p.id) AS num_pedidos
FROM empleados e
LEFT JOIN pedidos p ON p.empleado_id = e.id
GROUP BY e.id, e.nombre, e.cargo
ORDER BY num_pedidos DESC, e.nombre;
-- Anti-join: productos que NUNCA se vendieron (ids 21-24)
SELECT pr.id, pr.nombre, pr.stock
FROM productos pr
LEFT JOIN detalle_pedidos d ON d.producto_id = pr.id
WHERE d.id IS NULL
ORDER BY pr.id;
-- RIGHT JOIN: parte de pedidos pero conserva TODOS los empleados (espejo del LEFT)
SELECT e.nombre, COUNT(p.id) AS num_pedidos
FROM pedidos p
RIGHT JOIN empleados e ON p.empleado_id = e.id
GROUP BY e.id, e.nombre
ORDER BY num_pedidos DESC, e.nombre;
💡 Para contar coincidencias en unLEFT JOINusaCOUNT(p.id)(cuenta solo lo no nulo), nuncaCOUNT(*), que daría 1 incluso para los empleados sin pedidos.
| Forma | Qué hace |
|---|---|
a LEFT JOIN b ON ... | Todas las de a + coincidencias de b (NULL si falta) |
a RIGHT JOIN b ON ... | Todas las de b + coincidencias de a |
LEFT JOIN b ... WHERE b.id IS NULL | Anti-join: filas de a sin pareja en b |
Filtro en ON (lado derecho) | Conserva las filas sin pareja |
Filtro en WHERE (lado derecho) | Elimina los NULL (vuelve a INNER) |
---