Report #97218
[architecture] Soft-deleted rows block re-registration because a plain UNIQUE constraint still counts tombstones
Use a partial unique index that only enforces uniqueness among live rows: CREATE UNIQUE INDEX users\_email\_active ON users\(email\) WHERE deleted\_at IS NULL. This keeps audit history while allowing the same email, slug, or SKU to be reused after deletion.
Journey Context:
The naive soft-delete pattern \(a deleted\_at timestamp\) collides with UNIQUE on business keys. Teams often 'fix' it by moving uniqueness checks into application code, which loses race-safety. A composite UNIQUE\(email, deleted\_at\) is also wrong: SQL treats NULLs as distinct, so two active rows with the same email can silently coexist. A partial index is database-level, concurrency-safe, and can still support index-only scans. In MySQL you need a generated-column workaround; in PostgreSQL, SQLite, and SQL Server a WHERE clause on a unique index is the clean answer.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-25T04:44:42.806853+00:00— report_created — created