Por que existem as STRICT tables
O SQLite, por padrão, é famoso por ser relaxado com tipos. Você declara uma coluna como INTEGER, insere a string "hello" e o SQLite simplesmente dá de ombros e guarda a string assim mesmo. Essa flexibilidade foi uma escolha de design intencional lá nos anos 90, mas pega de surpresa quem vem do Postgres ou do MySQL — e, pior, esconde bugs.
As STRICT tables, introduzidas no SQLite 3.37, resolvem isso. Você ativa o modo strict por tabela e, a partir daí, os tipos das colunas passam a valer de verdade.
A palavra-chave STRICT vem depois do parêntese de fechamento. O resto continua igual a um CREATE TABLE comum. A diferença só aparece na hora em que você tenta gravar um valor do tipo errado em uma coluna.
O que o STRICT realmente garante
Numa tabela comum, a type affinity do SQLite tenta converter o valor para o tipo declarado e, se não rolar, ainda assim guarda como veio. Já numa tabela STRICT, qualquer divergência de tipo vira erro.
Faça o mesmo teste em uma tabela não-STRICT e o terceiro insert vai passar numa boa — o SQLite alegremente guarda a string 'oops' numa coluna que você declarou como INTEGER. Meses depois, uma consulta de agregação retorna um valor sem pé nem cabeça e você perde uma tarde inteira caçando o bug. Com STRICT, o erro estoura na hora do insert, exatamente onde dá pra corrigir.
O erro que aparece:
Erro de execução: não é possível armazenar valor TEXT na coluna INTEGER accounts.balance
Claro, direto e impossível de ignorar.
Os cinco tipos permitidos
Tabelas STRICT só aceitam cinco nomes de tipo:
INTEGER— números inteiros.REAL— números de ponto flutuante.TEXT— strings.BLOB— bytes brutos.ANY— qualquer tipo, sem conversão.
É só isso. Os apelidos informais que o SQLite normalmente aceita — VARCHAR(255), DOUBLE, BOOLEAN, DATETIME, INT — todos disparam erro dentro de uma tabela STRICT:
O erro:
Erro de análise: tipo de dado desconhecido para bad.name: "VARCHAR(255)"
A solução é usar um dos cinco nomes canônicos. VARCHAR(255) vira TEXT, DATETIME vira TEXT (afinal, o SQLite já armazena datas como strings ISO) e BOOLEAN vira INTEGER (com 0 e 1).
A válvula de escape: o tipo ANY
ANY é o único tipo que permite uma coluna STRICT guardar valores de tipos diferentes — útil, por exemplo, numa coluna value genérica de uma tabela chave/valor:
ANY tem um comportamento especial em tabelas STRICT: os valores são guardados sem a coerção de tipo que essa mesma palavra causaria em outros contextos. Uma string '100' continua sendo string; um inteiro 100 continua sendo inteiro. As chamadas a typeof() na consulta acima comprovam isso.
Numa tabela não-STRICT, uma coluna com afinidade ANY converteria strings que parecem números em números de fato. Já a STRICT preserva o tipo original na íntegra.
STRICT e PRIMARY KEY
Tem uma diferença sutil aqui: em uma tabela comum, INTEGER PRIMARY KEY é um caso especial — vira um alias para rowid e só aceita inteiros. Outras declarações de chave primária são mais flexíveis.
Numa tabela STRICT, o tipo da coluna é validado independentemente de ela ser chave primária ou não:
O segundo INSERT falha. Numa tabela não-STRICT, o valor 42 seria gravado silenciosamente na coluna TEXT da chave primária. Aqui, o SQLite avisa na hora.
Misturando tabelas STRICT e não-STRICT
O modo STRICT vale por tabela, não pelo banco inteiro. Dá pra ter uma tabela users no modo strict e uma events no modo relaxado dentro do mesmo arquivo. As foreign keys continuam funcionando entre elas normalmente, como em qualquer outro caso.
A tabela events não tem STRICT nem tipo declarado em payload, então ela aceita qualquer coisa que você jogar lá dentro. Útil de vez em quando — perigoso como padrão. Deixe esse tipo de armazenamento sem tipo para casos em que você realmente precisa de uma coluna coringa.
Quando usar STRICT em tabelas SQLite
Para schemas novos, a resposta é "quase sempre". O custo é mínimo — uma palavra-chave por tabela e lembrar dos cinco nomes de tipo canônicos. Em troca, bugs que normalmente ficariam escondidos nos seus dados aparecem logo no insert que os causou.
Pule o STRICT quando:
- Você está mantendo um banco SQLite antigo cujo schema depende da tipagem relaxada.
- Você precisa rodar em versões do SQLite anteriores à 3.37 (outubro de 2021) — a palavra-chave simplesmente não existe nelas.
- Você realmente quer que uma coluna armazene tipos mistos; mesmo nesse caso, prefira uma tabela
STRICTcom uma colunaANYem vez de uma tabela semSTRICT, porque todo o resto continua sendo validado.
Um checklist rápido para converter uma tabela comum em STRICT:
- Troque
VARCHAR,CHAR,NVARCHARporTEXT. - Troque
DOUBLE,FLOAT,NUMERICporREAL. - Troque
BOOLEAN,BIT,TINYINTporINTEGER. - Troque
DATETIME,TIMESTAMP,DATEporTEXT(ouINTEGERse você guarda timestamps unix). - Adicione
STRICTdepois do parêntese de fechamento.
A seguir: chaves primárias
Tabelas STRICT apertam o controle sobre como cada coluna guarda seus dados. O próximo ponto que vale apertar é qual coluna identifica cada linha — e as chaves primárias do SQLite têm algumas peculiaridades (principalmente em torno de INTEGER PRIMARY KEY e do rowid) que vale a pena conhecer antes de modelar um schema de verdade.
Perguntas frequentes
O que é uma tabela STRICT no SQLite?
Uma tabela STRICT obriga o SQLite a respeitar o tipo declarado de cada coluna — se você disse que a coluna é INTEGER, qualquer valor que não seja inteiro ou NULL é rejeitado. Para ativar, basta colocar a palavra-chave STRICT depois do parêntese de fechamento do CREATE TABLE. Sem isso, o SQLite usa type affinity, que tenta converter o valor quando dá e armazena do jeito que veio quando não dá.
Quais tipos posso usar em uma tabela STRICT?
Apenas cinco: INTEGER, REAL, TEXT, BLOB e ANY. Aqueles aliases que funcionam em tabelas comuns — VARCHAR, DOUBLE, BOOLEAN, DATETIME — todos disparam erro em uma tabela STRICT. A coluna ANY serve como válvula de escape: aceita qualquer tipo sem fazer conversão.
Vale a pena usar tabelas STRICT em bancos SQLite novos?
Para a maioria dos esquemas novos, sim. Tabelas STRICT pegam bugs que as tabelas comuns engolem caladas — uma string parar em uma coluna INTEGER, uma lista serializada por acidente em um REAL. O custo é uma palavra-chave a mais por tabela e abrir mão dos nomes de tipo exóticos. Disponível desde o SQLite 3.37 (2021).