UNIQUE: nada de valores duplicados
A constraint UNIQUE no SQLite é a forma de avisar ao banco que os valores de uma coluna (ou de um conjunto de colunas) não podem se repetir entre as linhas. É com ela que você garante regras do tipo "dois usuários não podem ter o mesmo e-mail" ou "um código de produto aparece no máximo uma vez."
O terceiro INSERT falha com falha na restrição UNIQUE: users.email. O SQLite verifica a restrição a cada escrita e rejeita qualquer coisa que gere duplicata. As duas primeiras linhas são gravadas; a terceira nunca entra.
Nos bastidores, o UNIQUE é implementado como um índice único — a mesma estrutura de dados que o SQLite usa para buscas rápidas — então a checagem sai barato e a coluna já fica indexada automaticamente.
Sintaxe por coluna vs por tabela
Dá para escrever UNIQUE de duas formas: inline, ao lado da coluna, ou como uma cláusula separada no final da definição da tabela:
Para uma única coluna, as duas formas são equivalentes — use a que ficar mais legível. Já a versão a nível de tabela vira indispensável no momento em que você precisa garantir unicidade combinando mais de uma coluna.
UNIQUE composto: chave única em múltiplas colunas no SQLite
Tem situações em que nenhuma coluna sozinha é única, mas a combinação delas precisa ser. Um usuário pode se inscrever em vários cursos, e um curso pode ter vários usuários — mas o mesmo par (user_id, course_id) não pode aparecer duas vezes:
A constraint vale para o par, não para nenhuma das colunas isoladamente. O usuário 1 pode se matricular em vários cursos, o curso 100 pode ter vários usuários — só não pode repetir a mesma combinação.
Esse é o padrão clássico para tabelas de junção em relacionamentos muitos-para-muitos.
UNIQUE vs PRIMARY KEY no SQLite
Os nomes parecem próximos e os conceitos têm a ver, mas não são a mesma coisa:
- Cada tabela tem no máximo uma
PRIMARY KEY, mas pode ter várias constraintsUNIQUE. - A
PRIMARY KEYé a identidade da linha — é para ela que as foreign keys apontam, e é dela que orowidvira apelido. - Já o
UNIQUEsó diz "esse valor (ou combinação) não se repete". - Em uma tabela comum, uma coluna
UNIQUEaceita valoresNULL; umaPRIMARY KEYnão (existe uma exceção histórica que vamos deixar de lado).
Um formato bem comum:
id é o que o resto do banco referencia. Já email e username são únicos porque a aplicação exige isso, não porque sejam a identidade. Se o usuário trocar de e-mail, o id continua o mesmo — é justamente para isso que separamos os dois.
A pegadinha do NULL
Essa aqui pega quase todo mundo de primeira viagem. Uma coluna UNIQUE no SQLite aceita quantos valores NULL você quiser:
Três NULLs, sem problema. Dois 'ada@example.com', aí sim temos conflito.
O motivo: o SQL trata NULL como "desconhecido", e dois valores desconhecidos não são considerados iguais — então a checagem de unicidade não tem como afirmar que são duplicados. Se você precisa permitir no máximo um NULL, a solução mais limpa é NOT NULL UNIQUE. Se NULLs são válidos mas você quer apenas um por combinação das outras colunas, parta para um índice único parcial (assunto do capítulo de índices, mais adiante).
Lidando com conflitos: ON CONFLICT
Por padrão, uma violação de UNIQUE aborta o comando. Mas, em algumas situações, você quer um comportamento diferente — substituir a linha existente, ignorar a nova ou atualizar colunas específicas. O SQLite oferece duas formas de pedir isso.
A primeira fica embutida na própria constraint, com ON CONFLICT:
Na segunda vez que theme é inserido, a linha existente é apagada e a nova ocupa o lugar dela. Outras opções são IGNORE (ignora silenciosamente), ABORT (o padrão), FAIL e ROLLBACK.
A segunda forma é definida por instrução, usando a sintaxe de upsert — geralmente mais flexível, já que permite atualizar colunas específicas:
O primeiro INSERT cria a linha. Os dois seguintes esbarram na constraint UNIQUE e caem no ramo DO UPDATE, incrementando count. Esse é o padrão de upsert com INSERT ... ON CONFLICT — tem uma página dedicada a ele mais adiante.
UNIQUE constraint vs índice único no SQLite
O CREATE UNIQUE INDEX faz o mesmo trabalho que uma constraint UNIQUE. Aliás, quando você declara uma constraint UNIQUE, o SQLite cria um índice único nos bastidores — é praticamente o mesmo mecanismo usando chapéus diferentes.
Quando usar cada um:
- Constraint quando a unicidade faz parte da definição da tabela. Fica documentada ali, ao lado das colunas.
- Índice único quando você precisa de um índice parcial (com cláusula
WHERE), quer dar um nome específico ao índice, ou precisa adicioná-lo a uma tabela já existente sem ter que recriá-la. OALTER TABLEdo SQLite não consegue adicionar uma constraint, mas sempre dá pra criar um índice depois.
Na hora da escrita, o comportamento é idêntico. A escolha é mais sobre onde você quer que a regra fique declarada no schema.
Adicionando UNIQUE a uma tabela existente
O ALTER TABLE do SQLite é propositalmente limitado — não existe ALTER TABLE ... ADD CONSTRAINT. As duas saídas práticas são:
A Opção 2 — quando você realmente quer ter a cláusula UNIQUE cravada na definição da tabela — exige aquela dança de recriar a tabela: cria uma nova com a constraint, copia os dados, dropa a antiga e renomeia. Isso a gente vê na próxima página.
Um aviso importante: se você está aplicando unicidade numa coluna que já tem valores duplicados, o CREATE UNIQUE INDEX vai falhar. Limpe os registros duplicados primeiro e só depois crie o índice.
Quando o UNIQUE falha: entendendo o erro
A mensagem de erro mostra exatamente qual constraint estourou:
Erro: falha na restrição UNIQUE: users.email
Erro: falha na restrição UNIQUE: enrollments.user_id, enrollments.course_id
A primeira forma é uma restrição em uma única coluna sobre users.email. A segunda é composta — as duas colunas aparecem juntas porque é a combinação delas que já existe. Quando você se deparar com esse erro:
- Descubra qual linha já tem o valor em conflito (
SELECT ... WHERE email = '...'). - Decida se vai atualizar essa linha, pular o insert ou usar um valor diferente.
- Se duplicatas são esperadas e a ideia é mesclá-las, troque para
INSERT ... ON CONFLICT DO UPDATE.
O erro é barulhento de propósito: na maioria das vezes você realmente quer saber. Duplicatas silenciosas seriam bem piores do que uma gravação que falha na sua cara.
A seguir: removendo e alterando tabelas
Restrições UNIQUE não podem ser adicionadas a uma tabela já existente com um simples ALTER TABLE. É justamente por causa dessa limitação que o SQLite tem aquela coreografia específica para mudanças de schema — a reescrita da tabela —, e esse é o assunto da próxima página, junto com o básico de como remover tabelas sem deixar lixo para trás.
Perguntas frequentes
Como adicionar uma constraint UNIQUE no SQLite?
Você pode declarar UNIQUE direto na coluna (email TEXT UNIQUE) ou, se quiser unicidade em mais de uma coluna ao mesmo tempo, usar a forma de tabela: UNIQUE(col1, col2). Por baixo dos panos, o SQLite cria um índice único e rejeita qualquer INSERT ou UPDATE que gere duplicata.
Qual a diferença entre UNIQUE e PRIMARY KEY no SQLite?
Uma tabela só pode ter uma PRIMARY KEY, mas pode ter quantas constraints UNIQUE você quiser. A PRIMARY KEY também implica NOT NULL (em tabelas STRICT e em INTEGER PRIMARY KEY), enquanto colunas UNIQUE aceitam vários NULL. Use a primary key para identificar a linha e UNIQUE para outras colunas que não podem se repetir.
Por que o SQLite permite vários NULL em uma coluna UNIQUE?
Porque, no SQL, NULL significa 'desconhecido', e dois desconhecidos não são considerados iguais entre si. Por isso uma coluna UNIQUE aceita quantos NULL você quiser — só os valores não nulos é que precisam ser distintos. Se você quer no máximo um NULL, adicione NOT NULL ou crie um índice único parcial.
Como resolver o erro 'UNIQUE constraint failed'?
Esse erro aparece quando um INSERT ou UPDATE tentaria gravar um valor duplicado em uma coluna UNIQUE (ou PRIMARY KEY). Você tem três caminhos: trocar o valor que está inserindo, apagar antes a linha que já existe, ou usar INSERT ... ON CONFLICT (upsert) para dizer ao SQLite o que fazer quando o conflito acontecer.