Skip to content
GreenSnippets

Soft delete with a partial unique index

Keep deleted rows but still enforce unique emails among active rows using a partial unique index.

SQL
ALTER TABLE users ADD COLUMN deleted_at timestamptz;

CREATE UNIQUE INDEX users_email_active_key
  ON users (lower(email))
  WHERE deleted_at IS NULL;

CREATE VIEW active_users AS
  SELECT * FROM users WHERE deleted_at IS NULL;

-- "Delete" a user
UPDATE users SET deleted_at = now() WHERE id = 42 AND deleted_at IS NULL;

Example / expected output

Output
-- The same email can exist once active and any number of times deleted.

How it works

A plain UNIQUE (email) blocks re-registering a deleted user's email. A partial unique index applies only to rows where deleted_at IS NULL. Make sure every query that should ignore deleted rows filters on it, or hide them behind a view.

This snippet has been reviewed but was not executed by an automated test. Test it in your own environment before relying on it.