Add a row number to result set of a SQL query
Adding Row Numbers to SQL Query Results

Learn how to assign sequential row numbers to your SQL query result sets using various techniques in SQL Server, enhancing data analysis and reporting capabilities.
Assigning a row number to each record in a SQL query's result set is a common requirement in many data manipulation and reporting scenarios. This functionality allows you to easily identify the order of rows, paginate results, or perform calculations based on a row's position. While some database systems offer direct functions for this, SQL Server provides powerful window functions like ROW_NUMBER()
, RANK()
, and DENSE_RANK()
to achieve this with flexibility.
Understanding ROW_NUMBER()
The ROW_NUMBER()
window function assigns a unique, sequential integer to each row within a partition of a result set, starting at 1 for the first row in each partition. The order of rows within each partition is determined by the ORDER BY
clause specified within the OVER()
clause. This function is particularly useful when you need a distinct numbering for each row based on a specific sorting criterion, regardless of duplicate values in the ordering column.
SELECT
ROW_NUMBER() OVER (ORDER BY OrderDate ASC) AS RowNum,
OrderID,
OrderDate,
CustomerID
FROM
Orders;
Basic usage of ROW_NUMBER() to assign a sequential number to each order based on its date.
flowchart TD A[Start Query] --> B{SELECT ROW_NUMBER() OVER (ORDER BY Column) AS RowNum, ...} B --> C[Define Partition (Optional)] C --> D[Define Order (Mandatory)] D --> E[Assign Sequential Number] E --> F[Return Result Set with Row Numbers]
Flowchart illustrating the process of using ROW_NUMBER() in a SQL query.
ROW_NUMBER() with PARTITION BY
The PARTITION BY
clause within the OVER()
clause allows you to divide the result set into logical groups (partitions). ROW_NUMBER()
is then applied independently to each partition, restarting the numbering from 1 for the first row in each new partition. This is invaluable for scenarios like numbering items within categories, or transactions per customer.
SELECT
ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderDate ASC) AS CustomerOrderNum,
CustomerID,
OrderID,
OrderDate,
TotalAmount
FROM
Orders;
Using ROW_NUMBER() with PARTITION BY to number orders sequentially for each customer.
ROW_NUMBER()
, ensure your ORDER BY
clause within OVER()
is sufficiently unique to guarantee a consistent row number assignment, especially if you need deterministic results for identical values.Distinguishing ROW_NUMBER(), RANK(), and DENSE_RANK()
While ROW_NUMBER()
assigns a unique number to every row, RANK()
and DENSE_RANK()
handle ties differently. RANK()
assigns the same rank to rows with identical values in the ORDER BY
clause and then skips the next rank(s). DENSE_RANK()
also assigns the same rank to ties but does not skip any ranks, assigning consecutive ranks. Understanding these differences is crucial for choosing the correct function for your specific ranking needs.
SELECT
ProductName,
Price,
ROW_NUMBER() OVER (ORDER BY Price DESC) AS RowNum,
RANK() OVER (ORDER BY Price DESC) AS RankNum,
DENSE_RANK() OVER (ORDER BY Price DESC) AS DenseRankNum
FROM
Products
ORDER BY
Price DESC;
Comparison of ROW_NUMBER(), RANK(), and DENSE_RANK() with example product prices.
IDENTITY
columns, or SET ROWCOUNT
with a loop, which are generally not recommended for performance.