Menu

Type Affinity en SQLite: cómo se comportan los tipos

Cómo funciona el sistema de type affinity de SQLite: las cinco afinidades, las reglas que eligen una a partir de la declaración de la columna y por qué una columna INTEGER puede acabar guardando un texto.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

La afinidad es una preferencia, no una regla

SQLite usa tipado dinámico, así que conviene entender desde el principio cómo funcionan los tipos de datos en SQLite. Cada valor lleva consigo su propia clase de almacenamiento (NULL, INTEGER, REAL, TEXT, BLOB), y el tipo declarado de una columna no restringe de forma estricta lo que puedes guardar en ella. Lo que sí hace ese tipo declarado es asignarle a la columna una afinidad de tipo: la clase de almacenamiento preferida a la que SQLite intentará convertir los valores que reciba.

Mira lo que pasa cuando la afinidad no basta para evitar un desajuste:

La segunda fila guarda la cadena 'two' dentro de una columna INTEGER. SQLite intentó convertir 'two' a número, no pudo (no es numérico) y aun así lo almacenó como TEXT. La función typeof() te muestra la clase de almacenamiento real de cada valor, y no siempre coincide con lo que declara la columna.

A quien viene de Postgres o MySQL esto le choca, pero está hecho a propósito.

Las cinco afinidades de tipos en SQLite

Toda columna de una tabla que no sea STRICT recibe exactamente una de estas afinidades:

  • TEXT — prefiere cadenas.
  • NUMERIC — prefiere números, pero acepta texto si no logra convertirlo.
  • INTEGER — igual que NUMERIC, salvo que guarda como entero todo valor sin parte decimal.
  • REAL — prefiere números de punto flotante.
  • BLOB — sin preferencia, guarda tal cual lo que le pases.

A la afinidad BLOB también se le dice "sin afinidad": es la que te toca cuando no declaras ningún tipo.

La misma entrada — la cadena '42' — y cinco tipos almacenados distintos. Cada columna convirtió (o no) el valor según su afinidad.

Cómo decide SQLite la afinidad a partir de tu declaración

Aquí está el detalle que confunde a mucha gente: SQLite no tiene una lista cerrada de tipos "válidos". Puedes escribir casi cualquier cosa después del nombre de una columna, y SQLite deduce la afinidad buscando subcadenas en el texto, siguiendo este orden:

  1. Contiene INTINTEGER
  2. Contiene CHAR, CLOB o TEXTTEXT
  3. Contiene BLOB, o no tiene tipo declarado → BLOB
  4. Contiene REAL, FLOA o DOUBREAL
  5. Cualquier otra cosa → NUMERIC

Ese es todo el algoritmo. Y explica un montón de rarezas:

FLOATING_POINTS termina con afinidad INTEGER porque la subcadena INT aparece dentro de POINTS. Gana la primera regla que coincida, empezando por arriba. Por eso, copiar tipos a ciegas desde otra base de datos puede darte resultados distintos a los que esperabas.

La afinidad de tipos en acción: conversiones al insertar

La afinidad de tipos en SQLite cobra importancia cuando el motor decide si convertir tu valor o guardarlo tal cual. Estas son las reglas:

  • Afinidad TEXT: los números y los BLOB se convierten a texto.
  • Afinidades NUMERIC, INTEGER y REAL: el texto que parece un número se convierte; el que no, se queda como texto.
  • Afinidad BLOB: no se convierte nada.

Fila por fila:

  • '123' en una columna NUMERIC se convierte en el entero 123. La conversión de texto a número se hizo sin perder información.
  • '12.5' pasa a ser el real 12.5.
  • 'hello' en NUMERIC se queda como texto, porque no hay ningún número al que convertirlo.
  • La columna TEXT convierte los números a su forma de cadena.
  • La columna BLOB guarda todo tal cual se lo pasas, tipo incluido.

El detalle entre INTEGER y REAL

La afinidad INTEGER se comporta casi igual que NUMERIC, salvo por un matiz: un valor como 3.0, que en realidad no tiene parte fraccionaria, se almacena como el entero 3 para ahorrar espacio.

3.0 se guarda como INTEGER en ambas columnas — la optimización también ocurre con NUMERIC. 3.5 conserva su parte decimal y se queda como REAL. La moraleja: no confíes en typeof() para saber si una columna fue declarada como INTEGER o REAL. Lo que te devuelve es lo que hay almacenado de verdad, y eso puede cambiar fila por fila.

Cuándo la afinidad de tipos en SQLite te juega en contra

Esa flexibilidad es cómoda… hasta que deja de serlo. En código real aparecen dos problemas típicos:

1. Se cuelan datos malos. Si tu aplicación tiene un bug y manda 'N/A' a una columna INTEGER, SQLite lo acepta sin chistar. Luego, las consultas que hacen aritmética sobre esa columna devuelven resultados raros o NULL. Ni un error, ni un aviso: corrupción silenciosa y a otra cosa.

2. Las comparaciones se vuelven raras. El orden y las comprobaciones de igualdad tratan los valores de forma distinta según su clase de almacenamiento:

Los enteros se ordenan numéricamente, luego los textos se ordenan lexicográficamente — y aparecen después de todos los números. Por eso obtienes 2, 3, 10 (los enteros en orden numérico) y a continuación '20', '100' (las cadenas en orden alfabético). No es lo que la mayoría espera.

Si controlas los INSERT y validas con cuidado, las tablas normales te sirven. Si no — o simplemente prefieres que la base de datos te imponga los tipos — hay una opción mejor.

Lo que viene: tablas STRICT

SQLite 3.37 incorporó las tablas STRICT, que desactivan la afinidad de tipos y rechazan los valores que no coincidan con el tipo declarado. Te dan el tipado dinámico de SQLite cuando lo quieres, y una validación al estilo Postgres cuando no. Eso lo vemos en la próxima página.

Preguntas frecuentes

¿Qué es el type affinity en SQLite?

El type affinity es la clase de almacenamiento preferida para una columna. SQLite tiene cinco: TEXT, NUMERIC, INTEGER, REAL y BLOB. Cuando insertas un valor, SQLite intenta convertirlo a la afinidad de la columna, pero si la conversión sería imposible o supondría perder información, guarda el valor tal cual. La afinidad es una sugerencia, no una restricción rígida.

¿Cómo decide SQLite la afinidad de una columna?

SQLite busca subcadenas dentro del nombre de tipo que escribiste en el CREATE TABLE, en este orden: si contiene INT, es INTEGER; si no, CHAR, CLOB o TEXT la convierten en TEXT; si no, BLOB (o ningún tipo) la deja como BLOB; si no, REAL, FLOA o DOUB la hacen REAL; en cualquier otro caso, NUMERIC. Por eso VARCHAR(50) acaba siendo TEXT y BIGINT se convierte en INTEGER: lo que escribes se compara por patrones.

¿Puede una columna de SQLite guardar valores del tipo equivocado?

Sí, en tablas normales sí. Una columna declarada como INTEGER te aceptará sin problema la cadena 'hello', porque la afinidad solo sugiere una conversión. Si necesitas que el tipo se respete a rajatabla, usa tablas STRICT, que rechazan directamente los valores que no encajen. Lo vemos a continuación.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR