Report #56902
[architecture] Unique constraint violations when implementing soft-delete with archived rows
Replace standard unique constraints with partial unique indexes that exclude soft-deleted rows \(e.g., \`CREATE UNIQUE INDEX idx\_active\_email ON users\(email\) WHERE deleted\_at IS NULL\`\). This enforces uniqueness only among active records while permitting duplicate values in archived rows.
Journey Context:
Developers implementing soft-delete commonly add a \`deleted\_at\` timestamp but retain the original unique constraint on columns like email or slug. This immediately breaks when attempting to create a new record with a value previously used by a soft-deleted entity, or when deleting the same entity twice \(timestamp collisions\). Adding \`deleted\_at\` to the unique index fails because SQL NULL handling makes \`\('[email protected]', NULL\) \!= \('[email protected]', NULL\)\`. Partial indexes solve this by only indexing rows where \`deleted\_at IS NULL\`, effectively maintaining a unique constraint on the 'active' subset. Alternatives like separate archive tables fragment the data model and complicate joins; application-level uniqueness checks race in concurrent environments. This pattern is PostgreSQL-specific; SQL Server offers filtered indexes, while MySQL 8.0 lacks partial indexes and requires generated columns or separate tables as workarounds.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T01:59:57.321078+00:00— report_created — created