没有 Rank() 的排名

Ranking without Rank()

这里是 table:

StuId   Name    Class   Marks
-----------------------------
2003    aman    X-A     91
2005    ankita  X-A     89
2010    Aakash  X-A     87
2011    Cyril   X-A     87
2012    Bala    X-B     87
2013    Sara    X-C     89
2014    Katlyn  X-C     89
2015    Casy    X-C     87
2016    Katie   X-B     93

我需要输出 table 为:

StuId   Name    Class   Marks Rank
-----------------------------------
2003    aman    X-A     91    1
2005    ankita  X-A     89    2
2010    Aakash  X-A     87    3
2011    Cyril   X-A     87    3
2016    Katie   X-B     93    1
2012    Bala    X-B     87    2
2013    Sara    X-C     89    1
2014    Katlyn  X-C     89    1
2015    Casy    X-C     87    3

为此我执行了以下查询:

SELECT *,
    RANK() OVER (PARTITION BY Class ORDER BY Marks DESC) AS Rank
FROM StudentTable;

但是如何在不使用 Rank() 的情况下获得相同的结果?

我正在使用身份键创建临时 table 以便对行进行排序。当对具有 IDENTITY 列的 table 执行 INSERT 时,SQL 引擎会遵守 ORDER BY 子句。

然后,我使用递归 CTE 为每一行创建一个 RANK 列。思路很简单:

  • 如果class发生变化,重新开始排名
  • 如果 class 相同且 marks 相同 - 使用相同等级(增加当前等级的计数器)
  • 如果 class 相同而 marks 不同,则用 1 增加等级并重置等级计数器

我们正在使用这样的计数器来实现 RANK 行为,对于 DENSE_RANK 我们不需要这样的计数器。

所以,代码是这样的:

DECLARE @DataSource TABLE
(
    [StudID] INT
   ,[Name] VARCHAR(12)
   ,[Class] VARCHAR(12)
   ,[Marks] TINYINT
);

INSERT INTO @DataSource ([StudID], [Name], [Class], [Marks])
VALUES ('2003', 'aman', 'X-A', '91')
      ,('2005', 'ankita', 'X-A', '89')
      ,('2010', 'Aakash', 'X-A', '87')
      ,('2011', 'Cyril', 'X-A', '87')
      ,('2012', 'Bala', 'X-B', '87')
      ,('2013', 'Sara', 'X-C', '89')
      ,('2014', 'Katlyn', 'X-C', '89')
      ,('2015', 'Casy', 'X-C', '87')
      ,('2016', 'Katie', 'X-B', '93');

CREATE TABLE #DataSource
(
    [StudID] INT
   ,[Name] VARCHAR(12)
   ,[Class] VARCHAR(12)
   ,[Marks] TINYINT
   ,[RowID] INT IDENTITY(1,1)
)

INSERT INTO #DataSource ([StudID], [Name], [Class], [Marks])
SELECT [StudID], [Name], [Class], [Marks]
FROM @DataSource
ORDER BY [Class] ASC, [Marks] DESC;

WITH DataSource AS
(
    SELECT *
          ,1 AS [Rank]
          ,0 AS [RanksCount]
    FROM #DataSource
    WHERE [RowID] = 1
    UNION ALL
    SELECT DS1.*
          ,CASE WHEN DS1.[Class] = DS2.[Class] 
                THEN CASE WHEN DS1.[Marks] = DS2.[Marks] THEN DS2.[Rank] ELSE DS2.[Rank] + DS2.[RanksCount] + 1 END
                ELSE 1
           END
          ,CASE WHEN DS1.[Class] = DS2.[Class] 
                THEN CASE WHEN DS1.[Marks] = DS2.[Marks] THEN DS2.[RanksCount] + 1 ELSE 0 END
                ELSE 0
           END
    FROM #DataSource DS1
    INNER JOIN DataSource DS2
        ON DS1.[RowID] = DS2.[RowID] + 1

)
SELECT *
FROM DataSource
ORDER BY [RowID];


DROP TABLE #DataSource;

注意,这是一个想法。您可以用 IIF 替换 CASE WHEN 语句,或者您可以用不同的方式编写 CTE(不使用第二个 table 来存储数据)。

祝你好运。

尽管您最好使用排名,但以下内容仅适用于计数:

COUNT(*) OVER (PARTITION BY Class ORDER BY Marks ASC RANGE BETWEEN UNBOUNDED PRECEDING  AND CURRENT ROW)
    - COUNT(*) OVER (PARTITION BY Class,Marks) + 1

或者您可以使用相关的子查询,这只使用纯计数但会更慢:

(SELECT COUNT(*) FROM StudentTable AS CountMe WHERE StudentTable.Class = CountMe.Class AND StudentTable.Marks > CountMe.Marks) + 1 AS Rank

您可以尝试使用相关子查询,该子查询对同一 "Class".

中高于或等于的标记使用不同的计数
SELECT *, 
(
 SELECT COUNT(DISTINCT s2.Marks) 
 FROM StudentTable s2 
 WHERE s2.Class = s.Class 
   AND s2.Marks >= s.Marks
) AS Rank
FROM StudentTable s
ORDER BY Class, Marks DESC;

可以找到一个测试db<>fiddlehere

但是RANK会更有效率

换句话说,学生的排名就是比他得分高的学生加一的学生数。例如。 87 分的学生前面有 89 分和 91 分,所以他们排在第 3 位:

SELECT t.*, (
    SELECT COUNT(*)
    FROM StudentTable AS x
    WHERE x.Class = t.Class
    AND x.Marks > t.Marks
) + 1 AS Rank
FROM StudentTable AS t
ORDER BY t.Class, Rank

SQL Fiddle