Consultas em Várias Tabelas
Grande parte das consultas realizadas em um banco de dados dependem de informações presentes em mais de uma tabela. Por exemplo: "Desejo recuperar todas as tarefas e o nome de seus respectivos usuários". Para conseguir recuperar tal informação, é necessário acessar a tabela usuarios para recuperar o nome do usuário, e a tabela tarefas, para recuperar as tarefas existentes.
Para conseguir recuperar essas informações, é necessário fazer uma consulta que combine os dados de várias tabelas, de forma a obter a informação desejada. O comando SELECT
permite fazer esse cruzamento de dados por meio da junção de tabelas. Mais especificamente, esta junção pode ser feita de duas formas:
- Utilizando a cláusula
FROM
- Utilizando a cláusula
JOIN
Utilizando FROM para junção de tabelas
A utilização da cláusula FROM
era a forma padrão para junção de tabelas em antigas versões da linguagem SQL. Basicamente, a junção é feita através da declaração de mais de uma tabela na cláusula FROM
de um comando SELECT
. Ao se utilizar a cláusula FROM
para fazer junção de tabelas, o SGBD faz um produto cartesiano entre as tabelas selecionadas. Posteriormente, a filtragem desses registros é feito por meio das restrições declaradas na cláusula WHERE
.
O trecho de código abaixo mostra a sintaxe básica de junção por meio do FROM
:
SELECT campos FROM tabela1, tabela2, tabelaN
WHERE tabela1.pkey = tabela2.fkey AND tabela2.pkey = tabelaN.fkey
Como se pode observar, a relação entre as tabelas é feita por meio da comparação entre as chaves primárias e estrangeiras que cada uma possui. Nota-se também que a referência dos campos deve estar sempre atrelado a tabela na qual aquele campo pertence. Sem essa relação, o SGBD não saberia distinguir qual campo pertence a qual tabela, inviabilizando assim as comparações de suas chaves. O trecho de código abaixo representa a consulta descrita no início dessa seção:
SELECT tarefas.*, usuarios.nome FROM tarefas, usuarios
WHERE tarefas.user_id = usuarios.id
É possível atribuir nome as tabelas, de forma a facilitar a escrita de consultas mais elaboradas. O exemplo abaixo mostra como ficaria a consulta anterior com as tabelas devidamente nomeadas:
SELECT t.*, u.nome FROM tarefas t, usuarios u
WHERE t.user_id = u.id
O problema do Produto Cartesiano
Como já dito anteriormente, a junção por meio do FROM
gera um produto cartesiano de todas os registros das tabelas envolvidas na consulta. O produto cartesiano nada mais é do que a combinação de um registro de uma tabela com todas as demais. Como se pode imaginar, tal ação implica um overhead de processamento, uma vez que o SGBD seleciona todas as combinações possíveis, e somente então descarta as que não se encaixam nas restrições impostas pela cláusula WHERE
.
Para exemplificar o funcionamento do produto cartesiano, vamos analisar o exemplo a seguir:
usuarios | |||
---|---|---|---|
id | nome | senha | |
01 | Joao | [email protected] | 123 |
02 | Pedro | [email protected] | 123 |
tarefas | |||
---|---|---|---|
id | descricao | usuarios_id | categorias_id |
01 | Fazer dever | 01 | 01 |
02 | Lavar carro | 01 | 02 |
03 | Comprar presente | 02 | 02 |
04 | Fazer jantar | 01 | 02 |
A Tabela usuarios contém os dados dos usuários cadastrados no sistema. Já a tabela tarefas contém as tarefas de todos os usuários cadastrados. Em um determinado momento, o usuário do banco de dados deseja recuperar todas as tarefas do João. Para isso, o usuário escreve a seguinte consulta:
SELECT tarefas.*, usuarios.* FROM tarefas, usuarios
WHERE tarefas.usuarios_id = usuarios.id AND usuarios.nome LIKE '%João%'
Ao executar essa consulta, o SGBD primeiro faz o produto cartesiano, e somente então filtras as tarefas. Neste exemplo, a consulta geraria a seguinte tabela:
tarefas_usuarios | |||||||
---|---|---|---|---|---|---|---|
id | descricao | usuarios_id | categorias_id | id | nome | senha | |
01 | Fazer dever | 01 | 01 | 01 | João | [email protected] | 123 |
02 | Lavar carro | 01 | 02 | 01 | João | [email protected] | 123 |
03 | Comprar presente | 02 | 02 | 01 | João | [email protected] | 123 |
04 | Fazer jantar | 01 | 02 | 01 | João | [email protected] | 123 |
01 | Fazer dever | 01 | 01 | 02 | Pedro | [email protected] | 123 |
02 | Lavar carro | 01 | 02 | 02 | Pedro | [email protected] | 123 |
03 | Comprar presente | 02 | 02 | 02 | Pedro | [email protected] | 123 |
04 | Fazer jantar | 01 | 02 | 02 | Pedro | [email protected] | 123 |
Esta tabela então sofreria a filtragem dos dados por meio da cláusula WHERE
, gerando o seguinte resultado:
tarefas_usuarios | |||||||
---|---|---|---|---|---|---|---|
id | descricao | usuarios_id | categorias_id | id | nome | senha | |
01 | Fazer dever | 01 | 01 | 01 | João | [email protected] | 123 |
02 | Lavar carro | 01 | 02 | 01 | João | [email protected] | 123 |
04 | Fazer jantar | 01 | 02 | 01 | João | [email protected] | 123 |
Junção de tabelas com a cláusula JOIN
A cláusula JOIN
surgiu como solução para prover a junção de tabelas de forma mais eficiente. Diferentemente do que é feito na cláusula FROM
, o JOIN
seleciona os registros com base nas chaves antes de executar a filtragem do WHERE
. Desta forma, a filtragem é feita em uma quantidade muito menor de registros, tornando a consulta mais rápida.
O JOIN
faz a junção das tabelas com base na lógica de conjuntos. Imagine dois conjuntos distintos, onde cada conjunto representa uma determinada tabela. A cláusula JOIN
fornece funcionalidades para realizar operações de conjuntos essas tabelas. Assim é possível recuperar dados que são em comum entre as duas tabelas (interseção), ou todos os dados das duas tabelas (união). A figura abaixo mostra graficamente as possibilidades que a cláusula JOIN
é capaz de fornecer:
Mais especificamente, a cláusula JOIN
disponibiliza quatro tipos de junções, seguida da sintaxe básica de cada uma:
- JOIN: Listar os registros que possuem relacionamento nas duas tabelas.
SELECT campos FROM tabela1
JOIN tabela2 ON tabela1.pkey = tabela2.fkey
[WHERE restricoes]
- LEFT JOIN: Listar todos os registros da tabela da esquerda relacionando com os registros da tabela da direita, e caso não haja relacionamento, preencher os campos com
null
.
SELECT campos FROM tabela1
LEFT JOIN tabela2 ON tabela1.pkey = tabela2.fkey
[WHERE restricoes]
- RIGHT JOIN: Listar todos os registros da tabela da direita relacionando com os registros da tabela da esquerda, e caso não haja relacionamento, preencher os campos com
null
.
SELECT campos FROM tabela1
RIGHT JOIN tabela2 ON tabela1.pkey = tabela2.fkey
[WHERE restricoes]
- FULL JOIN: Listar todos os registros, e caso não haja relacionamento (tanto para esquerda quanto para direita), preencher os campos com
null
. OBS: O MySQL não possui FULL JOIN.
SELECT campos FROM tabela1
FULL JOIN tabela2 ON tabela1.pkey = tabela2.fkey
[WHERE restricoes]
Naturalmente, a cláusula mais utilizada é o JOIN
, uma vez que raramente é necessário ter conhecimento de registros que não possuem relação entre as tabelas utilizadas na junção.