T-SQL Ninja #12

RANK

Was tut RANK?

Wie schon die in der letzten Woche vorgestellte ROW_NUMBER Funktion, ist auch RANK eine Window Function, also eine Funktion, die die Daten sortiert in sogenannten „Fenstern“ oder „Partitionen“ abarbeitet. Die Sortierung und die Partitionen definiert ihr, indem ihr nach dem Funktionsaufruf die Schlüsselworte ORDER BY und PARTITION BY verwendet. Die RANK-Funktion nummeriert dann alle Zeilen in der Ergebnismenge, anfangend bei 1 aufsteigend durch. Dabei startet jede Partition wieder bei 1. Anders als die ROW_NUMBER-Funktion, nummeriert die RANK-Funktion dabei Zeilen, die in der Sortierung an derselben Stelle stehen, mit derselben Nummer. Bei ROW_NUMBER wird hier eine zufällige Sortierung gewählt und eindeutig fortlaufend nummeriert.

Wie könnt ihr mit RANK arbeiten?

Um die RANK Funktion zu verwenden, erstellen wir uns nun in der Wide World Importers Datenbank eine Abfrage, die sortierbare Artikel liefert, bei denen die Sortierung aber nicht eindeutig sein muss. Dafür verwenden wir eine CTE mit der wir aus den OrderLines die Anzahl der Bestellungen zählen, in denen jedes Produkt auftauchte:

WITH cte_sales AS (
    SELECT 
         StockItemID
        ,COUNT(*) AS nOrders
        ,SUM(Quantity*UnitPrice) AS TotalSales 
    FROM 
        Sales.OrderLines
    GROUP BY StockItemID
)
SELECT  
     StockItemID
    ,nOrders
    ,TotalSales
FROM cte_sales 
ORDER BY 
     nOrders DESC
    ,TotalSales DESC

Nun möchten wir aber nicht mehr nur zählen und ausgeben, sondern eine Rangliste der populärsten Produkte erstellen. Würden wir hierfür die ROW_NUMBER-Funktion verwenden, so hätten die beiden populärsten Produkte mit den IDs 104 und 120, die in jeweils 1123 mal bestellt wurden die Nummern 1 und 2, welches die 1 und welches die 2 hätte, könnten wir beispielsweise über die Sortierung nach TotalSales bestimmen:

WITH cte_sales AS (
    SELECT 
         StockItemID
        ,COUNT(*) AS nOrders
        ,SUM(Quantity*UnitPrice) AS TotalSales 
    FROM 
        Sales.OrderLines
    GROUP BY StockItemID
)
SELECT  
     StockItemID
    ,nOrders
    ,TotalSales
    ,ROW_NUMBER() OVER (ORDER BY nOrders DESC, TotalSales DESC) as Popularity
FROM cte_sales 
ORDER BY 
     nOrders DESC
    ,TotalSales DESC

Unter kaufmännischen Gesichtspunkten mag diese zusätzliche Sortierung nach den Verkaufssummen sinnvoll sein, jedoch spiegelt sie ja nicht die tatsächliche Popularität des Produkts wider. Wenn wir rein nach Verkaufszahlen gehen, müssten nämlich beide Produkte auf der Nummer eins stehen und genau das ermöglicht die RANK-Funktion. Hierbei können wir dann auch auf den Tiebreaker verzichten, der zwischen der ersten und der zweiten Zeile unterschieden hat und brauchen dementsprechend nur noch ein ORDER BY Argument:

WITH cte_sales AS (
    SELECT 
         StockItemID
        ,COUNT(*) AS nOrders
        ,SUM(Quantity*UnitPrice) AS TotalSales 
    FROM 
        Sales.OrderLines
    GROUP BY StockItemID
)
SELECT  
     StockItemID
    ,nOrders
    ,TotalSales
    ,ROW_NUMBER() OVER (ORDER BY nOrders DESC, TotalSales DESC) as Popularity
    ,RANK() OVER (ORDER BY nOrders DESC) as Real_Popularity
FROM cte_sales 
ORDER BY 
     nOrders DESC
    ,TotalSales DESC

Im Resultat seht ihr, dass nun beide Produkte, 104 und 120 die (echte) Popularität 1 erhalten haben, während dem nächsten Produkt, ähnlich wie das bei Sportwettkämpfen üblich ist, der dritte Rang zugewiesen wurde. Wenn ihr also anhand der Daten nicht unterscheiden könnt, welche Zeile nun die „bessere“ ist und eine zufällige Nummerierung vermeiden möchtet, dann verwendet statt der ROW_NUMBER-Funktion lieber die RANK-Funktion. Zum Aussortieren von Doubletten eignet sie sich aber natürlich im Gegenzug nicht, da die Dubletten hier alle denselben Rang zugewiesen bekämen…

Referenzen

Ninja-Notebooks @ GitHub

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.