Soft delete trades storage-level deletion for a convention — and conventions don't enforce themselves. Two failure modes: forgetting the filter resurrects deleted rows (in counts, joins, exports), and mis-writing it as = NULL deletes everything from view. The mitigations are structural, not vigilance: an active view as the default query surface; deletion-aware unique keys (partial indexes); joins audited (a LEFT JOIN to a soft-deleted dimension resurrects retired lookups).
SELECT name FROM designs WHERE deleted_at = NULL ORDER BY name;
SELECT name FROM designs WHERE deleted_at IS NULL ORDER BY name;
The active-designs query written as deleted_at = NULL returned zero rows — a comparison with NULL is UNKNOWN, never TRUE, so everything vanished; deleted_at IS NULL keeps the one live design.
The active-view convention; deletion-aware unique keys; IS NULL, mechanically.