L'affinità è una preferenza, non una regola
SQLite ha una tipizzazione dinamica. Un valore porta con sé la propria classe di memorizzazione (NULL, INTEGER, REAL, TEXT, BLOB) e il tipo dichiarato di una colonna non limita in modo rigido cosa puoi metterci dentro. Quello che il tipo dichiarato fa è dare alla colonna un'affinità: una classe di memorizzazione preferita in cui SQLite prova a convertire i valori in arrivo.
Guarda cosa succede quando l'affinità non basta a fermare un valore non compatibile:
La seconda riga memorizza la stringa 'two' in una colonna INTEGER. SQLite ha provato a convertire 'two' in un numero, non ci è riuscito (non è numerico) e l'ha memorizzata comunque come TEXT. typeof() rivela la classe di memorizzazione effettiva di ogni valore, e non sempre è quella che la dichiarazione della colonna lascia intendere.
Chi arriva da Postgres o MySQL resta sorpreso. È una scelta di progetto.
Le cinque affinità
Ogni colonna di una tabella non STRICT riceve esattamente una di queste:
TEXT: preferisce le stringhe.NUMERIC: preferisce i numeri, ma accetta il testo se non riesce a convertirlo.INTEGER: comeNUMERIC, ma memorizza come interi i valori senza parte frazionaria.REAL: preferisce i numeri in virgola mobile.BLOB: nessuna preferenza, memorizza qualsiasi cosa gli passi.
L'affinità BLOB si chiama anche "nessuna affinità": è quella che ottieni quando non dichiari affatto un tipo.
Stesso input, la stringa '42', e cinque tipi memorizzati diversi. Ogni colonna ha convertito (o no) in base alla propria affinità.
Come SQLite sceglie un'affinità dalla tua dichiarazione
Ecco la parte che fa inciampare: SQLite non ha un elenco fisso di tipi "validi". Puoi scrivere quasi qualsiasi cosa dopo il nome di una colonna, e SQLite ricava l'affinità cercando delle sottostringhe nel testo, in quest'ordine:
- Contiene
INT→INTEGER - Contiene
CHAR,CLOBoTEXT→TEXT - Contiene
BLOB, oppure nessun tipo →BLOB - Contiene
REAL,FLOAoDOUB→REAL - Qualsiasi altra cosa →
NUMERIC
Questo è tutto l'algoritmo. E spiega parecchie stranezze:
FLOATING_POINTS diventa INTEGER perché la sottostringa INT compare in POINTS. Vince la prima regola che corrisponde, dall'alto verso il basso. Ecco perché copiare alla cieca i tipi da un altro database può darti qualcosa di diverso da quello che ti aspettavi.
L'affinità in azione: conversioni all'inserimento
L'affinità conta soprattutto quando SQLite decide se convertire il tuo valore o memorizzarlo così com'è. Le regole:
- Affinità
TEXT: i numeri e iBLOBvengono convertiti in testo. - Affinità
NUMERIC,INTEGER,REAL: il testo che sembra un numero viene convertito; quello che non lo sembra resta testo. - Affinità
BLOB: non viene convertito nulla.
Riga per riga:
'123'in una colonnaNUMERICdiventa l'intero123. La conversione da testo a numero è riuscita senza perdite.'12.5'diventa il reale12.5.'hello'inNUMERICresta testo: non c'è un numero in cui convertirlo.- La colonna
TEXTconverte i numeri nella loro forma di stringa. - La colonna
BLOBmemorizza tutto esattamente come gliel'hai passato, tipo compreso.
La sfumatura tra INTEGER e REAL
L'affinità INTEGER si comporta quasi come NUMERIC, con una differenza: un valore come 3.0, che non ha una vera parte frazionaria, viene memorizzato come intero 3 per risparmiare spazio.
3.0 finisce come INTEGER in entrambe le colonne: l'ottimizzazione avviene anche per NUMERIC. 3.5 mantiene la parte frazionaria e resta REAL. La lezione: non affidarti a typeof() per sapere se una colonna è stata dichiarata INTEGER o REAL. Ti dice cosa è memorizzato davvero, e questo può cambiare da riga a riga.
Quando l'affinità ti morde
La flessibilità è comoda finché non lo è più. Nel codice reale si vedono due tipi di problema:
1. Entrano dati sbagliati. Se la tua applicazione ha un bug che manda 'N/A' a una colonna INTEGER, SQLite lo memorizza. Le query successive che fanno calcoli sulla colonna restituiscono risultati strani o NULL. Nessun errore, nessun avviso: solo dati corrotti in silenzio.
2. I confronti diventano strani. L'ordinamento e i controlli di uguaglianza trattano in modo diverso i valori con classi di memorizzazione diverse:
Gli interi vengono ordinati numericamente, poi i valori di testo in ordine lessicografico, e finiscono dopo tutti i numeri. Così ottieni 2, 3, 10 (gli interi in ordine numerico), poi '20', '100' (le stringhe in ordine alfabetico). Non è quello che vuole la maggior parte delle persone.
Se controlli tu gli inserimenti e li validi con cura, le tabelle normali vanno bene. Se non è così, o se vuoi semplicemente che sia il database a imporre i tipi, c'è un'opzione migliore.
Prossimo passo: tabelle STRICT
SQLite 3.37 ha introdotto le tabelle STRICT, che disattivano l'affinità e rifiutano i valori che non corrispondono al tipo dichiarato. Ti danno la tipizzazione dinamica predefinita quando la vuoi e un controllo in stile Postgres quando non la vuoi. È la prossima pagina.
Domande frequenti
Cos'è la type affinity in SQLite?
La type affinity è la classe di memorizzazione preferita di una colonna. SQLite ne ha cinque: TEXT, NUMERIC, INTEGER, REAL e BLOB. Quando inserisci un valore, SQLite prova a convertirlo nell'affinità della colonna, ma se la conversione comporterebbe una perdita o è impossibile, memorizza il valore così com'è. L'affinità è un suggerimento, non un vincolo rigido.
Come decide SQLite l'affinità di una colonna?
SQLite cerca delle sottostringhe nel nome del tipo che hai scritto in CREATE TABLE, in quest'ordine: se contiene INT è INTEGER; altrimenti CHAR, CLOB o TEXT la rendono TEXT; altrimenti BLOB (o nessun tipo) la rende BLOB; altrimenti REAL, FLOA o DOUB la rendono REAL; in tutti gli altri casi è NUMERIC. Ecco perché VARCHAR(50) diventa TEXT e BIGINT diventa INTEGER: le parole che scrivi vengono confrontate con dei pattern.
Una colonna SQLite può contenere valori del tipo sbagliato?
Sì, nelle tabelle normali. Una colonna dichiarata INTEGER memorizza tranquillamente la stringa 'hello', perché l'affinità suggerisce soltanto una conversione. Se vuoi un controllo rigido dei tipi, usa le tabelle STRICT, che rifiutano senza mezzi termini i valori non compatibili. Le vediamo nella prossima pagina.