Report #70870
[architecture] Unique constraint violations when using soft deletes \(deleted\_at\) in SQL databases
Use a partial unique index that excludes soft-deleted rows \(e.g., \`CREATE UNIQUE INDEX idx\_unique\_active ON users\(email\) WHERE deleted\_at IS NULL\`\) or a composite unique index on \`\(email, COALESCE\(deleted\_at, 'infinity'\)\)\` to allow multiple deleted records but enforce uniqueness only among active rows.
Journey Context:
The naive approach of adding a \`deleted\_at\` timestamp breaks unique constraints because deleted records still occupy the unique slot \(e.g., can't reuse an email even if 'deleted'\). Alternatives considered: 1\) Hard delete \(loses audit trail\), 2\) Status enum with unique constraint \(complex state machine\), 3\) Partial indexes \(cleanest\). Partial indexes are superior because they truly remove soft-deleted rows from the constraint scope, but require database support \(PostgreSQL has full support; MySQL 8.0.16\+ supports functional indexes but partial index emulation requires generated columns\). The composite index with sentinel value approach works across more databases but pollutes the schema with implementation details.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T01:32:13.934776+00:00— report_created — created