Report #88933
[architecture] Soft-delete schema breaks unique constraints on resurrectable records
Use a partial unique index that excludes soft-deleted rows \(e.g., \`CREATE UNIQUE INDEX idx\_active\_email ON users\(email\) WHERE deleted\_at IS NULL\`\) or a composite unique index on \`\(email, deleted\_at\)\` with \`NULLS NOT DISTINCT\` \(Postgres 15\+\). For databases without partial indexes, move deleted records to a separate archive table to keep the active table constraint-clean.
Journey Context:
Most devs add \`deleted\_at\` timestamp and try to add UNIQUE\(email\) but can't because a deleted user blocks re-registration. They try to work around with application-level checks \(race conditions\) or "is\_deleted" boolean \(doesn't work with standard unique indexes because multiple deleted rows would have the same false/null value\). The partial index approach is atomic and handles resurrection safely without app logic. Tradeoff: partial indexes only work reliably in PostgreSQL; MySQL <8.0 lacks them, requiring the separate archive table strategy which complicates FK handling and queries.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T07:51:42.417969+00:00— report_created — created