DELETE entfernt Zeilen – mehr nicht
Mit DELETE löschst du Zeilen aus einer Tabelle. Die Tabelle selbst bleibt erhalten, das Schema wird nicht angefasst, und andere Tabellen sind ebenfalls unberührt (es sei denn, du hast Cascades eingerichtet). Die Syntax ist kurz und knackig:
DELETE FROM users WHERE id = 2; sucht alle Zeilen, die zur Bedingung passen, und entfernt sie. Die übrigen zwei Zeilen bleiben unberührt. Die Tabelle selbst existiert weiterhin – du kannst nach wie vor neue Zeilen einfügen.
Das Modell dahinter: DELETE ist im Grunde ein SELECT, das die gefundenen Zeilen wegwirft, statt sie zurückzugeben.
Die WHERE-Klausel macht die ganze Arbeit
Jedes ernstzunehmende DELETE steht und fällt mit seiner WHERE-Klausel. Stimmt sie, löschst du genau das, was du löschen wolltest. Stimmt sie nicht, fliegt deutlich mehr raus – im schlimmsten Fall die komplette Tabelle.
Beide unveröffentlichten Entwürfe ohne Aufrufe sind weg. Die veröffentlichten Zeilen bleiben erhalten, weil die Bedingung auf sie nicht zutrifft. Im WHERE kannst du jeden beliebigen Ausdruck verwenden — IN, LIKE, BETWEEN, Subqueries, Kombinationen aus AND/OR.
Eine Angewohnheit, die sich lohnt: Bevor du ein DELETE absetzt, führe dieselbe WHERE-Klausel erst einmal als SELECT aus.
-- Vorschau, was gelöscht wird:
SELECT * FROM posts WHERE published = 0 AND views = 0;
-- Mit den Zeilen zufrieden? Jetzt löschen:
DELETE FROM posts WHERE published = 0 AND views = 0;
Dieser zweistufige Ablauf hat schon mehr Datenbanken gerettet als sämtliche Backup-Tools zusammen.
DELETE ohne WHERE leert die ganze Tabelle
Lässt du das WHERE weg, löscht DELETE jede einzelne Zeile:
Die Tabelle ist zwar leer, existiert aber weiterhin. SQLite kennt kein TRUNCATE – das Pendant lautet DELETE FROM tabelle;, wobei SQLite intern eine sogenannte "Truncate-Optimierung" anwendet: Statt jede Zeile einzeln zu entfernen, werden alle Pages auf einen Schlag verworfen. Schnell, aber trotzdem eine transaktionale Operation, die sich per Rollback rückgängig machen lässt – die ideale SQLite-Truncate-Alternative.
Falls du AUTOINCREMENT auf dem Primärschlüssel nutzt, wird der Zähler dabei nicht automatisch zurückgesetzt. Damit die IDs wieder bei 1 beginnen, musst du zusätzlich die Sequence-Zeile leeren:
DELETE FROM log;
DELETE FROM sqlite_sequence WHERE name = 'log';
Bei einem schlichten INTEGER PRIMARY KEY (also ohne AUTOINCREMENT) vergibt SQLite freigewordene IDs ohnehin neu – der Schritt erübrigt sich also.
Mehrere bestimmte Zeilen löschen
Um eine bekannte Menge an Zeilen zu entfernen, ist IN die sauberste Variante eines sqlite delete:
Du kannst ein DELETE auch über eine Subquery steuern – praktisch, wenn sich die zu löschenden Zeilen über einen Join oder eine andere Tabelle ergeben:
SQLite kennt keine DELETE ... JOIN-Syntax wie MySQL – mit einer Subquery im WHERE kommst du aber zum gleichen Ergebnis.
RETURNING: gelöschte Zeilen zurückgeben
Hängst du RETURNING an, bekommst du die gelöschten Datensätze als Ergebnismenge zurück – genau wie bei einem SELECT:
Du bekommst zu jeder gelöschten Zeile die id und die email zurück. Das ist Gold wert, und zwar für:
- Saubere Logs darüber, was genau entfernt wurde.
- Undo-Funktionen (die zurückgegebenen Zeilen einfach zwischenspeichern).
- Die Bestätigung in einem einzigen Roundtrip, dass wirklich die erwarteten Zeilen betroffen waren.
RETURNING funktioniert übrigens nicht nur hier, sondern auch bei INSERT und UPDATE. Eine ausführliche Erklärung findest du auf der eigenen Seite dazu.
SQLite ON DELETE CASCADE für verknüpfte Zeilen
Wenn Eltern- und Kindtabelle über einen Fremdschlüssel verbunden sind und du den Eltern-Datensatz löschst, bleiben die Kinder als Waisen zurück – es sei denn, du sagst SQLite ausdrücklich, dass es kaskadieren soll:
Wenn der Autor gelöscht wird, verschwinden automatisch auch seine Bücher. Ohne ON DELETE CASCADE würde dasselbe DELETE entweder durchgehen und verwaiste Bücher zurücklassen (wenn Foreign Keys deaktiviert sind) oder mit einem Constraint-Fehler abbrechen (wenn sie aktiv sind).
Die große Stolperfalle: Foreign Keys sind in SQLite standardmäßig deaktiviert. Du musst für jede Verbindung PRAGMA foreign_keys = ON; ausführen. Ist das Pragma nicht gesetzt, wird ON DELETE CASCADE stillschweigend ignoriert – die Bücher bleiben einfach stehen. Die meisten Treiber setzen das entweder automatisch oder bieten eine entsprechende Option an; schau am besten in der Doku deines Treibers nach.
Weitere Cascade-Optionen, die du kennen solltest: ON DELETE SET NULL (setzt den Fremdschlüssel auf NULL), ON DELETE RESTRICT (verhindert das Löschen, solange Kindzeilen existieren) und ON DELETE NO ACTION (der Standard – verhält sich in den meisten Fällen wie RESTRICT).
DELETE mit LIMIT (Compile-Time-Option)
Manche SQLite-Builds unterstützen DELETE ... LIMIT – praktisch, um riesige Tabellen schrittweise in Batches abzuarbeiten:
DELETE FROM logs
WHERE created_at < '2024-01-01'
ORDER BY created_at
LIMIT 1000;
Dafür muss SQLite mit SQLITE_ENABLE_UPDATE_DELETE_LIMIT kompiliert sein. Bei den offiziellen Binaries und den meisten Sprachbindings (sqlite3 in Python, better-sqlite3 in Node) ist das Flag aktiv. Falls bei dir nicht, kommt ein Syntaxfehler – dann hilft eine Subquery als Workaround:
DELETE FROM logs
WHERE id IN (
SELECT id FROM logs
WHERE created_at < '2024-01-01'
ORDER BY created_at
LIMIT 1000
);
Batched Deletes halten die Transaktionen klein – das ist wichtig, wenn parallel noch andere Verbindungen lesend auf die Datenbank zugreifen.
Große Löschvorgänge in eine Transaktion packen
Ein DELETE läuft implizit in einer Transaktion – entweder verschwinden alle passenden Zeilen oder keine. Wenn du aber gleich richtig viel löschen willst, lohnt sich eine explizite Transaktion: So kannst du per ROLLBACK zurück, falls dir etwas spanisch vorkommt:
ROLLBACK macht das DELETE komplett rückgängig. In einer echten Sitzung würdest du COMMIT ausführen, sobald die Anzahl plausibel aussieht. Transaktionen sind außerdem deutlich schneller, wenn du viele Zeilen einzeln löschst — packst du die Schleife in BEGIN/COMMIT, sparst du dir ein fsync pro DELETE.
Was nicht gelöscht wird
Ein paar typische Stolperfallen, die man kennen sollte:
DELETE FROM table;leert die Tabelle, entfernt sie aber nicht. Um die Tabelle selbst loszuwerden, brauchst duDROP TABLE table;.DELETEverkleinert die Datenbankdatei nicht. Die Seiten werden lediglich zur Wiederverwendung freigegeben. Um den Speicherplatz tatsächlich zurückzugewinnen, führeVACUUM;aus (siehe Performance-Kapitel).- Beim Löschen einer Zeile werden zugehörige Zeilen in anderen Tabellen nicht automatisch mitgelöscht — das passiert nur, wenn
ON DELETE CASCADEgesetzt ist und Foreign Keys aktiviert sind. - Ein
DELETE, das auf keine Zeile passt, ist kein Fehler. Es ist ein erfolgreiches Statement mitchanges() = 0. Wenn du wissen willst, ob wirklich etwas gelöscht wurde, prüfe den Zähler.
Als Nächstes: UPSERT
Oft willst du eigentlich gar nicht löschen — sondern eine Zeile einfügen, falls sie neu ist, oder aktualisieren, falls sie schon existiert. SQLite nennt das UPSERT, und mit der ON CONFLICT-Klausel wird daraus ein einziges Statement statt dreier. Darum geht's im nächsten Abschnitt.
Häufig gestellte Fragen
Wie löscht man eine einzelne Zeile in SQLite?
Mit DELETE FROM tabellenname WHERE bedingung;. Die WHERE-Klausel entscheidet, welche Zeilen rausfliegen. Beispiel: DELETE FROM users WHERE id = 7; entfernt genau den User mit der id 7. Ohne WHERE ist die komplette Tabelle leer — also Vorsicht.
Wie lösche ich alle Zeilen einer SQLite-Tabelle?
Einfach DELETE FROM tabellenname; ohne WHERE ausführen. SQLite kennt kein TRUNCATE — DELETE ohne Filter ist das Pendant, und SQLite optimiert das intern (die sogenannte 'Truncate Optimization'). Wenn auch die AUTOINCREMENT-Zähler zurückgesetzt werden sollen, musst du anschließend noch aus sqlite_sequence löschen.
Kann SQLite Löschungen auf verknüpfte Tabellen kaskadieren?
Ja, sofern du ON DELETE CASCADE am Foreign Key definierst und die Foreign Keys per PRAGMA foreign_keys = ON; aktiviert hast. In SQLite sind Foreign Keys standardmäßig aus — ohne das Pragma werden Cascades stillschweigend ignoriert.
Wie sehe ich, welche Zeilen tatsächlich gelöscht wurden?
Häng eine RETURNING-Klausel an: DELETE FROM users WHERE active = 0 RETURNING id, email; liefert die gelöschten Zeilen zurück, genau wie ein SELECT. Praktisch für Logging, Undo-Funktionen oder einfach um zu prüfen, dass wirklich nur das weg ist, was weg sollte.