Kurs
SQLite nutzt ein flexibles Typsystem, das auf Speicherklassen und Typaffinität basiert. Im Gegensatz zu herkömmlichen RDBMS, die eine strenge Typisierung erfordern, ermöglicht SQLite eine dynamischere Typbehandlung. Dieses Modell zu verstehen ist wichtig, um Datenkonsistenz zu gewährleisten und subtile Fehler in der Anwendungslogik zu vermeiden.
In diesem Artikel werde ich das SQLite-Typsystem anschauen und dir zeigen, wie du mit gängigen Datentypen in SQLite arbeitest. Wenn du noch keine Erfahrung mit SQL hast, solltest du vielleicht mit unserem Kurs „Einführung in SQL“ anfangen, um dir eine solide Grundlage zu schaffen. Außerdem finde ich das SQL-Grundlagen-Spickzettel, das du runterladen kannst, echt hilfreich, weil es die gängigsten SQL-Funktionen enthält.
Grundlegende Konzepte des Typsystems von SQLite
Um mit SQLite zu arbeiten, ist es wichtig zu verstehen, wie es Daten intern speichert und interpretiert. Im Folgenden erkläre ich, wie ein SQLite-System zuverlässige und effiziente Anwendungen entwickelt.
Speicherklassen erklärt
SQLite nutzt fünf Speicherklassen, um Werte intern darzustellen:
-
NULL
bedeutet, dass ein Wert fehlt oder nicht definiert ist. -
INTEGER
stellt eine vorzeichenbehaftete Ganzzahl dar, die je nach Größe in 1 bis 8 Bytes gespeichert wird. -
REAL
beschreibt eine Gleitkommazahl, die als 8-Byte-IEEE-754-Wert gespeichert ist. -
TEXT
Speichert Textzeichenfolgen, die in UTF-8, UTF-16BE oder UTF-16LE kodiert sind. -
BLOB
speichert Binärdaten genau so, wie sie eingegeben wurden, ohne sie zu verändern.
SQLite-Datentypen. Bildquelle: OpenAI
Diese Speicherklassen unterscheiden sich von den in einem Tabellenschema angegebenen Datentypen. Eine Spalte kann als „ VARCHAR(50)
“ oder „ BOOLEAN
“ deklariert werden, aber der tatsächliche Wert wird je nach Inhalt in einer der fünf Speicherklassen gespeichert. Diese Trennung macht SQLite so flexibel und tolerant, aber es bedeutet auch, dass Entwickler bei ihrem Anwendungscode auf Datenvalidierung und Typkonsistenz achten müssen.
Typ-Affinität und Säulenverhalten
Die Typaffinität in SQLite ist ein empfohlener Typ, der einer Spalte basierend auf ihrem deklarierten Datentyp zugewiesen wird. SQLite nutzt die folgenden Regeln, um die Affinität anhand des deklarierten Typtextes zu bestimmen.
-
Spalten, die als „
VARCHAR
“, „CHAR
“ oder „TEXT
“ deklariert sind, erhalten die Affinität „TEXT
“. -
Typen wie „
INT
“ und „INTEGER
“ bekommen die Affinität „INTEGER
“. -
Deklarationen wie „
DECIMAL
“, „NUMERIC
“ oder „BOOLEAN
“ bekommen die Affinität „NUMERIC
“. -
Typen wie „
REAL
“, „FLOAT
“ oder „DOUBLE
“ bekommen die Affinität „REAL
“. -
Wenn kein Typ erkannt wird, wird standardmäßig „
BLOB
“ verwendet.
Eine Spalte, die als „ VARCHAR(100)
“ deklariert ist, hat zum Beispiel die Affinität „ TEXT
“, was bedeutet, dass SQLite versucht, Werte beim Speichern in Text umzuwandeln. Diese Ähnlichkeit bestimmt, wie SQLite Daten umwandelt und speichert, schränkt aber nicht die Speicherung anderer Typen in dieser Spalte ein.
Manifest-Typisierung und was das bedeutet
SQLite nutzt explizite Typisierung, was bedeutet, dass der Datentyp mit dem einzelnen Wert verknüpft ist und nicht mit der Spalte, in der er steht. Dadurch können verschiedene Arten von Werten in einer einzigen Spalte nebeneinander existieren. Zum Beispiel kann eine Spalte, die als „ INTEGER
“ deklariert ist, Werte wie „ TEXT
“ oder „ BLOB
“ haben.
Mit Manifest-Typisierung können Entwickler verschiedene Datentypen einfügen, ohne das Schema zu ändern. Der Nachteil ist aber, dass es zu inkonsistenten Daten, unvorhersehbarer Sortierung und mehr Abhängigkeit von der Anwendungslogik kommen kann, um Typregeln durchzusetzen.
Ich empfehle dir unseren Kurs „Datenbankdesign“, umDatenbankverwaltung zu lernen,einschließlich der Organisation von Daten in verschiedenen Datentypen und der Verwaltung von Datenbanken.
Arbeiten mit gängigen Datentypen in SQLite
Schauen wir uns jetzt an, wie man mit gängigen Datentypen in SQLite umgeht, zum Beispiel Text, Zahlen, Boolesche Werte, Datumsangaben und Binärdaten.
Text, Zahlen und Boolesche Werte
Zu den häufig verwendeten Datendeklarationen in SQLite gehören „ TEXT
“, „ INTEGER
“, „ REAL
“, „ NUMERIC
“ und „ BOOLEAN
“. Die Werte werden aber nicht streng nach Typen gespeichert, sondern nach Typähnlichkeit. Hier sind ein paar Beispiele, wie die Datentypen in einem echten Schema verwendet werden können.
-
TEXT
Speichert Zeichenfolgen, die für Namen, E-Mails, URLs und allgemeine Textdaten geeignet sind. -
INTEGER
ist super für ganze Zahlen wie Zählungen, IDs und Codes und macht das Speichern und Sortieren echt einfach. -
REAL
Speichert Fließkommazahlen für Messungen, Währungen oder nicht ganzzahlige Werte. -
Die Affinität „
NUMERIC
“ umfasst Werte, die als „DECIMAL
“, „BOOLEAN
“ oder ähnlich deklariert sind. Je nach Wert versucht SQLite, diese als „INTEGER
“ oder „REAL
“ zu speichern. -
BOOLEAN
ist keine native Speicherklasse, sondern wird simuliert, indem hinter den Kulissen „0
“ (FALSE) oder „1
“ (TRUE) als „INTEGER
“-Werte gespeichert werden.
Ich empfehle, „ INTEGER
” für Boolesche Werte mit expliziten Einschränkungen auf der Anwendungsseite zu verwenden, während „ NUMERIC
” für Werte geeignet ist, die eine flexible Zahlenanalyse erfordern.
Termine, Zeiten und zeitliche Strategien
SQLite hat keinen eigenen Typ für Datums- und Uhrzeitangaben ( DATE
oder DATETIME
), aber es kann folgende Formate für temporäre Daten speichern:
-
TEXT
: Speichert Datums- und Zeitangaben als ISO8601-Zeichenfolgen wie „YYYY-MM-DD HH:MM:SS
“. Dieses Format ist für Menschen lesbar und mit vielen Tools kompatibel, kann aber bei Vergleichen etwas langsamer sein. -
REAL
: Speichert Daten als Julianische Tageszahlen (Gleitkommazahlen), was für Datumsberechnungen und Bereichsabfragen nützlich ist, aber nicht so leicht zu lesen. -
INTEGER
: Speichert Unix-Zeitstempel, also Sekunden seit dem 01.01.1970. Es ist super zum Speichern und für schnelle numerische Vergleiche, aber nicht für Menschen lesbar.
Wenn du mit Zeitdaten arbeitest, kannst du diese Funktionen nutzen: date()
, time()
, datetime()
, julianday()
und strftime()
zum Parsen, Formatieren und Konvertieren zwischen Zeitformaten.
Binäre und numerische Randfälle
SQLite kann auch Binärdaten über die Speicherklasse „ BLOB
“ verarbeiten. BLOBs sind super, um Bilder, Dateien oder andere Daten, die kein Text sind, zu speichern. Wenn du mit Daten vom Typ „ BLOB
” arbeitest, solltest du immer parametrisierte Abfragen oder Bindungen verwenden, um BLOBs einzufügen. Du kannst auch große Binärdateien extern speichern und mit Pfaden oder Hashes darauf verweisen.
SQLite kann zwar Ganzzahlen bis zu 8 Byte speichern (-2^63
bis 2^63-1
), aber Werte außerhalb dieses Bereichs werden möglicherweise stillschweigend in den Datentyp „ REAL
” umgewandelt. Dadurch können die Werte an Genauigkeit verlieren. SQLite hat keine strenge Kontrolle für die Dezimalgenauigkeit, also nimm immer externe Bibliotheken oder Anwendungslogik für Finanzdaten, wenn du Genauigkeit brauchst.
Struktur mit STRICT-Tabellen durchsetzen
Anders als das traditionelle flexible Typisierungssystem hat SQLite in Version 3.37.0 Tabellen mit flexiblen Typen ( STRICT
) eingeführt. Eine Tabelle vom Typ „ STRICT
“ hat einen strengeren Typisierungsmodus, der genau die richtigen Typen für die Spaltenwerte verlangt. Wenn du eine Tabelle als „ STRICT
“ deklarierst, muss jeder eingegebene Wert mit dem deklarierten Typ der Spalte übereinstimmen, sonst kommt es zu einem Fehler.
Der Modus „ STRICT
“ macht die Daten konsistenter und besser validiert. Außerdem hilft es, Typfehler schon früh in der Entwicklung zu erkennen und macht das Verhalten von SQLite berechenbarer, so dass es eher wie bei herkömmlichen RDBMS ist.
Bevor du „ STRICT
“ als Entwickler nutzt, solltest du wissen, dass es nur in SQLite 3.37.0 und höher verfügbar ist. Außerdem kannst du die Typisierung „ STRICT
“ und „manifest“ nicht in derselben Tabelle mischen.
Überlegungen zu Leistung und Design
SQLite hat ein flexibles Typsystem, das sich auf die Leistung auswirkt. SQLite verwendet eine variable Längenkodierung für die Werte „ INTEGER
“ und „ REAL
“, was bedeutet, dass kleinere Zahlen weniger Bytes benötigen. Das hilft, die Datenbankgröße zu minimieren, vor allem bei großen Datensätzen.
Die Typaffinität beeinflusst auch, wie Werte gespeichert und verglichen werden. Unterschiedliche Typen in einer Spalte können die Effizienz von Indizes beeinträchtigen, weil SQLite bei Suchvorgängen zusätzliche Typkonvertierungen durchführen muss, was die Abfragen verlangsamt.
Überleg dir mal die folgenden Strategien, um effiziente Schemata und Abfragen zu entwerfen.
-
Sag klar, welche Spalten zusammengehören, damit du nicht so oft Typkonvertierungen machen musst.
-
Wenn du sichergehen willst, dass alles wie erwartet läuft, solltest du in Abfragen explizites Casting verwenden, wenn Typkonvertierungen nötig sind.
-
Verwende „
INTEGER
“ für numerische IDs und Zähler, „REAL
“ für Gleitkommadaten und „TEXT
“ für Zeichenfolgen. -
Vermeide es, verschiedene Datentypen in derselben Spalte zu mischen, um Probleme beim Sortieren und Vergleichen zu vermeiden.
-
Schemas normalisieren, um unnötige Typumwandlungen oder doppelte Daten zu vermeiden.
-
Nutze „
EXPLAIN QUERY PLAN
“, um die Leistung zu checken und Abfragen zu optimieren.
Beispiele für SQLite-Datentypen mit Schemamustern
Die folgenden Tabellen zeigen, wie man die Datentypen, Typanpassungen und Einschränkungen von SQLite clever nutzen kann.
Benutzertabelle mit Booleschen Simulation und Einschränkungen
Im folgenden Beispiel:
-
is_active
simuliert einen Booleschen Wert mit „INTEGER
” und einer Einschränkung „CHECK
”. -
TEXT
Der Datentyp sorgt für einheitliche Zeichenfolgenwerte, während „INTEGER
“ für boolesche Logik gedacht ist.
-- Create users table with auto-incrementing rowid and enforce uniqueness
CREATE TABLE users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)) -- Simulates boolean
);
Tabelle sortiert nach Zeitangaben und numerischer Genauigkeit
In der folgenden Abfrage nutzt „ total
“ „ NUMERIC
“ für Währungswerte, um die Genauigkeit auf Anwendungsebene zu regeln. „ order_date
“ wird als „ TEXT
“ im ISO-8601-Format gespeichert, was die Lesbarkeit und Kompatibilität mit Datumsfunktionen sicherstellt.
-- Create orders table with row identifier and foreign key in practice
-- ISO date format "YYYY-MM-DD"
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
total NUMERIC NOT NULL,
order_date TEXT NOT NULL CHECK (length(order_date) = 10) );
Tabelle mit Dateien mit Binär- und Typüberprüfungen
In der folgenden Abfrage speichert „ content
“ mit „ BLOB
“ rohe Binärdaten. „ uploaded_at
“ speichert die Zeit als „ INTEGER
“, um Speicherplatz zu sparen und die Leistung zu verbessern.
-- Create files table with raw binary data and Unix timestamp (epoch seconds)
CREATE TABLE files (
file_id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
content BLOB NOT NULL,
uploaded_at INTEGER NOT NULL
);
Du kannst den Typ zur Laufzeit mit der folgenden Abfrage überprüfen:
SELECT typeof(uploaded_at), typeof(content) FROM files;
Ich empfehle dir unseren Kurs „Einführung in relationale Datenbanken in SQL“, ummehr über Datenstrukturen und das Erstellen von Tabellen in Datenbanken zu erfahren.
SQLite mit anderen Datenbanken vergleichen
Das Typsystem von SQLite ist flexibler als das von klassischen RDBMS wie MySQL oder PostgreSQL. Die Tabelle unten zeigt, wie SQLite im Vergleich zu diesen Datenbanken abschneidet.
Funktion |
SQLite |
MySQL |
PostgreSQL |
Tippmodell |
Dynamisch (manifeste Typisierung, Typaffinität) |
Streng (mit ein paar Macken, wie stiller Zwang) |
Streng (starke Typisierung wird durchgesetzt) |
Durchsetzung des deklarierten Typs |
Wird nicht erzwungen (es sei denn, der Modus „ |
Meistens durchgesetzt, aber ein bisschen Flexibilität ist okay. |
Vollständig durchgesetzt |
Boolesche Verarbeitungen |
Simuliert wie |
Hat den Typ „ |
Nativer Typ „ |
Datums-/Zeitangaben |
Manuell gespeichert als „ |
Native Datums-/Zeit-Typen |
Native und umfangreiche Zeitangaben |
Indexempfindlichkeit |
Empfindlich gegenüber gemischten Typen in einer Spalte |
Zuverlässig durch Typisierung |
Zuverlässig durch Typisierung |
Flexibilität des Schemas |
Sehr hoch (standardmäßig wenige Einschränkungen) |
Mäßig |
Gut organisiert und stabil |
Umstellung auf SQLite |
Möglicherweise musst du die strenge Typisierung lockern. |
Im Allgemeinen reibungslos mit kleinen Anpassungen |
Muss vielleicht vereinfacht werden oder es kann zu Genauigkeitsverlusten kommen. |
Umzug von SQLite |
Muss die Daten aufräumen und die Eingaben normalisieren. |
Die Daten müssen strengeren Typisierungsregeln entsprechen. |
Braucht Datenkonformität und ein strengeres Schema. |
Fazit
SQLite hat ein flexibles Typsystem, das bei durchdachter Nutzung verschiedene Vorteile bietet. Entwickler können Schemata entwerfen, die Anpassungsfähigkeit und Zuverlässigkeit in Einklang bringen, indem sie Speicherklassen, Typaffinität und Manifesttypisierung verstehen. Funktionen wie Tabellen mit „ STRICT
“, einheitliche Typenverwendung und Normalisierung helfen dabei, die Struktur dort zu stärken, wo es nötig ist.
Als nächsten Schritt empfehle ich die Kurse „Explorative Datenanalyse in SQL“ und „Datenbearbeitung in SQL“ , um zu lernen, wie man Daten verschiedener Datentypen mit fortgeschrittenen SQL-Abfragen analysiert.
Häufig gestellte Fragen
Was sind die Speicherklassen von SQLite?
SQLite-Speicherklassen umfassen „ NULL
“, „ INTEGER
“, „ REAL
“, „ TEXT
“ und „ BLOB
“.
Kann eine Spalte mehrere Datentypen speichern?
Ja, außer die Tabelle als „ STRICT
“ deklariert ist, kann jede Spalte Werte unterschiedlicher Typen enthalten.
Was ist Typanomalie in SQLite?
Der Typ „Typ-Affinität“ ist ein empfohlener Typ für eine Spalte, basierend auf ihrem deklarierten Typ. SQLite versucht, eingefügte Werte in den Affinitätstyp der Spalte zu konvertieren, setzt dies aber nicht strikt durch.
Wie werden BOOLEAN-Werte in SQLite verarbeitet?
SQLite hat keinen eigenen Typ für den Wert „ BOOLEAN
“. Boolesche Werte werden normalerweise als „ INTEGER
“, „ 0
“ (FALSE) oder „ 1
“ (TRUE) gespeichert, wobei die Affinität „ NUMERIC
“ verwendet wird.
Wie kann ich überprüfen, wie ein Wert gespeichert ist?
Mit der Funktion „ typeof()
“ kannst du die tatsächliche Speicherklasse eines Werts überprüfen.