O que são SQLite STRICT Tables

O SQLite tem uma peculiaridade famosa: você pode inserir um texto em uma coluna declarada como INTEGER e ele aceita sem reclamar. Isso e chamado de type affinity - o banco tenta converter, mas não rejeita o dado. Para muitos projetos isso é um pesadelo.

Na versão 3.37.0 (novembro de 2021), o SQLite ganhou um novo modo: STRICT tables. Com ele, a tabela passa a ter verificação de tipos real, igual ao que você espera de PostgreSQL ou MySQL. Tenta inserir texto onde e número? Erro. Simples assim.

A funcionalidade existia como pedido da comunidade ha anos. Finalmente chegou sem quebrar a compatibilidade retroativa - você escolhe tabela por tabela quais devem ser STRICT.

Como funciona

Basta adicionar a palavra STRICT no final do CREATE TABLE. A partir dai, o SQLite rejeita qualquer valor que não seja do tipo correto. Não ha conversão implícita.

Os tipos validos em modo STRICT são apenas seis: INT, INTEGER, REAL, TEXT, BLOB e ANY. O tipo ANY e especial - aceita qualquer valor como no modo normal, mas preserva o tipo original sem tentar converter. Você perde tipos como VARCHAR(100), BOOLEAN e DATETIME - eles simplesmente não existem no modo STRICT.

O motor faz a verificação no momento da inserção ou atualização. Se o dado não bate com o tipo, a operação retorna erro imediatamente. Nenhum dado ruim entra no banco.

💡
Dica

Você pode misturar tabelas STRICT e não-STRICT no mesmo banco .db. Migrar só as tabelas críticas e uma estratégia valida.

Principais recursos

O modo STRICT vai além da verificação de tipos. Veja o que muda no comportamento do banco:

  • Rejeição de tipo errado: inserir 'abc' em coluna INTEGER gera erro, sem exceção.
  • Tipo ANY preservado: ao contrario do modo normal, ANY no STRICT preserva o tipo exato do valor inserido - texto fica texto, número fica número.
  • Compatível com CHECK constraints: você ainda pode adicionar CHECK para validações extras além do tipo.
  • Sem impacto em performance: a verificação de tipo e trivial para o SQLite. Benchmarks mostram diferença desprezível.
  • Funciona com WAL, FTS, JSON: todos os recursos do SQLite continuam funcionando em tabelas STRICT.

Uma limitação importante: WITHOUT ROWID e compatível com STRICT - você pode combinar os dois modificadores escrevendo STRICT, WITHOUT ROWID no final da declaração.

Como começar: criando sua primeira tabela STRICT

Você não precisa instalar nada além do SQLite 3.37 ou superior. No terminal, verifique sua versão:

sqlite3 --version
# Deve mostrar 3.37.0 ou superior

Para criar uma tabela STRICT, a sintaxe e a mesma de sempre, com STRICT no final:

CREATE TABLE usuários (
  id      INTEGER PRIMARY KEY,
  nome    TEXT NOT NULL,
  email   TEXT NOT NULL,
  pontos  REAL DEFAULT 0.0
) STRICT;

Agora tente inserir um tipo errado e veja o erro:

-- Isso vai funcionar normalmente
INSERT INTO usuários (nome, email, pontos) VALUES ('Ana', '[email protected]', 10.5);

-- Isso vai retornar erro: "cannot store TEXT value in INTEGER column"
INSERT INTO usuários (id, nome, email) VALUES ('não-sou-número', 'Carlos', '[email protected]');
⚠️
Atenção

Tipos como BOOLEAN, VARCHAR, DATETIME e NUMERIC não existem no modo STRICT. Você precisa mapear para TEXT, INTEGER ou REAL.

Exemplo prático com Python

Em Python, o módulo sqlite3 da stdlib funciona normalmente com tabelas STRICT. O erro de tipo vira uma exceção sqlite3.OperationalError:

import sqlite3

con = sqlite3.connect("app.db")
cur = con.cursor()

cur.execute("""
  CREATE TABLE IF NOT EXISTS pedidos (
    id         INTEGER PRIMARY KEY,
    produto    TEXT NOT NULL,
    quantidade INTEGER NOT NULL,
    preço      REAL NOT NULL
  ) STRICT
""")

# Inserção valida
cur.execute(
  "INSERT INTO pedidos (produto, quantidade, preço) VALUES (?, ?, ?)",
  ("Teclado", 2, 149.90)
)

# Isso vai lançar OperationalError
try:
    cur.execute(
      "INSERT INTO pedidos (produto, quantidade, preço) VALUES (?, ?, ?)",
      ("Mouse", "dois", 89.90)  # quantidade deveria ser INTEGER
    )
except sqlite3.OperationalError as e:
    print(f"Erro capturado: {e}")

con.commit()
con.close()

O comportamento e previsível: o erro aparece na hora da inserção, não depois de horas debugando por que um calculo retornou resultado errado por causa de dado mal tipado.

🚀
Pro tip

Use parâmetros com ? (ou :nome) em vez de interpolar strings. O driver Python passa os valores com tipo preservado, o que colabora com STRICT.

Comparação com alternativas

O SQLite normal usa type affinity: tenta converter, aceita qualquer coisa. PostgreSQL e MySQL sempre tiveram tipos fortes. Com STRICT, o SQLite se aproxima desses bancos em termos de integridade, mas sem precisar de servidor.

  • SQLite normal: flexível, aceita tudo, bom para prototipagem rápida. Pode esconder bugs.
  • SQLite STRICT: integridade de tipo, melhor para produção, ainda sem servidor. Requer SQLite 3.37+.
  • PostgreSQL/MySQL: tipos fortes nativos, mas exigem servidor, configuração, Docker ou serviço pago.
  • DuckDB: tipos fortes, otimizado para analítica. Não substitui SQLite para apps transacionais.

O ponto forte do STRICT e justamente o equilíbrio: você tem integridade sem sair do arquivo .db local que já funciona em mobile, desktop e edge.

Pontos positivos e limitações

O que vai bem com STRICT:

  • Pega erros de tipo no momento da inserção, não semanas depois
  • Torna o schema mais autodocumentado - o tipo e real, não decorativo
  • Compatível com ORMs modernos (SQLAlchemy, Prisma, Drizzle) que já mapeiam tipos corretamente
  • Sem overhead perceptivel de performance

O que você precisa saber antes de adotar:

  • Não funciona em versões anteriores ao SQLite 3.37 - cheque o ambiente de produção
  • Tipos como BOOLEAN não existem: você guarda 0 e 1 como INTEGER
  • Datas ficam como TEXT (ISO 8601) ou INTEGER (Unix timestamp) - escolha e documente
  • Migrar tabelas existentes para STRICT exige recriar a tabela, não ha ALTER TABLE ... STRICT
🔴
Cuidado

Não ha como converter uma tabela existente para STRICT com ALTER TABLE. Você precisa criar nova tabela STRICT, copiar os dados e renomear. Faca isso dentro de uma transação.

Casos de uso reais

O STRICT e especialmente útil em cenários onde a integridade do dado e crítica:

  • Apps mobile (Android/iOS): bancos locais em apps Flutter ou React Native onde o schema e definido uma vez e deve ser rígido. Erros de tipo viram crashes difíceis de reproduzir.
  • CLIs e ferramentas de linha de comando: ferramentas que processam arquivos CSV ou JSON e precisam garantir que os tipos importados são validos antes de salvar.
  • Edge functions e SQLite distribuído (Turso, Cloudflare D1): ambientes sem servidor central onde você quer a mesma previsibilidade de um banco relacional tradicional.
  • Scripts de ETL e pipelines de dados: quando você importa dados de fontes externas e quer rejeitar linhas invalidas no banco em vez de propagar dados sujos.

Dicas e boas práticas

💡
Dica

Adote uma convenção para datas desde o inicio: TEXT em formato ISO 8601 ("2026-07-12") e legível e ordenável. INTEGER Unix timestamp e mais compacto. Escolha um padrão e documente no schema.

💡
Dica

Para valores booleanos, crie uma CHECK constraint: CHECK (ativo IN (0, 1)). Combina bem com STRICT e documenta a intenção no próprio schema.

🚀
Pro tip

Se você usa Drizzle ORM, o modo STRICT e suportado desde a versão 0.28. Basta adicionar strict: true na definição da tabela e o Drizzle gera o DDL correto automaticamente.

⚠️
Atenção

Se você usa um ORM que gera tipos como VARCHAR ou NUMERIC, o DDL vai falhar em tabelas STRICT. Verifique o SQL gerado antes de executar em banco de produção.

Vale a pena?

Se você esta começando um projeto novo com SQLite, a resposta e quase sempre sim. O custo de adicionar STRICT no CREATE TABLE e zero, e o beneficio de pegar erros de tipo cedo e real.

Se você tem um banco existente com dados heterogéneos (o tipo de coisa que acontece quando você insere dados de APIs externas ha anos), a migração exige cuidado. Vale auditar os dados antes de migrar.

Para projetos que usam SQLite em mobile, edge ou como banco embedded em ferramentas desktop, o STRICT e hoje a escolha mais defensiva. Você ganha previsibilidade sem abrir mao da simplicidade que fez o SQLite ser adotado em mais de 1 trilhao de instâncias no mundo.

O próximo passo: verifique sua versão do SQLite com sqlite3 --version e experimente criar uma tabela STRICT no seu próximo projeto. A mudança e pequena, o impacto na qualidade do seu banco pode ser grande.