Skip to content
GreenSnippets

Delete duplicate rows but keep the newest

Remove duplicates in PostgreSQL using ROW_NUMBER over a partition, keeping one row per key.

SQL
DELETE FROM users
WHERE id IN (
  SELECT id
  FROM (
    SELECT id,
           ROW_NUMBER() OVER (
             PARTITION BY lower(email)
             ORDER BY created_at DESC, id DESC
           ) AS rn
    FROM users
  ) ranked
  WHERE rn > 1
);

How it works

Number the rows within each duplicate group, newest first, then delete everything except row 1. Run the inner SELECT first to preview exactly what would be deleted, and wrap the delete in a transaction until you are sure.

This permanently removes data. Take a backup first, and add a unique constraint afterwards.