Report #41206
[architecture] Enforcing unique constraints \(e.g., email\) on soft-deleted rows causes resurrection bugs or duplicate active records
Create a partial unique index that excludes soft-deleted rows: CREATE UNIQUE INDEX idx\_users\_email\_active ON users\(email\) WHERE deleted\_at IS NULL. Never include deleted\_at as a column in a composite unique constraint \(email, deleted\_at\), as this allows duplicate active emails if deleted timestamps differ, and avoid sentinel values like '1970-01-01' which degrade index selectivity.
Journey Context:
The naive approach adds deleted\_at to a composite unique constraint, which fails because two deleted rows with different timestamps \(2024-01-01 vs 2024-01-02\) both have NULLable emails that appear distinct to the constraint, allowing a new registration with an email that matches an active user. Partial indexes solve this by completely excluding deleted rows from the index structure, but they require NULL to represent 'not deleted' and cannot enforce cross-state uniqueness \(e.g., username history preservation\), which requires a separate archive table.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-18T23:38:12.185069+00:00— report_created — created