¡Hola, chicos!
En este post me gustaría compartir con ustedes el funcionamiento de un operador que rara vez veo que alguien use, que es CROSS JOIN, y que en ciertos escenarios puede ser útil.

¿Qué es la unión cruzada?

CROSS JOIN devuelve la combinación de cada una de las filas entre las tablas involucradas, también conocido como producto cartesiano, que demostraré a continuación para facilitar la comprensión.

Ejemplo 1

En este ejemplo, crearé 2 tablas y cruzaré los datos de las dos tablas para crear un producto cartesiano combinando cada fila de la tabla #Ice Cream con cada fila de la tabla #Complement (sí, tengo ganas de comer helado).

Script para generar los datos para este ejemplo:

IF (OBJECT_ID('tempdb..#Sorvete') IS NOT NULL) DROP TABLE #Sorvete
CREATE TABLE #Sorvete (
    Nome VARCHAR(60)
)

INSERT INTO #Sorvete VALUES ('Chocolate'), ('Baunilha'), ('Morango')
GO

IF (OBJECT_ID('tempdb..#Complemento') IS NOT NULL) DROP TABLE #Complemento
CREATE TABLE #Complemento (
    Nome VARCHAR(60)
)
GO

INSERT INTO #Complemento VALUES ('Paçoca'), ('Granulado'), ('Castanha'), ('Nutella')
GO

SELECT * FROM #Sorvete
SELECT * FROM #Complemento

Resultado:

Bueno, ahora creemos el producto cartesiano entre las dos tablas:

¿La única forma de crear un producto cartesiano es mediante CROSS JOIN?

Como hemos visto, el producto cartesiano crea todas las combinaciones posibles entre los datos involucrados. Como en nuestro ejemplo había una tabla con 3 registros en una operación CROSS JOIN con otra tabla con 4 registros, nuestro resultado final presentó 12 filas (3 x 4).

Si cada tabla tuviera 1000 registros, el resultado final tendría 1 millón de registros (1000 x 1000), por lo que el producto cartesiano generalmente no es deseable en una operación JOIN y puede causar problemas de rendimiento si ocurre de forma no deseada (como en una cláusula de unión incorrecta, por ejemplo).

Ejemplo 2

En este ejemplo, demostraré un escenario práctico donde el producto cartesiano justifica su uso, utilizando los organizadores y oradores del 1er Día SQL ES.

Script para generar los datos para este ejemplo:

IF (OBJECT_ID('tempdb..#Pessoas') IS NOT NULL) DROP TABLE #Pessoas
CREATE TABLE #Pessoas (
    Id_Pessoa INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    Nome VARCHAR(60) NOT NULL,
    Sexo VARCHAR(1) NOT NULL,
    Estado VARCHAR(2) NOT NULL,
    Cidade VARCHAR(60) NOT NULL
)
GO

INSERT INTO #Pessoas (Nome, Sexo, Estado, Cidade) 
VALUES 
    ('Dirceu', 'M', 'ES', 'Vitória'),
    ('Vithor', 'M','ES', 'Vila Velha'),
    ('Fabrício', 'M','ES', 'Vila Velha'),
    ('Tiago', 'M','ES', 'Serra'),
    ('Caio', 'M','SP', 'São José dos Campos'),
    ('Cláudio', 'M','RJ', 'Rio de Janeiro'),
    ('Daniel', 'M','ES', 'Vila Velha'),
    ('Felippe', 'M','RJ', 'Rio de Janeiro'),
    ('Jéssica', 'F','SP', 'São José dos Campos'),
    ('Raiane', 'F','DF', 'Brasília'),
    ('Reginaldo', 'M','SP', 'Boituva'),
    ('Sulamita', 'F', 'MG', 'Belo Horizonte')

Ahora quiero mostrarles cómo crear un informe que muestre el género, el estado y cuántas personas tenemos en este grupo:

SELECT 
    Estado,
    Cidade,
    Sexo,
    COUNT(*) AS Qt_Pessoas
FROM 
    #Pessoas
GROUP BY
    Estado,
    Cidade,
    Sexo
ORDER BY
    1, 2, 3

Resultado:

Como habrás notado, solo se devuelven los registros existentes en la tabla. Por ejemplo, en el estado “DF” solo arrojó resultados con el género “F”, porque en nuestra tabla solo hay personas con el género Mujer en el estado DF.

¿Pero qué pasa si también quiero mostrar los géneros que no tienen personas registradas en la tabla? ¿Cómo lo hago? Bueno, para empezar, usemos el CTE (Expresiones de tabla comunes) para hacer coincidir el género con las ciudades y estados y crear todas las combinaciones posibles:

;WITH cteEstados AS (
    SELECT DISTINCT Estado, Cidade
    FROM #Pessoas
),
cteSexo AS (
    SELECT DISTINCT Sexo
    FROM #Pessoas
)
SELECT 
    *
FROM
    cteEstados
    CROSS JOIN cteSexo
ORDER BY
    1, 2, 3

Ahora, modifiquemos este script para que devuelva el número de personas en cada una de las combinaciones, que es lo que queremos mostrar en el resultado de nuestra consulta:

;WITH cteEstados AS (
    SELECT DISTINCT Estado, Cidade
    FROM #Pessoas
),
cteSexo AS (
    SELECT DISTINCT Sexo
    FROM #Pessoas
),
cteQuantidade AS (
    SELECT Cidade, Estado, Sexo, COUNT(*) AS Quantidade
    FROM #Pessoas
    GROUP BY Cidade, Estado, Sexo
)
SELECT
    cteEstados.Estado,
    cteEstados.Cidade,
    cteSexo.Sexo,
    ISNULL(cteQuantidade.Quantidade, 0) AS Quantidade
FROM
    cteEstados
    CROSS JOIN cteSexo
    LEFT JOIN cteQuantidade ON cteEstados.Cidade = cteQuantidade.Cidade AND cteEstados.Estado = cteQuantidade.Estado AND cteQuantidade.Sexo = cteSexo.Sexo
ORDER BY
    1, 2, 3

Resultado:

Conclusión

Bueno chicos, espero haber podido demostrar que el producto cartesiano puede ser útil en el desarrollo de consultas T-SQL. Para crear el ejemplo 2 sin usarlo, tendríamos que crear un cursor o bucle while, lo cual tiene un rendimiento pésimo y la rutina termina siendo mucho más compleja.

Aunque he demostrado un buen uso de esta característica, hay que tener mucho cuidado al utilizarla, especialmente con grandes volúmenes de datos, ya que el CROSS JOIN o producto cartesiano, crea todas las combinaciones posibles entre 2 o más ResultSets, cruzando cada fila de la tabla A con cada fila de la tabla B.

A menudo observamos JOINs (INNER, LEFT, RIGHT) mal construidas que terminan creando un producto cartesiano no intencionado, generando muchas filas no deseadas y provocando un problema de rendimiento en la consulta.

Dicho esto cierro este post.
Un fuerte abrazo para ti y hasta la próxima.