row_number
PG 8.4+→ bigintReturns the sequential row number within the current partition, starting from 1. No ties — every row gets a unique number.
Signature
row_number ( ) → bigintExamples
SELECT name, salary, row_number() OVER (ORDER BY salary DESC) AS rn FROM employees;Rank 1, 2, 3... by salary (no ties)SELECT *, row_number() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank FROM employees;Rank within each departmentWITH deduped AS (SELECT *, row_number() OVER (PARTITION BY email ORDER BY created_at DESC) AS rn FROM users) SELECT * FROM deduped WHERE rn = 1;Most recent row per email addressSELECT *, row_number() OVER (PARTITION BY customer_id ORDER BY order_date) AS order_seq FROM orders;Sequential order number per customer (1st, 2nd, 3rd...)You cannot reference a window function alias in a WHERE clause — it is evaluated after SELECT. Wrapping in a subquery or CTE is required.
✓ Instead: WITH numbered AS (SELECT *, row_number() OVER (PARTITION BY key ORDER BY updated_at DESC) AS rn FROM t) SELECT * FROM numbered WHERE rn = 1;
Use `row_number() OVER (PARTITION BY key ORDER BY updated_at DESC)` to number duplicates, then filter `WHERE rn = 1` to keep only the most recent row per key — a clean deduplication pattern.
DELETE FROM t WHERE id IN (SELECT id FROM (SELECT id, row_number() OVER (PARTITION BY email ORDER BY created_at DESC) AS rn FROM t) x WHERE rn > 1);Removes duplicate emails, keeping the newest