SQL Tabelas que se Relacionam
Abra a tabela de pedidos da Loja Aurora e olhe o pedido 7: lá está cliente_id = 7 e funcionario_id = 2. Não tem nome de cliente, nem de vendedor, só números. Quem é o cliente 7? A resposta está em outra tabela, a de clientes, na linha cujo id é 7. Esse jeito de uma tabela "apontar" para outra é o que dá o nome de banco de dados relacional ao SQLite, ao MySQL, ao PostgreSQL e aos outros. Nesta aula você vai entender essas ligações antes de aprender, na próxima, o comando que as segue sozinho: o JOIN.
- Por que um banco guarda clientes, produtos e pedidos em tabelas separadas, e não numa planilha só.
- O que é chave primária (o "número de identidade" de cada linha) e chave estrangeira (a coluna que aponta para outra tabela).
- Como seguir uma ligação "à mão", com duas consultas.
- Os tipos de relação: um-para-muitos, muitos-para-muitos (com tabela no meio), um-para-um e a tabela que aponta para ela mesma.
- O mapa completo da Loja Aurora, e como ver as ligações de uma tabela no terminal.
- O que o SQLite faz quando alguém tenta apontar para uma linha que não existe: o erro
FOREIGN KEY constraint failed.
Os exemplos rodam de verdade no seu navegador, no banco da Loja Aurora, e o quadro "Resultado" é o SQLite respondendo.
A ideia: por que não uma tabela só?
Imagine que a Ana anotasse os pedidos numa planilha só, com tudo na mesma linha. O cliente José Ribamar Silva fez 4 pedidos, então os dados dele apareceriam 4 vezes:
| pedido | data | cliente | email do cliente | cidade |
|---|---|---|---|---|
| 1 | 2024-01-05 | José Ribamar Silva | jose.ribamar@exemplo.com.br | Teresina |
| 27 | 2024-10-29 | José Ribamar Silva | jose.ribamar@exemplo.com.br | Teresina |
| 53 | 2025-09-03 | José Ribamar Silva | jose.ribamar@exemplo.com.br | Teresina |
| 79 | 2026-06-29 | José Ribamar Silva | jose.ribamar@exemplo.com.br | Teresina |
Parece inofensivo, mas traz três problemas:
- Repetição. O nome, o email e a cidade estão escritos 4 vezes. Com 1.000 pedidos, seriam 1.000 cópias de dados de cliente.
- Mudar dá trabalho. Se o José trocar de email, a Ana tem de corrigir 4 linhas. Se esquecer uma, a planilha passa a ter dois emails para a mesma pessoa, e ninguém sabe qual é o certo.
- Quem nunca comprou some. Um cliente que se cadastrou e ainda não fez pedido não tem linha nenhuma para aparecer.
A solução do banco relacional é guardar cada coisa num lugar só: os dados do cliente ficam uma vez só na tabela clientes, e cada pedido guarda apenas o número do cliente. Funciona como o prontuário de um posto de saúde: a ficha da paciente fica no arquivo, com um número; cada consulta anotada no caderno diz só "paciente nº 7". Para saber quem é, você vai ao arquivo e procura a ficha 7.
1. A chave primária: o número de cada linha
Toda tabela da Loja Aurora tem uma coluna id, um número que nunca se repete dentro da tabela. É a chave primária (primary key): ela identifica cada linha sem confusão, mesmo que dois clientes tenham o mesmo nome.
SELECT id, nome, cidade FROM clientes WHERE id = 7;
Existe exatamente uma linha com id = 7, e sempre vai existir só uma. Como criar uma chave primária numa tabela nova é assunto da aula PRIMARY KEY.
2. A chave estrangeira: a coluna que aponta
Agora o pedido 7:
SELECT id, cliente_id, funcionario_id, data FROM pedidos WHERE id = 7;
A coluna cliente_id guarda um número que é o id de alguém na tabela clientes. Uma coluna assim, que guarda a chave primária de outra tabela, se chama chave estrangeira (foreign key): "estrangeira" porque a chave é de fora, pertence à outra tabela. Veja a ligação lado a lado:
| pedidos | ||
|---|---|---|
| id | cliente_id | data |
| 6 | 6 | 2024-02-26 |
| 7 | 7 | 2024-03-08 |
| 8 | 8 | 2024-03-15 |
| clientes | ||
|---|---|---|
| id | nome | cidade |
| 6 | Rafael Monteiro | São Paulo |
| 7 | Luís Otávio Brito | Parnaíba |
| 8 | Fernanda Carvalho | Fortaleza |
- pedidos.id
- A chave primária do pedido: identifica o pedido 7.
- pedidos.cliente_id
- A chave estrangeira: o número 7 aqui quer dizer "o cliente cujo
idé 7". O nome segue um costume muito usado: nome da tabela no singular +_id. - clientes.id
- A chave primária do cliente, para onde a chave estrangeira aponta.
Repare que os dois 7 são coincidência: o pedido 7 foi do cliente 7, mas o pedido 27 também é do cliente 1. O que importa é o valor dentro de cliente_id. A escrita tabela.coluna (pedidos.cliente_id) é como se diz em SQL "a coluna tal daquela tabela"; você vai usá-la muito nas próximas aulas.
3. Seguindo a ligação à mão
Sem nenhum comando novo, dá para descobrir quem fez o pedido 7 e quem o atendeu: basta pegar os números do pedido e procurar nas outras tabelas. A consulta de cima mostrou cliente_id = 7 e funcionario_id = 2. Agora as duas fichas:
SELECT id, nome, cidade FROM clientes WHERE id = 7; SELECT id, nome, cargo FROM funcionarios WHERE id = 2;
O pedido 7 foi do Luís Otávio Brito, de Parnaíba, e quem atendeu foi o vendedor Caio Teles. Funciona, mas foram três consultas para responder uma pergunta, e a Ana teve de copiar os números de uma para a outra. Imagine fazer isso para os 90 pedidos. É exatamente esse trabalho que o JOIN faz sozinho, numa consulta só: a próxima aula, O que é um JOIN, começa daqui.
Experimente: descubra o nome do funcionário que atendeu o pedido 50. Rode a consulta como está, veja o funcionario_id do pedido e depois troque-a por uma consulta na tabela funcionarios que mostre o nome dele.
SELECT id, cliente_id, funcionario_id, data FROM pedidos WHERE id = 50;
SELECT id, nome, cargo FROM funcionarios WHERE id = 3;
4. Um-para-muitos: um cliente, vários pedidos
O José Ribamar Silva (cliente 1) aparece em quantos pedidos?
SELECT id, cliente_id, data FROM pedidos WHERE cliente_id = 1;
Quatro. Um cliente pode ter muitos pedidos, mas cada pedido tem um cliente só (a coluna cliente_id guarda um número, não uma lista). Essa é a relação um-para-muitos, a mais comum de todas. A chave estrangeira fica sempre do lado "muitos": é o pedido que aponta para o cliente, nunca o contrário.
Na Loja Aurora há várias relações assim:
| Um… | …tem muitos | Chave estrangeira |
|---|---|---|
| cliente | pedidos | pedidos.cliente_id → clientes.id |
| funcionário | pedidos atendidos | pedidos.funcionario_id → funcionarios.id |
| categoria | produtos | produtos.categoria_id → categorias.id |
| fornecedor | produtos | produtos.fornecedor_id → fornecedores.id |
Com o que você aprendeu em GROUP BY, dá para contar os pedidos de cada cliente pela chave estrangeira:
SELECT cliente_id, COUNT(*) AS pedidos FROM pedidos GROUP BY cliente_id;
São 26 linhas, mas a loja tem 30 clientes. Quem faltou? Os clientes 27, 28, 29 e 30 (Paulo Sérgio Andrade, Tainá Soares, Elias Furtado e Rosângela Dias) se cadastraram e nunca fizeram pedido. Como eles não aparecem em nenhuma linha de pedidos, uma consulta só na tabela de pedidos nunca vai mostrá-los. Mostrar "todos os clientes, inclusive quem nunca comprou" é o trabalho do LEFT JOIN. Guarde esses quatro nomes: eles vão aparecer de novo.
5. Muitos-para-muitos: pedidos e produtos
Um pedido pode ter vários produtos. E um produto pode estar em vários pedidos. Os dois lados são "muitos". Onde fica a chave estrangeira, então? Não dá para pôr um produto_id em pedidos (seria um produto só por pedido), nem um pedido_id em produtos (seria um pedido só por produto).
A solução é uma terceira tabela, no meio, com uma linha para cada par "este produto está neste pedido". Na Loja Aurora ela se chama itens_pedido, e tem duas chaves estrangeiras:
SELECT * FROM itens_pedido WHERE pedido_id = 1;
- pedido_id
- Chave estrangeira para
pedidos.id: o pedido a que o item pertence. - produto_id
- Chave estrangeira para
produtos.id: o produto comprado. - quantidade, preco_unitario
- Dados que não são nem do pedido nem do produto, mas do par: quantas unidades daquele produto entraram naquele pedido, e por quanto cada uma.
O pedido 1 tem 3 itens: os produtos 1, 2 e 3 (caderno, caneta e lápis). E o caminho inverso, em quais pedidos o produto 1 aparece:
SELECT pedido_id, quantidade FROM itens_pedido WHERE produto_id = 1;
O Caderno Pautado foi vendido em 6 pedidos. A relação muitos-para-muitos é, na verdade, duas relações um-para-muitos: um pedido tem muitos itens, e um produto aparece em muitos itens. A aula Relação Muitos-para-Muitos mostra como desenhar uma dessas do zero.
A loja tem 45 produtos, mas só 42 aparecem em itens_pedido. Os produtos 43, 44 e 45 (Vaso de Cerâmica Médio, Tesoura de Poda e Luvas de Jardinagem) nunca foram vendidos. É o mesmo caso dos clientes sem pedido, do outro lado do banco, e você vai encontrá-los com o LEFT JOIN.
6. A tabela que aponta para ela mesma
Na tabela funcionarios, a coluna chefe_id guarda o id de outro funcionário, o chefe:
SELECT id, nome, cargo, chefe_id FROM funcionarios;
A gerente Marta Lustosa (1) não tem chefe: o chefe_id dela é NULL, "sem valor" (aula O Valor NULL). Ela chefia os funcionários 2, 3, 4 e 5; a supervisora Luana Castelo (5) chefia o 6, o 7 e o 8. A chave estrangeira chefe_id aponta para a chave primária da própria tabela. Para juntar cada funcionário com o nome do chefe, existe o Self Join.
7. Um-para-um
Existe ainda a relação um-para-um: cada linha de uma tabela corresponde a no máximo uma linha da outra. Por exemplo, se a loja guardasse os dados bancários de cada funcionário numa tabela separada (para pouca gente ter acesso), cada funcionário teria uma linha só lá. A Loja Aurora não tem nenhuma relação assim; ela é bem menos comum que as outras, e costuma existir por motivo de organização ou de segurança.
| Tipo | Exemplo na Loja Aurora | Onde fica a chave estrangeira |
|---|---|---|
| Um-para-muitos | um cliente, muitos pedidos | no lado "muitos" (pedidos.cliente_id) |
| Muitos-para-muitos | pedidos e produtos | numa tabela no meio, com duas chaves (itens_pedido) |
| Com ela mesma | funcionário e chefe | na própria tabela (funcionarios.chefe_id) |
| Um-para-um | (não há) | em qualquer um dos lados, sem repetir |
8. O mapa da Loja Aurora
Juntando tudo, este é o mapa das ligações do banco. Cada caixa é uma tabela (com as colunas principais), e cada seta sai de uma chave estrangeira e aponta para a chave primária que ela referencia. Um desenho assim se chama diagrama de entidade-relacionamento (DER); a aula Diagramas (DER) ensina a desenhar um.
PK marca a chave primária (primary key) e FK, a chave estrangeira (foreign key). Siga uma seta qualquer: pedidos.cliente_id desce até clientes.id; itens_pedido.produto_id volta até produtos.id; e funcionarios.chefe_id dá a volta e aponta para a própria tabela.
Vendo as ligações de uma tabela
Quem criou a tabela deixou as ligações registradas: a palavra REFERENCES ("faz referência a") na definição de cada chave estrangeira. No terminal do SQLite, o comando de ponto .schema mostra essa definição (aula Comandos de Ponto):
$ sqlite3 loja.db
SQLite version 3.50.4 2025-07-30 19:33:53
Enter ".help" for usage hints.
sqlite> .schema pedidos
CREATE TABLE pedidos (
id INTEGER PRIMARY KEY,
cliente_id INTEGER NOT NULL REFERENCES clientes(id),
funcionario_id INTEGER NOT NULL REFERENCES funcionarios(id),
data TEXT NOT NULL,
entregue_em TEXT,
frete REAL NOT NULL DEFAULT 0
);
A linha cliente_id INTEGER NOT NULL REFERENCES clientes(id) se lê: "a coluna cliente_id guarda um número inteiro, não pode ficar vazia e faz referência à coluna id da tabela clientes". No editor do guia não existem comandos de ponto, mas o SQLite tem um comando SQL que lista as chaves estrangeiras de uma tabela, o PRAGMA foreign_key_list (os PRAGMAs têm aula própria: PRAGMAs Úteis):
PRAGMA foreign_key_list(pedidos);
Cada linha é uma chave estrangeira: a coluna from ("de") é a coluna de pedidos, e table e to ("para") dizem para qual tabela e coluna ela aponta. As outras colunas tratam do que fazer quando a linha apontada muda ou é apagada, assunto da aula FOREIGN KEY.
Experimente: troque o nome da tabela para ver as chaves estrangeiras de produtos. Para quais tabelas ela aponta?
PRAGMA foreign_key_list(pedidos);
PRAGMA foreign_key_list(produtos);
Formas diferentes de chegar ao mesmo dado
Para responder "qual o nome do cliente do pedido 7?", existem três caminhos. Você já conhece o primeiro; os outros dois são os próximos assuntos do guia:
| Forma | Como é | Quando usar |
|---|---|---|
| Seguir à mão | Uma consulta em pedidos, copiar o cliente_id, outra consulta em clientes | Para entender as ligações, ou conferir um caso só |
| JOIN | Uma consulta só, que junta as linhas das duas tabelas pela ligação | O jeito normal: listas, relatórios, qualquer coisa com mais de um caso |
| Subconsulta | Uma consulta dentro da outra: WHERE id = (SELECT cliente_id FROM pedidos WHERE id = 7) | Quando você só precisa filtrar por algo da outra tabela |
Quando dá errado
A loja tem clientes de 1 a 30. O que acontece se alguém registrar um pedido para o cliente 99?
INSERT INTO pedidos (cliente_id, funcionario_id, data, frete) VALUES (99, 2, '2026-10-18', 10.0);
A mensagem: FOREIGN KEY constraint failed, "a restrição de chave estrangeira falhou". O SQLite conferiu a ligação, não achou nenhum cliente com id = 99 e recusou o pedido. Sem essa conferência, o banco ganharia um pedido órfão: um pedido de ninguém, que some de qualquer relatório por cliente.
Como consertar: use o id de um cliente que existe ou, se for um cliente novo, cadastre-o antes em clientes (aula INSERT INTO) e só depois o pedido.
DELETE FROM clientes WHERE id = 1;
A mensagem: a mesma, FOREIGN KEY constraint failed. O José Ribamar Silva tem 4 pedidos apontando para ele. Se a ficha dele sumisse, esses 4 pedidos ficariam órfãos. O SQLite protege a ligação dos dois lados: nem se cria uma seta para o nada, nem se apaga a linha para onde setas apontam.
No editor, cada Rodar parte do banco limpo, então pode testar sem medo.
No editor do guia, a conferência está ligada (o banco da Loja Aurora começa com PRAGMA foreign_keys = ON). No programa sqlite3, porém, ela vem desligada a cada vez que você abre o banco. A documentação do SQLite explica: as chaves estrangeiras são desligadas por padrão, por compatibilidade com programas antigos, e precisam ser ligadas em cada conexão. Veja a Ana tentando o mesmo pedido no terminal:
$ sqlite3 loja.db SQLite version 3.50.4 2025-07-30 19:33:53 Enter ".help" for usage hints. sqlite> PRAGMA foreign_keys; 0 sqlite> INSERT INTO pedidos (cliente_id, funcionario_id, data, frete) VALUES (99, 2, '2026-10-18', 10.0); sqlite> PRAGMA foreign_key_check; pedidos|91|clientes|1 sqlite> PRAGMA foreign_keys = ON; sqlite> INSERT INTO pedidos (cliente_id, funcionario_id, data, frete) VALUES (98, 2, '2026-10-18', 10.0); Runtime error: FOREIGN KEY constraint failed (19) sqlite> .quit
- PRAGMA foreign_keys;
- Pergunta se a conferência está ligada:
0= desligada. - INSERT … (99, …)
- Sem conferência, o pedido para o cliente 99 entrou calado. O banco agora tem um órfão.
- PRAGMA foreign_key_check;
- Procura órfãos: a resposta diz que, na tabela
pedidos, a linha 91 aponta para umclientesque não existe. - PRAGMA foreign_keys = ON;
- Liga a conferência (só até fechar o
sqlite3). - Runtime error: FOREIGN KEY constraint failed (19)
- Com ela ligada, o pedido para o cliente 98 foi recusado. No terminal a mensagem vem com o prefixo
Runtime error:("erro durante a execução") e o código do erro, 19.
Como evitar: rode PRAGMA foreign_keys = ON; logo depois de abrir o banco no terminal. Para limpar o órfão que entrou, apague-o: DELETE FROM pedidos WHERE id = 91;.
PostgreSQL e SQL Server conferem as chaves estrangeiras sempre, sem precisar ligar nada; no MySQL, o motor de tabelas padrão (InnoDB) também. Essa conferência desligada por padrão é uma particularidade do SQLite. As mensagens de erro mudam de banco para banco, mas a ideia é a mesma: o banco não deixa uma chave estrangeira apontar para o nada.
Referência rápida
| Termo | O que é | Na Loja Aurora |
|---|---|---|
| Chave primária (PK) | Coluna que identifica cada linha; nunca se repete | clientes.id, pedidos.id… |
| Chave estrangeira (FK) | Coluna que guarda a chave primária de outra tabela | pedidos.cliente_id → clientes.id |
| Um-para-muitos | Uma linha de A, várias de B; a FK fica em B | cliente → pedidos; categoria → produtos |
| Muitos-para-muitos | Tabela no meio com duas FKs | itens_pedido (pedidos ↔ produtos) |
| Auto-relacionamento | A FK aponta para a própria tabela | funcionarios.chefe_id |
REFERENCES tabela(coluna) | Como a FK aparece na definição da tabela | .schema pedidos no terminal |
PRAGMA foreign_key_list(t) | Lista as FKs da tabela t | funciona no editor |
PRAGMA foreign_keys = ON | Liga a conferência das FKs (no terminal vem desligada) | já ligada no editor |
FOREIGN KEY constraint failed | Tentou criar uma ligação para o nada, ou apagar uma linha que tem ligações | pedido para o cliente 99 |
Exercícios
1. Qual é a chave estrangeira?
Na tabela produtos, qual coluna aponta para a tabela categorias?
2. A tabela do meio
Qual tabela da Loja Aurora liga pedidos e produtos numa relação muitos-para-muitos?
3. Na relação "um cliente tem muitos pedidos", onde fica a chave estrangeira?
4. Por que a consulta SELECT cliente_id, COUNT(*) FROM pedidos GROUP BY cliente_id; mostra 26 clientes, se a loja tem 30?
5. No editor, o que acontece com DELETE FROM clientes WHERE id = 1;?
Missão final: os produtos do pedido 3
Missão: a consulta abaixo mostra os itens do pedido 3, mas só com os números dos produtos. Rode-a, anote os três produto_id e troque-a por uma consulta na tabela produtos que mostre o nome e o preço desses três produtos (dica: WHERE id IN (…), da aula IN).
SELECT pedido_id, produto_id, quantidade FROM itens_pedido WHERE pedido_id = 3;
SELECT id, nome, preco FROM produtos WHERE id IN (7, 8, 9);
Deu certo, mas você precisou de duas consultas e copiou números de uma para a outra. Na próxima aula, O que é um JOIN, o SQL faz essa ponte sozinho.
Resumo
- Um banco relacional guarda cada coisa num lugar só (clientes numa tabela, pedidos em outra) e liga as tabelas por números, em vez de repetir dados.
- A chave primária (
id) identifica cada linha. A chave estrangeira (cliente_id) guarda a chave primária de outra tabela: ela "aponta" para lá. - Um-para-muitos: a chave estrangeira fica no lado "muitos". Muitos-para-muitos: uma tabela no meio com duas chaves estrangeiras (
itens_pedido). Uma tabela pode apontar para ela mesma (chefe_id). - Na Loja Aurora, 4 clientes nunca pediram (27 a 30) e 3 produtos nunca venderam (43 a 45): eles não aparecem quando você olha só a tabela do outro lado.
- O SQLite recusa ligações para o nada com
FOREIGN KEY constraint failed, mas só se a conferência estiver ligada: no editor ela está; no terminal, ligue comPRAGMA foreign_keys = ON;.