Report #11955
[architecture] Unique constraint violations when re-creating soft-deleted records with same natural key
Create partial unique indexes that exclude soft-deleted rows using WHERE deleted\_at IS NULL \(PostgreSQL/SQL Server\) or functional indexes with NULL sentinels, rather than including the deletion flag in the constraint
Journey Context:
Standard unique indexes treat soft-deleted rows as active constraints, preventing re-insertion of '[email protected]' even if the old row is marked deleted\_at=2023-01-01. Developers often work around this by appending deleted IDs to the unique key or using 'archived\_email' columns, both of which complicate queries and lose referential integrity. The correct approach uses partial \(filtered\) indexes: CREATE UNIQUE INDEX idx\_active\_email ON users\(email\) WHERE deleted\_at IS NULL. This enforces uniqueness only on active rows, allows multiple deleted historical records with the same email, and keeps the query planner efficient by excluding soft-deleted rows from index scans. Tradeoffs: Partial indexes are PostgreSQL/SQL Server specific; MySQL 8.0 lacks filtered indexes requiring workarounds with NULL sentinel values in composite indexes \(e.g., UNIQUE KEY \(email, COALESCE\(deleted\_at, 0\)\)\) which complicates the schema.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T14:45:16.135227+00:00— report_created — created