I’ve recently been working on a data migration project and have found myself utilizing the ROW_NUMBER() function in SQL Server a lot. This function has been around since SQL Server 2005 and at its core, provides a way to provide sequential numbering for rows returned by a query.
One of the requirements for an export process was to return the most recent two customer orders for each customer account and outputting this to CSV file.
As well as sequential numbering, ROW_NUMBER() provides a way to partition the results returned so this suited me just fine 🙂
Let’s take a look at the problem in more detail and I’m going to use our good friend the AdventureWorks database to help demonstrate.
The first query returns the data by customer and with the most recent orders for each customer at the top.
SELECT SalesOrderID ,OrderDate ,SalesOrderNumber ,AccountNumber ,CustomerID ,SubTotal ,TaxAmt ,TotalDue FROM [AdventureWorks2012].[Sales].[SalesOrderHeader] ORDER BY CustomerID, OrderDate DESC
Here are the results of this query sampled.
How to return the top two rows for each group in the result set?
We will use ROW_NUMBER() to place a sequential id on the results but also partition the results so that each instance of the customerID value has its sequential id reset.
SELECT SalesOrderID ,OrderDate ,SalesOrderNumber ,AccountNumber ,CustomerID ,SubTotal ,TaxAmt ,TotalDue ,ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY CustomerID, OrderDate DESC) AS RowNum FROM [AdventureWorks2012].[Sales].[SalesOrderHeader]
This returns a new column called RowNum which is providing the sequential numbering for each instance of CustomerID based on the PARTITION BY and ORDER BY of the RowNum column.
All that remains is to now select the first two rows for each CustomerID. So I am wrapping it up in a CTE and returning the first two rows using the WHERE clause.
WITH MyRowSet AS ( SELECT SalesOrderID ,OrderDate ,SalesOrderNumber ,AccountNumber ,CustomerID ,SubTotal ,TaxAmt ,TotalDue ,ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY CustomerID, OrderDate DESC) AS RowNum FROM [AdventureWorks2012].[Sales].[SalesOrderHeader] ) SELECT * FROM MyRowSet WHERE RowNum <= 2
Which returns our desired result…
I hope you found this post useful. 🙂
Michal Pawlikowski says
And if you want to skip any sorting just order by SELECT NULL:
SELECT rown = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),*
FROM TABLE
Andy Hayes says
Thank you Michal, very useful! 🙂
Hien Dang says
Thanks! helped me today
Faroque says
Very Nice. Clear & Step by Step instruction.
David says
Great post. I think you could do all without a CTE by using a SELECT TOP WITH TIES:
SELECT TOP 2 WITH TIES SalesOrderID
,OrderDate
,SalesOrderNumber
,AccountNumber
,CustomerID
,SubTotal
,TaxAmt
,TotalDue
FROM [AdventureWorks2012].[Sales].[SalesOrderHeader]
ORDER BY ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY CustomerID, OrderDate DESC)
Peter Moore says
My tests show this is far better from a performance standpoint on a larger table
Channdeep Singh says
Many thanks Andy and Michal. Regards.
Ranjan says
How can I pick only 3 orderdate for each date for example
2011-05-31 00:00:00.000
2011-05-31 00:00:00.000
2011-05-31 00:00:00.000
2011-06-01 00:00:00.000
2011-06-01 00:00:00.000
2011-06-01 00:00:00.000
and so on also with other dates.
I used this command but only get three of following dates: 2011-05-31 00:00:00.000 and everything of all others
select r1.OrderDate
from (select *, rank() over(partition by OrderDate order by (select null)) as r
from AdventureWorks.Sales.SalesOrderHeader)r1 where r <= 3
Edoardo says
very useful, thanks|
Josh Christian says
this helped me a lot. made it nice and simple. thanks for your help
vimal says
It saved my hours, perfect and to the point detail
Simon says
Works great!
Thanks a lot~ 😉
gzdxtr says
thanks,
it’s useful.
Baibhav Bajpai says
Thanks Andy! Saved my day.
Mehak says
Perfect solution. Thanks.
Jared says
I stumbled across this wonderful solution and it really helped me out, thank you!
JIm P says
Thanks for taking the time to post. Got me going in the right direction.
Al says
You save my day thanks lot , you deserve cup of coffee 🙂
Dale Jordaan says
This helped me so much!
thanks