Em bancos de dados, dificilmente consultamos apenas uma tabela para analisar ou criar sets de dados para estudo. Um dos, se não o principal recurso para unir tabelas na linguagem SQL é o operador JOIN.

Igualando uma ou mais colunas na consulta, desde que haja relação entre elas, conseguimos unir tabelas para exibir resultado único; facilitando a exibição de múltiplas colunas que até então, não seria possível visualizar juntas.

Sabendo da real utilidade do JOIN, vamos nos aprofundar e entender como cada um funciona e quais cenários eles são melhores aplicados em banco de dados.

INTRODUÇÃO

A linguagem SQL possui três tipos de JOIN em bancos de dados, são eles:

  1. CROSS JOIN
  2. INNER JOIN
  3. OUTER JOIN (FULL, LEFT E RIGHT)

Cada um deles possui uma forma diferente de comparar as tabelas envolvidas na query e entregar os resultados para o usuário. Porém, todos possuem algo em comum: começam avaliando os valores que possuem correspondência.

O CROSS JOIN iguala e cruza os valores como um produto cartesiano (A x B). Já o INNER JOIN compara as colunas das tabelas envolvidas e retorna somente os valores correspondentes e não nulos, diferente do OUTER. Este último, é o único que possui três operações antes de entregar o resultado, que são:

  1. Iguala os valores das colunas envolvidas;
  2. Retorna os dados correspondentes;
  3. Retorna os dados não correspondentes – que seriam os nulos excluídos no INNER JOIN.

Por fim, sempre que for criar consultas que envolvam estes operadores, atribua um alias para as tabelas; evitará ambiguidade e erro no resultado. O banco saberá de qual tabela cada coluna está sendo consultada.

CROSS JOIN

Agora que já expliquei as bases, podemos avançar para os exemplos práticos envolvendo consultas e o entendimento de como cada um deles funciona.

Como disse, o CROSS JOIN opera como um plano cartesiano multiplicando cada valor da tabela A por B. Logo, se uma possui 10 linhas e a outra 20, teremos uma consulta resultando em 200 linhas.

A nível ilustrado, seria desta forma:

Funcionamento do CROSS JOIN.

Sabendo da sua aplicação, vamos entender como funciona sua sintaxe no banco de dados.

Exemplo do uso de CROSS JOIN.

O interessante do CROSS JOIN é a capacidade de utilizar igualdade entre as colunas na cláusula WHERE. Veja que a mesma query pode ser construída da seguinte forma:

CROSS JOIN igualando consultas na cláusula WHERE.

O único ponto é que a quantidade de linhas é diminuída em relação ao CROSS, visto que estamos reduzindo a correspondência. Mas o processamento no geral não muda.

E para finalizar o CROSS JOIN, em algumas situações o uso de SELF CROSS pode ser muito bem vindo para criar sequenciais numéricos que auxiliam em operações de JOIN entre queries.

Criando sequencias numéricas com o CROSS JOIN

INNER JOINS

Avançando no assunto, vamos ao que talvez seja um dos tipos de união entre tabelas mais utilizados em queries dentro de banco de dados, o INNER JOIN.

Com esse operador, o que temos é a união entre valores correspondentes e a exclusão dos que não são, como uma verdadeira interseção. É neste JOIN que estamos unindo as primary e foreign key das tabelas.

Diagrama de ação do INNER JOIN

Não existe alteração da ordem dos resultados quando mudamos as tabelas de lugar. Não há influência.

Exemplo prático de INNER JOIN.

Na query acima, por exemplo, acabei selecionando o nome e o ID das subcategorias dos produtos.

É possível unir consultas através de JOINS embora não seja um código muito limpo, existe aplicabilidade. Além disso, você acaba limitando a quantidade de colunas possíveis de selecionar.

Quando utilizamos essa estrutura, podemos selecionar apenas as colunas que aparecem na query interna.

É preciso destacar que toda a query interna será única, o que nos leva a igualar com a consulta externa.

Utilizando INNER JOIN com SELECT.

OUTER JOIN

São os JOINS que não excluem do resultado os valores que não possuem correspondência entre as colunas igualadas.

São perfeitos para preservar os resultados nulls das tabelas ou quando não temos certeza da integridade.

Como dito no início do artigo, são três tipos de OUTER JOIN.

  • LEFT JOIN → mantém a tabela da esquerda comparando com a direita;
  • RIGHT JOIN → mantém a tabela da direita comparando com a esquerda;
  • FULL JOIN → iguala e agrupa as duas.

Dependendo do tipo de JOIN utilizado (left ou right), o que estamos informando ao banco é qual lado da query queremos preservar e qual iremos comparar na sua construção e resultado.

Perceba que em comparação com o INNER, agora temos os produtos que não possuíam uma subcategoria.

Exemplo de LEFT JOIN

Até o número de linhas é diferente. Executando a query com INNER e LEFT ou RIGHT, perceberá que com OUTER, há mais linhas retornadas.

Se fôssemos representar o LEFT JOIN  através de um diagrama, seria desta forma:

Diagrama LEFT JOIN

Utilizando o RIGHT JOIN  a lógica é basicamente a mesma, porém, pode haver divergência no resultado, mesmo que a estrutura que se deseja manter, seja a mesma.

No primeiro exemplo, veja que estou utilizando a mesma estrutura mudando apenas o tipo de JOIN.

Exemplo RIGHT JOIN

A pegadinha aqui é que agora, a coluna que estamos preservando é a ProductSubCategory, logo, teremos apenas os produtos que possuem correspondência com ela. 

Se quiséssemos o mesmo resultado da construção utilizando o LEFT JOIN, precisaríamos alternar a ordem das tabelas.

Exemplo RIGHT JOIN.

Representando através de um diagrama, teríamos:

Diagrama RIGHT JOIN.

Como podemos perceber nos exemplos acima, precisamos estar muito atentos às nuances que envolvem a construção de queries e uso de JOINS.

Nos dois exemplos utilizados para mostrar como o RIGHT JOIN funciona, uma simples troca de posição das tabelas alterou o resultado, se tornando um INNER, quando queríamos um RIGHT  e retornar NULLS.

No primeiro exemplo preservamos a tabela ProductSubCategory e no segundo exemplo, a tabela Product (que era a correta). Assim, é importante destacar que durante a escolha e construção da query com o seu operador de tabela, tenha em mente a possibilidade de valores nulos e qual forma se encaixa melhor.

JOINS AVANÇADOS E SEUS USOS

A versatilidade do uso dos JOINS vai muito além da união de duas tabelas. A linguagem SQL permite que utilizemos tantas tabelas quanto quisermos na query, desde que se respeite os operadores e o lado que desejamos manter.

Não é nada incomun começarmos utilizando o LEFT JOIN e por descuído, transformamos em um INNER, alterando todo o resultado. Abaixo, irei mostrar com alguns exemplos, como podemos evitar esse erro.

A primeira construção é a padrão, mantendo os NULLS como vimos no LEFT JOIN.

Trabalhando corretamente com múltiplos OUTER JOINS.

Podemos dizer que todas as linhas da tabela retornaram, mesmo as que não possuíam correspondência.

Quando utilizamos múltiplos JOINS em uma consulta, o resultado do primeiro é utilizado e mantido para a próxima comparação. Então, o que foi retornado entre as tabelas T1 e T2 foi utilizado para o T3 – sempre mantendo o lado esquerdo.

Agora perceba o que acontece quando “viro” o lado da query.

Transformando OUTER JOIN em INNER JOIN.

Perceba que até a quantidade de linhas retornadas diminuiu. Isso quer dizer que ao “virar o lado” na query, o que fizemos foi transformar um OUTER em INNER, reduzindo as correspondências.

Se fossem INNER JOINS, não haveria problema algum, seria o esperado. 

Perceba que na query abaixo, não importa a disposição das tabelas, o resultado seria o mesmo ao final.

Demonstração de múltiplos INNER JOINS. Confirmando o exemplo anterior.

Algumas pessoas podem confundir ou achar que possuem o mesmo tipo de avaliação e construir a seguinte query.

Falha do ON como WHERE.

Pode parece que estaríamos filtrando como seria se houvesse um WHERE ali, mas não. Não é isso o que ocorre, veja o resultado.

Resultado da query anterior.

Não há filtro na query pois o ON do JOIN não atua como um, mas sim, como uma igualdade. Um operador de igualdade de tabela.

Desse modo, tentar filtar qualquer valor na cláusula ON seria perda de tempo. A construção correta seria.

Correção do exemplo anterior. Filtrando com WHERE.

Desse modo, tenha cuidado e não confunda o ON com WHERE na construção da sua query.

NULL COM OUTER JOIN

Muitas vezes quando estamos construindo uma query, fazemos de modo tão automático que certas situações podem passar despercebidas e acabar gerando um resultado diferente do esperado.

É o caso da query abaixo.

Como o WHERE altera o resultado de um OUTER JOIN.

Podemos pensar que o resultado está correto, mas não. A query deixou de ser um OUTER JOIN, se tornando INNER. 

Como criamos um filtro com o WHERE na query, na avaliação lógica, ele considera NULL como UNKNOWN. Sabendo que o filtro para a coluna ProductSubCategoryID não reconhece UNKNOWN, apenas as funções IS NULL e IS NOT NULL, os resultados acabam sendo excluídos.

Corrigindo a estrutura da query, temos:

Exibindo NULLS com WHERE em OUTER JOINS.

Então, a boa prática quando for construir uma query que envolva OUTER JOINS é considerar que naquele resultado pode haver valores desconhecidos e que serão excluídos ao final.

Para melhor estrutura de uma consulta, considere sempre utilizar o IS NULL na cláusula WHERE ou HAVING para não perder nenhuma linha.

EVITANDO NULLS COM A FUNÇÃO COUNT

Existe uma situação que pode prejudicar a contagem na sua query quando utilizamos a função COUNT no banco de dados.

Veja essa query abaixo:

Exemplo de query com OUTER JOIN.

Como não há pedido para os clientes de id 22 e 57 e estamos utilizando um LEFT JOIN, tanto a coluna ORDERID quanto ORDERDATE retornam NULL – o que é normal.

Agora, perceba o que acontece quando eu faço uma contagem de pedidos por cliente, utilizando a mesma query.

Adicionando COUNT na query com OUTER JOIN. Veja o “erro” no resultado.

Veja que o banco considerou os clientes 22 e 57 na contagem e nos pedidos, mesmo não havendo, como mostrado no exemplo anterior.

Não pense que é um erro. Isso é o comportamento padrão da linguagem SQL. Como a função COUNT não consegue distinguir e tratar o NULL corretamente, ela considera o valor na contagem.

Diferente do WHERE, ela não aplica UNKNOWN para desconhecidos (NULOS) e por isso, temos essa situação.

Para evitar que isso aconteça no seu resultado, utilize o COUNT com uma coluna que seja PRIMARY KEY.

Correção do “erro” de COUNT com OUTER JOIN.

Agora, corrigindo a coluna na função, o resultado fica condizente com a realidade. Não há contagem para os clientes 22 e 57, como deveria ser.

CONCLUSÃO

Há muito tempo atrás eu tinha escrito um artigo sobre JOINS, mas relendo, vi que ficou incompleto e resolvi atualizar.

Neste, consegui abordar todas as questões importantes que envolvem esse operador de tabela, inclusive pequenas particularidades que podem alterar o resultado de forma negativa.

Muita atenção com o WHERE  nas suas queries, principalmente quando há possibilidade de valores nulos no resultado.

Sempre que for unir tabelas, utilize um OUTER JOIN – LEFT de preferência. É uma boa prática considerar que pode haver valores NULLS na tabela e estes não devem ser negligenciados.

Em funções de agrupamento, tente trabalhar sempre com as colunas que são PRIMARY KEY. Como vimos no último exemplo, elas não conseguem distinguir bem entre nulos e não-nulos.

OLÁ!

Se chegou até aqui, deixe seu like. Ajude o blog a crescer!

Compartilhe com seus amigos e em suas redes. Me ajude a alcançar mais gente!

Comente o que achou desse artigo e sugira melhorias!

Se inscreva e receba as novidades!

Obrigado!!