SQL INNER JOIN
Na aula O que é um JOIN, a Ana juntou cada pedido com o seu cliente. Aquele JOIN tem nome completo: INNER JOIN, a "junção interna", que guarda só os pares que combinam. É o tipo de JOIN mais usado no dia a dia. Nesta aula a Ana vai mais longe: junta três e quatro tabelas para saber quem comprou o quê e de quem, soma quanto cada cliente gastou e conhece dois atalhos, o USING e o NATURAL JOIN. Um deles é útil; o outro parece uma mão na roda e esconde uma armadilha.
- O que o
INNER JOINguarda e o que ele descarta. - Como juntar três ou mais tabelas, um
JOIN … ONde cada vez. - Como usar
JOINcomGROUP BYpara somar e contar por nome, e não por número. - A diferença (ou a falta dela) entre pôr um filtro no
ONou noWHERE. - O atalho
USINGe oNATURAL JOIN, e por que este último é perigoso. - Os erros reais:
ambiguous column namecom três tabelas,cannot join using columnea NATURAL join may not have an ON or USING clause.
Os exemplos rodam de verdade no seu navegador, no banco da Loja Aurora.
A ideia: o encontro dos dois lados
Pense numa festa em que cada convidado chega com uma pulseira numerada, e cada mesa tem um número. Para o fotógrafo, "INNER JOIN" é fotografar cada pessoa na mesa do seu número. Quem não tem mesa não sai em foto nenhuma. Mesa vazia também não. Só entram na foto os encontros que aconteceram.
Na Loja Aurora: um INNER JOIN entre clientes e pedidos mostra cada pedido com o seu cliente. Os 4 clientes que nunca compraram (27 a 30) ficam de fora, porque não têm par.
1. INNER JOIN com filtro
Os pedidos que ainda não foram entregues, com o nome do cliente para a Ana poder ligar para ele:
SELECT p.id, p.data, c.nome FROM pedidos p INNER JOIN clientes c ON p.cliente_id = c.id WHERE p.entregue_em IS NULL;
- FROM pedidos p
- A tabela da esquerda, com o apelido
p. - INNER JOIN clientes c
- Inner join = "junção interna": junta
clientes(apelidoc) guardando só os pares que combinam. Escrever sóJOINdá no mesmo. - ON p.cliente_id = c.id
- A regra do par: a chave estrangeira do pedido igual à chave primária do cliente.
- WHERE p.entregue_em IS NULL
- Depois de formar os pares, fica só quem ainda não tem data de entrega (aula IS NULL).
Cinco pedidos pendentes, do 86 ao 90. A ordem das partes é a de sempre: SELECT, FROM com os seus JOINs, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT. Os JOINs fazem parte do FROM: eles dizem de onde vêm as linhas.
2. Três tabelas: quem comprou e quem vendeu
O pedido tem duas chaves estrangeiras: cliente_id e funcionario_id. Para mostrar o nome do cliente e o nome do vendedor, a Ana encadeia um segundo JOIN:
SELECT p.id, c.nome AS cliente, f.nome AS vendedor FROM pedidos p JOIN clientes c ON p.cliente_id = c.id JOIN funcionarios f ON p.funcionario_id = f.id WHERE p.entregue_em IS NULL;
- FROM pedidos p
- O ponto de partida: os pedidos.
- JOIN clientes c ON p.cliente_id = c.id
- Primeira junção: cada pedido ganha as colunas do seu cliente.
- JOIN funcionarios f ON p.funcionario_id = f.id
- Segunda junção: o resultado da primeira ganha as colunas do funcionário. Cada
JOINleva o seu próprioON. - c.nome AS cliente, f.nome AS vendedor
- As duas tabelas têm uma coluna
nome. Os apelidos de coluna deixam claro qual é qual no resultado.
O SQLite processa os JOINs da esquerda para a direita: primeiro junta pedidos com clientes, depois junta esse resultado com funcionarios. Com INNER JOIN, a ordem em que você escreve as tabelas não muda quais linhas saem (a ordem delas no resultado, só o ORDER BY garante); escolha a que for mais fácil de ler. Um bom costume é começar pela tabela "do meio", a que tem as chaves estrangeiras (aqui, pedidos), e ir pendurando as outras nela.
Experimente: esta consulta lista todos os pedidos com o cliente. Acrescente um segundo JOIN com funcionarios para mostrar também o nome de quem vendeu, numa coluna chamada vendedor.
SELECT p.id AS pedido, c.nome AS cliente FROM pedidos p JOIN clientes c ON p.cliente_id = c.id;
SELECT p.id AS pedido, c.nome AS cliente, f.nome AS vendedor FROM pedidos p JOIN clientes c ON p.cliente_id = c.id JOIN funcionarios f ON p.funcionario_id = f.id;
3. Quatro tabelas: o que cada um comprou
Os produtos de um pedido ficam em itens_pedido, a tabela do meio da relação muitos-para-muitos (aula Tabelas que se Relacionam). Para ler o pedido 1 "em português", com o nome do cliente e o nome de cada produto, são quatro tabelas:
SELECT p.id AS pedido, c.nome AS cliente,
pr.nome AS produto, i.quantidade
FROM itens_pedido i
JOIN pedidos p ON i.pedido_id = p.id
JOIN clientes c ON p.cliente_id = c.id
JOIN produtos pr ON i.produto_id = pr.id
WHERE p.id = 1;
Siga o caminho no mapa do banco: o item aponta para o pedido, o pedido aponta para o cliente, e o item também aponta para o produto. Cada seta do mapa vira um JOIN … ON. Repare no apelido pr para produtos: o p já estava em uso por pedidos, e dois apelidos iguais na mesma consulta não podem.
Num JOIN que segue as setas a partir da tabela "mais detalhada", sai uma linha por linha dessa tabela. Começando por itens_pedido (220 itens), cada item tem exatamente um pedido, um cliente e um produto, então saem 220 linhas sem o WHERE. Se sair muito mais que isso, algum ON está faltando ou errado.
4. JOIN com GROUP BY: somando por nome
Na aula Agrupando por Mais de uma Coluna, os resultados vinham com números (categoria_id 1, 2…). Com um JOIN, o agrupamento pode mostrar nomes. Quanto cada cliente já gastou em produtos (sem contar o frete)? Os 5 maiores:
SELECT c.nome AS cliente,
SUM(i.quantidade * i.preco_unitario) AS gasto
FROM clientes c
JOIN pedidos p ON p.cliente_id = c.id
JOIN itens_pedido i ON i.pedido_id = p.id
GROUP BY c.id, c.nome
ORDER BY gasto DESC
LIMIT 5;
- FROM clientes c JOIN pedidos p … JOIN itens_pedido i …
- Cada linha do JOIN é um item comprado, com o cliente que o comprou ao lado.
- SUM(i.quantidade * i.preco_unitario)
- Em cada item, quantidade vezes preço; a soma de todos os itens do grupo (aula SUM()).
- GROUP BY c.id, c.nome
- Um grupo por cliente. O
c.idgarante que dois clientes com o mesmo nome não se misturem; oc.nomeestá lá porque aparece noSELECT(a regra da coluna esquecida, da aula anterior).
A Conceição Prado lidera, com 1.345,73 em produtos. E o faturamento por categoria? Três tabelas de novo, outro caminho no mapa:
SELECT c.nome AS categoria, SUM(i.quantidade) AS unidades FROM itens_pedido i JOIN produtos p ON i.produto_id = p.id JOIN categorias c ON p.categoria_id = c.id GROUP BY c.id, c.nome ORDER BY unidades DESC;
Os eletrônicos venderam 72 unidades; a jardinagem, só 13. (Repare que aqui o apelido p é de produtos: apelidos valem só dentro de cada consulta, e cada consulta escolhe os seus.)
5. O filtro no ON ou no WHERE?
Num INNER JOIN, uma condição extra pode ir no ON ou no WHERE, e o resultado é o mesmo. Os pedidos de clientes de Pernambuco, das duas formas:
SELECT p.id, c.nome FROM pedidos p JOIN clientes c ON p.cliente_id = c.id AND c.estado = 'PE';
SELECT p.id, c.nome FROM pedidos p JOIN clientes c ON p.cliente_id = c.id WHERE c.estado = 'PE';
Os mesmos 7 pedidos, da Juliana e do Bruno. Mesmo assim, o costume é: no ON, só a ligação entre as tabelas; no WHERE, os filtros. A consulta fica mais fácil de ler, e você evita uma surpresa: no LEFT JOIN, mudar um filtro do WHERE para o ON muda o resultado. A aula dele mostra por quê.
6. O atalho USING
Quando a coluna da ligação tem o mesmo nome nas duas tabelas, dá para trocar o ON a.x = b.x por USING (x) ("usando x"). Na Loja Aurora, as chaves estrangeiras têm nomes diferentes das primárias (cliente_id × id), então o USING não serve para elas. Mas clientes e fornecedores têm, os dois, a coluna cidade. Quais fornecedores ficam na mesma cidade de cada cliente VIP?
SELECT c.nome AS cliente, cidade, f.nome AS fornecedor FROM clientes c JOIN fornecedores f USING (cidade) WHERE c.vip = 1;
- JOIN fornecedores f USING (cidade)
- O mesmo que
ON c.cidade = f.cidade. Os parênteses são obrigatórios, e dentro deles pode ir mais de uma coluna, separadas por vírgula. - cidade (sem apelido)
- Com
USING, a coluna da ligação vira uma só, e pode ser citada sem a tabela na frente: não há ambiguidade, porque os dois lados têm o mesmo valor.
O José Ribamar aparece duas vezes porque Teresina tem dois fornecedores. Com SELECT *, o USING também mostra a coluna cidade uma vez só, em vez de duas:
SELECT * FROM clientes c JOIN fornecedores f USING (cidade) WHERE c.id = 1;
Conte as colunas: são 12, e só há uma cidade. (Já id, nome e estado aparecem duas vezes, porque não estão no USING.)
cliente_id
Há quem dê à chave primária o mesmo nome da chave estrangeira: clientes.cliente_id e pedidos.cliente_id. Aí todo JOIN pode ser escrito com USING (cliente_id). A Loja Aurora segue o outro costume, também muito comum: id em toda tabela e tabela_id na chave estrangeira. Os dois funcionam; o importante é escolher um e manter.
7. NATURAL JOIN: o atalho que dá errado
O NATURAL JOIN ("junção natural") vai além do USING: ele mesmo procura as colunas que têm o mesmo nome nas duas tabelas e liga por todas elas, sem você escrever condição nenhuma. Parece cômodo. Veja o que acontece com pedidos e clientes:
SELECT * FROM pedidos NATURAL JOIN clientes WHERE id IN (27, 28);
O único nome em comum entre pedidos e clientes é id. Então o NATURAL JOIN ligou pedidos.id = clientes.id: o pedido 27 com o cliente 27, o Paulo Sérgio Andrade, que nunca fez pedido! É o mesmo erro do "ON nas colunas erradas" da aula O que é um JOIN, só que agora sem você ter escrito o ON errado. A coluna cliente_id do resultado mostra a verdade: o pedido 27 é do cliente 1. E com outras tabelas é pior:
SELECT COUNT(*) FROM produtos NATURAL JOIN categorias; SELECT COUNT(*) FROM clientes NATURAL JOIN fornecedores;
Zero e zero. produtos e categorias têm em comum id e nome; o NATURAL JOIN exigiu que um produto tivesse o mesmo número e o mesmo nome de uma categoria, e nenhum tem. Clientes e fornecedores têm em comum id, nome, cidade e estado.
O maior perigo é o futuro: uma consulta com NATURAL JOIN que funciona hoje pode passar a dar outro resultado amanhã, se alguém acrescentar a uma das tabelas uma coluna com nome igual a uma da outra (uma data, um status, uma observacao). Ninguém mexeu na consulta, e ela mudou. A documentação do PostgreSQL faz o mesmo alerta: o USING é razoavelmente seguro, porque só junta as colunas que você listou; o NATURAL é bem mais arriscado, porque qualquer coluna nova com nome repetido entra na ligação. Saiba reconhecer o NATURAL JOIN, mas não use.
Formas diferentes de escrever a ligação
| Forma | Exemplo | Quando usar |
|---|---|---|
ON | JOIN clientes c ON p.cliente_id = c.id | Sempre que puder. Funciona com quaisquer nomes de coluna, e a ligação fica escrita, à vista. |
USING | JOIN fornecedores f USING (cidade) | Quando as colunas têm o mesmo nome nos dois lados. Não existe no SQL Server. |
NATURAL JOIN | pedidos NATURAL JOIN clientes | Evite. Liga por todos os nomes repetidos, inclusive os que não têm nada a ver (como id). |
Vírgula + WHERE | FROM pedidos p, clientes c WHERE p.cliente_id = c.id | Saiba ler (código antigo), mas escreva com JOIN … ON. |
Quando dá errado
ambiguous column name: nome com três tabelas
SELECT p.id, nome FROM pedidos p JOIN clientes c ON p.cliente_id = c.id JOIN funcionarios f ON p.funcionario_id = f.id;
A mensagem: ambiguous column name: nome. Com só pedidos e clientes, um nome sem apelido funcionaria, porque só clientes tem essa coluna. Ao entrar funcionarios, que também tem nome, a mesma consulta passou a dar erro. Quanto mais tabelas, mais nomes se repetem.
Como consertar: apelido na frente de toda coluna: c.nome ou f.nome. Se precisar dos dois, dê nomes diferentes: c.nome AS cliente, f.nome AS vendedor.
USING com uma coluna que só existe de um lado
SELECT * FROM pedidos JOIN clientes USING (cliente_id);
A mensagem: cannot join using column cliente_id - column not present in both tables, "não dá para juntar usando a coluna cliente_id: a coluna não está presente nas duas tabelas". pedidos tem cliente_id, mas em clientes a coluna se chama id.
Como consertar: quando os nomes são diferentes, use ON: JOIN clientes ON pedidos.cliente_id = clientes.id.
USING sem parênteses
SELECT * FROM clientes JOIN fornecedores USING cidade;
A mensagem: near "cidade": syntax error. Os parênteses do USING são obrigatórios, mesmo com uma coluna só.
Como consertar: USING (cidade).
NATURAL JOIN com ON
SELECT * FROM pedidos NATURAL JOIN clientes ON pedidos.cliente_id = clientes.id;
A mensagem: a NATURAL join may not have an ON or USING clause, "um NATURAL join não pode ter ON nem USING". O NATURAL já escolhe a condição sozinho, então não aceita outra.
Como consertar: tire o NATURAL e fique com o JOIN … ON, que é o que você queria.
ON de um dos JOINs
SELECT COUNT(*) FROM pedidos p JOIN clientes c ON p.cliente_id = c.id JOIN funcionarios f;
O que acontece: nenhum erro e 720 linhas. Os 90 pares pedido-cliente foram combinados com cada um dos 8 funcionários (90 × 8 = 720), como num CROSS JOIN. Cada pedido aparece 8 vezes, uma com cada funcionário, inclusive com quem não o vendeu.
Como consertar: todo JOIN precisa do seu ON: JOIN funcionarios f ON p.funcionario_id = f.id. Conte as tabelas e conte os ON: com 3 tabelas, são 2 ligações.
INNER JOIN … ONé igual em todos os bancos.USINGeNATURAL JOINexistem no MySQL e no PostgreSQL. O SQL Server não tem nenhum dos dois: a gramática doJOINdele só aceitaON. Mais um motivo para preferir oON, que funciona em todo lugar.- Com
SELECT *eUSING, o PostgreSQL e o MySQL também mostram a coluna da ligação uma vez só, mas a põem em primeiro lugar, antes de todas as outras. O SQLite, como você viu, a deixa na posição que ela tem na primeira tabela.
Referência rápida
| Escrita | O que faz |
|---|---|
a INNER JOIN b ON a.x = b.y | Só os pares de linhas que obedecem à condição |
a JOIN b ON … | O mesmo que INNER JOIN |
a JOIN b ON … JOIN c ON … | Três tabelas: cada JOIN com o seu ON, processados da esquerda para a direita |
a JOIN b USING (x) | O mesmo que ON a.x = b.x; a coluna x aparece uma vez só |
a NATURAL JOIN b | Liga por todas as colunas de mesmo nome (evite) |
JOIN … GROUP BY c.id, c.nome | Agrupa e mostra nomes vindos da outra tabela |
| Mensagem do SQLite | Quer dizer |
|---|---|
ambiguous column name: nome | Mais de uma tabela do JOIN tem essa coluna |
cannot join using column X - column not present in both tables | O USING pede uma coluna com o mesmo nome dos dois lados |
near "cidade": syntax error (no USING) | Faltaram os parênteses: USING (cidade) |
a NATURAL join may not have an ON or USING clause | NATURAL não aceita condição |
| (nenhuma, mas linhas multiplicadas) | Um dos JOINs ficou sem ON |
Exercícios
1. A segunda ligação
Complete a consulta que mostra o cliente e o vendedor de cada pedido:
2. O atalho
Reescreva ON c.cidade = f.cidade com o atalho:
3. Uma consulta junta 4 tabelas com INNER JOIN. Quantos ON ela precisa ter?
4. Por que SELECT * FROM pedidos NATURAL JOIN clientes dá um resultado errado na Loja Aurora?
5. Num INNER JOIN, mover c.estado = 'PE' do WHERE para o ON…
Missão final: os pedidos de cada vendedor
Missão: a gerente quer ver quantos pedidos cada vendedor atendeu, com o nome do vendedor (coluna vendedor) e a contagem (coluna pedidos). A consulta atual só tem o número do funcionário. Junte com funcionarios e agrupe pelo vendedor.
SELECT funcionario_id, COUNT(*) AS pedidos FROM pedidos GROUP BY funcionario_id;
SELECT f.nome AS vendedor, COUNT(*) AS pedidos FROM pedidos p JOIN funcionarios f ON p.funcionario_id = f.id GROUP BY f.id, f.nome;
Saíram 6 vendedores, com 15 pedidos cada. A loja tem 8 funcionários: a gerente Marta e o estoquista Davi não atendem pedidos, e por isso o INNER JOIN os deixou de fora. Para que eles aparecessem com 0, seria preciso um LEFT JOIN, o assunto da próxima aula.
Resumo
INNER JOIN(ou sóJOIN) guarda só os pares que combinam; quem não tem par fica de fora.- Para três ou mais tabelas, encadeie
JOIN … ON, um por ligação. Com N tabelas, são N − 1 condiçõesON. JOINcomGROUP BYpermite somar e contar mostrando nomes. Agrupe peloide pelo nome.- No
INNER JOIN, um filtro noONou noWHEREdá o mesmo resultado; por costume, a ligação vai noONe os filtros noWHERE. USING (x)é um atalho paraON a.x = b.xquando os nomes são iguais.NATURAL JOINliga por todos os nomes repetidos: evite.- Num JOIN, ponha a tabela ou o apelido na frente de toda coluna, para não cair no
ambiguous column name.