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.