SQL RANK () contre ROW_NUMBER ()

191

Je suis confus au sujet des différences entre ces derniers. L'exécution du SQL suivant me donne deux ensembles de résultats identiques. Quelqu'un peut-il expliquer les différences?

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
la source

Réponses:

223

ROW_NUMBER: renvoie un numéro unique pour chaque ligne commençant par 1. Pour les lignes qui ont des valeurs en double, des nombres sont attribués arbitrairement.

Rang: attribue un numéro unique pour chaque ligne commençant par 1, sauf pour les lignes qui ont des valeurs en double, auquel cas le même classement est attribué et un espace apparaît dans la séquence pour chaque classement en double.

Ritesh Mengji
la source
327

Vous ne verrez la différence que si vous avez des liens dans une partition pour une valeur de commande particulière.

RANKet DENSE_RANKsont déterministes dans ce cas, toutes les lignes avec la même valeur pour les colonnes de classement et de partitionnement aboutiront à un résultat égal, alors ROW_NUMBERqu'elles attribueront arbitrairement (de manière non déterministe) un résultat incrémentiel aux lignes liées.

Exemple: (Toutes les lignes sont identiques StyleIDet sont donc dans la même partition et dans cette partition, les 3 premières lignes sont liées lorsqu'elles sont triées par 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  

Retour

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

Vous pouvez voir que pour les trois lignes identiques les ROW_NUMBERincréments, la RANKvaleur reste la même puis elle saute 4. DENSE_RANKattribue également le même rang aux trois lignes, mais la valeur distincte suivante se voit attribuer une valeur de 2.

Martin Smith
la source
26
Super! ... Merci de mentionner DENSE_RANK
Sandeep Thomas
7
Merci pour un excellent exemple. M'a aidé à réaliser que j'avais mal utilisé la fonction RANK () alors que ROW_NUMBER () aurait été beaucoup plus approprié.
Ales Potocnik Hahonina
2
sérieusement, c'est génial.
Matt Felzani
35

Cet article couvre une relation intéressante entre ROW_NUMBER()etDENSE_RANK() (la RANK()fonction n'est pas traitée spécifiquement). Lorsque vous avez besoin d'un généré ROW_NUMBER()sur une SELECT DISTINCTinstruction, le ROW_NUMBER()produira des valeurs distinctes avant qu'elles ne soient supprimées par le DISTINCTmot - clé. Par exemple cette requête

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

... pourrait produire ce résultat ( DISTINCTsans effet):

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

Alors que cette requête:

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

... produit ce que vous voulez probablement dans ce cas:

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

Notez que la ORDER BYclause de la DENSE_RANK()fonction aura besoin de toutes les autres colonnes de la SELECT DISTINCTclause pour fonctionner correctement.

La raison en est que logiquement, les fonctions de fenêtre sont calculées avant d' DISTINCTêtre appliquées .

Comparaison des trois fonctions

Utilisation de la syntaxe standard PostgreSQL / Sybase / SQL ( WINDOWclause):

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

... tu auras:

+---+------------+------+------------+
| 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
la source
1
ROW_NUMBER et DENSE_RANK produisent des valeurs avant que distinct ne soit appliqué. En fait, toute fonction de classement ou toute fonction produit des résultats avant que DISTINCT ne soit appliqué.
Thanasis Ioannidis
1
@ThanasisIoannidis: Absolument. J'ai mis à jour ma réponse avec un lien vers un article de blog, où j'ai expliqué le véritable ordre des opérations SQL
Lukas Eder
3

Un peu:

Le rang d'une ligne est égal à un plus le nombre de rangs qui précèdent la ligne en question.

Row_number est le rang distinct des lignes, sans aucune lacune dans le classement.

http://www.bidn.com/blogs/marcoadf/bidn-blog/379/ranking-functions-row_number-vs-rank-vs-dense_rank-vs-ntile

Pas moi
la source
Ah, je pense que c'est ce qui me manquait -> Row_number est le rang distinct des lignes, sans aucune lacune dans le classement.
dotNET Hobbiest
1

Requête simple sans clause de partition:

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 

Production:

    --------|-------|-----------|----------
    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
la source
0

Regardez cet exemple.

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

Insérez quelques données

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')

Répéter les mêmes valeurs pour 1

INSERT INTO dbo. # TestTable (id, create_date, info1, info2) VALUES (1, '1/1/09', 'Blue', 'Green')

Regardez tout

SELECT * FROM #TestTable

Regardez vos résultats

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

Besoin de comprendre les différents

sansalk
la source
-1

Faites également attention à ORDER BY dans PARTITION (Standard AdventureWorks db est utilisé par exemple) lors de l'utilisation de RANK.

SÉLECTIONNER as1.SalesOrderID, as1.SalesOrderDetailID, RANK () OVER (PARTITION BY as1.SalesOrderID ORDER BY as1.SalesOrderID) ranknoequal, RANK () OVER (PARTITION BY as1.SalesOrderID ORDER BY as1.SalesOrderDetiffailDailDail. SalesOrderId = 43659 ORDER BY SalesOrderDetailId;

Donne résultat:

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

Mais si vous modifiez l'ordre par à (utilisez OrderQty:

SELECT 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.SalesOrderID SalesOrderId = 43659 ORDER BY OrderQty;

Donne:

SalesOrderID OrderQty 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

Notez comment le rang change lorsque nous utilisons OrderQty (deuxième table de la colonne la plus à droite) dans ORDER BY et comment il change lorsque nous utilisons SalesOrderDetailID (première table de la colonne la plus à droite) dans ORDER BY.

user2629395
la source
-1

Je n'ai rien fait avec rank, mais j'ai découvert cela aujourd'hui avec row_number ().

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

Cela entraînera la répétition des numéros de ligne car dans mon cas, chaque nom contient tous les éléments. Chaque article sera commandé en fonction du nombre vendu.

+--------+------+-----+----+
|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
la source