Soft Delete with Filtered Indexes
Implement soft deletes using a deleted_at column with partial indexes and views for transparent querying.
-- Add soft delete column
ALTER TABLE users ADD COLUMN deleted_at timestamptz DEFAULT NULL;
-- Partial index: only index active rows for performance
CREATE INDEX idx_users_active_email
ON users (email)
WHERE deleted_at IS NULL;
-- View for active records (use this in your app)
CREATE VIEW active_users AS
SELECT * FROM users WHERE deleted_at IS NULL;
-- Soft delete function
CREATE FUNCTION soft_delete(table_name text, row_id int)
RETURNS void AS $$
BEGIN
EXECUTE format(
'UPDATE %I SET deleted_at = NOW() WHERE id = $1 AND deleted_at IS NULL',
table_name
) USING row_id;
END;
$$ LANGUAGE plpgsql;
-- Restore a soft-deleted row
UPDATE users SET deleted_at = NULL WHERE id = 42;
-- Hard delete old soft-deleted records (cleanup job)
DELETE FROM users
WHERE deleted_at IS NOT NULL
AND deleted_at < NOW() - INTERVAL '90 days';
-- Usage:
-- SELECT * FROM active_users WHERE email = 'ada@example.com';
-- SELECT soft_delete('users', 42);Use Cases
- User account deletion
- Audit trail preservation
- Undo/restore functionality
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Keyset Pagination vs Offset
Efficient keyset (cursor) pagination pattern compared to traditional OFFSET for large datasets in PostgreSQL.
UPSERT with ON CONFLICT
Insert or update rows atomically using PostgreSQL ON CONFLICT clause with partial indexes and conditions.
Full-Text Search with Ranking
PostgreSQL full-text search using tsvector, tsquery, and ts_rank with trigram similarity for fuzzy matching.
Window Functions with RANK and LAG
Use window functions to rank rows, calculate running totals, and compare with previous rows without self-joins.