Menu

SQLite Foreign Keys: REFERENCES, ON DELETE & PRAGMA

So funktionieren Foreign Keys in SQLite: REFERENCES richtig deklarieren, Enforcement per PRAGMA aktivieren und die passende ON DELETE-Strategie wählen.

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

Ein Foreign Key ist ein Verweis zwischen Tabellen

Ein sqlite foreign key (Fremdschlüssel) ist eine Spalte in einer Tabelle, deren Wert mit einer Zeile in einer anderen Tabelle übereinstimmen muss. Auf diese Weise drückt eine relationale Datenbank aus: „Diese Zeile in posts gehört zu jener Zeile in authors" — und zwar ohne Name und E-Mail des Autors in jeden Beitrag zu kopieren.

Hier das kleinstmögliche Beispiel — eine Eltern- und eine Kindtabelle, verbunden über einen Fremdschlüssel:

author_id INTEGER REFERENCES authors(id) ist hier die komplette Foreign-Key-Deklaration. Sie sagt aus: Diese Spalte enthält eine id aus der Tabelle authors. Damit weiß die Datenbank, dass beide Tabellen miteinander verknüpft sind – und wenn die Prüfung aktiv ist, lehnt sie Inserts ab, die auf nicht existierende Autoren zeigen.

Foreign Keys sind in SQLite standardmäßig deaktiviert

Das ist der wichtigste Fakt rund um SQLite Foreign Keys, und er überrascht wirklich jeden: SQLite versteht zwar die REFERENCES-Klausel beim Parsen, erzwingt sie aber nur, wenn du es explizit verlangst. Der Grund liegt in der Abwärtskompatibilität – ältere Datenbanken entstanden, bevor es das Feature überhaupt gab.

So sieht es aus, wenn die Prüfung aus ist:

Die verwaiste Zeile wurde anstandslos eingefügt. Damit der Fremdschlüssel-Schutz tatsächlich greift, musst du zu Beginn jeder Verbindung PRAGMA foreign_keys = ON; ausführen:

Jetzt schlägt das Insert mit FOREIGN KEY constraint failed fehl. Das Pragma gilt pro Verbindung, nicht pro Datenbank – die Einstellung wird also nicht in der Datei gespeichert. Jede Anwendung, jede CLI-Session und jedes Test-Fixture muss es selbst setzen. Im Produktivcode ist es üblich, direkt nach dem Öffnen einer Verbindung PRAGMA foreign_keys = ON; auszuführen.

Was die REFERENCES-Klausel wirklich voraussetzt

Die referenzierte Spalte muss entweder ein PRIMARY KEY sein oder eine UNIQUE-Bedingung haben. Nur so kann SQLite garantieren, dass ein Lookup eindeutig ist. Auch die Datentypen sollten zusammenpassen – SQLite nimmt es zwar mit Typen nicht so genau, aber wer hier mischt, handelt sich Überraschungen ein.

Den Fremdschlüssel kannst du auf zwei Arten schreiben. Inline an der Spalte:

Oder als eigenständige Tabellen-Constraint – das ist nötig, sobald sich der Fremdschlüssel über mehrere Spalten erstreckt:

Beide Schreibweisen erzeugen exakt denselben Constraint. Nimm einfach die Variante, die sich für deine Tabelle besser liest.

ON DELETE: Was passiert mit den Kind-Datensätzen?

Sobald du eine Parent-Zeile löschst, muss SQLite entscheiden, was mit den Kind-Zeilen passieren soll, die noch darauf verweisen. Diese Strategie legst du über ON DELETE fest:

Mit dem Löschen von Ada wurden auch ihre beiden Posts entfernt. Folgende Optionen stehen zur Verfügung:

  • CASCADE — löscht die Kindzeilen gleich mit. Sinnvoll bei "besitzartigen" Daten, etwa Posts unter einem Autor oder Positionen unter einer Bestellung.
  • SET NULL — setzt die FK-Spalte auf NULL. Praktisch, wenn die Kindzeilen ohne Eltern weiterleben sollen (z. B. werden Kommentare gelöschter Nutzer zu anonymen Kommentaren).
  • SET DEFAULT — schreibt den deklarierten Defaultwert in die FK-Spalte.
  • RESTRICT — blockiert das Löschen, falls Kindzeilen existieren. Schlägt sofort beim Statement fehl.
  • NO ACTION — der Standard. In der Praxis fast identisch zu RESTRICT (die Prüfung wird auf den Commit-Zeitpunkt verschoben, aber das Ergebnis ist dasselbe: hängende Kindzeilen sind nicht erlaubt).

ON UPDATE funktioniert analog, falls sich der Schlüssel des Elternteils ändert — Primärschlüssel werden allerdings selten aktualisiert.

Foreign key constraint failed: Was steckt dahinter?

Diesen Fehler bekommst du in zwei Situationen zu sehen. Erstens: beim Einfügen oder Aktualisieren einer Kindzeile mit einem Wert, zu dem es keine passende Elternzeile gibt:

sqlite> INSERT INTO posts (title, author_id) VALUES ('Stray', 999);
Runtime error: FOREIGN KEY constraint failed

Entweder existiert der Autor mit der ID 999 nicht, oder du hast die Spaltentypen vertauscht. Leg zuerst den Parent-Datensatz an oder korrigiere den Wert.

Zweitens: Das Löschen (oder Ändern) eines Parent-Datensatzes, zu dem noch Kinder existieren, schlägt fehl, wenn der Foreign Key auf RESTRICT oder NO ACTION steht:

sqlite> DELETE FROM authors WHERE id = 1;
Runtime error: FOREIGN KEY constraint failed

Entweder löschst du die Kind-Datensätze zuerst, oder du änderst den Fremdschlüssel auf ON DELETE CASCADE bzw. SET NULL – falls Kaskadierung tatsächlich das gewünschte Verhalten ist.

Es gibt noch einen selteneren Verwandten: den FOREIGN KEY mismatch. Dieser Fehler tritt auf, wenn die referenzierte Spalte weder Primärschlüssel noch UNIQUE ist oder wenn die Spaltenanzahl nicht zusammenpasst. Das ist ein Schemafehler, kein Datenfehler.

Fremdschlüssel zu bestehenden Tabellen hinzufügen

Das ALTER TABLE von SQLite ist eingeschränkt – du kannst zwar eine neue Spalte mitsamt Fremdschlüssel hinzufügen, aber keinen Fremdschlüssel an eine bereits vorhandene Spalte nachträglich anhängen. Der übliche Weg ist daher der Umweg über Umbenennen und Neuaufbau:

Das Muster sieht so aus: Foreign Keys deaktivieren, die neue Tabelle mit den gewünschten Constraints anlegen, Daten kopieren, alte Tabelle löschen, umbenennen. Das BEGIN/COMMIT macht das Ganze atomar. Am Ende schaltest du die Prüfung wieder an, und SQLite validiert alle bestehenden Zeilen gegen die neuen Constraints — falls Daten ungültig sind, ist die Transaktion aber längst durch. Wenn du dir unsicher bist, prüf also lieber vorher.

Lass nach der Migration noch PRAGMA foreign_key_check; laufen, um sicherzugehen, dass keine verwaisten Zeilen übrig sind.

Ein realistisches Schema

Jetzt alles zusammen — ein kleines Blog-Schema mit Eltern-, Kind-Tabellen und einer Verknüpfungstabelle für Tags (n:m-Beziehung):

Drei Dinge sind hier wichtig. author_id ist NOT NULL – jeder Post braucht also zwingend einen Autor. Der Fremdschlüssel von posts → authors ist mit Cascade versehen: Löschst du einen Autor, verschwinden auch alle seine Posts. Die Verknüpfungstabelle post_tags kaskadiert auf beiden Seiten, sodass beim Löschen eines Posts oder Tags die Verknüpfungseinträge automatisch mitaufgeräumt werden.

Gewohnheiten, die dir später viel Ärger ersparen

  • Setze PRAGMA foreign_keys = ON; bei jeder Verbindung. Bau das fest in die Routine ein, mit der deine Anwendung die Datenbank öffnet – verlass dich nicht darauf, dass du daran denkst.
  • Leg einen Index auf die FK-Spalte an. SQLite indiziert zwar den Schlüssel der Elterntabelle automatisch, aber nicht den der Kindtabelle. Und ON DELETE CASCADE macht bei jedem Löschen in der Elterntabelle einen Lookup im Kind.
  • Wähl ON DELETE bewusst. Der Standardwert (NO ACTION) ist zwar sicher, sorgt aber dafür, dass du beim Aufräumen ständig auf FOREIGN KEY constraint failed stößt. Überleg dir, was passieren soll, und schreib es explizit hin.
  • Lass nach Migrationen oder Bulk-Imports einmal PRAGMA foreign_key_check; laufen, um Waisen-Datensätze zu erwischen, bevor sie zu Bugs werden.

Weiter geht's: INNER JOIN

Fremdschlüssel beschreiben die Beziehung – mit Joins fragst du tatsächlich über sie hinweg ab. Auf der nächsten Seite geht's um INNER JOIN: Zeilen aus verknüpften Tabellen kombinieren und gezielt die gewünschten Spalten aus beiden zurückbekommen.

Häufig gestellte Fragen

Wie lege ich in SQLite einen Foreign Key an?

Hänge im CREATE TABLE an die Spaltendefinition eine REFERENCES andere_tabelle(spalte)-Klausel an. Mit author_id INTEGER REFERENCES authors(id) zeigt author_id zum Beispiel auf eine Zeile in authors. Die referenzierte Spalte muss dabei PRIMARY KEY sein oder einen UNIQUE-Constraint haben.

Warum greifen meine Foreign Keys in SQLite nicht?

SQLite parst die Foreign-Key-Deklarationen zwar, erzwingt sie aber erst, wenn du das Enforcement explizit einschaltest. Setze dazu am Anfang jeder Verbindung PRAGMA foreign_keys = ON;. Die Einstellung gilt pro Connection und wird nicht in der Datenbank gespeichert – jede Library und auch die CLI muss sie also bei jedem Verbindungsaufbau erneut setzen.

Was macht ON DELETE CASCADE in SQLite?

ON DELETE CASCADE weist SQLite an, abhängige Kindzeilen automatisch mitzulöschen, sobald die Elternzeile entfernt wird. Alternativen sind RESTRICT (Löschen wird blockiert), SET NULL (FK-Spalte wird auf NULL gesetzt), SET DEFAULT und NO ACTION (Default – verhält sich in der Praxis wie RESTRICT). Die Wahl hängt davon ab, ob die Kindzeilen ohne ihren Parent überhaupt sinnvoll sind.

Wie behebe ich 'foreign key constraint failed' in SQLite?

Die Fehlermeldung heißt: Du hast eine Zeile eingefügt oder aktualisiert, deren Foreign-Key-Wert in der referenzierten Tabelle gar nicht existiert – oder du wolltest einen Parent löschen, der noch Kinder hat. Prüfe also vorher, ob die referenzierte Zeile wirklich da ist, oder richte ON DELETE CASCADE ein, wenn die Kindzeilen automatisch mit weg sollen.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S