row_number
SQLite 3.25.0→ INTEGERAssigns a unique sequential integer to each row within its window partition, ordered by the ORDER BY clause inside the OVER clause. The numbering starts at 1. Unlike rank(), row_number() never produces ties — every row receives a distinct number, even rows with identical ORDER BY values.
Signature
row_number() OVER (window-defn)Examples
Use row_number() when you need a guaranteed-unique sequential number, such as for pagination or deduplication.
SELECT name, salary,
row_number() OVER (ORDER BY salary DESC) AS rn
FROM employees;Each employee gets a unique rank by salary, highest first. Two employees with the same salary still get different numbers (e.g., 3 and 4).PARTITION BY resets the counter for every distinct dept value.
SELECT dept, name, salary,
row_number() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank
FROM employees;Numbering restarts at 1 for each department. The top earner in each dept gets rn=1.SQLite does not support LIMIT/OFFSET inside a window frame, so this subquery approach is the standard way to paginate.
-- Pagination: page 2 of 10 rows
SELECT * FROM (
SELECT *, row_number() OVER (ORDER BY created_at DESC) AS rn
FROM orders
) WHERE rn BETWEEN 11 AND 20;Returns rows 11-20 ordered by most recent first.row_number() is the canonical way to deduplicate — rank() won't work reliably here because ties would give the same number.
-- Remove duplicate emails, keeping the most recent row
DELETE FROM users WHERE id IN (
SELECT id FROM (
SELECT id,
row_number() OVER (PARTITION BY email ORDER BY created_at DESC) AS rn
FROM users
) WHERE rn > 1
);Deletes all but the most recently created row for each duplicate email.Useful for tracking the nth occurrence of an event per entity — e.g., the 3rd purchase by a customer.
SELECT product_id, sale_date, amount,
row_number() OVER (PARTITION BY product_id ORDER BY sale_date) AS sale_seq
FROM sales;Each sale for a product gets a sequential number (1st sale, 2nd sale, etc.).Calling row_number() without an ORDER BY inside OVER produces non-deterministic numbering — the database engine may assign numbers in any order it chooses, and that order can change between queries or after a VACUUM. This silently undermines deduplication and pagination logic that depends on a stable sequence.
✓ Instead: Always include ORDER BY inside the OVER clause, e.g. row_number() OVER (PARTITION BY email ORDER BY created_at DESC), to guarantee a deterministic and meaningful row number.
Always use row_number() for deduplication tasks, not rank(). When two rows tie on the ORDER BY column, rank() assigns the same number to both and you cannot predict which one will be kept. row_number() assigns a unique value to each row, so 'WHERE rn = 1' always returns exactly one row per partition.
Added in SQLite 3.25.0 (2018-09-15). Behavior is identical to PostgreSQL's row_number(). SQLite does not support the RESPECT NULLS / IGNORE NULLS modifier that some other databases allow on certain window functions — row_number() does not take arguments so this is not relevant here.