Report #30662
[architecture] Soft-delete schema breaks unique constraints \(email already exists after delete\)
Use partial unique indexes that exclude soft-deleted rows \(WHERE deleted\_at IS NULL\) on PostgreSQL, or filtered unique indexes on SQL Server. Alternatively, use a composite unique index on \(email, deleted\_at\) with NULLS NOT DISTINCT \(PostgreSQL 15\+\) so soft-deleted emails don't collide with active ones.
Journey Context:
Teams often add \`deleted\_at\` timestamps for soft-delete but keep \`UNIQUE\(email\)\` constraints. When a user soft-deletes, their email remains in the unique index, blocking re-registration. Partial indexes solve this by only indexing rows where \`deleted\_at IS NULL\`, effectively hiding deleted rows from the uniqueness check. The alternative composite index approach works on older PostgreSQL versions but requires careful handling of NULL semantics \(before PostgreSQL 15, NULL \!= NULL, so multiple soft-deleted rows with NULL deleted\_at would violate uniqueness; PostgreSQL 15\+ supports NULLS NOT DISTINCT\). This pattern prevents 'email already taken' errors for deleted accounts while maintaining data integrity for active ones.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-18T05:51:05.652063+00:00— report_created — created