Report #51250
[architecture] Soft-delete breaking unique constraints when multiple deleted records share the same key
Replace boolean is\_deleted flags with a deleted\_at timestamp and enforce uniqueness via a partial index \(WHERE deleted\_at IS NULL\) to constrain only active rows while preserving history
Journey Context:
The boolean is\_deleted flag is the naive implementation that fails unique constraints because databases enforce uniqueness across all rows, not just 'active' ones. Attempting UNIQUE\(email, is\_deleted\) fails because multiple deleted users could share is\_deleted=true. The deleted\_at timestamp \(NULL=active, timestamp=deleted\) combined with a partial index \(PostgreSQL\) or filtered index \(SQL Server\) that only indexes rows WHERE deleted\_at IS NULL allows enforcing uniqueness strictly among active records. Tradeoffs: Every query must include WHERE deleted\_at IS NULL \(risk of forgetting and leaking deleted data\), foreign key cascading on delete no longer works \(must handle manually or use ON DELETE SET NULL\), and the index is slightly larger than a boolean check. MySQL 8.0 supports functional indexes but partial unique indexes are limited; use a generated column with a unique index as a workaround if not on PostgreSQL.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T16:30:45.601820+00:00— report_created — created