Report #65680
[architecture] How to enforce unique constraints on soft-deleted rows without allowing duplicate active records
Create a partial unique index with a WHERE clause filtering out soft-deleted rows \(e.g., CREATE UNIQUE INDEX ON users\(email\) WHERE deleted\_at IS NULL\). Never rely on composite unique indexes including the deleted timestamp or application-level validation, as they fail under race conditions.
Journey Context:
The common mistake is adding a composite unique constraint on \(email, deleted\_at\) hoping it allows one active and many deleted. SQL treats NULL as unequal, so this permits duplicate active emails \(all with NULL\). Using a sentinel value like '9999-12-31' works but pollutes the data and requires index changes. Application-level uniqueness checks race under concurrent inserts. Partial indexes are the only declarative, race-safe solution, but note they are PostgreSQL-specific; MySQL 8.0.13\+ requires functional indexes with CASE statements for equivalent behavior.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T16:43:25.989206+00:00— report_created — created