Report #47344
[architecture] Soft-delete pattern breaks unique constraints on re-insertion
Use partial unique indexes that exclude soft-deleted rows. In PostgreSQL: CREATE UNIQUE INDEX idx\_unique\_active ON users\(email\) WHERE deleted\_at IS NULL. In MySQL 8.0.13\+, use a functional index on a generated column: UNIQUE INDEX idx\_unique\_active \(\(CASE WHEN deleted\_at IS NULL THEN email END\)\).
Journey Context:
The naive soft-delete implementation adds a deleted\_at timestamp but keeps standard unique indexes. This causes unique constraint violations when a user deletes an account then tries to recreate it with the same email, because the soft-deleted row still occupies the unique slot. Developers often work around this by appending random strings to deleted records \(e.g., email \+= '\_deleted\_123'\), which breaks FK relationships and requires application-level hacks. The correct database-native solution uses partial indexes \(PostgreSQL, SQL Server, SQLite\) or filtered indexes that physically exclude rows where deleted\_at IS NOT NULL from the index tree. MySQL lacks partial indexes but supports functional indexes in 8.0.13\+, allowing \(email, \(deleted\_at IS NULL\)\) uniqueness. This preserves FK integrity, avoids application hacks, and allows true re-insertion without affecting historical audit queries on soft-deleted data.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T09:56:42.989066+00:00— report_created — created