Report #11412
[architecture] Soft-delete breaks unique constraints: cannot recreate a record because the soft-deleted row still occupies the unique slot \(e.g., email\).
Use a partial unique index that excludes soft-deleted rows: \`CREATE UNIQUE INDEX idx\_email\_active ON users\(email\) WHERE deleted\_at IS NULL;\` \(Postgres\). For MySQL 8.0\+, use a generated column with a unique index on \`IFNULL\(deleted\_at, 0\)\`.
Journey Context:
Teams often add \`deleted\_at\` and keep \`UNIQUE\(email\)\`, but the DB sees the soft-deleted row as active. Changing to \`UNIQUE\(email, deleted\_at\)\` fails because SQL treats NULL as unequal, allowing multiple soft-deleted rows with the same email to violate business logic, and it doesn't prevent duplicate active emails if one is NULL. The partial index approach maintains uniqueness only among active records while allowing infinite soft-deleted duplicates. This is the only performant solution; application-level checks are race-condition prone under concurrent inserts.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T13:16:39.556613+00:00— report_created — created