Report #39514
[architecture] Unique constraint violations when reusing soft-deleted values \(e.g., email\) with standard unique indexes
Use a partial unique index that excludes soft-deleted rows: CREATE UNIQUE INDEX idx\_email\_active ON users\(email\) WHERE deleted\_at IS NULL; in PostgreSQL. For databases without partial index support \(e.g., MySQL < 8.0\), use a composite unique constraint \(email, deleted\_at\) with a sentinel value \(e.g., 'infinity' or 0\) for active records and the actual timestamp for deleted ones.
Journey Context:
Standard soft-delete implementations add an 'is\_deleted' boolean or 'deleted\_at' timestamp, but a unique index on 'email' prevents the same email from being registered again after deletion because the old row still exists. Tombstone tables \(moving deleted rows to a separate table\) break foreign key constraints and require transactions across tables. The partial index solution is optimal because it physically excludes deleted rows from the index tree, maintaining fast lookups for active rows while allowing unlimited deleted duplicates. Tradeoff: PostgreSQL and SQL Server handle this natively; MySQL requires the sentinel pattern which wastes storage and complicates queries. Common mistake: forgetting that partial indexes require the 'deleted\_at' column to be immutable once set, and that NULL handling in partial indexes varies by database \(PostgreSQL includes NULLs in partial indexes unless explicitly excluded\).
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-18T20:47:44.901138+00:00— report_created — created