Fala pessoal!
Nesse post eu gostaria de compartilhar com vocês o funcionamento de um operador que raramente vejo alguém utilizando, que é o CROSS JOIN, e que em determinados cenários, pode ter a sua utilidade.
O que é o CROSS JOIN?
O CROSS JOIN retorna a combinação de cada uma das linhas entre as tabelas envolvidas, também conhecido como Produto cartesiano, qual vou demonstrar abaixo para facilitar o entendimento.
Exemplo 1
Nesse exemplo vou criar 2 tabelas e cruzar os dados das duas tabelas para criar um produto cartesiano combinando cada linha da tabela #Sorvete com cada linha da tabela #Complemento (sim, estou com vontade de tomar sorvete).
Script para geração dos dados desse exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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 |
Pois bem, agora vamos criar o produto cartesiano entre as duas tabelas:
A única forma de se criar um produto cartesiano é utilizando CROSS JOIN?
Como pudemos observar, o produto cartesiano cria todas as combinações possíveis entre os dados envolvidos. Como no nosso exemplo era uma tabela de 3 registros em uma operação de CROSS JOIN com outra tabela de 4 registros, o nosso resultado final apresentou 12 linhas (3 x 4).
Se cada tabela tivesse 1.000 registros, o resultado final teria 1 milhão de registros (1.000 x 1.000), por isso o produto cartesiano geralmente não é algo desejável em uma operação de JOIN, e pode causar problemas de performance se ele ocorrer de forma não desejada (como em uma cláusula join errada, por exemplo)
Exemplo 2
Nesse exemplo, vou demonstrar um cenário prático onde o produto cartesiano justifica o seu uso, utilizando os organizadores e palestrantes do 1º SQL Day ES.
Script para geração dos dados desse exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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') |
Agora, eu quero mostrar como criar um relatório que me mostre sexo, estado e quantas pessoas temos nesse agrupamento:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
SELECT Estado, Cidade, Sexo, COUNT(*) AS Qt_Pessoas FROM #Pessoas GROUP BY Estado, Cidade, Sexo ORDER BY 1, 2, 3 |
Como vocês devem ter percebido, apenas os registros existentes na tabela são retornados. Por exemplo, no estado do “DF” só retornou resultados com o sexo “F”, pois na nossa tabela, existem apenas pessoas com o sexo Feminino no estado DF.
Mas e se eu quiser mostrar também os gêneros que que não possuem pessoas cadastradas na tabela? Como faço? Bem, para começar, vamos utilizar o CTE (Common Table Expressions) para combinar o gênero com as cidades e estados e criar todas as combinações possíveis:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
;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 |
Agora, vamos modificar esse script para nos retornar a quantidade de pessoas de cada uma das combinações, que é o que queremos mostrar no resultado da nossa query:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
;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 |
Conclusão
Bom pessoal, espero que eu tenha conseguido demonstrar que o produto cartesiano pode sim, ter sua utilidade no desenvolvimento de consultas T-SQL. Para criar o exemplo 2 sem utilizá-lo, teríamos que criar cursor ou loop while, o que tem uma péssima performance e a rotina acaba sendo bem mais complexa.
Embora eu tenha demonstrado um bom uso desse recurso, muito cuidado ao utilizá-lo, especialmente com grandes volumes de dados, uma vez que o CROSS JOIN ou produto cartesiano, cria todas as combinações possíveis entre 2 ou mais ResultSets, cruzando cada linha da tabela A com cada linha da tabela B.
Muitas vezes, observamos JOIN’s (INNER, LEFT, RIGHT) mal construídos e que acabam fazendo um produto cartesiano não intencional, gerando muitas linhas indesejadas e trazendo um problema de performance na consulta.
Dito isso, encerro esse post.
Um grande abraço pra vocês e até a próxima.
Parabéns pelas dicas! Me ajudou muito! Grande abraço e todo o sucesso do mundo para ti!
Excelente post, você explica com clareza e exemplos claros! Obrigada
O uso do carácter ‘#’ no nome da tabela siguinifs que você está criando uma tabela temporária?
Olá, Marcos. Boa noite.
Sim, é isso mesmo. Nesses exemplos utilizei tabelas temporárias.