SQL RANK () a ROW_NUMBER ()

190

Jestem zmieszany różnicami między nimi. Uruchomienie następującego kodu SQL daje mi dwa identyczne zestawy wyników. Czy ktoś może wyjaśnić różnice?

SELECT ID, [Description], RANK()       OVER(PARTITION BY StyleID ORDER BY ID) as 'Rank'      FROM SubStyle
SELECT ID, [Description], ROW_NUMBER() OVER(PARTITION BY StyleID ORDER BY ID) as 'RowNumber' FROM SubStyle
dotNET Hobbiest
źródło

Odpowiedzi:

222

ROW_NUMBER: Zwraca unikalny numer dla każdego wiersza, zaczynając od 1. W przypadku wierszy, które mają zduplikowane wartości, liczby są arbitralnie przypisywane.

Ranga: Przypisuje unikalny numer dla każdego wiersza zaczynającego się od 1, z wyjątkiem wierszy, które mają zduplikowane wartości, w którym to przypadku przypisywany jest ten sam ranking i pojawia się przerwa w sekwencji dla każdego duplikatu rankingu.

Ritesh Mengji
źródło
327

Różnicę zobaczysz tylko wtedy, gdy masz powiązania w obrębie partycji dla określonej wartości zamówienia.

RANKi DENSE_RANKsą deterministyczne w tym przypadku, wszystkie wiersze o tej samej wartości zarówno dla kolumn porządkujących, jak i dzielących kończą się jednakowym wynikiem, podczas gdy ROW_NUMBERarbitralnie (nie deterministycznie) przypiszą wynik rosnący do powiązanych wierszy.

Przykład: (Wszystkie wiersze mają to samo, StyleIDwięc znajdują się w tej samej partycji i w tej partycji pierwsze 3 wiersze są powiązane, jeśli są uporządkowane według ID)

WITH T(StyleID, ID)
     AS (SELECT 1,1 UNION ALL
         SELECT 1,1 UNION ALL
         SELECT 1,1 UNION ALL
         SELECT 1,2)
SELECT *,
       RANK() OVER(PARTITION BY StyleID ORDER BY ID)       AS 'RANK',
       ROW_NUMBER() OVER(PARTITION BY StyleID ORDER BY ID) AS 'ROW_NUMBER',
       DENSE_RANK() OVER(PARTITION BY StyleID ORDER BY ID) AS 'DENSE_RANK'
FROM   T  

Zwroty

StyleID     ID       RANK      ROW_NUMBER      DENSE_RANK
----------- -------- --------- --------------- ----------
1           1        1         1               1
1           1        1         2               1
1           1        1         3               1
1           2        4         4               2

Możesz zobaczyć, że dla trzech identycznych rzędów ROW_NUMBERprzyrosty, RANKwartość pozostaje taka sama, a następnie przeskakuje 4. DENSE_RANKprzypisuje również tę samą pozycję do wszystkich trzech wierszy, ale następnie następnej odrębnej wartości przypisuje się wartość 2.

Martin Smith
źródło
26
Świetnie! ... Dzięki wzmianka o DENSE_RANK
Sandeep Thomas
7
Dzięki za świetny przykład. Pomógł mi uświadomić sobie, że niewłaściwie użyłem funkcji RANK (), gdy ROW_NUMBER () byłby o wiele bardziej odpowiedni.
Ales Potocnik Hahonina
2
poważnie, to jest niesamowite.
Matt Felzani,
35

W tym artykule opisano interesujący związek między ROW_NUMBER()iDENSE_RANK() ( RANK()funkcja nie jest specjalnie traktowana). Kiedy trzeba wygenerowany ROW_NUMBER()w SELECT DISTINCToświadczeniu, ROW_NUMBER()będzie produkować różne wartości zanim zostaną one usunięte przez DISTINCTsłowa kluczowego. Np. To zapytanie

SELECT DISTINCT
  v, 
  ROW_NUMBER() OVER (ORDER BY v) row_number
FROM t
ORDER BY v, row_number

... może dać ten wynik ( DISTINCTnie ma efektu):

+---+------------+
| V | ROW_NUMBER |
+---+------------+
| a |          1 |
| a |          2 |
| a |          3 |
| b |          4 |
| c |          5 |
| c |          6 |
| d |          7 |
| e |          8 |
+---+------------+

Podczas gdy to zapytanie:

SELECT DISTINCT
  v, 
  DENSE_RANK() OVER (ORDER BY v) row_number
FROM t
ORDER BY v, row_number

... produkuje to, co prawdopodobnie chcesz w tym przypadku:

+---+------------+
| V | ROW_NUMBER |
+---+------------+
| a |          1 |
| b |          2 |
| c |          3 |
| d |          4 |
| e |          5 |
+---+------------+

Zauważ, że ORDER BYklauzula DENSE_RANK()funkcji będzie potrzebowała wszystkich innych kolumn z SELECT DISTINCTklauzuli, aby działać poprawnie.

Powodem tego jest to, że logicznie funkcje okna są obliczane przed DISTINCTzastosowaniem .

Wszystkie trzy funkcje w porównaniu

Korzystanie ze standardowej składni PostgreSQL / Sybase / SQL ( WINDOWklauzula):

SELECT
  v,
  ROW_NUMBER() OVER (window) row_number,
  RANK()       OVER (window) rank,
  DENSE_RANK() OVER (window) dense_rank
FROM t
WINDOW window AS (ORDER BY v)
ORDER BY v

... dostaniesz:

+---+------------+------+------------+
| V | ROW_NUMBER | RANK | DENSE_RANK |
+---+------------+------+------------+
| a |          1 |    1 |          1 |
| a |          2 |    1 |          1 |
| a |          3 |    1 |          1 |
| b |          4 |    4 |          2 |
| c |          5 |    5 |          3 |
| c |          6 |    5 |          3 |
| d |          7 |    7 |          4 |
| e |          8 |    8 |          5 |
+---+------------+------+------------+
Lukas Eder
źródło
1
Zarówno ROW_NUMBER, jak i DENSE_RANK wytwarzają wartości przed zastosowaniem odrębnego. Właściwie wszystkie funkcje rankingu lub dowolne funkcje dają wyniki przed zastosowaniem DISTINCT.
Thanasis Ioannidis
1
@ThanasisIoannidis: Absolutnie. Zaktualizowałem swoją odpowiedź linkiem do postu na blogu, w którym wyjaśniłem prawdziwą kolejność operacji SQL
Lukas Eder
1

Proste zapytanie bez klauzuli partycji:

select 
    sal, 
    RANK() over(order by sal desc) as Rank,
    DENSE_RANK() over(order by sal desc) as DenseRank,
    ROW_NUMBER() over(order by sal desc) as RowNumber
from employee 

Wynik:

    --------|-------|-----------|----------
    sal     |Rank   |DenseRank  |RowNumber
    --------|-------|-----------|----------
    5000    |1      |1          |1
    3000    |2      |2          |2
    3000    |2      |2          |3
    2975    |4      |3          |4
    2850    |5      |4          |5
    --------|-------|-----------|----------
DSR
źródło
0

Spójrz na ten przykład.

CREATE TABLE [dbo].#TestTable(
    [id] [int] NOT NULL,
    [create_date] [date] NOT NULL,
    [info1] [varchar](50) NOT NULL,
    [info2] [varchar](50) NOT NULL,
)

Wstaw trochę danych

INSERT INTO dbo.#TestTable (id, create_date, info1, info2)
VALUES (1, '1/1/09', 'Blue', 'Green')
INSERT INTO dbo.#TestTable (id, create_date, info1, info2)
VALUES (1, '1/2/09', 'Red', 'Yellow')
INSERT INTO dbo.#TestTable (id, create_date, info1, info2)
VALUES (1, '1/3/09', 'Orange', 'Purple')
INSERT INTO dbo.#TestTable (id, create_date, info1, info2)
VALUES (2, '1/1/09', 'Yellow', 'Blue')
INSERT INTO dbo.#TestTable (id, create_date, info1, info2)
VALUES (2, '1/5/09', 'Blue', 'Orange')
INSERT INTO dbo.#TestTable (id, create_date, info1, info2)
VALUES (3, '1/2/09', 'Green', 'Purple')
INSERT INTO dbo.#TestTable (id, create_date, info1, info2)
VALUES (3, '1/8/09', 'Red', 'Blue')

Powtórz te same wartości dla 1

INSERT INTO dbo. # TestTable (id, create_date, info1, info2) WARTOŚCI (1, „1/1/09”, „Niebieski”, „Zielony”)

Look All

SELECT * FROM #TestTable

Spójrz na swoje wyniki

SELECT Id,
    create_date,
    info1,
    info2,
    ROW_NUMBER() OVER (PARTITION BY Id ORDER BY create_date DESC) AS RowId,
    RANK() OVER(PARTITION BY Id ORDER BY create_date DESC)    AS [RANK]
FROM #TestTable

Musisz zrozumieć inaczej

sansalk
źródło
-1

Należy również zwrócić uwagę na ORDER BY w PARTITION (na przykład używana jest standardowa baza danych AdventureWorks) podczas korzystania z funkcji RANK.

WYBIERZ as1.SalesOrderID, as1.SalesOrderDetailID, RANK () OVER (PARTITION BY as1.SalesOrderID ORDER BY as1.SalesOrderID) ranknoequal, RANK () OVER (PARTITION BY as1.SalesOrderID ORDER BY as1.SalesOrderDetailId) rank1odiff ODWROT SalesOrderId = 43659 ORDER BY SalesOrderDetailId;

Daje wynik:

SalesOrderID SalesOrderDetailID rank_same_as_partition rank_salesorderdetailid
43659 1 1 1
43659 2 1 2
43659 3 1 3
43659 4 1 4
43659 5 1 5
43659 6 1 6
43659 7 1 7
43659 8 1 8
43659 9 1 9
43659 10 1 10
43659 11 1
4365 1 12

Ale jeśli zmień kolejność na do (użyj OrderQty:

WYBIERZ as1.SalesOrderID, as1.OrderQty, RANK () OVER (PARTITION BY as1.SalesOrderID ORDER BY as1.SalesOrderID) ranknoequal, RANK () OVER (PARTITION BY as1.SalesOrderID ORDER BY as1.OrderQty) rank_orderqty FROM Sales.Sales ORderDetail SalesOrderId = 43659 ORDER BY OrderQty;

Daje:

SalesOrderID Zamówienie Ilość rank_salesorderid rank_orderqty
43659 1 1 1
43659 1 1 1
43659 1 1 1
43659 1 1 1
43659 1 1 1
43659 1 1 1
43659 2 1 7
43659 2 1 7
43659 3 1 9
43659 3 1 9
43659 4 1 11
43659 6 1 12

Zauważ, jak zmienia się Ranga, gdy używamy OrderQty (druga kolumna po prawej stronie) w ORDER BY i jak zmienia się, gdy używamy SalesOrderDetailID (pierwsza kolumna po prawej stronie) w ORDER BY.

użytkownik2629395
źródło
-1

Nie zrobiłem nic z rank, ale odkryłem to dzisiaj za pomocą row_number ().

select item, name, sold, row_number() over(partition by item order by sold) as row from table_name

Spowoduje to kilka powtarzających się numerów wierszy, ponieważ w moim przypadku każda nazwa zawiera wszystkie elementy. Każdy przedmiot zostanie uporządkowany według ilości sprzedanych.

+--------+------+-----+----+
|glasses |store1|  30 | 1  |
|glasses |store2|  35 | 2  |
|glasses |store3|  40 | 3  |
|shoes   |store2|  10 | 1  |
|shoes   |store1|  20 | 2  |
|shoes   |store3|  22 | 3  |
+--------+------+-----+----+
SarahLaMont
źródło