Co się dzieje na olimpiadzie, kiedy 2 zawodników uzyska dokładnie taki sam wynik? Powiedzmy w skoku wzwyż obaj przeskoczyli poprzeczkę na wysokości 2,20 i był to najwyższy wynik na zawodach. Gdyby, chociaż jeden z nich był kobietą, a jeden mężczyzną, to dalibyśmy im po medalu w osobnych kategoriach. Ale jeżeli nie można zróżnicować ich wyników?
Jedna z propozycji jest taka, aby dać im po „złotym medalu”, ale kolejny zawodnik nie dostanie miejsca drugiego, tylko trzecie. Pozycja numer 2 i srebrny medal zostaną nieprzydzielone.
SQL Serwer ma dwie funkcje pozwalające budować taki ranking: RANK() i DENSE_RANK().
Przyjrzyjmy się najpierw funkcji RANK(). Działa ona „po olimpijsku”. Załóżmy, że chcemy stworzyć zestawienie pokazujące produkty podzielone ze względu na kolory. W ramach każdego koloru chcemy zobaczyć listę produktów od najdroższego do najtańszego. Produkt najdroższy ma otrzymać numer 1, a kolejne produkty coraz to większe wartości.
Składnia funkcji RANK() w tym przypadku będzie wyglądać następująco:
RANK() OVER(PARTITION BY Color ORDER BY ListPrice DESC)
Całe zaś zapytanie może wyglądać tak:
SELECT Color, Name, ListPrice, RANK() OVER(PARTITION BY Color ORDER BY ListPrice DESC) FROM Production.Product WHERE Color IS NOT NULL
Zauważ, że w ramach czarnego koloru, kilka produktów ma cenę 3374,99. Te kilka produktów sprawiedliwie otrzymało od funkcji RANK pierwsze miejsce na liście najdroższych produktów. Za to kolejne produkty o cenie 2443,35 nie trafią na pozycję drugą. W numeracji powstaje dziura i produkty te klasyfikują się na miejsce 5.
Cóż. Nie podoba Ci się, że funkcja zostawiła „dziury”. Możesz ponumerować rekordy tak, aby nie pozostawały między nimi luki. Użyj w tym przypadku funkcji DENSE_RANK(). Składania tej funkcji jest identyczna, co w przypadku funkcji RANK:
DENSE_RANK() OVER(PARTITION BY Color ORDER BY ListPrice DESC)
Oto całe zapytanie:
SELECT Color, Name, ListPrice, DENSE_RANK() OVER(PARTITION BY Color ORDER BY ListPrice DESC) FROM Production.Product WHERE Color IS NOT NULL
Zmienia się jednak wynik działania. Mimo iż rekordy o tej samej cenie otrzymały numer 1, to jednak rekordy z kolejną ceną trafiły od razu na pozycję 2. Nie powstały żadne luki w numeracji. Funkcja DENSE_RANK() jest przy tym sprawiedliwa i rekordy z tą samą ceną umieszcza w rankingu na tej samej pozycji.
Funkcja ROW_NUMBER() również potrafi ustawić ranking rekordów, w tym jednak przypadku każdy rekord otrzymuje swój własny i niezależny numer. Gdyby zaś dwa rekordy miały taką samą cenę, to jeden z nich otrzyma wyższy numer od drugiego.
SELECT Color, Name, ListPrice, ROW_NUMBER() OVER(PARTITION BY Color ORDER BY ListPrice DESC) FROM Production.Product WHERE Color IS NOT NULL
ROW_NUMBER() jest więc funkcją niesprawiedliwą i opisałem ją szerzej na blogu Mobilo (mobilo24.eu).
Jeżeli temat Cię zainteresował, to zapraszam na szkolenia Microsoft w Opolu, Katowicach i Warszawie do autoryzowanego ośrodka szkoleniowego bit Polska na szkolenie 2778 Writing Queries using Microsoft SQL Server 2008.