Afinidade é uma preferência, não uma regra
O SQLite usa tipagem dinâmica. Cada valor carrega sua própria classe de armazenamento (NULL, INTEGER, REAL, TEXT, BLOB), e o tipo declarado de uma coluna não restringe de fato o que pode ser gravado nela. O que o tipo declarado faz é atribuir à coluna uma afinidade — ou seja, uma classe de armazenamento preferida para a qual o SQLite tenta converter os valores que chegam.
Veja o que acontece quando a afinidade não é suficiente para impedir uma divergência de tipos:
A segunda linha guarda a string 'two' em uma coluna INTEGER. O SQLite tentou converter 'two' para número, não conseguiu (não é numérico) e acabou armazenando como TEXT mesmo assim. A função typeof() mostra a classe de armazenamento real de cada valor — e nem sempre bate com o que a declaração da coluna sugere.
Isso costuma pegar de surpresa quem vem do Postgres ou do MySQL. Mas é assim por design.
As cinco afinidades de tipo do SQLite
Toda coluna em uma tabela não-STRICT recebe exatamente uma destas afinidades:
TEXT— prefere strings.NUMERIC— prefere números, mas aceita texto se não conseguir converter.INTEGER— igual àNUMERIC, só que valores sem parte fracionária são guardados como inteiros.REAL— prefere números de ponto flutuante.BLOB— sem preferência, armazena qualquer coisa que você jogar nela.
A afinidade BLOB também é chamada de "sem afinidade" — é o que você ganha quando não declara tipo nenhum na coluna.
A mesma entrada — a string '42' — virou cinco tipos armazenados diferentes. Cada coluna converteu (ou não) o valor de acordo com sua afinidade.
Como o SQLite escolhe a afinidade a partir da sua declaração
Aqui mora a pegadinha que confunde muita gente: o SQLite não tem uma lista fixa de tipos "válidos". Você pode escrever praticamente qualquer coisa depois do nome da coluna, e o SQLite decide a afinidade procurando substrings no texto, nesta ordem:
- Contém
INT→INTEGER - Contém
CHAR,CLOBouTEXT→TEXT - Contém
BLOB, ou não tem tipo nenhum →BLOB - Contém
REAL,FLOAouDOUB→REAL - Qualquer outra coisa →
NUMERIC
É esse o algoritmo inteiro. E ele explica boa parte das esquisitices que você encontra por aí:
FLOATING_POINTS vira INTEGER porque a substring INT aparece em POINTS. A primeira regra que casa, de cima para baixo, é a que vence. É por isso que copiar tipos de outro banco sem pensar pode te dar um resultado bem diferente do esperado.
Type affinity na prática: conversões no INSERT
A afinidade de tipo entra em jogo principalmente quando o SQLite decide se vai converter o valor ou guardar como está. As regras são:
- Afinidade
TEXT: números eBLOBs são convertidos para texto. - Afinidades
NUMERIC,INTEGEReREAL: texto que parece número é convertido; texto que não parece, fica como texto mesmo. - Afinidade
BLOB: nada é convertido.
Linha por linha:
'123'numa colunaNUMERICvira o inteiro123. A conversão de texto para número funcionou sem perda.'12.5'vira o real12.5.'hello'na colunaNUMERICcontinua como texto — não há número pra onde converter.- A coluna
TEXTconverte os números para sua representação em string. - Já a coluna
BLOBguarda tudo exatamente como veio, tipo e tudo mais.
A pegadinha entre INTEGER e REAL
A afinidade INTEGER se comporta praticamente igual à NUMERIC, mas com um detalhe: um valor como 3.0, que não tem parte fracionária real, acaba armazenado como o inteiro 3 para economizar espaço.
3.0 cai como INTEGER nas duas colunas — essa otimização também acontece com NUMERIC. Já o 3.5 preserva a parte fracionária e continua como REAL. A moral da história: não confie no typeof() para descobrir se uma coluna foi declarada como INTEGER ou REAL. Ele te diz o que está realmente armazenado, e isso pode variar de linha para linha.
Quando a type affinity te morde
Essa flexibilidade é prática — até deixar de ser. Dois tipos de problema costumam aparecer em código de verdade:
1. Dados ruins entram escondidos. Se a sua aplicação tiver um bug que mande 'N/A' para uma coluna INTEGER, o SQLite guarda numa boa. Depois, qualquer consulta que faça aritmética com essa coluna devolve resultados estranhos ou NULL. Sem erro, sem aviso — corrupção silenciosa.
2. Comparações ficam esquisitas. Ordenação e testes de igualdade tratam valores de storage classes diferentes de formas distintas:
Inteiros são ordenados numericamente, depois os textos seguem a ordem lexicográfica — e os textos sempre vêm depois de todos os números. O resultado é algo como 2, 3, 10 (os inteiros em ordem numérica) seguido de '20', '100' (as strings em ordem alfabética). Não é o que a maioria das pessoas espera.
Se você controla os INSERTs e valida tudo direitinho, as tabelas comuns dão conta do recado. Mas se não for esse o caso — ou se você simplesmente quer que o próprio banco garanta os tipos pra você — existe uma opção melhor.
A seguir: tabelas STRICT
A versão 3.37 do SQLite trouxe as tabelas STRICT, que desligam a type affinity e rejeitam valores que não batem com o tipo declarado. Você fica com a tipagem dinâmica do SQLite quando quiser, e ganha uma checagem rígida no estilo Postgres quando precisar. É esse o assunto da próxima página.
Perguntas frequentes
O que é type affinity no SQLite?
Type affinity é a classe de armazenamento preferida de uma coluna. O SQLite tem cinco: TEXT, NUMERIC, INTEGER, REAL e BLOB. Quando você insere um valor, o SQLite tenta convertê-lo para a afinidade da coluna, mas se a conversão for inviável ou causar perda de dados, o valor é armazenado como veio. Ou seja, a afinidade é uma sugestão, não uma restrição rígida.
Como o SQLite decide a afinidade de uma coluna?
O SQLite analisa o nome do tipo que você escreveu no CREATE TABLE procurando substrings, nesta ordem: se contém INT, vira INTEGER; senão, se contém CHAR, CLOB ou TEXT, vira TEXT; senão, se contém BLOB (ou nenhum tipo foi declarado), vira BLOB; se contém REAL, FLOA ou DOUB, vira REAL; caso contrário, vira NUMERIC. É por isso que VARCHAR(50) acaba como TEXT e BIGINT vira INTEGER — as palavras que você escreve passam por um pattern matching.
Uma coluna no SQLite pode guardar valores do tipo errado?
Pode, em tabelas comuns. Uma coluna declarada como INTEGER aceita tranquilamente a string 'hello', porque a afinidade só sugere uma conversão. Se você quer enforcement de verdade, use tabelas STRICT, que rejeitam valores incompatíveis na hora. Falamos delas no próximo tópico.