Report #44066
[architecture] Enforcing unique constraints on soft-deleted rows \(e.g., reusable email addresses\)
Create a partial unique index that excludes soft-deleted rows: CREATE UNIQUE INDEX idx\_email\_active ON users\(email\) WHERE deleted\_at IS NULL. Do not use composite unique keys on \(email, deleted\_at\) as they allow duplicate active emails if the database treats NULLs as distinct.
Journey Context:
Standard soft-delete breaks unique constraints because the 'deleted' row still occupies the unique slot. Developers often try \(email, deleted\_at\) unique constraints, but SQL standard \(and PostgreSQL\) treats each NULL as distinct, allowing multiple active emails with NULL deleted\_at. Partial indexes solve this by only indexing active rows, enforcing uniqueness only among them. Tradeoff: Queries must include deleted\_at IS NULL or they may use the partial index incorrectly \(though PostgreSQL is smart about this\). MySQL 8.0.13\+ supports functional indexes but not partial indexes in the same way; for MySQL, use a generated column or trigger-based approach, making this pattern predominantly PostgreSQL-specific.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T04:26:09.533068+00:00— report_created — created