---
title: "Plan how rows are removed · Agent Wranglers"
url: https://agentwranglers.se/playbook/data/schema-design/deletion-design
updated: 2026-10-20
licence: "All rights reserved"
---

# Plan how rows are removed

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Recommended
- Chapter: [Schema design](https://agentwranglers.se/playbook/data/schema-design)
- Rule: Decide for each table what deleting means, and make the foreign keys say it.
- How: Use `ON DELETE CASCADE` for rows that only exist as part of a parent, such as order lines. Use `RESTRICT` where a delete must be stopped. With a `deleted_at` column for soft deletes, every query must filter on it. Make unique indexes partial, with `WHERE deleted_at IS NULL`, so a deleted email can sign up again. Personal data must still be erased when the law asks, as the [data requests rule](https://agentwranglers.se/playbook/website/privacy/data-requests) says.
- Check: Delete a test parent row. Its owned rows disappear, protected references block the delete, and no orphan rows remain.

Example:

```sql
CREATE TABLE order_lines (
  order_id bigint NOT NULL REFERENCES orders (id) ON DELETE CASCADE,
  product_id bigint NOT NULL REFERENCES products (id) ON DELETE RESTRICT,
  quantity int NOT NULL CHECK (quantity > 0)
);
-- With soft deletes, uniqueness applies to live rows only
CREATE UNIQUE INDEX customers_email_live ON customers (lower(email)) WHERE deleted_at IS NULL;
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers data playbook: https://agentwranglers.se/playbook/data/schema-design/deletion-design`.

- Previous: [Keep one fact in one place](https://agentwranglers.se/playbook/data/schema-design/normalise)
- Next: [Keep each customer's data apart](https://agentwranglers.se/playbook/data/schema-design/tenant-isolation)
- [Schema design](https://agentwranglers.se/playbook/data/schema-design)
