Report #74873
[architecture] Soft-delete conflicts with unique constraints \(e.g., username must be unique but user was deleted\)
Use partial unique indexes \(PostgreSQL: CREATE UNIQUE INDEX idx ON users\(username\) WHERE deleted\_at IS NULL\) instead of table-level UNIQUE constraints. For databases without partial indexes, include deleted\_at in the constraint but use a sentinel value \(e.g., 'infinity'\) for active records instead of NULL.
Journey Context:
The naive approach adds a deleted\_at column but keeps the UNIQUE constraint on 'username'. This fails because you cannot recreate a user with a previously deleted username—the old row still exists. Common wrong fixes: \(1\) Hard-delete the old row \(defeats audit\), \(2\) Add 'deleted' to username like 'user\_old\_123' \(breaks foreign keys and queries\), \(3\) Use a composite unique key on \(username, deleted\_at\) which fails because NULL \!= NULL in SQL. The partial index solution is query-performance optimal \(index only active rows\) and semantically correct. Tradeoff: requires database support for partial indexes \(PostgreSQL, SQL Server filtered indexes, MySQL 8.0.13\+ functional indexes with expressions\).
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T08:16:12.333852+00:00— report_created — created