DELETE
DELETE removes rows from a table. Like UPDATE, its WHERE clause decides which rows are affected, and leaving it out affects every row. This lesson also covers the alternatives: emptying tables quickly, and "soft" deletes that keep history.
Deleting specific rows
DELETE FROM order_items WHERE order_id = 5; SELECT COUNT(*) AS items_left_in_order_5 FROM order_items WHERE order_id = 5;
| items_left_in_order_5 |
|---|
| 0 |
Deleting with conditions and subqueries
DELETE FROM order_items WHERE order_id IN (SELECT id FROM orders WHERE status = 'cancelled'); DELETE FROM orders WHERE status = 'cancelled'; SELECT status, COUNT(*) AS orders FROM orders GROUP BY status;
| status | orders |
|---|---|
| delivered | 9 |
| pending | 3 |
| shipped | 2 |
Foreign keys protect related rows
DELETE FROM customers WHERE id = 1;
Error: FOREIGN KEY constraint failed
Ana has orders, and those orders reference her id. Deleting her would leave orphaned orders pointing at nobody, so the foreign-key constraint blocks it. You'd need to delete or reassign her orders first — or define the key with ON DELETE CASCADE (lesson 43). (SQLite only enforces foreign keys when PRAGMA foreign_keys = ON, which this course's editors switch on.)
DELETE FROM customers WHERE id = 10; SELECT COUNT(*) AS customers FROM customers;
| customers |
|---|
| 9 |
Julia has no orders, so she can be deleted.
Deleting everything
DELETE FROM order_items; SELECT COUNT(*) AS remaining FROM order_items;
| remaining |
|---|
| 0 |
Without WHERE, all rows go; the table itself remains. Most databases also have TRUNCATE TABLE order_items, which empties a table much faster for large tables (it doesn't delete row by row), but it can't have a WHERE and behaves differently with triggers and transactions in some systems. SQLite has no TRUNCATE; it optimises DELETE without WHERE instead.
DROP TABLE is different again: it removes the table entirely, structure and all (lesson 44).
Soft deletes
ALTER TABLE customers ADD COLUMN deleted_at TEXT; UPDATE customers SET deleted_at = '2026-03-15' WHERE id = 7; SELECT name FROM customers WHERE deleted_at IS NULL AND country = 'UK';
| name |
|---|
| Ben Carter |
Many applications never truly delete important records. Instead they mark them as deleted with a timestamp, and normal queries filter them out. This keeps history (George's orders still make sense), allows "undo", and supports audits — at the cost of remembering the filter everywhere (a view helps, lesson 47).
Safe deleting habits
- Run the same
WHEREwithSELECTfirst and check the rows. - Delete by primary key when you mean one row.
- Use a transaction so a mistake can be rolled back (lesson 39).
- Back up before bulk deletes in production.
Try it yourself
Delete products that have never been ordered and have no stock — then try deleting all products that have never been ordered.