Lo has visto. Lo has sufrido. Y si aún no te ha pasado, enhorabuena, pero no te relajes, tu día llegará. Me refiero al clásico caso del LEFT JOIN que acaba comportándose como un INNER JOIN porque alguien decidió, en un momento de inspiración, colocar un filtro donde no debía. Y claro, la consulta no devuelve lo que se espera, o peor: devuelve justo lo que se espera, pero está mal.
Hoy vamos a hablar de un problema que se cuela en muchos desarrollos y auditorías de código: el efecto de los filtros en un LEFT JOIN, y cómo una mala ubicación puede cambiar radicalmente el comportamiento de tu consulta. Así que prepara café, que vienen curvas.
El LEFT JOIN que no era
Un LEFT JOIN debería, en teoría, devolver todos los registros de la tabla izquierda, aunque no haya coincidencia en la tabla derecha. Hasta aquí, todo bien. Pero esto solo se cumple si no saboteas la lógica desde el WHERE.
Veamos un ejemplo simple pero revelador:
SELECT c.ClienteID, c.Nombre, f.FacturaID
FROM Clientes c
LEFT JOIN Facturas f
ON c.ClienteID = f.ClienteID
WHERE f.Fecha >= '2025-01-01'
A simple vista parece correcto. Queremos todos los clientes y, si tienen facturas a partir de 2025, las mostramos. Pero no. Lo que acaba devolviendo esta consulta es únicamente aquellos clientes que tienen facturas desde 2025. ¿Y los que no tienen ninguna factura? Fuera del resultado. Despedidos.
¿Por qué? Porque estás aplicando un filtro sobre una columna de la tabla derecha (Facturas) después del JOIN, pero en el WHERE, que se evalúa después del JOIN, cuando los NULL ya están presentes. Y claro, NULL >= ‘2025-01-01’ es FALSE. El motor hace su trabajo, y tú te quedas sin clientes sin facturas.
¿Dónde va el filtro entonces?
En el ON. Sí, ese que muchos usan como si fuera una casilla para emparejar claves primarias con foráneas y poco más. Pero el ON tiene un papel más interesante aquí, si metes el filtro ahí, estarás condicionando el JOIN, no el resultado final.
Así que la versión correcta sería:
SELECT c.ClienteID, c.Nombre, f.FacturaID
FROM Clientes c
LEFT JOIN Facturas f
ON c.ClienteID = f.ClienteID AND f.Fecha >= '2025-01-01'
Ahora sí, todos los clientes aparecen. Los que tienen facturas desde 2025 las verán en su fila. Los que no, tendrán NULL en FacturaID. Tal y como un LEFT JOIN debe comportarse.
¿Y si necesito más filtros?
Buena pregunta. No es raro que quieras filtrar también por condiciones de la tabla izquierda. ¿Qué pasa entonces? Pues ahí sí puedes usar el WHERE, con criterio.
Por ejemplo:
SELECT c.ClienteID, c.Nombre, f.FacturaID
FROM Clientes c
LEFT JOIN Facturas f
ON c.ClienteID = f.ClienteID AND f.Fecha >= '2025-01-01'
WHERE c.Activo = 1
Esto funciona como debe. El filtro en Clientes actúa después del JOIN, pero como no anula la lógica del LEFT, todo sigue su curso. Los clientes activos salen todos, tengan o no facturas desde 2025. Así sí.
¿Y si necesito filtrar por una columna de la derecha, pero sólo cuando no sea NULL?
Aquí empieza el juego sucio. Lo que quieres, probablemente, es quedarte con clientes que tengan facturas desde 2025, pero también mostrar los que no tienen ninguna factura. En ese caso, tienes que pensar en lógica condicional.
Una forma válida sería:
SELECT c.ClienteID, c.Nombre, f.FacturaID
FROM Clientes c
LEFT JOIN Facturas f
ON c.ClienteID = f.ClienteID
WHERE f.Fecha >= '2025-01-01' OR f.FacturaID IS NULL
Esto ya es más decente. Estás diciendo: «dame los clientes que tienen facturas desde 2025, o los que no tienen ninguna factura». Y eso se respeta.
Pero seamos sinceros, si tienes que hacer esto a menudo, algo en tu modelo de consultas no huele bien. O tu reporting, o tu forma de pensar los datos. En esos casos, es mejor plantearte si necesitas un LEFT JOIN, unas vacaciones o modificar la lógica entera de la consulta.
No es un detalle menor
Aquí no estamos hablando de una microoptimización ni de una virguería SQL. Estamos hablando de lógica rota. El tipo de error que pasa todos los entornos, llega a producción, y luego nadie entiende por qué los informes no cuadran con la realidad.
Y lo peor, es un error silencioso. No da fallo. No lanza excepciones. Simplemente te da datos incorrectos con cara de datos correctos. Lo cual, en nuestro mundo, es el infierno.
En auditorías de rendimiento y revisiones de lógica de negocio, me encuentro este patrón con una frecuencia desesperante. Muchas veces me dicen: «Pero es que es un LEFT JOIN, ¿por qué no salen todos?». Y la respuesta es casi siempre la misma: porque tú mismo has convertido ese LEFT en un INNER, sin darte cuenta.
¿Qué pasa con los ORMs?
Sí, los ORMs. Esos grandes generadores de consultas elegantes como un campo minado. Hay ORMs que optimizan mal los JOIN, o que aplican los filtros fuera del ON por defecto. Y luego te toca a ti arreglar el entuerto porque el modelo “no da para más”.
Así que si estás usando Entity Framework, Hibernate o cualquier ORM moderno, revísalo tú a mano. No confíes en que el generador de consultas haya entendido tus intenciones como tú las entiendes.
Y ya que estamos, un apunte sobre MERGE
Sí, ya sé que esto va de LEFT JOIN, pero la confusión entre el WHERE y el ON en MERGE también merece un recordatorio. Porque ahí también es fácil liarla. En un MERGE, el ON es la condición de emparejamiento, no de filtrado. Si lo usas mal, puedes terminar haciendo UPDATE a registros que no deberías tocar, o insertando cosas que ni sabías que existían.
Lo dejo ahí, pero si te interesa, en el blog ya hablé en profundidad sobre los problemas de MERGE.
Conclusión
Si vas a usar un LEFT JOIN, asegúrate de entender cómo funciona y dónde colocar los filtros. No es un tema de estilo, es un tema de lógica. Un WHERE mal puesto no solo degrada el rendimiento: rompe la semántica de tu consulta.
Así que la próxima vez que revises una query con LEFT JOIN, pregúntate si realmente está haciendo lo que debería. Y si no, ya sabes dónde mirar primero: el WHERE. Porque sí, puedes hacer un LEFT JOIN que se comporte como un INNER, y también puedes correr con los cordones desatados. Ambas cosas acaban mal.
Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!




