Report #23173
[architecture] Maintaining unique constraints with soft-deleted records in SQL databases
Use partial unique indexes \(WHERE deleted\_at IS NULL\) in PostgreSQL, or filtered indexes in SQL Server. For MySQL 8.0.13\+, use functional indexes on \(email, \(deleted\_at IS NULL\)\) to simulate partial uniqueness. If the database lacks partial index support, either accept that soft-deleted emails cannot be reused \(business constraint\) or use a sentinel value like 'infinity' for deleted\_at rather than NULL in the unique key.
Journey Context:
The common trap is adding deleted\_at to a unique constraint: \(email, deleted\_at\) fails because SQL treats NULL \!= NULL, allowing duplicate active emails if deleted\_at is NULL. Developers then try COALESCE\(deleted\_at, 'epoch'\) but this bloats indexes and complicates queries. Partial indexes solve this elegantly by excluding soft-deleted rows from uniqueness checks entirely. Without native support \(older MySQL\), you must choose between strict uniqueness and soft-delete capability. The sentinel value approach works but pollutes the temporal semantics of deleted\_at.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T17:18:16.588691+00:00— report_created — created