Menu

Type Affinity no SQLite: como os tipos funcionam

Entenda como funciona o type affinity do SQLite: as cinco afinidades, as regras que escolhem uma a partir da declaração da coluna e por que uma coluna INTEGER pode guardar uma string.

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

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:

  1. Contém INTINTEGER
  2. Contém CHAR, CLOB ou TEXTTEXT
  3. Contém BLOB, ou não tem tipo nenhum → BLOB
  4. Contém REAL, FLOA ou DOUBREAL
  5. 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 e BLOBs são convertidos para texto.
  • Afinidades NUMERIC, INTEGER e REAL: texto que parece número é convertido; texto que não parece, fica como texto mesmo.
  • Afinidade BLOB: nada é convertido.

Linha por linha:

  • '123' numa coluna NUMERIC vira o inteiro 123. A conversão de texto para número funcionou sem perda.
  • '12.5' vira o real 12.5.
  • 'hello' na coluna NUMERIC continua como texto — não há número pra onde converter.
  • A coluna TEXT converte os números para sua representação em string.
  • Já a coluna BLOB guarda 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR