Menu

SQLite NOT NULL & DEFAULT: Spalten richtig absichern

Wie NOT NULL und DEFAULT in SQLite wirklich funktionieren – inklusive CURRENT_TIMESTAMP-Trick und den Stolperfallen, wenn man sie nachträglich hinzufügen will.

Diese Seite enthält ausführbare Editoren — bearbeiten, ausführen und Ausgabe sofort sehen.

Zwei Constraints, die ihr Geld wert sind

Die meisten Bugs aus einem schlampigen Schema lassen sich auf zwei Ursachen zurückführen: eine Spalte ist plötzlich NULL, obwohl niemand damit gerechnet hat, oder es fehlt ein Wert, den die Anwendung stillschweigend vorausgesetzt hat. Genau hier setzen NOT NULL und DEFAULT an – und sie sind quasi geschenkt, was den Aufwand angeht.

Eine Spalte ist Pflicht und hat keinen Fallback. Zwei haben einen Fallback. Beim Insert musste nur email angegeben werden – den Rest hat SQLite selbst eingesetzt. Damit ist das Feature im Kern auch schon erklärt – der Rest dieser Seite dreht sich um die Sonderfälle.

NOT NULL heißt „NULL wird abgelehnt, ohne Ausnahmen"

NOT NULL macht in SQLite genau das, was draufsteht: Jeder Versuch, ein NULL in die Spalte zu schreiben, schlägt fehl – egal ob du die Spalte beim INSERT weglässt (ohne Default) oder explizit NULL einträgst:

Die Fehlermeldung lautet:

Runtime error: NOT NULL constraint failed: posts.title

Dasselbe Ergebnis erhältst du, wenn du NULL direkt übergibst:

INSERT INTO posts (id, title) VALUES (1, NULL);
-- Laufzeitfehler: NOT NULL constraint failed: posts.title

Das ist die Abmachung. Wenn eine Spalte logisch zwingend erforderlich ist, markiere sie als NOT NULL – damit hast du eine ganze Klasse von Bugs ein für alle Mal vom Tisch. Kein Anwendungscode kann dann noch heimlich ein NULL an der Datenbank vorbeischmuggeln.

DEFAULT liefert einen Wert, wenn der Aufrufer keinen mitgibt

Der DEFAULT-Wert greift nur dann, wenn ein INSERT die Spalte gar nicht erst erwähnt. Ein explizit übergebenes NULL rettet er dir nicht:

Beim ersten Insert greift der Standardwert. Beim zweiten wird er überschrieben. Hättest du dagegen INSERT INTO tasks (title, status) VALUES ('x', NULL) geschrieben, würdest du den Fehler NOT NULL constraint failed zu sehen bekommen – die Spalte wurde nämlich explizit angegeben, also kommt der Default gar nicht erst zum Zug.

Das mentale Modell, das du dir merken solltest: DEFAULT springt ein, wenn eine Spalte gar nicht erst genannt wird. NOT NULL weist NULL-Werte zurück, egal wie sie reinkommen. Beides sind voneinander unabhängige Mechanismen, die sich aber gut kombinieren lassen.

SQLite Default als Ausdruck definieren

Der typische Fall ist ein einfacher Literalwert (DEFAULT 0, DEFAULT '', DEFAULT 'pending'), aber SQLite erlaubt zusätzlich einen Ausdruck in Klammern als Default. Genau so versiehst du Datensätze mit ihrem Erstellungszeitpunkt oder erzeugst eine zufällige ID:

Ein paar Dinge solltest du wissen:

  • Der Ausdruck wird bei jedem Insert ausgewertet, nicht einmalig beim Anlegen der Tabelle. Jede Zeile bekommt also ihren eigenen Zeitstempel und ihren eigenen Token.
  • CURRENT_TIMESTAMP, CURRENT_DATE und CURRENT_TIME sind die drei Spezial-Keywords, die ohne Klammern auskommen. Bei allem anderen brauchst du Klammern.
  • Der Ausdruck darf weder andere Spalten noch Subqueries referenzieren – er muss in sich abgeschlossen sein.

Wenn du eine Spalte optional halten, sie aber automatisch stempeln willst, sobald ein Wert vorhanden ist, lass das NOT NULL weg und behalte nur den Default. Soll sie hingegen Pflichtfeld und automatisch gestempelt sein, kombinierst du beides.

DEFAULT NULL ist erlaubt – und manchmal genau der Punkt

DEFAULT NULL zu schreiben hat denselben Effekt, wie gar keinen Standardwert zu setzen: Die Spalte ist NULL, wenn du keinen Wert lieferst. Trotzdem ist es sinnvoll, das ausdrücklich ins Schema zu schreiben, wenn du klarmachen willst, dass „kein Wert" der bewusst gewollte Ausgangszustand ist:

bio und avatar verhalten sich hier identisch. Das DEFAULT NULL bei bio ist im Grunde ein Kommentar in Form von Code – es signalisiert allen, die das Schema lesen, dass ein fehlender Bio-Eintrag völlig normal ist und nicht etwa vergessen wurde.

NOT NULL nachträglich zu einer bestehenden Tabelle hinzufügen

Hier wird's etwas frickelig. Das ALTER TABLE von SQLite ist bewusst eingeschränkt – ein ALTER COLUMN ... SET NOT NULL wie in Postgres gibt es schlicht nicht. Was tatsächlich geht, hängt davon ab, ob die Spalte bereits existiert oder nicht.

Bei einer komplett neuen Spalte funktioniert ADD COLUMN ... NOT NULL, allerdings musst du einen Standardwert mitgeben – sonst hätten alle bestehenden Zeilen plötzlich NULL in einer NOT NULL-Spalte, was natürlich nicht zusammenpasst:

Wenn du dasselbe ohne DEFAULT versuchst, bekommst du einen Fehler:

ALTER TABLE products ADD COLUMN sku TEXT NOT NULL;
-- Laufzeitfehler: Cannot add a NOT NULL column with default value NULL

Bei einer bestehenden Spalte gibt es keine direkte Änderung. Der übliche Weg ist der sogenannte Rebuild-Tanz: eine neue Tabelle mit der gewünschten Constraint anlegen, die Daten rüberkopieren, die alte Tabelle löschen und die neue umbenennen. Das schauen wir uns auf der Seite drop-and-alter-table genauer an – fürs Erste reicht es zu wissen, dass diese Einschränkung wirklich existiert, und du dein Schema entsprechend planen solltest.

Eine realistische Kombination

In produktiven Tabellen werden meistens beide Constraints zusammen eingesetzt, um abzubilden, „was die Anwendung als gegeben voraussetzt":

Wer dieses Schema von oben nach unten liest, kann ohne eine einzige Zeile Code erraten, was die Anwendung macht. customer ist Pflicht und hat keinen Fallback – der Aufrufer muss wissen, für wen die Bestellung ist. Betrag, Währung und Status haben alle sinnvolle Standardwerte, sodass selbst das simpelste Insert eine in sich stimmige Zeile erzeugt. notes ist optional. created_at wird von der Datenbank gefüllt, und das ist auch der einzige Ort, an dem das passieren sollte.

Genau darin liegt der Wert dieser Constraints: Sie machen aus stillschweigenden Annahmen Regeln, die die Datenbank selbst durchsetzt.

Typische Stolperfallen

Eine kurze Liste von Dingen, über die man immer wieder stolpert:

  • Ein explizites NULL hebelt DEFAULT aus. INSERT INTO t (col) VALUES (NULL) greift nicht auf den Standardwert zurück. Die Spalte muss in der Spaltenliste komplett fehlen.
  • Defaults mit Ausdrücken brauchen Klammern. DEFAULT CURRENT_TIMESTAMP funktioniert (das ist eines von drei Sonder-Schlüsselwörtern). DEFAULT lower(hex(randomblob(8))) dagegen nicht – also einklammern: DEFAULT (lower(hex(randomblob(8)))).
  • NOT NULL und der leere String sind nicht dasselbe. '' ist ein gültiger TEXT-Wert und löst das Constraint nicht aus. Wer auch leere Strings verbieten will, braucht dafür CHECK (siehe nächste Seite).
  • ADD COLUMN ... NOT NULL verlangt einen DEFAULT, der nicht NULL ist. Ohne das verweigert SQLite die Änderung.

Als Nächstes: CHECK-Constraints

NOT NULL und DEFAULT decken die Fälle „muss vorhanden sein" und „fülle automatisch aus, falls leer" ab. Für die nächste Validierungsebene – „muss positiv sein", „muss einer dieser Werte sein", „Enddatum muss nach Startdatum liegen" – bietet SQLite CHECK-Constraints. Damit lassen sich beliebige boolesche Ausdrücke formulieren, die jede Zeile erfüllen muss. Das ist Thema der nächsten Seite.

Häufig gestellte Fragen

Wie mache ich eine Spalte in SQLite zur Pflichtspalte?

Einfach NOT NULL an die Spaltendefinition hängen: email TEXT NOT NULL. Jedes INSERT oder UPDATE, das die Spalte auf NULL lassen würde, scheitert dann mit NOT NULL constraint failed. In Kombination mit einem DEFAULT hast du gleich einen Fallback, falls der Aufrufer nichts mitliefert.

Wie funktionieren Standardwerte (DEFAULT) in SQLite?

DEFAULT <wert> legt fest, welcher Wert eingesetzt wird, wenn ein INSERT keinen Wert für die Spalte angibt. Erlaubt sind Literale (DEFAULT 0, DEFAULT 'pending'), NULL oder Ausdrücke in Klammern wie DEFAULT (CURRENT_TIMESTAMP) oder DEFAULT (lower(hex(randomblob(8)))). Ausdrücke werden bei jedem Insert neu ausgewertet.

Warum kommt beim Insert der Fehler 'NOT NULL constraint failed'?

Du fügst eine Zeile ein, ohne einen Wert für eine NOT NULL-Spalte anzugeben, die auch keinen DEFAULT hat. Drei Optionen: die Spalte explizit ins INSERT aufnehmen, einen DEFAULT definieren oder die Constraint lockern. Achtung: Auch ein explizit übergebenes NULL löst den Fehler aus – NOT NULL lehnt Nulls grundsätzlich ab, egal woher sie kommen.

Kann ich NOT NULL nachträglich zu einer bestehenden Spalte hinzufügen?

Direkt nicht – ALTER TABLE ... ALTER COLUMN gibt es in SQLite schlicht nicht. Du hast zwei Wege: entweder eine neue Spalte mit NOT NULL DEFAULT <wert> anlegen (der Default ist Pflicht, weil sonst die bestehenden Zeilen die Constraint verletzen), oder die Tabelle neu aufbauen – neue Tabelle mit der Constraint anlegen, Daten rüberkopieren, alte Tabelle droppen, neue umbenennen.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S