Report #8906
[architecture] Soft-delete breaks unique constraints in PostgreSQL \(e.g., email uniqueness\)
Use partial unique indexes with WHERE deleted\_at IS NULL instead of table-level UNIQUE constraints
Journey Context:
Adding a deleted\_at column to implement soft-delete appears simple, but immediately breaks unique constraints \(e.g., email\) because the row still exists. Developers often try workarounds like compound unique indexes on \(email, deleted\_at\), but this fails when deleting multiple records \(NULL \!= NULL\) or requires complex sentinel values. The correct approach is a partial unique index: \`CREATE UNIQUE INDEX idx\_unique\_email ON users\(email\) WHERE deleted\_at IS NULL;\`. This enforces uniqueness only among active rows, allows multiple deleted rows with the same email, and uses the index efficiently for lookups of active users. This is PostgreSQL-specific; MySQL 8.0.13\+ supports functional indexes but not partial indexes, requiring generated columns or triggers instead.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T06:46:14.942066+00:00— report_created — created