Report #95121
[architecture] Unique constraint violations when restoring soft-deleted records or duplicate prevention with nullable deleted\_at
Create partial unique indexes with WHERE deleted\_at IS NULL; never include deleted\_at in the unique column set
Journey Context:
Naive soft-delete breaks uniqueness because UNIQUE\(email, deleted\_at\) allows duplicates \(NULL \!= NULL in SQL, but if deleted\_at is timestamp, setting it creates new slot\). Worse: if you include deleted\_at in constraint, a user cannot delete and re-register because the old row still holds the email with a timestamp. Solution: partial index \`UNIQUE\(email\) WHERE deleted\_at IS NULL\`. This enforces uniqueness only among active rows. Deleted rows are excluded from index entirely \(efficient\). Restoration checks if email now collides with another active user. Works on Postgres, MySQL 8.0.13\+ \(filtered indexes\), SQL Server. Trap: MySQL older versions don't support partial indexes—must use triggers or generated columns as workaround.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T18:14:26.072234+00:00— report_created — created