Affinity ist eine Präferenz, keine Regel
SQLite arbeitet mit dynamischer Typisierung. Jeder Wert trägt seine eigene Speicherklasse mit sich (NULL, INTEGER, REAL, TEXT, BLOB), und der deklarierte Spaltentyp schränkt nicht strikt ein, was du dort ablegen darfst. Was der deklarierte Typ wirklich macht: Er gibt der Spalte eine Affinität — also eine bevorzugte Speicherklasse, in die SQLite eingehende Werte umzuwandeln versucht.
Schau dir an, was passiert, wenn die Affinität allein nicht reicht, um einen Typkonflikt zu verhindern:
Die zweite Zeile speichert den String 'two' in einer INTEGER-Spalte. SQLite hat versucht, 'two' in eine Zahl umzuwandeln, hat es nicht geschafft (kein numerischer Wert) – und es trotzdem als TEXT abgelegt. typeof() zeigt dir die tatsächliche Speicherklasse jedes Wertes an, und die deckt sich nicht immer mit dem, was die Spaltendeklaration vermuten lässt.
Wer von Postgres oder MySQL kommt, ist davon erstmal irritiert. Aber genau so ist es in SQLite gewollt.
Die fünf Type Affinities in SQLite
Jede Spalte in einer Tabelle ohne STRICT-Modus bekommt genau eine dieser Affinitäten zugewiesen:
TEXT– bevorzugt Strings.NUMERIC– bevorzugt Zahlen, akzeptiert aber auch Text, wenn die Umwandlung nicht klappt.INTEGER– wieNUMERIC, speichert Werte ohne Nachkommastellen aber als Integer.REAL– bevorzugt Fließkommazahlen.BLOB– keine Vorliebe, speichert das, was du übergibst, einfach so.
Die BLOB-Affinität nennt man auch „keine Affinität" – sie greift, wenn du gar keinen Datentyp angibst.
Gleicher Input – der String '42' – fünf unterschiedlich gespeicherte Typen. Jede Spalte hat ihn entsprechend ihrer Affinity konvertiert (oder eben nicht).
Wie SQLite die Affinity aus deiner Deklaration ableitet
Hier kommt der Punkt, an dem viele stolpern: SQLite hat keine feste Liste „gültiger" Datentypen. Du kannst hinter einem Spaltennamen praktisch alles schreiben, und SQLite ermittelt die Affinity, indem es den Text in dieser Reihenfolge nach Teilstrings durchsucht:
- Enthält
INT→INTEGER - Enthält
CHAR,CLOBoderTEXT→TEXT - Enthält
BLOBoder gar keinen Typ →BLOB - Enthält
REAL,FLOAoderDOUB→REAL - Alles andere →
NUMERIC
Das ist der komplette Algorithmus. Und er erklärt einiges an scheinbar merkwürdigem Verhalten:
FLOATING_POINTS wird zu INTEGER, weil die Zeichenfolge INT in POINTS vorkommt. Es gewinnt immer die erste passende Regel von oben nach unten. Genau deshalb kann es passieren, dass du beim simplen Übernehmen von Datentypen aus einer anderen Datenbank plötzlich etwas ganz anderes bekommst, als du erwartet hast.
SQLite Typumwandlung in der Praxis: Konvertierungen beim Insert
Die Type Affinity wird vor allem dann relevant, wenn SQLite entscheidet, ob dein Wert konvertiert oder unverändert gespeichert wird. Die Regeln im Überblick:
- Affinität
TEXT: Zahlen undBLOBs werden zu Text konvertiert. - Affinitäten
NUMERIC,INTEGER,REAL: Text, der wie eine Zahl aussieht, wird konvertiert; alles andere bleibt Text. - Affinität
BLOB: Es wird nichts konvertiert.
Zeile für Zeile:
'123'in einerNUMERIC-Spalte wird zum Integer123. Die Umwandlung von Text in eine Zahl hat funktioniert – und zwar verlustfrei.'12.5'wird zum Real-Wert12.5.'hello'inNUMERICbleibt Text – da gibt es schlicht keine Zahl, in die man umwandeln könnte.- Die
TEXT-Spalte verwandelt Zahlen in ihre String-Darstellung. - Die
BLOB-Spalte speichert alles exakt so, wie es reinkommt – Typ inklusive.
Der feine Unterschied zwischen INTEGER und REAL
Die INTEGER-Affinität verhält sich praktisch genauso wie NUMERIC, aber mit einem kleinen Kniff: Ein Wert wie 3.0, der keinen echten Nachkommaanteil hat, wird platzsparend als Integer 3 abgelegt.
3.0 landet in beiden Spalten als INTEGER – diese Optimierung greift auch bei NUMERIC. 3.5 behält dagegen seinen Nachkommaanteil und bleibt REAL. Merke: Verlass dich nicht auf typeof(), um herauszufinden, ob eine Spalte als INTEGER oder REAL deklariert wurde. Die Funktion zeigt dir nur, was tatsächlich gespeichert ist – und das kann von Zeile zu Zeile unterschiedlich sein.
Wann SQLite Type Affinity zur Falle wird
Die dynamische Typisierung von SQLite ist praktisch – bis sie es nicht mehr ist. In echtem Code begegnen einem vor allem zwei Stolperfallen:
1. Müll-Daten rutschen durch. Wenn deine Anwendung wegen eines Bugs den Wert 'N/A' an eine INTEGER-Spalte schickt, speichert SQLite ihn anstandslos. Spätere Abfragen, die mit der Spalte rechnen, liefern dann komische Ergebnisse oder NULL. Keine Fehlermeldung, keine Warnung – stille Datenkorruption.
2. Vergleiche verhalten sich seltsam. Beim Sortieren und bei Gleichheitsprüfungen behandelt SQLite Werte unterschiedlicher Storage Classes nicht gleich:
Integers werden numerisch sortiert, danach kommen die Textwerte in lexikografischer Reihenfolge — und zwar nach allen Zahlen. Das Ergebnis ist also 2, 3, 10 (die Integers in numerischer Reihenfolge), gefolgt von '20', '100' (die Strings alphabetisch sortiert). In den meisten Fällen genau nicht das, was man eigentlich will.
Wenn du die Inserts selbst in der Hand hast und sauber validierst, reichen normale Tabellen völlig aus. Falls nicht — oder falls du die Typprüfung lieber der Datenbank überlassen möchtest — gibt es eine bessere Lösung.
Als Nächstes: STRICT Tables
Mit SQLite 3.37 kamen STRICT Tables dazu. Sie schalten die Type Affinity ab und weisen Werte zurück, die nicht zum deklarierten Spaltentyp passen. So bekommst du die dynamische Typisierung von SQLite dort, wo du sie willst, und eine strikte Typprüfung wie in Postgres dort, wo du sie brauchst. Genau darum geht es auf der nächsten Seite.
Häufig gestellte Fragen
Was ist Type Affinity in SQLite überhaupt?
Type Affinity ist die bevorzugte Speicherklasse einer Spalte. SQLite kennt davon fünf: TEXT, NUMERIC, INTEGER, REAL und BLOB. Beim Insert versucht SQLite, den Wert in die Affinität der Spalte zu konvertieren – wäre die Konvertierung verlustbehaftet oder gar nicht möglich, wird der Wert einfach so gespeichert, wie er reinkommt. Affinity ist also eher ein Hinweis als eine harte Regel.
Wie bestimmt SQLite die Affinität einer Spalte?
SQLite durchsucht den Typnamen aus deinem CREATE TABLE der Reihe nach nach Teilstrings: enthält er INT, wird's INTEGER; sonst machen CHAR, CLOB oder TEXT daraus TEXT; sonst ergibt BLOB (oder gar kein Typ) ein BLOB; sonst führen REAL, FLOA oder DOUB zu REAL; alles andere wird NUMERIC. Genau deshalb landet VARCHAR(50) bei TEXT und BIGINT bei INTEGER – die Wörter, die du hinschreibst, werden schlicht per Pattern-Matching ausgewertet.
Kann eine SQLite-Spalte Werte vom „falschen“ Typ enthalten?
In normalen Tabellen: ja. Eine als INTEGER deklarierte Spalte speichert klaglos den String 'hello', weil Affinity nur eine Konvertierung vorschlägt. Wenn du echte Typprüfung willst, nimm STRICT-Tabellen – die weisen unpassende Werte direkt ab. Darum geht's im nächsten Abschnitt.