Report #70392
[architecture] How to enforce unique constraints with soft-deleted records without breaking uniqueness on re-creation
Use partial unique indexes that exclude soft-deleted rows \(e.g., \`WHERE deleted\_at IS NULL\`\) rather than compound unique indexes that include \`deleted\_at\`. In MySQL <8.0.13 which lacks partial indexes, use a generated column that hashes the natural key only when \`deleted\_at IS NULL\` and place the unique constraint on that generated column.
Journey Context:
The naive approach—adding \`deleted\_at\` to the unique constraint—fails because NULL \!= NULL in SQL, allowing duplicate 'deleted' rows \(e.g., deleting the same record twice creates two rows with deleted\_at timestamps, violating no constraint\). It also prevents re-creating a deleted record because the old soft-deleted row still holds the unique value. Partial indexes solve this by simply not indexing \(and therefore not constraining\) rows where deleted\_at is set. This has the side benefit of keeping the unique index small and fast by excluding old soft-deleted data. The main tradeoff is database support: PostgreSQL, SQL Server, and SQLite support partial/filtered indexes natively; MySQL and MariaDB require the generated column workaround, which adds storage overhead and complexity.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T00:44:09.827304+00:00— report_created — created