In two previous posts I shown how to use the newly introduced WINDOW clause in SQL Server 2022 for simple aggregations, respectively running totals, by providing some historical context concerning what it took to do the same simple aggregations as SUM or AVG within previous versions of SQL Server. Let's look at another scenario based on the previously created Sales.vSalesOrders view - ranking records within a partition.
There are 4 ranking functions that work across partitions: Row_Number, Rank, Dense_Rank and NTile. However, in SQL Server 2000 only Row_Number could be easily implemented, and this only if there is a unique identifier (or one needed to create one on the fly):
-- ranking based on correlated subquery (SQL Server 2000+) SELECT SOL.SalesOrderId , SOL.ProductId , SOL.OrderDate , SOL.[Year] , SOL.[Month] , SOL.OrderQty , (-- correlated subquery SELECT count(SRT.SalesOrderId) FROM Sales.vSalesOrders SRT WHERE SRT.ProductId = SOL.ProductId AND SRT.[Year] = SOL.[Year] AND SRT.[Month] = SOL.[Month] AND SRT.SalesOrderId <= SOL.SalesOrderId ) RowNumberByDate FROM Sales.vSalesOrders SOL WHERE SOL.ProductId IN (745) AND SOL.[Year] = 2012 AND SOL.[Month] BETWEEN 1 AND 3 ORDER BY SOL.[Year] , SOL.[Month] , SOL.OrderDate ASC
As alternative for implementing the other ranking functions, one could use procedural language for looping, though this approach was not recommendable given the performance concerns.
SQL Server 2005 introduced all 4 ranking functions, as they are in use also today:
-- ranking functions (SQL Server 2005+) SELECT SOL.SalesOrderId , SOL.ProductId , SOL.OrderDate , SOL.[Year] , SOL.[Month] , SOL.OrderQty -- rankings , Row_Number() OVER (PARTITION BY SOL.ProductId, SOL.[Year], SOL.[Month] ORDER BY SOL.OrderQty DESC) RowNumberQty , Rank() OVER (PARTITION BY SOL.ProductId, SOL.[Year], SOL.[Month] ORDER BY SOL.OrderQty DESC) AS RankQty , Dense_Rank() OVER (PARTITION BY SOL.ProductId, SOL.[Year], SOL.[Month] ORDER BY SOL.OrderQty DESC) AS DenseRankQty , NTile(4) OVER (PARTITION BY SOL.ProductId, SOL.[Year], SOL.[Month] ORDER BY SOL.OrderQty DESC) AS NTileQty FROM Sales.vSalesOrders SOL WHERE SOL.ProductId IN (745) AND SOL.[Year] = 2012 AND SOL.[Month] BETWEEN 1 AND 3 ORDER BY SOL.[Year] , SOL.[Month] , SOL.OrderQty DESC
Now, in SQL Server 2022 the WINDOW clause allows simplifying the query as follows by defining the partition only once:
-- ranking functions (SQL Server 2022+) SELECT SOL.SalesOrderId , SOL.ProductId , SOL.OrderDate , SOL.[Year] , SOL.[Month] , SOL.OrderQty -- rankings
, Row_Number() OVER SalesByMonth AS RowNumberQty, Rank() OVER SalesByMonth AS RankQty , Dense_Rank() OVER SalesByMonth AS DenseRankQty , NTile(4) OVER SalesByMonth AS NTileQty FROM Sales.vSalesOrders SOL WHERE SOL.ProductId IN (745) AND SOL.[Year] = 2012 AND SOL.[Month] BETWEEN 1 AND 3 WINDOW SalesByMonth AS (PARTITION BY SOL.ProductId, SOL.[Year], SOL.[Month] ORDER BY SOL.OrderQty DESC) ORDER BY SOL.[Year] , SOL.[Month] , SOL.OrderQty DESC
-- ranking functions with ascending/descending sorting (SQL Server 2022+) SELECT SOL.SalesOrderId , SOL.ProductId , SOL.OrderDate , SOL.[Year] , SOL.[Month] , SOL.OrderQty -- rankings (descending) , Row_Number() OVER SalesByMonthSortedDESC AS DescRowNumberQty , Rank() OVER SalesByMonthSortedDESC AS DescRankQty , Dense_Rank() OVER SalesByMonthSortedDESC AS DescDenseRankQty , NTile(4) OVER SalesByMonthSortedDESC AS DescNTileQty -- rankings (ascending) , Row_Number() OVER SalesByMonthSortedASC AS AscRowNumberQty , Rank() OVER SalesByMonthSortedASC AS AscRankQty , Dense_Rank() OVER SalesByMonthSortedASC AS AscDenseRankQty , NTile(4) OVER SalesByMonthSortedASC AS AscNTileQty FROM Sales.vSalesOrders SOL WHERE SOL.ProductId IN (745) AND SOL.[Year] = 2012 AND SOL.[Month] BETWEEN 1 AND 3 WINDOW SalesByMonth AS (PARTITION BY SOL.ProductId, SOL.[Year], SOL.[Month]) , SalesByMonthSortedDESC AS (SalesByMonth ORDER BY SOL.OrderQty DESC) , SalesByMonthSortedASC AS (SalesByMonth ORDER BY SOL.OrderQty ASC) ORDER BY SOL.[Year] , SOL.[Month] , SOL.OrderQty DESC
Happy coding!