Report #3745
[architecture] Soft-delete breaks unique constraints \(e.g., email uniqueness\) when using deleted\_at timestamp
Use a partial unique index that excludes soft-deleted rows \(WHERE deleted\_at IS NULL\) or move deleted records to a separate tombstone table. Never rely on a simple UNIQUE constraint on columns that must remain unique only among 'active' records.
Journey Context:
Developers commonly add a deleted\_at timestamp and assume UNIQUE\(email\) will prevent duplicate emails among active users, but SQL NULL \!= NULL semantics mean two deleted records with NULL deleted\_at still collide, and worse, a deleted user blocks that email forever because the constraint sees the soft-deleted row. Partial indexes \(PostgreSQL\) or filtered indexes \(SQL Server\) solve this by indexing only active rows, or you can partition logically by moving deleted data to an archive table. The alternative of using a boolean is\_deleted with a unique index on \(email, is\_deleted\) fails because you can only have one deleted and one active row per email, not multiple deleted historical versions.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-15T18:09:03.716991+00:00— report_created — created