Chave estrangeira é um ponteiro entre tabelas
Uma chave estrangeira (foreign key no SQLite) é uma coluna de uma tabela cujo valor precisa corresponder a uma linha de outra tabela. É assim que o banco relacional diz "este registro de posts pertence àquele registro de authors" sem precisar duplicar o nome e o e-mail do autor em cada post.
Veja o exemplo mais simples possível — uma tabela pai e uma tabela filha ligadas por uma FK:
author_id INTEGER REFERENCES authors(id) é a declaração completa da chave estrangeira. Ela diz o seguinte: esta coluna guarda um id vindo da tabela authors. Agora o banco sabe que as duas tabelas estão relacionadas e — se a verificação estiver ligada — vai recusar inserts apontando para autores que não existem.
Foreign keys vêm desativadas por padrão
Esse é o fato mais importante sobre chave estrangeira no SQLite, e pega todo mundo de surpresa: o SQLite até entende a cláusula REFERENCES, mas não aplica a restrição a menos que você peça. O motivo é compatibilidade histórica — bancos antigos foram criados antes desse recurso existir.
Veja o que acontece sem a verificação ligada:
A linha órfã entrou sem reclamação. Pra ter de fato a proteção que você espera de uma chave estrangeira, execute PRAGMA foreign_keys = ON; no início de cada conexão:
Agora o insert falha com FOREIGN KEY constraint failed. O pragma vale por conexão, e não por banco — essa configuração não fica salva no arquivo. Toda aplicação, toda sessão da CLI, todo fixture de teste precisa setar de novo. Em código de produção, o normal é rodar PRAGMA foreign_keys = ON; logo depois de abrir a conexão.
O que a cláusula REFERENCES realmente exige
A coluna referenciada precisa ser PRIMARY KEY ou ter uma constraint UNIQUE. É assim que o SQLite garante que a busca seja inequívoca. Os tipos também devem ser compatíveis — o SQLite é flexível com tipos, mas misturar tudo é pedir para ter dor de cabeça.
Dá para declarar a foreign key de duas formas. Inline, direto na coluna:
Ou como uma constraint a nível de tabela, o que é obrigatório quando a foreign key abrange várias colunas:
As duas formas geram exatamente a mesma constraint. Use a que ficar mais legível pra cada tabela.
ON DELETE: o destino das linhas filhas
Quando você apaga uma linha pai, o SQLite precisa decidir o que fazer com as linhas filhas que apontam pra ela. Essa política é definida com ON DELETE:
Apagar a Ada apagou os dois posts dela junto. As opções são:
CASCADE— apaga os filhos também. Bom pra dados que "pertencem" a alguém, tipo posts de um autor ou itens de um pedido.SET NULL— coloca NULL na coluna da FK. Útil quando os filhos devem continuar existindo mesmo sem o pai (por exemplo, comentários de um usuário apagado viram anônimos).SET DEFAULT— define a coluna da FK com o valor padrão declarado.RESTRICT— barra a exclusão se existir qualquer filho. Falha na hora, no momento do comando.NO ACTION— é o padrão. Na prática funciona quase igual aoRESTRICT(a checagem é adiada pro commit, mas o resultado é o mesmo: você não consegue deixar filhos órfãos).
O ON UPDATE segue a mesma lógica quando a chave do pai muda, embora atualizar chave primária seja coisa rara.
O que significa "foreign key constraint failed" no SQLite
Esse erro aparece em duas situações. A primeira é quando você insere ou atualiza um filho com um valor que não tem pai correspondente:
sqlite> INSERT INTO posts (title, author_id) VALUES ('Stray', 999);
Runtime error: FOREIGN KEY constraint failed
Ou o autor 999 não existe, ou você trocou os tipos das colunas. Insira o pai primeiro, ou corrija o valor.
Segundo: apagar (ou atualizar) um pai que ainda tem filhos, quando a FK usa RESTRICT ou NO ACTION:
sqlite> DELETE FROM authors WHERE id = 1;
Runtime error: FOREIGN KEY constraint failed
Ou você apaga os filhos primeiro, ou troca a FK para ON DELETE CASCADE/SET NULL se o que você realmente quer é cascateamento.
Existe também uma prima menos famosa: FOREIGN KEY mismatch. Esse erro aparece quando a coluna referenciada não é chave primária nem tem UNIQUE, ou quando a quantidade de colunas não bate. É um erro de schema, não de dados.
Adicionando foreign key em tabelas que já existem no SQLite
O ALTER TABLE do SQLite é bem limitado — dá para adicionar uma coluna nova já com foreign key, mas não dá para grudar uma foreign key numa coluna que já existe. A saída clássica é a famosa dança do renomear-e-reconstruir:
O padrão é o seguinte: desligue a verificação, crie a nova tabela com as constraints que você quer, copie os dados, dropa a tabela antiga e renomeia. O BEGIN/COMMIT garante que tudo aconteça de forma atômica. No final, religue a verificação — o SQLite vai validar todas as linhas existentes contra as novas constraints. Se algum dado estiver inválido, a transação já foi commitada, então confira antes se isso te preocupa.
Depois da migração, rode PRAGMA foreign_key_check; para confirmar que não sobrou nenhuma linha órfã.
Um schema realista
Juntando tudo num exemplo prático — um schema mínimo de blog com tabelas pai, filho e uma tabela de junção para o relacionamento muitos-para-muitos com tags:
Três detalhes para reparar. O author_id é NOT NULL — todo post precisa ter um autor. A FK de posts → authors faz cascade, então apagar um autor remove junto todos os posts dele. Já a tabela de junção post_tags faz cascade dos dois lados, então remover um post ou uma tag limpa automaticamente as linhas de ligação.
Boas práticas que evitam dor de cabeça depois
- Ative
PRAGMA foreign_keys = ON;em toda conexão. Coloque isso na rotina que abre o banco da sua aplicação — não dá pra contar com a memória. - Crie um índice na coluna da FK. O SQLite indexa a chave da tabela pai automaticamente, mas não a da filha, e o
ON DELETE CASCADEfaz uma busca na filha toda vez que você apaga um registro pai. - Escolha o
ON DELETEcom cuidado. O padrão (NO ACTION) é seguro, mas significa que você vai esbarrar em "foreign key constraint failed" toda vez que tentar limpar dados. Decida o que deve acontecer e deixe explícito. - Rode
PRAGMA foreign_key_check;depois de migrations ou importações em massa para pegar registros órfãos antes que virem bug.
A seguir: INNER JOIN
A chave estrangeira descreve o relacionamento entre tabelas; os joins são a forma de fazer consultas que cruzam essas tabelas. A próxima página cobre INNER JOIN — combinando linhas de tabelas relacionadas e trazendo de volta exatamente as colunas que você quer de cada uma.
Perguntas frequentes
Como criar uma chave estrangeira no SQLite?
Adicione uma cláusula REFERENCES outra_tabela(coluna) na definição da coluna dentro do CREATE TABLE. Por exemplo, author_id INTEGER REFERENCES authors(id) faz com que author_id aponte para uma linha da tabela authors. A coluna referenciada precisa ser PRIMARY KEY ou ter uma restrição UNIQUE.
Por que o SQLite não está validando minhas chaves estrangeiras?
O SQLite até reconhece a declaração da chave estrangeira, mas não impõe a restrição a menos que você ative essa verificação. Execute PRAGMA foreign_keys = ON; logo no início de cada conexão. Essa configuração vale por conexão e não fica salva no banco — então tanto a sua biblioteca quanto a CLI precisam ativar isso toda vez que abrirem o banco.
O que faz o ON DELETE CASCADE no SQLite?
O ON DELETE CASCADE manda o SQLite apagar automaticamente as linhas filhas quando a linha pai é excluída. As outras opções são RESTRICT (bloqueia o delete), SET NULL (zera a coluna FK), SET DEFAULT e NO ACTION (o padrão — na prática, igual ao RESTRICT). A escolha depende de fazer sentido ou não manter as linhas filhas sem o pai.
Como resolver o erro 'foreign key constraint failed' no SQLite?
Esse erro aparece quando você tenta inserir ou atualizar uma linha cujo valor de chave estrangeira não bate com nenhuma linha da tabela referenciada, ou quando tenta apagar um pai que ainda tem filhos. Confira se a linha referenciada realmente existe antes do INSERT, ou configure ON DELETE CASCADE se quiser que os filhos sejam removidos junto.