Menu

SQLite CHECK Constraints: Daten auf Tabellenebene prüfen

So setzt du CHECK-Constraints in SQLite richtig ein: Prüfungen auf einzelnen Spalten, Regeln über mehrere Spalten hinweg, benannte Constraints und die typischen Stolperfallen.

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

Ein CHECK Constraint ist eine Regel, die jede Zeile erfüllen muss

Ein CHECK Constraint in SQLite ist ein boolescher Ausdruck, den du an eine Tabelle hängst. Bei jedem INSERT und UPDATE wertet SQLite den Ausdruck aus – ergibt er FALSE, schlägt die Operation fehl. So verankerst du eine fachliche Regel direkt im Schema: „Preis darf nicht negativ sein" oder „Status muss einer von drei erlaubten Werten sein".

Die ersten beiden Zeilen landen in der Tabelle. Die dritte löst CHECK constraint failed aus und wird abgelehnt – sie kommt gar nicht erst in der Tabelle an. Der Constraint setzt die Regel für jeden Schreibzugriff durch: egal ob aus deiner App, einem Migrationsskript oder weil jemand auf der CLI herumprobiert.

CHECK auf Spaltenebene vs. Tabellenebene

Einen CHECK kannst du an zwei Stellen platzieren: direkt hinter einer Spaltendefinition (Spaltenebene) oder nach allen Spalten (Tabellenebene). Funktional sind beide identisch – es geht nur darum, was sich besser liest.

Die erste Buchung wird eingefügt. Die zweite scheitert — das Ende liegt vor dem Start. Regeln, die nur eine einzelne Spalte betreffen, schreibt man besser auf Spaltenebene; sobald mehrere Spalten miteinander verglichen werden, gehört der Constraint auf Tabellenebene.

Werte auf eine feste Liste einschränken

Ein klassischer Anwendungsfall ist es, eine Spalte auf eine feste Auswahl von Werten zu begrenzen. Da SQLite keinen eigenen Enum-Typ kennt, ist CHECK ... IN (...) die übliche Lösung:

Die dritte Zeile scheitert — 'pending' steht nicht auf der erlaubten Liste. Wenn du später einen neuen Status hinzufügen willst, musst du die Tabelle neu aufbauen (dazu gleich mehr). Überleg also kurz, bevor du die Liste festzurrst. Für wirklich feststehende Wertemengen wie Rollennamen oder Bestellzustände ist genau das aber der passende Constraint.

Benannter CHECK Constraint in SQLite

Standardmäßig ist ein Constraint anonym. Die Fehlermeldung sagt dann nur CHECK constraint failed plus den Ausdruck — bei einem einzigen CHECK pro Tabelle reicht das, bei fünf wird's schnell unübersichtlich. Mit CONSTRAINT vergibst du einen Namen:

Jetzt steht der Name des Constraints in der Fehlermeldung – du siehst also auf einen Blick, welche Regel verletzt wurde. Ein paar Zeichen mehr für den Namen, und die Investition zahlt sich beim ersten Produktionsfehler bereits aus.

CHECK und NULL: die Stolperfalle

Ein CHECK-Constraint geht durch, wenn der Ausdruck TRUE oder NULL ergibt. Fehlschlagen tut er nur bei einem expliziten FALSE. Klingt erstmal komisch, ergibt aber Sinn, sobald man sich klarmacht, dass praktisch jeder Vergleich mit NULL wieder NULL liefert – und eben nicht TRUE oder FALSE.

Die Zeile mit NULL wird ohne Murren aufgenommen — NULL >= 0 ergibt nämlich NULL und nicht FALSE, deshalb schlägt der CHECK nicht an. Wenn du wirklich sowohl negative Werte als auch fehlende Einträge verhindern willst, kombinierst du NOT NULL mit dem CHECK:

Jetzt scheitert das INSERT schon am NOT NULL-Constraint, bevor der CHECK überhaupt zum Zuge kommt. Beide Constraints ergänzen sich also gut: NOT NULL fängt fehlende Werte ab, CHECK prüft die Form.

Nützliche eingebaute Funktionen im CHECK-Constraint

Im Ausdruck lassen sich fast alle eingebauten Funktionen von SQLite verwenden. Ein paar Klassiker, die immer wieder auftauchen:

Drei Fehlschläge: eine kaputte E-Mail-Form, ein zu kurzer Username und ein klein geschriebener Ländercode. LIKE reicht für einfache Muster; length(), upper(), lower() und auch Arithmetik sind alle erlaubt. Wichtig ist nur: Der Ausdruck muss deterministisch bleiben — wer random() oder current_timestamp in einen CHECK packt, baut sich Regeln, die von Zeile zu Zeile umkippen können, und das will man so gut wie nie.

SQLite CHECK vs Trigger

Sowohl CHECK als auch Trigger können fehlerhafte Daten abweisen, und am Anfang fragt man sich oft, wann man was nimmt. Als Faustregel:

  • CHECK, wenn die Regel nur von der gerade geschriebenen Zeile abhängt. Also: „diese Spalte verglichen mit jener Spalte", „dieser Wert innerhalb eines Bereichs", „dieser String passt auf ein Muster".
  • Trigger (genauer gesagt ein BEFORE INSERT/UPDATE-Trigger mit RAISE), wenn die Regel auf andere Zeilen, andere Tabellen zugreifen muss oder mehr braucht als einen einzelnen booleschen Ausdruck.

CHECK ist schneller, einfacher und steht direkt im Schema — wer sich CREATE TABLE ansieht, sieht auch sofort die Regel. Zum Trigger greift man erst, wenn CHECK das Gewünschte nicht ausdrücken kann.

CHECK Constraint mit ALTER TABLE löschen geht nicht

Das ist die eine unschöne Stelle. SQLite kennt kein ALTER TABLE ... DROP CONSTRAINT. Um einen CHECK zu entfernen oder zu ändern, muss man die Tabelle neu aufbauen:

BEGIN;

CREATE TABLE products_new (
    id    INTEGER PRIMARY KEY,
    name  TEXT NOT NULL,
    price REAL NOT NULL CHECK (price >= 0 AND price <= 1000000)
);

INSERT INTO products_new SELECT * FROM products;
DROP TABLE products;
ALTER TABLE products_new RENAME TO products;

COMMIT;

Pack das Ganze in eine Transaktion, damit ein Fehler mittendrin die Datenbank unangetastet lässt. Wenn andere Tabellen per Fremdschlüssel auf die Tabelle zeigen, die du gerade umbaust, wird der Tanz länger — foreign_keys deaktivieren, umbauen, wieder aktivieren, erneut prüfen. Das schauen wir uns später im Kurs im Migrations-Kapitel genauer an.

Als Nächstes: UNIQUE Constraints

CHECK prüft die Form der Werte innerhalb einer Zeile. Der nächste Constraint, UNIQUE, prüft Beziehungen zwischen Zeilen — er stellt sicher, dass keine zwei Zeilen denselben Wert in einer Spalte oder Spaltenkombination teilen. Darum geht's gleich im nächsten Abschnitt.

Häufig gestellte Fragen

Was ist ein CHECK-Constraint in SQLite überhaupt?

Ein CHECK-Constraint ist ein boolescher Ausdruck, der an eine Tabelle gebunden ist und für jede Zeile erfüllt sein muss. SQLite wertet ihn bei jedem INSERT und UPDATE aus und lehnt die Änderung ab, sobald der Ausdruck false ergibt. Das ist der einfachste Weg, eine Regel wie „Preis darf nicht negativ sein" durchzusetzen, ohne sie in der Anwendung zu codieren.

Kann ein CHECK-Constraint mehrere Spalten gleichzeitig prüfen?

Ja — schreib ihn dann als Constraint auf Tabellenebene statt direkt an eine Spalte zu hängen. Beispiel: CHECK (start_date <= end_date) hinter der Spaltenliste deklariert kann beide Spalten referenzieren. Technisch dürfen auch spaltenbasierte CHECKs auf andere Spalten zugreifen, aber sobald mehr als eine Spalte im Spiel ist, liest sich die Tabellen-Variante deutlich klarer.

Warum greift mein CHECK-Constraint bei NULL nicht?

CHECK gilt als erfüllt, wenn der Ausdruck true oder NULL ist — er schlägt nur fehl, wenn das Ergebnis explizit false ist. CHECK (age >= 0) lässt also einen NULL-Wert für age durch, weil NULL >= 0 eben NULL ergibt und nicht false. Wenn du NULL ebenfalls verbieten willst, ergänze ein NOT NULL neben dem CHECK.

Lassen sich CHECK-Constraints in SQLite nachträglich entfernen oder ändern?

Nicht direkt. SQLite kennt kein ALTER TABLE ... DROP CONSTRAINT. Du hast zwei Optionen: entweder das sqlite_schema per PRAGMA writable_schema direkt bearbeiten (heikel und nur für Fortgeschrittene), oder die Tabelle neu aufbauen — also eine neue Tabelle mit den gewünschten Constraints anlegen, Daten rüberkopieren, alte Tabelle droppen und die neue umbenennen. Wenn du deine Constraints benannt hast, ist das Rebuild-Skript hinterher deutlich lesbarer.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S