ROW_NUMBER assigns a unique sequential number to each row within a result-set partition, ordered by the columns you specify.
ROW_NUMBER is calculated on the fly inside a SELECT, UPDATE, or INSERT … SELECT. It does not create a physical column or require a sequence, making it perfect for ad-hoc ranking, pagination, and de-duplicating query results.
PostgreSQL evaluates the OVER()
clause after the WHERE and GROUP BY stages. It partitions the rows (optional), orders them, and then assigns consecutive integers starting at 1 within each partition.
The minimal pattern is ROW_NUMBER() OVER (ORDER BY column)
. Add PARTITION BY
to reset the counter per logical group, such as each customer.
Wrap your query in a CTE, add ROW_NUMBER, and filter by the desired page range. This prevents OFFSET from scanning unwanted rows and keeps performance predictable.
Use ROW_NUMBER in a subquery ordered by created_at
DESC, keep rows where row_num = 1
, and DELETE the rest. This keeps the earliest or latest row per email.
Yes. PostgreSQL allows UPDATE … FROM. Compute ROW_NUMBER in a subquery, JOIN it, and update only rows with a specific row number, such as setting a preferred
flag on the first product per category.
Create indexes that match the PARTITION BY
and ORDER BY
columns. Avoid unnecessary ORDER BY expressions and limit partitions when possible to reduce memory usage during sorting.
Not always. LIMIT/OFFSET is simpler for one-time queries, but ROW_NUMBER enables stable, keyset-style pagination and lets you retrieve total counts without an extra query.
Yes—if the ORDER BY list uniquely identifies each row. Add a primary key to ensure predictable results.
It can if the ORDER BY causes a large sort. Proper indexes on partition and order columns mitigate this.
Use ROW_NUMBER in a subquery to mark duplicates, then filter where row_num = 1
. DISTINCT alone cannot keep a specific duplicate.