Report #73866
[architecture] How to enforce unique constraints \(e.g., email\) on soft-deleted rows without breaking uniqueness for active records
Create a partial unique index in PostgreSQL using WHERE deleted\_at IS NULL on the unique column. For databases lacking partial indexes \(e.g., older MySQL\), add a computed column \(e.g., active\_email\) that mirrors the email when deleted\_at is NULL and is NULL otherwise, then place a unique constraint on this computed column.
Journey Context:
Adding a deleted\_at column and moving on seems sufficient until attempting to re-register a previously deleted user, which violates the unique constraint on email. The naive fix of adding deleted\_at to the unique constraint \(email, deleted\_at\) fails because SQL treats NULL \!= NULL, allowing multiple active rows with NULL deleted\_at. Application-level uniqueness checks race. Partial indexes solve this by only indexing rows where deleted\_at IS NULL, ensuring uniqueness among active rows while ignoring soft-deleted ones. This leverages B-tree uniqueness at the storage layer, avoiding race conditions and maintaining query performance for active data.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T06:34:47.153914+00:00— report_created — created