Graduarse de “cuando dude, use LEFT JOIN”

El JOIN en SQL es sencillo de escribir, pero fácil de usar mal: elegir el tipo incorrecto duplica o elimina filas silenciosamente. Muchas personas se las arreglan con “cuando dude, LEFT JOIN”, pero entender las diferencias reales ahorra horas persiguiendo conteos de filas inesperados.

Esta es una referencia centrada en una chuleta y un recorrido por el conteo de filas para cada tipo de JOIN, además de cómo escribir JOINs con varias tablas y cómo revisar SQL existente por tipo de JOIN.

Chuleta de tipos de JOIN

Usando users (3 filas: Alice, Bob, Carol) y orders (Alice tiene 2, Bob tiene 1, Carol tiene 0) como ejemplo, esto es lo que devuelve cada JOIN.

Tipo de JOINDevuelveFilas de Alice y BobCarol (sin pedidos)Filas solo de pedidos
INNER JOINSolo filas que coinciden en ambos ladosAlice×2, Bob×1excluidaexcluidas
LEFT JOINToda la tabla izquierda, más coincidencias a la derechaAlice×2, Bob×11 fila (lado derecho NULL)excluidas
RIGHT JOINToda la tabla derecha, más coincidencias a la izquierdaAlice×2, Bob×1excluidaincluidas si existen (lado izquierdo NULL)
FULL JOINTodas las filas de ambas tablasAlice×2, Bob×11 filaincluidas si existen
CROSS JOINTodas las combinaciones (producto cartesiano)3 usuarios × 3 pedidos = 9 filas

INNER JOIN es el único que “filtra”. Todos los demás tipos funcionan en la dirección de “garantizar todas las filas de uno o ambos lados” — y esa diferencia es exactamente lo que causa conteos de filas sorprendentes.

Por qué el conteo de filas “aumenta”

Esta es la fuente de confusión más común. “Hice el JOIN y obtuve muchísimas más filas de las esperadas” es, nueve de cada diez veces, una relación uno a muchos haciendo exactamente lo que debe hacer.

SELECT users.name, orders.id
FROM users
INNER JOIN orders ON users.id = orders.user_id;

Aunque haya 3 filas en users y 3 en orders, el resultado tiene tantas filas como aporte la tabla de pedidos (si Alice tiene 2 pedidos, la fila de Alice aparece dos veces). Pásalo por alto al agregar y un COUNT(*) ingenuo cuenta pedidos, no usuarios.

-- Incorrecto: se pretendía contar usuarios, pero cuenta filas "usuario × pedido"
SELECT COUNT(*) FROM users INNER JOIN orders ON users.id = orders.user_id;

-- Correcto: conteo de usuarios sin duplicados
SELECT COUNT(DISTINCT users.id) FROM users INNER JOIN orders ON users.id = orders.user_id;

La regla práctica: sepa siempre cuántas filas tenía antes del JOIN, y si el conteo posterior no es un múltiplo exacto de ese número, sospeche de la condición de unión.

La trampa más común de LEFT JOIN: WHERE la deshace en silencio

Cambia a LEFT JOIN precisamente para incluir usuarios sin pedidos — y en el momento en que añade una condición WHERE, vuelve al comportamiento de INNER JOIN. Este es el segundo incidente más común.

-- Trampa: parece LEFT JOIN, pero filtrar orders en WHERE elimina las filas NULL
SELECT users.name, orders.status
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE orders.status = 'completed';   -- Carol (NULL) queda excluida aquí

Carol no tiene pedidos, así que orders.status es NULL en su fila. WHERE orders.status = 'completed' evalúa NULL = 'completed', y en SQL, cualquier comparación con NULL se evalúa como NULL (ni verdadero ni falso) — por lo que esa fila queda excluida del resultado. El efecto neto es funcionalmente idéntico a un INNER JOIN.

La solución es mover la condición a la cláusula ON:

-- Correcto: poner la condición en ON preserva la semántica de LEFT JOIN
SELECT users.name, orders.status
FROM users
LEFT JOIN orders ON users.id = orders.user_id AND orders.status = 'completed';
-- La fila de Carol sobrevive, con NULL en el lado de orders

La regla general: si quiere garantizar todas las filas de la tabla izquierda, filtre el lado derecho en ON; filtre el lado izquierdo en WHERE.

Unir más de dos tablas: anclarse en una tabla

Cuando no esté seguro de cómo estructurar JOINs de tres o más tablas, el truco es decidir primero cuál tabla es el sujeto.

SELECT
  users.name,
  orders.id       AS order_id,
  order_items.quantity,
  products.name   AS product_name
FROM users
LEFT JOIN orders      ON users.id = orders.user_id
LEFT JOIN order_items ON orders.id = order_items.order_id
LEFT JOIN products    ON order_items.product_id = products.id
WHERE users.deleted_at IS NULL;

Partiendo de FROM users, este encadena LEFT JOIN hacia pedidos, luego líneas de pedido, luego productos — cada JOIN mantiene a users como ancla. Gracias a eso, puede detectar tanto “usuarios sin pedidos” como “pedidos con líneas mal formadas” en la misma consulta (mezclar un INNER JOIN a mitad de camino empezaría a filtrar silenciosamente, así que tenga cuidado).

SELF JOIN: hacer que una tabla parezca dos

Un SELF JOIN une una tabla consigo misma, usando alias para tratarla como si fueran dos tablas distintas. El ejemplo clásico es una estructura autorreferencial de “empleado y su gerente”.

SELECT
  emp.name  AS employee_name,
  mgr.name  AS manager_name
FROM employees emp
LEFT JOIN employees mgr ON emp.manager_id = mgr.id;

Dar a employees los alias emp y mgr permite que SQL lo trate como un JOIN entre “dos tablas”. Como normalmente querrá incluir empleados sin gerente (manager_id es NULL), LEFT JOIN es también aquí la elección natural.

Construir y formatear JOINs

Si escribir un JOIN de varias tablas desde cero resulta tedioso, el Visual SQL Builder le permite ensamblar tablas y condiciones de JOIN desde un formulario. Cambiar entre tipos de JOIN (JOIN/LEFT JOIN/RIGHT JOIN/FULL JOIN) y observar cómo cambia el SQL generado es una buena forma de asimilar esta chuleta.

Cuando necesite leer una consulta JOIN existente y compleja, el Formateador de SQL facilita mucho ver qué JOIN aplica a qué tabla. Ambas herramientas se ejecutan íntegramente en su navegador — los nombres de tabla y los detalles del esquema nunca se envían a ningún sitio.

Preguntas frecuentes

¿INNER JOIN y JOIN son diferentes?

No — son lo mismo. En la mayoría de los SGBD, JOIN es la forma abreviada de INNER JOIN. Muchos equipos siguen escribiendo INNER JOIN explícitamente por claridad durante la revisión de código.

He oído que RIGHT JOIN se usa poco, ¿es cierto?

A RIGHT JOIN B produce el mismo resultado que B LEFT JOIN A invirtiendo el orden de las tablas, así que muchos equipos adoptan la convención de usar siempre LEFT JOIN y nunca RIGHT JOIN. Aun así, lo encontrará al leer consultas de otras personas, así que vale la pena entenderlo.

Escuché que algunas bases de datos no soportan FULL JOIN

MySQL ha carecido durante mucho tiempo de soporte directo para FULL JOIN (se emula comúnmente con un UNION de LEFT JOIN y RIGHT JOIN). PostgreSQL y SQL Server lo soportan de forma nativa. Verifique el soporte de su base de datos antes de depender de él.

Si pego una condición de JOIN para revisarla, ¿se envían mis datos a algún sitio?

No. Tanto el Visual SQL Builder como el Formateador de SQL se ejecutan íntegramente en su navegador — los nombres de tabla y las consultas que introduce nunca se transmiten a un servidor.

Resumen

  • INNER JOIN es el único tipo que “filtra”. LEFT/RIGHT/FULL garantizan filas de uno o ambos lados
  • Los JOIN uno a muchos aumentan el conteo de filas — considere COUNT(DISTINCT ...) antes de agregar
  • Para filtrar el lado derecho de un LEFT JOIN, use ON; para filtrar el lado izquierdo, use WHERE. Poner una condición del lado derecho en WHERE convierte un LEFT JOIN en un INNER JOIN de facto
  • Para JOINs de varias tablas, ánclese en una tabla y encadene LEFT JOINs desde ahí
  • SELF JOIN usa alias para hacer que una tabla parezca dos