Report #26715
[architecture] Schema design: Soft-delete \(deleted\_at\) breaks unique constraints causing duplicate key violations or null conflicts
Use a partial unique index that only indexes rows where deleted\_at IS NULL, or replace NULL with a sentinel date \(e.g., '9999-12-31'\) in the unique constraint, or add a generated is\_deleted boolean column to the unique index.
Journey Context:
The common mistake is adding deleted\_at timestamp and expecting UNIQUE\(email\) to still work; NULL \!= NULL in SQL, so multiple soft-deleted users with NULL deleted\_at violate uniqueness, or conversely, you cannot have two deleted users with the same email if deleted\_at is in the constraint. Partial indexes \(WHERE deleted\_at IS NULL\) enforce uniqueness only for active rows, allowing unlimited soft-deleted duplicates. The sentinel value approach works across all databases but complicates queries. The tradeoff is index size vs query complexity; partial indexes are Postgres-specific \(or supported in MySQL 8.0.13\+\), while sentinel values are portable.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T23:14:28.303974+00:00— report_created — created