Chapter 06 · SQLite Data Modification: INSERT, UPDATE, DELETE, UPSERT, and RETURNING

DELETE, Cascades, Soft Delete, and Data Lifecycle

Delete deliberately by verifying targets, measuring cascade scope, distinguishing hard from soft deletion, and understanding why free pages are not the same as a smaller file.

Beginner90–110 minutesTransaction-protected cleanup labSQLite 3.53.4 baselineUPDATE FROM: SQLite 3.33.0+UPSERT: SQLite 3.24.0+; generalized 3.35.0+RETURNING: SQLite 3.35.0+Last reviewed: August 2026

Learning outcomes

DELETE is simple syntactically and potentially large operationally. A one-row parent DELETE can remove many child rows through CASCADE, and deleting rows usually creates reusable free pages rather than immediately shrinking the database file.

01

Use verify-before-delete discipline and explicit transactions.

02

Measure direct target rows separately from foreign-key cascade effects.

03

Choose hard delete versus soft delete from lifecycle requirements.

04

Explain why row deletion does not imply immediate file-size reduction.

05

Use freelist_count as storage evidence without treating VACUUM as routine cleanup.

06

Build a transaction-protected cleanup lab with before/after counts.

DELETE targets rows exactly like a SELECT predicate

A DELETE without WHERE removes every row in the table. With WHERE, only rows for which the predicate is true are targeted. Preview first.

sql · preview delete candidates
DROP TABLE IF EXISTS cleanup_note;CREATE TABLE cleanup_note(  note_id INTEGER PRIMARY KEY,  device_code TEXT NOT NULL,  status TEXT NOT NULL,  occurred_at TEXT NOT NULL);INSERT INTO cleanup_note(device_code,status,occurred_at) VALUES('PUMP-007','closed','2025-01-01'),('PUMP-007','open','2026-08-12'),('FAN-014','closed','2024-05-01');SELECT note_id, device_code, status, occurred_atFROM cleanup_noteWHERE status='closed' AND occurred_at<'2026-01-01';

Only after the SELECT result matches the retention rule should the DELETE use that same predicate.

Cascades expand lifecycle scope

Chapter 5 established that ON DELETE CASCADE is a business ownership rule. It means deleting the parent causes SQLite to delete referencing child rows when foreign keys are enabled.

sql · one direct delete, multiple cascade effects
PRAGMA foreign_keys=ON;DROP TABLE IF EXISTS child_note;DROP TABLE IF EXISTS parent_device;CREATE TABLE parent_device(device_id INTEGER PRIMARY KEY, code TEXT UNIQUE NOT NULL);CREATE TABLE child_note(  note_id INTEGER PRIMARY KEY,  device_id INTEGER NOT NULL REFERENCES parent_device(device_id) ON DELETE CASCADE,  body TEXT NOT NULL);INSERT INTO parent_device VALUES (1,'PUMP-007'),(2,'FAN-014');INSERT INTO child_note(device_id,body) VALUES (1,'a'),(1,'b'),(2,'c');SELECT count(*) AS child_before FROM child_note; -- 3DELETE FROM parent_device WHERE device_id=1;SELECT changes() AS direct_parent_rows;          -- 1SELECT count(*) AS child_after FROM child_note;  -- 1

The SQL changes() value reports direct rows changed by the top-level statement, not auxiliary rows changed by foreign-key actions. Therefore a count of 1 does not mean the operation affected only one stored row.

Hard delete versus soft delete

A hard delete removes the row. A soft-delete design usually keeps the row and records lifecycle state such as deleted_at or is_deleted. Neither is universally superior.

ChoiceBenefitsCosts / risks
Hard deleteSimple semantics; queries do not need hidden filters; can satisfy true erasure requirements.Recovery requires backup/history; cascades can broaden scope.
Soft deleteSupports restore/audit workflows and references to historical entities.Every query must understand visibility; UNIQUE rules may need partial indexes; data is not actually erased; child lifecycle becomes more complex.
sql · soft delete is an application contract
ALTER TABLE cleanup_note ADD COLUMN deleted_at TEXT;UPDATE cleanup_noteSET deleted_at=CURRENT_TIMESTAMPWHERE note_id=1;-- Application-visible rows must deliberately filter:SELECT * FROM cleanup_note WHERE deleted_at IS NULL;

If users expect data to be erased, a soft-delete flag may be the wrong compliance behavior. If audit/history must be retained, immediate hard deletion may be wrong. Model the lifecycle from requirements.

Deleting rows does not necessarily shrink the file

SQLite stores tables and indexes in pages. Deleting rows can make pages reusable inside the database; it does not imply the operating-system file immediately becomes smaller. PRAGMA freelist_count reports currently unused pages in the database file.

sql · observe reusable pages
PRAGMA page_count;PRAGMA freelist_count;DELETE FROM cleanup_note WHERE status='closed';PRAGMA page_count;PRAGMA freelist_count;

For a tiny lab the values may not visibly change because pages still contain other records. On larger deletions, free pages can grow. Chapter 18 will cover maintenance and VACUUM choices. Do not schedule VACUUM merely because rows were deleted; it rewrites the database and has operational costs.

Transaction-protected cleanup lab

Build counts before and after the candidate deletion, then decide whether to commit.

sql · retention cleanup
DROP TABLE IF EXISTS retention_note;CREATE TABLE retention_note(  note_id INTEGER PRIMARY KEY,  status TEXT NOT NULL,  occurred_at TEXT NOT NULL);INSERT INTO retention_note(status,occurred_at) VALUES('closed','2024-01-01'),('closed','2025-06-01'),('open','2024-02-01'),('open','2026-08-12');SELECT count(*) AS total_before FROM retention_note;SELECT count(*) AS candidatesFROM retention_noteWHERE status='closed' AND occurred_at<'2026-01-01'; -- expect 2BEGIN;DELETE FROM retention_noteWHERE status='closed' AND occurred_at<'2026-01-01';SELECT changes() AS direct_deleted;                 -- expect 2SELECT count(*) AS total_after_candidate FROM retention_note; -- expect 2-- Lab choice: verify then commit.COMMIT;SELECT count(*) AS final_total FROM retention_note; -- 2

The important behavior is not the exact date rule; it is that candidate count, direct delete count, and final count agree with the intended lifecycle policy before COMMIT.

Failure cases and production judgment

FailureDiagnosisSafer response
Missing WHEREchanges() equals a much larger scope than expected.Keep operation inside a transaction and rollback before commit.
Unexpected cascadeParent relationship declares CASCADE and FK enforcement is on.Preview child counts and document ownership semantics.
Soft-deleted rows appear in reportsQueries forgot lifecycle filter.Centralize visibility rules or reconsider soft delete.
File size did not shrinkFree pages remain reusable internally.Measure page/freelist state; defer maintenance decisions to storage chapter.
DELETE count seems too smallchanges() excludes FK cascade/trigger auxiliary changes.Audit related tables and use lifecycle-specific counts.

DELETE checkpoint

Reason about lifecycle, not just syntax.

  1. Why preview a DELETE predicate with SELECT?
  2. Can changes() tell you how many cascade child rows were deleted?
  3. Does soft delete erase data?
  4. Does deleting rows guarantee a smaller .db file immediately?
  5. When should VACUUM decisions be made?
Review the answers

Previewing exposes scope. changes() counts direct top-level changes, not foreign-key cascades. Soft delete retains the row. Deleted space is usually reusable inside the file rather than immediately returned to the OS. VACUUM should be chosen from measured storage/operational requirements, covered later.

Summary and bridge

Safe deletion combines target verification, transaction protection, relationship knowledge, and lifecycle intent. Next comes a different write problem: you have an incoming logical entity and need to insert it if new, but update or ignore it when a uniqueness rule identifies an existing row. That is UPSERT—not REPLACE.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.