CROSS JOIN

El CROSS JOIN es fundamentalmente diferente a los otros tipos de JOIN. No necesita una condición de unión porque su propósito es generar el producto cartesiano de dos tablas: combina cada fila de la primera tabla con cada fila de la segunda. Si la primera tabla tiene 10 filas y la segunda tiene 5, el resultado tendrá 50 filas (10 x 5).

A primera vista puede parecer inútil o incluso peligroso (puede generar resultados enormes), pero tiene casos de uso muy específicos donde es exactamente la herramienta correcta. Mientras que un INNER JOIN combina filas que cumplen una condición de relación y un LEFT JOIN preserva las filas de la tabla izquierda aunque no tengan coincidencias, el CROSS JOIN no evalúa ninguna condición: simplemente multiplica todos los registros de una tabla por todos los registros de la otra. Comprender cuándo esta multiplicación es útil, y cuándo es un error accidental, es clave para trabajar con confianza con este tipo de JOIN.

Sintaxis

La sintaxis del CROSS JOIN es la más simple de todos los tipos de JOIN porque no requiere una cláusula ON. Existen dos formas equivalentes de escribirlo en MySQL, una explícita y otra implícita:

-- Sintaxis explícita
SELECT columnas
FROM tabla1
CROSS JOIN tabla2;
 
-- Sintaxis implícita (equivalente)
SELECT columnas
FROM tabla1, tabla2;

Ambas formas producen el mismo resultado. No hay cláusula ON porque no existe una condición de relación: se generan todas las combinaciones posibles. Si añades un WHERE, el resultado se filtra después de generar el producto cartesiano. La sintaxis explícita con CROSS JOIN es preferible porque deja clara la intención del programador. Cuando alguien lee la consulta, entiende inmediatamente que el producto cartesiano es deliberado y no un error por haber olvidado la condición de unión.

Es importante destacar que en MySQL, si usas la sintaxis implícita (comas entre tablas) sin una condición WHERE, el resultado es idéntico a un CROSS JOIN. Pero si añades una condición WHERE que relaciona las tablas, entonces la consulta se comporta como un INNER JOIN. Esta ambigüedad es precisamente la razón por la que la sintaxis moderna con JOIN explícito es más segura y recomendable.

Producto cartesiano

Para visualizar lo que hace un CROSS JOIN, piensa en una tabla de multiplicar donde cada celda es una combinación de un valor de cada tabla. Combinemos los vendedores con los estados posibles de un pedido para ver todas las parejas posibles:

SELECT
    e.nombre AS empleado,
    p.estado
FROM empleados e
CROSS JOIN (SELECT DISTINCT estado FROM pedidos) AS p
WHERE e.puesto LIKE '%Vendedor%' OR e.puesto LIKE '%Vendedora%'
ORDER BY e.nombre, p.estado;
empleadoestado
Danielcancelado
Danielentregado
Danielenviado
Danielpendiente
Danielprocesando
Nataliacancelado
Nataliaentregado
Nataliaenviado
Nataliapendiente
Nataliaprocesando
Patriciacancelado
Patriciaentregado
Patriciaenviado
Patriciapendiente
Patriciaprocesando
Raulcancelado
Raulentregado
Raulenviado
Raulpendiente
Raulprocesando

Cada empleado de ventas se combina con cada estado posible, generando 4 x 5 = 20 filas. Todavia no es util por si solo, pero es el primer paso para construir informes completos donde necesitas que aparezcan todas las combinaciones, incluso las que no tienen datos. Observa que la subconsulta (SELECT DISTINCT estado FROM pedidos) actua como una tabla derivada que contiene solo los valores unicos de estado. Esto es una tecnica muy comun con CROSS JOIN: en lugar de cruzar tablas completas, generas primero un conjunto reducido de valores y luego lo cruzas con otra tabla.

Generar una matriz completa con LEFT JOIN

El caso de uso mas practico y habitual del CROSS JOIN es combinarlo con un LEFT JOIN para crear una matriz completa que incluya los ceros. Este patron es esencial en la generacion de informes y dashboards, donde necesitas mostrar todas las combinaciones posibles de dos dimensiones, incluso aquellas donde no existen datos.

Imagina que tu jefe de ventas te pide un informe con el numero de pedidos que ha gestionado cada vendedor en cada estado. Si simplemente agrupas pedidos por empleado y estado con un GROUP BY, las combinaciones sin datos no apareceran en el resultado. Un vendedor que nunca ha tenido un pedido cancelado simplemente no tendra fila para ese estado, lo que puede dar la impresion erronea de que el dato falta o que la consulta esta incompleta.

La solucion es generar primero todas las combinaciones con CROSS JOIN y luego rellenar con los datos reales usando LEFT JOIN:

SELECT
    e.nombre AS empleado,
    estados.estado,
    COUNT(p.id) AS pedidos
FROM empleados e
CROSS JOIN (SELECT DISTINCT estado FROM pedidos) AS estados
LEFT JOIN pedidos p ON e.id = p.empleado_id
    AND p.estado = estados.estado
WHERE e.id IN (4, 5, 6)
GROUP BY e.nombre, estados.estado
ORDER BY e.nombre, estados.estado;
empleadoestadopedidos
Danielcancelado1
Danielentregado3
Danielenviado1
Danielpendiente2
Danielprocesando1
Nataliacancelado1
Nataliaentregado3
Nataliaenviado2
Nataliapendiente3
Nataliaprocesando1
Patriciacancelado1
Patriciaentregado1
Patriciaenviado1
Patriciapendiente2
Patriciaprocesando2

El CROSS JOIN genera todas las combinaciones empleado-estado (15 filas para 3 empleados x 5 estados). El LEFT JOIN con pedidos rellena los conteos reales. Si algun empleado no tuviera pedidos en algun estado, esa combinacion apareceria con 0 en lugar de estar ausente, lo que es exactamente lo que necesitas para un informe tipo tabla dinamica.

Este patron de CROSS JOIN + LEFT JOIN + COUNT() es uno de los mas potentes en SQL analitico. Lo encontraras en cualquier sistema de reporting donde necesites representar datos en formato de tabla cruzada o pivot table.

Generar combinaciones de productos

Otro uso practico del producto cartesiano es generar pares de elementos para analisis de afinidad o sistemas de recomendacion. En una tienda online, podrias querer saber que productos se compran juntos con frecuencia para mostrar sugerencias del tipo "Los clientes que compraron X tambien compraron Y".

El primer paso seria generar todas las combinaciones posibles de productos que aparecen en un mismo pedido. Para buscar que pares de productos se han comprado en el mismo pedido:

SELECT DISTINCT
    p1.nombre AS producto_1,
    p2.nombre AS producto_2
FROM detalle_pedidos dp1
INNER JOIN detalle_pedidos dp2 ON dp1.pedido_id = dp2.pedido_id
    AND dp1.producto_id < dp2.producto_id
INNER JOIN productos p1 ON dp1.producto_id = p1.id
INNER JOIN productos p2 ON dp2.producto_id = p2.id
ORDER BY p1.nombre
LIMIT 8;
producto_1producto_2
1984Eloquent JavaScript
1984Cable USB-C a Lightning
Banda elastica set x5Zapatillas running pro
Cable USB-C a LightningCamiseta algodon basica
Cable USB-C a LightningCamiseta tecnica running
Cable USB-C a LightningClean Code
Cable USB-C a LightningEloquent JavaScript
Cable USB-C a LightningRobot de cocina

Aunque esta consulta usa INNER JOIN en lugar de CROSS JOIN, el concepto subyacente es el mismo: generar combinaciones. La condicion dp1.producto_id < dp2.producto_id evita duplicados (no queremos ver tanto "iPhone + Funda" como "Funda + iPhone") y tambien evita que un producto se empareje consigo mismo.

Si quisieras generar todas las combinaciones posibles de productos (no solo los que se han comprado juntos), ahí sí usarías un CROSS JOIN directo:

SELECT
    p1.nombre AS producto_1,
    p2.nombre AS producto_2,
    p1.precio + p2.precio AS precio_combinado
FROM productos p1
CROSS JOIN productos p2
WHERE p1.id < p2.id
    AND p1.categoria_id = p2.categoria_id
ORDER BY precio_combinado DESC
LIMIT 5;

Esta consulta genera pares de productos de la misma categoria, lo que podría usarse para crear packs o promociones de "compra dos productos de la misma categoría".

Generar un calendario con CROSS JOIN

Otro caso de uso clasico del CROSS JOIN es la generacion de rangos de fechas o calendarios combinados con entidades del negocio. Supongamos que necesitas un informe mensual de ventas por categoria, y quieres que aparezcan todos los meses del año aunque alguna categoria no haya tenido ventas en un mes determinado.

El enfoque consiste en generar primero una tabla con los 12 meses y luego cruzarla con las categorias:

SELECT
    meses.mes,
    c.nombre AS categoria,
    COALESCE(SUM(dp.cantidad * dp.precio_unitario), 0) AS ventas
FROM (
    SELECT 1 AS mes UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
    UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8
    UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12
) AS meses
CROSS JOIN categorias c
LEFT JOIN pedidos p ON MONTH(p.fecha) = meses.mes
    AND YEAR(p.fecha) = 2025
LEFT JOIN detalle_pedidos dp ON p.id = dp.pedido_id
LEFT JOIN productos pr ON dp.producto_id = pr.id
    AND pr.categoria_id = c.id
GROUP BY meses.mes, c.nombre
ORDER BY meses.mes, c.nombre
LIMIT 12;

La subconsulta con UNION genera una tabla virtual con los numeros del 1 al 12, representando los meses. Al cruzarla con categorias, obtienes todas las combinaciones mes-categoria (12 x N). El LEFT JOIN posterior con pedidos y detalle rellena las ventas reales, y la funcion COALESCE() convierte los NULL en 0 para las combinaciones sin ventas.

Tamaño del resultado y rendimiento

El peligro principal del CROSS JOIN es el tamano del resultado. Si una tabla tiene 1.000 filas y otra tiene 10.000, el producto cartesiano genera 10.000.000 filas. Con tablas grandes, esto puede consumir toda la memoria disponible o bloquear el servidor. Por eso es imprescindible calcular el tamano del resultado antes de ejecutar un CROSS JOIN, especialmente si trabajas sobre tablas de produccion con millones de registros.

-- Para estimar el tamaño antes de ejecutar
SELECT
    (SELECT COUNT(*) FROM productos) AS filas_productos,
    (SELECT COUNT(*) FROM categorias) AS filas_categorias,
    (SELECT COUNT(*) FROM productos) *
    (SELECT COUNT(*) FROM categorias) AS filas_cross_join;
filas_productosfilas_categoriasfilas_cross_join
3016480

480 filas es manejable. Pero imagina hacer un CROSS JOIN entre una tabla de clientes (20.000) y una tabla de productos (50.000): 1.000 millones de filas. Antes de usar CROSS JOIN, siempre calcula mentalmente el tamano del resultado. Una buena practica es trabajar con subconjuntos reducidos: en lugar de cruzar tablas completas, usa subconsultas con DISTINCT para extraer solo los valores unicos que necesitas, o aplica filtros con WHERE para limitar las filas de entrada.

Otra estrategia para controlar el tamano es usar LIMIT en la consulta exterior para verificar los primeros resultados antes de ejecutar la consulta completa. Si los primeros registros tienen la estructura esperada, puedes eliminar el LIMIT con confianza.

CROSS JOIN accidental

Un error muy comun al usar la sintaxis antigua de JOIN (tablas separadas por comas) es olvidar la condicion WHERE. Sin ella, obtienes un producto cartesiano no deseado que puede saturar el servidor y devolver resultados absurdos:

-- ERROR común: olvidar la condición WHERE
-- Esto genera 30 × 16 = 480 filas (producto cartesiano completo)
SELECT p.nombre, c.nombre
FROM productos p, categorias c;
 
-- Lo que realmente querías era:
SELECT p.nombre, c.nombre
FROM productos p, categorias c
WHERE p.categoria_id = c.id;

Esta es otra razon por la que la sintaxis moderna con JOIN ... ON es preferible: hace explicita la relacion entre tablas y es imposible olvidarla accidentalmente. Si escribes INNER JOIN categorias c y no pones la clausula ON, MySQL te dara un error de sintaxis antes de ejecutar la consulta. Con la sintaxis de comas, el error pasa desapercibido y simplemente obtienes un resultado incorrecto.

Este tipo de error es especialmente peligroso en consultas con tres o mas tablas. Si olvidas una sola condicion de union entre dos de ellas, el resultado se multiplica de forma silenciosa. Una consulta que deberia devolver 50 filas puede devolver 50.000, y si no verificas el numero de resultados, la aplicacion procesara datos basura sin dar ninguna señal de error.

Diferencias con otros tipos de JOIN

Para entender correctamente cuando usar CROSS JOIN, es util compararlo con los otros tipos de JOIN disponibles en MySQL. Cada tipo de JOIN tiene un proposito diferente y produce resultados distintos a partir de las mismas tablas de entrada.

El INNER JOIN devuelve solo las filas donde la condicion ON se cumple en ambas tablas. Es el tipo mas comun y se usa cuando quieres combinar registros que tienen una relacion directa, como un producto con su categoria.

El LEFT JOIN devuelve todas las filas de la tabla izquierda, y rellena con NULL las columnas de la tabla derecha cuando no hay coincidencia. Es util cuando quieres preservar todos los registros de una tabla independientemente de si tienen datos relacionados.

El CROSS JOIN no evalua ninguna condicion. Simplemente genera todas las combinaciones matematicamente posibles. Esto lo hace unico: mientras los otros JOINs reducen o preservan filas, el CROSS JOIN las multiplica. Por eso su uso debe ser siempre intencional y controlado.

Cuando usar CROSS JOIN

Usa CROSS JOIN cuando necesitas generar explicitamente todas las combinaciones posibles entre dos conjuntos de datos. Los casos mas habituales incluyen la creacion de matrices completas para informes donde necesitas todas las combinaciones de dos dimensiones (empleado x mes, producto x region, categoria x año), la generacion de calendarios o rangos de fechas combinados con entidades del negocio, el relleno de datos faltantes en tablas de hechos donde las combinaciones sin datos deben mostrar ceros, y la creacion de datos de prueba con todas las combinaciones posibles para verificar el comportamiento de una aplicacion.

En todos estos casos, el CROSS JOIN se combina normalmente con un LEFT JOIN posterior para anadir los datos reales donde existan y dejar NULL (o 0 con COALESCE()) donde no. Este patron de dos pasos es la forma idiomatica de garantizar que un informe contiene todas las filas esperadas sin huecos.

Si te encuentras usando CROSS JOIN sin un LEFT JOIN posterior y sin un motivo claro, probablemente no sea la herramienta correcta. Revisa si lo que realmente necesitas es un INNER JOIN con una condicion de union adecuada.

Practica con CROSS JOIN

Usa el editor para generar combinaciones con CROSS JOIN:

Simulador SQL
Ctrl+Enter para ejecutar

En el siguiente articulo veremos el Self Join, donde una tabla se une consigo misma para explorar relaciones jerarquicas como la estructura de empleados o las categorias con subcategorias.

Escrito por Eduardo Lázaro