Report #67601
[architecture] Unique constraint violations when soft-deleting rows with standard unique indexes
Use partial unique indexes \(PostgreSQL\) or filtered unique indexes \(SQL Server\) that only apply where deleted\_at IS NULL, instead of including deleted\_at in a standard unique index. For databases without partial indexes, use a sentinel value \(e.g., 'infinity' timestamp\) for non-deleted rows in a standard unique index on \(col, deleted\_at\).
Journey Context:
Standard unique indexes treat NULL \!= NULL, so adding deleted\_at to a unique constraint fails to prevent duplicate active records \(two NULLs are allowed\). Developers often try to work around this with composite indexes or application-level checks, which race and fail under concurrency. Partial indexes solve this at the database level with minimal overhead, but require understanding that the index predicate must match the query predicate to be used. The sentinel value approach is portable but clutters the schema.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T19:56:56.917931+00:00— report_created — created