排名函数是SQL Server2005新加的功能。在SQL Server2005中有以下四个排名函数: 1.row_number 2.rank 3.dense_rank 4.ntile 下面分别介绍一下这四个排名函数的功能及用法。在介绍以前假设有一个t_table表,表结构与表中的数据如图1所示:其中field1字段的类型是int,field2字段的类型是varchar 1、row_number row_number函数的用途是很是普遍,这个函数的功能是为查询出来的每一行记录生成一个序号。row_number函数的用法以下面的SQL语句所示: select row_number() over(order by field1) as row_number,* fromt_table 上面的SQL语句的查询结果如图2所示。
图2 其中row_number列是由row_number函数生成的序号列。在使用row_number函数是要使用over子句选择对某一列进行排序,而后才能生成序号。 实际上,row_number函数生成序号的基本原理是先使用over子句中的排序语句对记录进行排序,而后按着这个顺序生成序号。over子句中的order by子句与SQL语句中的order by子句没有任何关系,这两处的order by 能够彻底不一样,以下面的SQL语句所示 select row_number() over(order by field2 desc) as row_number,*from t_table order by field1 desc 上面的SQL语句的查询结果如图3所示。
图3 咱们可使用row_number函数来实现查询表中指定范围的记录,通常将其应用到Web应用程序的分页功能上。下面的SQL语句能够查询t_table表中第2条和第3条记录: with t_rowtable as ( select row_number() over(order by field1) as row_number,*from t_table ) select * from t_rowtable where row_number>1 and row_number<4 order by field1 上面的SQL语句的查询结果如图4所示。
图4 上面的SQL语句使用了CTE,关于CTE的介绍将读者参阅《SQL Server2005杂谈(1):使用公用表表达式(CTE)简化嵌套SQL》。 另外要注意的是,若是将row_number函数用于分页处理,over子句中的order by 与排序记录的order by 应相同,不然生成的序号可能不是有续的。 固然,不使用row_number函数也能够实现查询指定范围的记录,就是比较麻烦。通常的方法是使用颠倒Top来实现,例如,查询t_table表中第2条和第3条记录,能够先查出前3条记录,而后将查询出来的这三条记录按倒序排序,再取前2条记录,最后再将查出来的这2条记录再按倒序排序,就是最终结果。SQL语句以下: select * from(select top2 * from(select top3 * from t_table order by field1)a order by field1 desc) b order by field1 上面的SQL语句查询出来的结果如图5所示。
图5 这个查询结果除了没有序号列row_number,其余的与图4所示的查询结果彻底同样。 2、rank rank函数考虑到了over子句中排序字段值相同的状况,为了更容易说明问题,在t_table表中再加一条记录,如图6所示。
图6 在图6所示的记录中后三条记录的field1字段值是相同的。若是使用rank函数来生成序号,这3条记录的序号是相同的,而第4条记录会根据当前的记录数生成序号,后面的记录依此类推,也就是说,在这个例子中,第4条记录的序号是4,而不是2。rank函数的使用方法与row_number函数彻底相同,SQL语句以下: select rank() over(order by field1),* from t_table order by field1 上面的SQL语句的查询结果如图7所示。
图7 3、dense_rank dense_rank函数的功能与rank函数相似,只是在生成序号时是连续的,而rank函数生成的序号有可能不连续。如上面的例子中若是使用dense_rank函数,第4条记录的序号应该是2,而不是4。以下面的SQL语句所示: select dense_rank() over(order by field1),* from t_table order by field1 上面的SQL语句的查询结果如图8所示。
图8 读者能够比较图7和图8所示的查询结果有什么不一样 4、ntile ntile函数能够对序号进行分组处理。这就至关于将查询出来的记录集放到指定长度的数组中,每个数组元素存放必定数量的记录。ntile函数为每条记录生成的序号就是这条记录全部的数组元素的索引(从1开始)。也能够将每个分配记录的数组元素称为“桶”。ntile函数有一个参数,用来指定桶数。下面的SQL语句使用ntile函数对t_table表进行了装桶处理: select ntile(4) over(order by field1)as bucket,* from t_table 上面的SQL语句的查询结果如图9所示。
图9 因为t_table表的记录总数是6,而上面的SQL语句中的ntile函数指定了桶数为4。 也许有的读者会问这么一个问题,SQL Server2005怎么来决定某一桶应该放多少记录呢?可能t_table表中的记录数有些少,那么咱们假设t_table表中有59条记录,而桶数是5,那么每一桶应放多少记录呢? 实际上经过两个约定就能够产生一个算法来决定哪个桶应放多少记录,这两个约定以下: 1.编号小的桶放的记录不能小于编号大的桶。也就是说,第1捅中的记录数只能大于等于第2桶及之后的各桶中的记录。 2.全部桶中的记录要么都相同,要么从某一个记录较少的桶开始后面全部捅的记录数都与该桶的记录数相同。也就是说,若是有个桶,前三桶的记录数都是10,而第4捅的记录数是6,那么第5桶和第6桶的记录数也必须是6。 根据上面的两个约定,能够得出以下的算法: //mod表示取余,div表示取整 if(记录总数mod桶数==0) { recordCount=记录总数div桶数; 将每桶的记录数都设为recordCount } else { recordCount1=记录总数div桶数+1; intn=1; // n表示桶中记录数为recordCount1的最大桶数 m=recordCount1*n; while(((记录总数-m) mod (桶数- n)) !=0) { n++; m=recordCount1*n; } recordCount2=(记录总数-m)div (桶数-n); 将前n个桶的记录数设为recordCount1 将n+1个至后面全部桶的记录数设为recordCount2 } 根据上面的算法,若是记录总数为59,桶数为5,则前4个桶的记录数都是12,最后一个桶的记录数是11。 若是记录总数为53,桶数为5,则前3个桶的记录数为11,后2个桶的记录数为10。 就拿本例来讲,记录总数为6,桶数为4,则会算出recordCount1的值为2,在结束while循环后,会算出recordCount2的值是1,所以,前2个桶的记录是2,后2个桶的记录是1。 ROW_NUMBER、RANK、DENSE_RANK 和 NTILE,这些新函数使您能够有效地分析数据以及向查询的结果行提供排序值。您可能发现这些新函数有用的典型方案包括:将连续整数分配给结果行,以便进行表示、分页、计分和绘制直方图。 Speaker Statistics 方案 下面的 Speaker Statistics 方案将用来讨论和演示不一样的函数和它们的子句。大型计算会议包括三个议题:数据库、开发和系统管理。十一位演讲者在会议中发表演讲,而且为他们的讲话得到 范围为 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) 每一个演讲者都在该表中具备一个行,其中含有该演讲者的名字、议题、平均得分、填写评价的与会者相对于参加会议的与会者数量的百分比以及该演讲者发表演讲的次数。本节演示如何使用新的排序函数分析演讲者统计数据以生成有用的信息。 ROW_NUMBER ROW_NUMBER 函数使您能够向查询的结果行提供连续的整数值。例如,假设您要返回全部演讲者的 speaker、track 和 score,同时按照 score 降序向结果行分配从 1 开始的连续值。如下查询经过使用 ROW_NUMBER 函数并指定 OVER (ORDER BY score DESC) 生成所需的结果: SELECT ROW_NUMBER() OVER(ORDER BY score DESC) AS rownum, speaker, track, scoreFROM SpeakerStatsORDER BY score DESC如下为结果集: rownum speaker track score ------ ---------- ---------- ----------- 1 Jessica Dev 9 2 Ron Dev 9 3 Suzanne DB 9 4 Kathy Sys 8 5 Michele Sys 8 6 Mike DB 8 7 Kevin DB 7 8 Brian Sys 7 9 Joe Dev 6 10 Robert Dev 6 11 Dan Sys 3 得 分最高的演讲者得到行号 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, numsessionsFROM SpeakerStatsORDER BY score DESC, pctfilledevals DESC, numsessions DESC, speaker如下为结果集: 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.numsessions > 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 ORDER BY score DESC, speaker 如下为结果集: 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 DWHERE rownum BETWEEN (@pagenum-1)*@pagesize+1 AND @pagenum*@pagesize ORDER BY score DESC, speaker 上述方法对于您只对行的一个特定页感兴趣的特定请求而言已经足够了。可是,当用户发出多个请求时,该方法就不能知足须要了,由于该查询的每一个调用都 须要您对表进行完整扫描,以便计算行号。当用户可能反复请求不一样的页时,为了更有效地进行分页,请首先用全部基础表行(包括计算获得的行号)填充一个临时 表,而且对包含这些行号的列进行索引: 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) 而后,对于所请求的每一个页,发出如下查询: 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 ORDER BY track, score DESC, speaker 如下为结果集: 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 的每一个行组单独计算行号。 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 ORDER BY score DESC 如下为结果集: 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 产生彻底相同的值。 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 ORDER BY score DESC 如下为结果集: 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 ORDER BY track, speaker 如下为结果集: 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