Menu

SQLite NOT NULL e DEFAULT: restrições de coluna na prática

Como NOT NULL e DEFAULT funcionam no SQLite: o que cada um realmente garante, o truque do CURRENT_TIMESTAMP e as pegadinhas ao adicionar essas restrições em tabelas que já existem.

Esta página tem editores executáveis — edite, execute e veja a saída na hora.

Duas constraints que valem cada caractere

A maior parte dos bugs que nascem de um schema mal feito cai em uma de duas situações: uma coluna que vem NULL quando ninguém esperava, ou uma coluna sem valor que a aplicação achava que estaria preenchida. NOT NULL e DEFAULT resolvem os dois casos — e custam praticamente nada para adicionar.

Uma coluna é obrigatória e não tem valor de reserva. Duas têm valores padrão. O insert só precisou informar o email, e o SQLite preencheu o resto. Esse é o recurso inteiro num único exemplo — o restante desta página é sobre os detalhes mais sutis.

NOT NULL significa "rejeita NULL, sem exceções"

A constraint NOT NULL faz exatamente o que o nome diz. Qualquer tentativa de gravar NULL na coluna falha — seja omitindo o campo num INSERT que não tem DEFAULT, seja escrevendo NULL de forma explícita:

O erro diz:

Runtime error: NOT NULL constraint failed: posts.title

O resultado é o mesmo se você passar NULL diretamente:

INSERT INTO posts (id, title) VALUES (1, NULL);
-- Erro de execução: falha na restrição NOT NULL: posts.title

Esse é o contrato. Se uma coluna é logicamente obrigatória, marque-a como NOT NULL e você elimina uma classe inteira de bugs de uma vez — nenhum código de aplicação consegue passar um NULL despercebido pelo banco.

DEFAULT: valor padrão quando o caller não informa

O DEFAULT só entra em ação quando o INSERT simplesmente não menciona a coluna. Ele não socorre um NULL explícito:

O primeiro INSERT se apoia no valor padrão. O segundo sobrescreve esse valor. Agora, se você tivesse escrito INSERT INTO tasks (title, status) VALUES ('x', NULL), ia tomar um erro NOT NULL constraint failed — como a coluna foi citada explicitamente, o default não entra em ação.

O modelo mental que vale guardar é esse: DEFAULT preenche colunas ausentes. Já NOT NULL rejeita valores nulos venham de onde vierem. São recursos independentes que se combinam muito bem.

Defaults também podem ser expressões no SQLite

O caso mais comum é um valor literal (DEFAULT 0, DEFAULT '', DEFAULT 'pending'), mas o SQLite também aceita uma expressão entre parênteses. É assim que você carimba cada linha com o horário em que ela foi criada, ou gera um ID aleatório:

Algumas coisas que vale saber:

  • A expressão é avaliada a cada insert, não uma única vez na criação da tabela. Cada linha recebe seu próprio timestamp e seu próprio token.
  • CURRENT_TIMESTAMP, CURRENT_DATE e CURRENT_TIME são as três palavras-chave especiais que dispensam parênteses. Qualquer outra coisa precisa.
  • A expressão não pode referenciar outras colunas nem subqueries — tem que ser autocontida.

Se você quer uma coluna opcional, mas que receba o carimbo automático quando vier preenchida, tire o NOT NULL e mantenha o default. Se quer obrigatória e com carimbo automático, use os dois juntos.

DEFAULT NULL é válido (e às vezes é exatamente o que você quer)

Escrever DEFAULT NULL tem o mesmo efeito de não declarar default nenhum — a coluna fica NULL quando você não passa valor. Ainda assim, vale usar quando você quer deixar explícito no schema que "sem valor" é o estado inicial pretendido:

bio e avatar se comportam exatamente da mesma forma aqui. O DEFAULT NULL em bio funciona como um comentário em forma de código — ele deixa claro para quem lê o schema que a ausência de uma bio é algo esperado, não um esquecimento.

Adicionando NOT NULL em uma tabela existente

É aqui que a coisa começa a complicar. O ALTER TABLE do SQLite é limitado de propósito — não dá para rodar ALTER COLUMN ... SET NOT NULL como você faria no Postgres. O que dá pra fazer depende de a coluna já existir ou não.

Para uma coluna nova em folha, ADD COLUMN ... NOT NULL funciona, mas você precisa informar um valor padrão — caso contrário, as linhas já existentes ficariam com NULL em uma coluna NOT NULL, o que é impossível:

Tente o mesmo sem o DEFAULT e você vai tomar um erro:

ALTER TABLE products ADD COLUMN sku TEXT NOT NULL;
-- Erro em tempo de execução: Não é possível adicionar uma coluna NOT NULL com valor padrão NULL

Quando a coluna já existe, não dá pra alterar no lugar. A receita padrão é o famoso rebuild: cria uma tabela nova com a restrição que você quer, copia os dados, descarta a antiga e renomeia a nova. Vamos detalhar esse processo na página sobre drop-and-alter-table — por ora, só tenha em mente que essa limitação existe e desenhe seu schema levando isso em conta.

Combinando NOT NULL e DEFAULT na prática

A maioria das tabelas em produção usa as duas restrições juntas para traduzir "aquilo que a aplicação espera que seja verdade":

Lendo esse schema de cima a baixo, dá pra adivinhar o que a aplicação faz sem precisar ver uma linha de código sequer. customer é obrigatório e não tem fallback — quem chama precisa saber para quem é o pedido. Valor, moeda e status têm defaults razoáveis, então até o insert mais simples já produz uma linha coerente. notes é opcional. created_at é preenchido pelo próprio banco, que é o único lugar onde deveria ser preenchido.

É aí que mora o valor dessas constraints: elas transformam suposições em regras que o próprio banco se encarrega de cumprir.

Pegadinhas comuns

Uma listinha curta de coisas que costumam pegar a galera de surpresa:

  • Passar NULL explícito anula o DEFAULT. INSERT INTO t (col) VALUES (NULL) não vai usar o valor padrão. A coluna precisa estar ausente da lista de colunas.
  • Defaults com expressão precisam de parênteses. DEFAULT CURRENT_TIMESTAMP funciona (é uma das três palavras-chave especiais). Já DEFAULT lower(hex(randomblob(8))) não — tem que envolver: DEFAULT (lower(hex(randomblob(8)))).
  • NOT NULL e string vazia não são a mesma coisa. '' é um valor TEXT válido e não dispara a constraint. Se você também quer barrar string vazia, isso é trabalho para o CHECK (próxima página).
  • ADD COLUMN ... NOT NULL exige um DEFAULT que não seja NULL. Sem isso, o SQLite recusa a alteração.

A seguir: CHECK Constraints

NOT NULL e DEFAULT cobrem o "tem que existir" e o "preenche se faltar". Para a próxima camada de validação — "tem que ser positivo", "tem que ser um destes valores", "a data final precisa vir depois da data inicial" — o SQLite oferece as CHECK constraints, que permitem escrever expressões booleanas arbitrárias que toda linha precisa satisfazer. É o assunto da próxima página.

Perguntas frequentes

Como deixar uma coluna obrigatória no SQLite?

Basta colocar NOT NULL na definição da coluna: email TEXT NOT NULL. Qualquer INSERT ou UPDATE que tente deixar essa coluna como NULL falha com NOT NULL constraint failed. Se quiser um valor de fallback para quando ninguém passar nada, combine com um DEFAULT.

Como funcionam os valores padrão (DEFAULT) no SQLite?

DEFAULT <valor> define um valor a ser usado quando o INSERT não especifica nada para aquela coluna. O default pode ser um literal (DEFAULT 0, DEFAULT 'pending'), NULL ou uma expressão entre parênteses, como DEFAULT (CURRENT_TIMESTAMP) ou DEFAULT (lower(hex(randomblob(8)))). Defaults baseados em expressão são reavaliados a cada novo insert.

Por que o SQLite retorna 'NOT NULL constraint failed' no meu insert?

Você está inserindo uma linha sem fornecer valor para uma coluna NOT NULL que não tem DEFAULT. Resolva incluindo a coluna no INSERT, definindo um DEFAULT ou afrouxando a restrição. Passar NULL explicitamente também dispara o erro — o NOT NULL rejeita nulos venha de onde vier.

Dá para adicionar NOT NULL em uma coluna que já existe no SQLite?

Diretamente, não — o SQLite não suporta ALTER TABLE ... ALTER COLUMN. As saídas são duas: adicionar uma nova coluna com NOT NULL DEFAULT <valor> (o default é obrigatório por causa das linhas existentes), ou recriar a tabela: criar uma nova com a restrição, copiar os dados, dropar a antiga e renomear a nova.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR