在SQL 2005中存在四种排名函数: ROW_NUMBER、RANK、DENSE_RANK 和 NTILE。这些新函数可以有效地分析数据以及向查询的结果行提供排序值。您可能发现这些新函数有用的典型方案包括:将连续整数分配给结果行,以便进行表示、分页、计分和绘制直方图。
下面通过具体的方案将用来讨论和演示不同的函数和它们的子句。
十一位演讲者在会议中发表演讲,并且为他们的讲话获得范围为 1 到 9 的分数。结果被总结并存储在下面的 SpeakerStats 表中:
- CREATE TABLE SpeakerStats(
- speaker VARCHAR(10) NOT NULL PRIMARY KEY
- , track VARCHAR(10) NOT NULL
- , score INT NOT NULL
- , pctfilledevals INT NOT NULL
- , numsessions INT NOT NULL)
- SET NOCOUNT ON
- INSERT INTO SpeakerStats VALUES('Dan', 'Sys', 3, 22, 4)
- INSERT INTO SpeakerStats VALUES('Ron', 'Dev', 9, 30, 3)
- INSERT INTO SpeakerStats VALUES('Kathy', 'Sys', 8, 27, 2)
- INSERT INTO SpeakerStats VALUES('Suzanne', 'DB', 9, 30, 3)
- INSERT INTO SpeakerStats VALUES('Joe', 'Dev', 6, 20, 2)
- INSERT INTO SpeakerStats VALUES('Robert', 'Dev', 6, 28, 2)
- INSERT INTO SpeakerStats VALUES('Mike', 'DB', 8, 20, 3)
- INSERT INTO SpeakerStats VALUES('Michele', 'Sys', 8, 31, 4)
- INSERT INTO SpeakerStats VALUES('Jessica', 'Dev', 9, 19, 1)
- INSERT INTO SpeakerStats VALUES('Brian', 'Sys', 7, 22, 3)
- INSERT INTO SpeakerStats VALUES('Kevin', 'DB', 7, 25, 4)
复制代码每个演讲者都在该表中具有一个行,其中含有该演讲者的名字、议题、平均得分、填写评价的与会者相对于参加会议的与会者数量的百分比以及该演讲者发表演讲的次数。本节演示如何使用新的排序函数分析演讲者统计数据以生成有用的信息。
1、ROW_NUMBER()函数 返回结果集分区内行的序列号,每个分区的第一行从 1 开始。一般与OVER连用。
例如,假设您要返回所有演讲者的 speaker、track 和 score,同时按照 score 降序向结果行分配从 1 开始的连续值。以下查询通过使用 ROW_NUMBER 函数并指定 OVER (ORDER BY score DESC) 生成所需的结果:
- SELECT ROW_NUMBER() OVER(ORDER BY score DESC) AS rownum, speaker, track, score
- FROM SpeakerStats
复制代码rownum speaker track score pctfilledevals numsessions ------ ---------- ---------- ----------- -------------- ----------- 1 Ron Dev 9 30 3 2 Suzanne DB 9 30 3 3 Jessica Dev 9 19 1 4 Michele Sys 8 31 4 5 Kathy Sys 8 27 2 6 Mike DB 8 20 3 7 Kevin DB 7 25 4 8 Brian Sys 7 22 3 9 Robert Dev 6 28 2 10 Joe Dev 6 20 2 11 Dan Sys 3 22 4 |
得分最高的演讲者获得行号 1,得分最低的演讲者获得行号 11。ROW_NUMBER 总是按照请求的排序为不同的行生成不同的行号。请注意,如果在 OVER() 选项中指定的 ORDER BY 列表不唯一,则结果是不确定的。这意味着该查询具有一个以上正确的结果;在该查询的不同调用中,可能获得不同的结果。例如,在我们的示例中,有三个不同的演讲者获得相同的最高得分 (9):Jessica、Ron 和 Suzanne。由于 SQL Server 必须为不同的演讲者分配不同的行号,因此您应当假设分别分配给 Jessica、Ron 和 Suzanne 的值 1、2 和 3 是按任意顺序分配给这些演讲者的。如果值 1、2 和 3 被分别分配给 Ron、Suzanne 和 Jessica,则结果应该同样正确。
如果您指定一个唯一的 ORDER BY 列表,则结果总是确定的。例如,假设在演讲者之间出现得分相同的情况时,您希望使用最高的 pctfilledevals 值来分出先后。如果值仍然相同,则使用最高的 numsessions 值来分出先后。最后,如果值仍然相同,则使用最低词典顺序 speaker 名字来分出先后。由于 ORDER BY 列表 — score、pctfilledevals、numsessions 和 speaker — 是唯一的,因此结果是确定的:
- SELECT ROW_NUMBER() OVER(ORDER BY score DESC, pctfilledevals DESC, numsessions DESC, speaker) AS rownum, speaker, track, score, pctfilledevals, numsessions
- FROM SpeakerStats
复制代码rownum speaker track score pctfilledevals numsessions ------ ---------- ---------- ----------- -------------- ----------- 1 Ron Dev 9 30 3 2 Suzanne DB 9 30 3 3 Jessica Dev 9 19 1 4 Michele Sys 8 31 4 5 Kathy Sys 8 27 2 6 Mike DB 8 20 3 7 Kevin DB 7 25 4 8 Brian Sys 7 22 3 9 Robert Dev 6 28 2 10 Joe Dev 6 20 2 11 Dan Sys 3 22 4 |
本节所讲到排序函数的重要好处之一是它们的效率。SQL Server 的优化程序只需要扫描数据一次,以便计算值。它完成该工作的方法是:使用在排序列上放置的索引的有序扫描,或者,如果未创建适当的索引,则扫描数据一次并对其进行排序。
另一个好处是语法的简单性。为了让您感受一下通过使用在 SQL Server 的较低版本中采用的基于集的方法来计算排序值是多么困难和低效,请考虑下面的 SQL Server 2000 查询,它返回与上一个查询相同的结果:
- SELECT (SELECT COUNT(*) FROM SpeakerStats AS S2
- WHERE S2.score > S1.score
- OR (S2.score = S1.score AND S2.pctfilledevals > S1.pctfilledevals)
- OR (S2.score = S1.score AND S2.pctfilledevals = S1.pctfilledevals AND
- S2.numsessios > S1.numsessions)
- OR (S2.score = S1.score AND S2.pctfilledevals = S1.pctfilledevals AND
- S2.numsessions = S1.numsessions AND S2.speaker < S1.speaker)
- ) + 1 AS rownum
- , speaker, track, score, pctfilledevals, numsessions
- FROM SpeakerStats AS S1
- ORDER BY score DESC, pctfilledevals DESC, numsessions DESC, speaker
复制代码该查询显然比 SQL Server 2005 查询复杂得多。此外,对于 SpeakerStats 表中的每个基础行,SQL Server 都必须扫描该表的另一个实例中的所有匹配行。对于基础表中的每个行,平均大约需要扫描该表的一半(最少)行。SQL Server 2005 查询的性能恶化是线性的,而 SQL Server 2000 查询的性能恶化是指数性的。即使是在相当小的表中,性能差异也是显著的。
行号的一个典型应用是通过查询结果分页。给定页大小(以行数为单位)和页号,需要返回属于给定页的行。例如,假设您希望按照“score DESC, speaker”顺序从 SpeakerStats 表中返回第二页的行,并且假定页大小为三行。下面的查询首先按照指定的排序计算派生表 D 中的行数,然后只筛选行号为 4 到 6 的行(它们属于第二页):
- SELECT *
- FROM (SELECT ROW_NUMBER() OVER(ORDER BY score DESC, speaker) AS rownum,
- speaker, track, score
- FROM SpeakerStats) AS D
- WHERE rownum BETWEEN 4 AND 6
复制代码以下为结果集:
rownum speaker track score ------ ---------- ---------- ----------- 4 Kathy Sys 8 5 Michele Sys 8 6 Mike DB 8 |
用更一般的术语表达就是,给定 @pagenum 变量中的页号和 @pagesize 变量中的页大小,以下查询返回属于预期页的行:
- DECLARE @pagenum AS INT, @pagesize AS INT
- SET @pagenum = 2
- SET @pagesize = 3
- SELECT * FROM (SELECT ROW_NUMBER() OVER(ORDER BY score DESC, speaker) AS rownum
- ,speaker
- , track
- , score
- FROM SpeakerStats)AS D
- WHERE rownum BETWEEN (@pagenum-1)*@pagesize+1 AND @pagenum*@pagesize
复制代码上述方法对于您只对行的一个特定页感兴趣的特定请求而言已经足够了。但是,当用户发出多个请求时,该方法就不能满足需要了,因为该查询的每个调用都需要您对表进行完整扫描,以便计算行号。当用户可能反复请求不同的页时,为了更有效地进行分页,请首先用所有基础表行(包括计算得到的行号)填充一个临时表,并且对包含这些行号的列进行索引:
- SELECT ROW_NUMBER() OVER(ORDER BY score DESC, speaker) AS rownum, *
- INTO #SpeakerStatsRN
- FROM SpeakerStats
- CREATE UNIQUE CLUSTERED INDEX idx_uc_rownum ON #SpeakerStatsRN(rownum)
复制代码然后,对于所请求的每个页,发出以下查询:
- DECLARE @pagenum AS INT, @pagesize AS INT
- SET @pagenum = 2
- SET @pagesize = 3
- SELECT rownum, speaker, track, score
- FROM #SpeakerStatsRN
- WHERE rownum BETWEEN (@pagenum-1)*@pagesize+1 AND @pagenum*@pagesize
- ORDER BY score DESC, speaker
复制代码只有属于预期页的行才会被扫描。
分段 可以在行组内部独立地计算排序值,而不是为作为一个组的所有表行计算排序值。为此,请使用 PARTITION BY 子句,并且指定一个表达式列表,以标识应该为其独立计算排序值的行组。例如,以下查询按照“score DESC, speaker”顺序单独分配每个 track 内部的行号:
- SELECT track,
- ROW_NUMBER() OVER(
- PARTITION BY track
- ORDER BY score DESC, speaker) AS pos,speaker, score
- FROM SpeakerStats
复制代码以下为结果集:
track pos speaker score ---------- --- ---------- ----------- DB 1 Suzanne 9 DB 2 Mike 8 DB 3 Kevin 7 Dev 1 Jessica 9 Dev 2 Ron 9 Dev 3 Joe 6 Dev 4 Robert 6 Sys 1 Kathy 8 Sys 2 Michele 8 Sys 3 Brian 7 Sys 4 Dan 3 |
在 PARTITION BY 子句中指定 track 列会使得为具有相同 track 的每个行组单独计算行号。
2、RANK, DENSE_RANK RANK 和 DENSE_RANK 函数非常类似于 ROW_NUMBER 函数,因为它们也按照指定的排序提供排序值,而且可以根据需要在行组(分段)内部提供。但是,与 ROW_NUMBER 不同的是,RANK 和 DENSE_RANK 向在排序列中具有相同值的行分配相同的排序。当 ORDER BY 列表不唯一,并且您不希望为在 ORDER BY 列表中具有相同值的行分配不同的排序时,RANK 和 DENSE_RANK 很有用。RANK 和 DENSE_RANK 的用途以及两者之间的差异可以用示例进行最好的解释。以下查询按照 score DESC 顺序计算不同演讲者的行号、排序和紧密排序值:
- SELECT speaker, track, score,
- ROW_NUMBER() OVER(ORDER BY score DESC) AS rownum,
- RANK() OVER(ORDER BY score DESC) AS rnk,
- DENSE_RANK() OVER(ORDER BY score DESC) AS drnk
- FROM SpeakerStats
复制代码speaker track score rownum rnk drnk ---------- ---------- ----------- ------ --- ---- Jessica Dev 9 1 1 1 Ron Dev 9 2 1 1 Suzanne DB 9 3 1 1 Kathy Sys 8 4 4 2 Michele Sys 8 5 4 2 Mike DB 8 6 4 2 Kevin DB 7 7 7 3 Brian Sys 7 8 7 3 Joe Dev 6 9 9 4 Robert Dev 6 10 9 4 Dan Sys 3 11 11 5 |
正如前面讨论的那样,score 列不唯一,因此不同的演讲者可能具有相同的得分。行号确实代表下降的 score 顺序,但是具有相同得分的演讲者仍然获得不同的行号。但是请注意,在结果中,所有具有相同得分的演讲者都获得相同的排序和紧密排序值。换句话说,当 ORDER BY 列表不唯一时,ROW_NUMBER 是不确定的,而 RANK 和 DENSE_RANK 总是确定的。排序值和紧密排序值之间的差异在于,排序代表:具有较高得分的行号加 1,而紧密排序代表:具有明显较高得分的行号加 1。从您迄今为止已经了解的内容中,您可以推导出当 ORDER BY 列表唯一时,ROW_NUMBER、RANK 和 DENSE_RANK 产生完全相同的值。
3、NTILE NTILE 使您可以按照指定的顺序,将查询的结果行分散到指定数量的组 (tile) 中。每个行组都获得不同的号码:第一组为 1,第二组为 2,等等。您可以在函数名称后面的括号中指定所请求的组号,在 OVER 选项的 ORDER BY 子句中指定所请求的排序。组中的行数被计算为 total_num_rows / num_groups。如果有余数 n,则前面 n 个组获得一个附加行。因此,可能不会所有组都获得相等数量的行,但是组大小最大只可能相差一行。例如,以下查询按照 score 降序将三个组号分配给不同的 speaker 行:
- SELECT speaker, track, score,
- ROW_NUMBER() OVER(ORDER BY score DESC) AS rownum,
- NTILE(3) OVER(ORDER BY score DESC) AS tile
- FROM SpeakerStats
复制代码以下为结果集:
speaker track score rownum tile ---------- ---------- ----------- ------ ---- Jessica Dev 9 1 1 Ron Dev 9 2 1 Suzanne DB 9 3 1 Kathy Sys 8 4 1 Michele Sys 8 5 2 Mike DB 8 6 2 Kevin DB 7 7 2 Brian Sys 7 8 2 Joe Dev 6 9 3 Robert Dev 6 10 3 Dan Sys 3 11 3 |
在 SpeakerStats 表中有 11 位演讲者。将 11 除以 3 得到组大小 3 和余数 2,这意味着前面 2 个组将获得一个附加行(每个组中有 4 行),而第三个组则不会得到附加行(该组中有 3 行)。组号(tile 号)1 被分配给行 1 到 4,组号 2 被分配给行 5 到 8,组号 3 被分配给行 9 到 11。通过该信息可以生成直方图,并且将项目均匀分布到每个梯级。在我们的示例中,第一个梯级表示具有最高得分的演讲者,第二个梯级表示具有中等得分的演讲者,第三个梯级表示具有最低得分的演讲者。可以使用 CASE 表达式为组号提供说明性的有意义的备选含义:
- SELECT speaker, track, score,
- CASE NTILE(3) OVER(ORDER BY score DESC)
- WHEN 1 THEN 'High'
- WHEN 2 THEN 'Medium'
- WHEN 3 THEN 'Low'
- END AS scorecategory
- FROM SpeakerStats
复制代码以下为结果集:
speaker track score scorecategory ---------- ---------- ----------- ------------- Kevin DB 7 Medium Mike DB 8 Medium Suzanne DB 9 High Jessica Dev 9 High Joe Dev 6 Low Robert Dev 6 Low Ron Dev 9 High Brian Sys 7 Medium Dan Sys 3 Low Kathy Sys 8 High Michele Sys 8 Medium |
文/
萧亚生 出处/博客园