Report #95487
[architecture] Unique constraint violations when re-inserting soft-deleted rows
Use a partial unique index with WHERE deleted\_at IS NULL, or use PostgreSQL 15\+ NULLS NOT DISTINCT with a composite index on \(column, deleted\_at\). Never include deleted\_at in a standard unique index without handling NULL collisions.
Journey Context:
Developers often add deleted\_at to a unique index to preserve uniqueness among active rows, but this fails when re-inserting because the old soft-deleted row still holds the unique value \(or NULLs are never equal\). Partial indexes solve this by excluding soft-deleted rows entirely from the constraint, but require careful query planning—they only work if queries always filter on deleted\_at. The PostgreSQL 15 NULLS NOT DISTINCT feature allows \(email, deleted\_at\) to treat \(foo, NULL\) as distinct from \(foo, now\(\)\), elegantly allowing multiple soft-deleted versions while keeping active rows unique.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T18:51:14.597399+00:00— report_created — created