ROW_NUMBER() assigns a unique, sequential number to each row—optionally restarting numbering for each partition—based on an ORDER BY clause.
ROW_NUMBER() adds an ordinal position to each returned row, making it easy to reference the first, last, or nth row after ordering. Because it’s a window function, it never collapses rows like aggregate functions do.
Use the function inside a SELECT list and pair it with the OVER clause. You must supply ORDER BY inside OVER; PARTITION BY is optional when you want numbering to restart per group.
SELECT ROW_NUMBER() OVER (PARTITION BY partition_expr ORDER BY sort_expr) AS row_num
FROM table_name;
Select the table, call ROW_NUMBER(), and order by the desired column(s). The result set will carry an extra column with incremental numbers starting at 1.
SELECT ROW_NUMBER() OVER (ORDER BY id) AS rn,
*
FROM employees;
Include PARTITION BY. PostgreSQL begins counting from 1 every time the partition key changes.
SELECT department_id,
employee_name,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY hire_date) AS dept_rank
FROM employees;
Wrap the query or use a CTE, then filter on the generated row number.
WITH ranked AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY total_sales DESC) AS rn
FROM sales
)
SELECT *
FROM ranked
WHERE rn <= 3;
Always ORDER BY deterministic columns to avoid unpredictable numbering. Index columns used in ORDER BY for performance. Prefer ROW_NUMBER() over LIMIT/OFFSET when you also need ranking.
Ignoring ORDER BY: Without a clear sort, numbers depend on planner whims. Always specify ORDER BY.
Filtering inside the same SELECT: You can’t reference an alias in the WHERE clause of the same level; filter in an outer query or CTE.
No, but duplicate sort values lead to arbitrary ordering for ties. Add a tiebreaker column when order matters.
It can be, because the window must process every qualifying row. Add proper indexes to mitigate.
Yes—subtract 1 in the SELECT: ROW_NUMBER() - 1 AS rn0.
Yes. Wrap the SELECT with row numbers in a CTE, then reference it in an UPDATE ... FROM clause.
ROW_NUMBER() never creates gaps; RANK() adds gaps after ties; DENSE_RANK() removes gaps but assigns the same rank to ties.
Yes, the function is part of the SQL:2003 window function specification supported by PostgreSQL.