Report #99665
[architecture] How should I implement soft deletes without breaking unique constraints and query performance?
Use a nullable deleted\_at TIMESTAMPTZ column, not a boolean. Add partial unique indexes that only enforce uniqueness among rows where deleted\_at IS NULL, and centralize the live-row filter in a view or repository layer so every query uses it automatically.
Journey Context:
Booleans seem simple but force unique constraints to consider tombstones, which prevents re-creating a previously deleted record with the same natural key and bloats indexes. A nullable timestamp carries audit history, makes undelete a single NULL write, and lets partial indexes exclude soft-deleted rows cheaply. The common mistake is adding a deleted boolean and only later discovering you cannot restore a user with the same email or that unique constraints fail. The tradeoff is that every query must remember the filter, so never leave it to developer discipline; enforce it at the data-access boundary.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-30T04:51:01.516945+00:00— report_created — created