Sql四⼤排名函数(ROW_NUMBER、RANK、DENSE_RANK、
NTILE)简介
排名函数是Sql Server2005新增的功能,下⾯简单介绍⼀下他们各⾃的⽤法和区别。我们新建⼀张Order表并添加⼀些初始数据⽅便我们查看效果。
CREATE TABLE [dbo].[Order](
[ID] [int] IDENTITY(1,1) NOT NULL,
[UserId] [int] NOT NULL,
[TotalPrice] [int] NOT NULL,
[SubTime] [datetime] NOT NULL,
CONSTRAINT [PK_Order] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Order] ON
GO
INSERT [dbo].[Order] ([ID], [UserId], [TotalPrice], [SubTime]) VALUES (1, 1, 100, CAST(0x0000A419011D32AF AS DateTime))
GO
INSERT [dbo].[Order] ([ID], [UserId], [TotalPrice], [SubTime]) VALUES (2, 2, 500, CAST(0x0000A419011D40BA AS DateTime))
GO
INSERT [dbo].[Order] ([ID], [UserId], [TotalPrice], [SubTime]) VALUES (3, 3, 300, CAST(0x0000A419011D4641 AS DateTime))
GO
INSERT [dbo].[Order] ([ID], [UserId], [TotalPrice], [SubTime]) VALUES (4, 2, 1000, CAST(0x0000A419011D4B72 AS DateTime))
GO
INSERT [dbo].[Order] ([ID], [UserId], [TotalPrice], [SubTime]) VALUES (5, 1, 520, CAST(0x0000A419011D50F3 AS DateTime))
GO
INSERT [dbo].[Order] ([ID], [UserId], [TotalPrice], [SubTime]) VALUES (6, 2, 2000, CAST(0x0000A419011E50C9 AS DateTime))
GO
SET IDENTITY_INSERT [dbo].[Order] OFF
rank函数怎么用
GO
ALTER TABLE [dbo].[Order] ADD  CONSTRAINT [DF_Order_SubTime]  DEFAULT (getdate()) FOR [SubTime]
GO
  附上表结构和初始数据图:
⼀、ROW_NUMBER
  row_number的⽤途的⾮常⼴泛,排序最好⽤他,⼀般可以⽤来实现web程序的分页,他会为查询出来的每⼀⾏记录⽣成⼀个序号,依次排序且不会重复,注意使⽤row_number函数时必须要⽤over⼦句选择对某⼀列进⾏排序才能⽣成序号。row_number⽤法实例:
select ROW_NUMBER() OVER(order by [SubTime] desc) as row_num,* from [Order]
  查询结果如下图所⽰:
  图中的row_num列就是row_number函数⽣成的序号列,其基本原理是先使⽤over⼦句中的排序语句对记录进⾏排序,然后按照这个顺序⽣成序号。over⼦句中的order by⼦句与SQL语句中的order by⼦句没有任何关系,这两处的order by 可以完全不同,如以下sql,over⼦句中根据SubTime降序排列,Sql语句中则按TotalPrice降序排列。
select ROW_NUMBER() OVER(order by [SubTime] desc) as row_num,* from [Order] order by [TotalPrice] desc
  查询结果如下图所⽰:
  利⽤row_number可以实现web程序的分页,我们来查询指定范围的表数据。例:根据订单提交时间倒序排列获取第三⾄第五条数据。
with orderSection as
(
select ROW_NUMBER() OVER(order by [SubTime] desc) rownum,* from [Order]
)
select * from [orderSection] where rownum between 3 and 5 order by [SubTime] desc
  查询结果如下图所⽰:
  注意:在使⽤row_number实现分页时需要特别注意⼀点,over⼦句中的order by 要与Sql排序记录中的order by 保持⼀致,否则得到的序号可能不是连续的。下⾯我们写⼀个例⼦来证实这⼀点,将上⾯Sql语句中的排序字段由SubTime改为TotalPrice。另外提⼀下,对于带有⼦查询和CTE 的查询,⼦查询和CTE查询有序并不代表整个查询有序,除⾮显⽰指定了order by。
with orderSection as
(
select ROW_NUMBER() OVER(order by [SubTime] desc) rownum,* from [Order]
)
select * from [orderSection] where rownum between 3 and 5 order by [TotalPrice] desc
  查询结果如下图所⽰:
⼆、RANK
  rank函数⽤于返回结果集的分区内每⾏的排名,⾏的排名是相关⾏之前的排名数加⼀。简单来说rank函数就是对查询出来的记录进⾏排名,与row_number函数不同的是,rank函数考虑到了over⼦句中排序字段值相同的情况,如果使⽤rank函数来⽣成序号,over⼦句中排序字段值相同的序号是⼀样的,后⾯字段值不相同的序号将跳过相同的排名号排下⼀个,也就是相关⾏之前的排名数加⼀,可以理解为根据当前的记录数⽣成序号,后⾯的记录依此类推。可能我描述的⽐较苍⽩,理解起来也⽐较吃⼒,我们直接上代码,rank函数的使⽤⽅法与row_number函数完全相同。select RANK() OVER(order by [UserId]) as rank,* from [Order]
  查询结果如下图所⽰:
  由上图可以看出,rank函数在进⾏排名时,同⼀组的序号是⼀样的,⽽后⾯的则是根据当前的记录数依次类推,图中第⼀、⼆条记录的⽤户Id 相同,所以他们的序号是⼀样的,第三条记录的序号则是3。 
三、DENSE_RANK
  dense_rank函数的功能与rank函数类似,dense_rank函数在⽣成序号时是连续的,⽽rank函数⽣成的序号有可能不连续。dense_rank函数出现相同排名时,将不跳过相同排名号,rank值紧接上⼀次的rank值。在各个分组内,rank()是跳跃排序,有两个第⼀名时接下来就是第四
名,dense_rank()是连续排序,有两个第⼀名时仍然跟着第⼆名。将上⾯的Sql语句改由dense_rank函数来实现。
select DENSE_RANK() OVER(order by [UserId]) as den_rank,* from [Order]
  查询结果如下图所⽰:
  图中第⼀、⼆条记录的⽤户Id相同,所以他们的序号是⼀样的,第三条记录的序号紧接上⼀个的序号,所以为2不为3,后⾯的依此类推。
四、NTILE
  ntile函数可以对序号进⾏分组处理,将有序分区中的⾏分发到指定数⽬的组中。各个组有编号,编号从⼀开始。对于每⼀个⾏,ntile 将返回此⾏所属的组的编号。这就相当于将查询出来的记录集放到指定长度的数组中,每⼀个数组元素存放⼀定数量的记录。ntile函数为每条记录⽣成的序号就是这条记录所有的数组元素的索引(从1开始)。也可以将每⼀个分配记录的数组元素称为“桶”。ntile函数有⼀
个参数,⽤来指定桶数。下⾯的SQL语句使⽤ntile函数对Order表进⾏了装桶处理:
select NTILE(4) OVER(order by [SubTime] desc) as ntile,* from [Order]
  查询结果如下图所⽰:
  Order表的总记录数是6条,⽽上⾯的Sql语句ntile函数指定的组数是4,那么Sql Server2005是怎么来决定每⼀组应该分多少条记录呢?这⾥我们就需要了解ntile函数的分组依据(约定)。
  ntile函数的分组依据(约定):
  1、每组的记录数不能⼤于它上⼀组的记录数,即编号⼩的桶放的记录数不能⼩于编号⼤的桶。也就是说,第1组中的记录数只能⼤于等于第2组及以后各组中的记录数。
  2、所有组中的记录数要么都相同,要么从某⼀个记录较少的组(命名为X)开始后⾯所有组的记录数都与该组(X组)的记录数相同。也就是说,如果有个组,前三组的记录数都是9,⽽第四组的记录数是8,那么第五组和第六组的记录数也必须是8。
  这⾥对约定2进⾏详细说明⼀下,以便于更好的理解。
  ⾸先系统会去检查能不能对所有满⾜条件的记录进⾏平均分组,若能则直接平均分配就完成分组了;若不能,则会先分出⼀个组,这个组分多少条记录呢?就是(总记录数/总组数)+1 条,之所以分配(总记录数/总组数)+1 条是因为当不能进⾏平均分组时,总记录数%总组数肯定是有余的,⼜因为分组约定1,所以先分出去的组需要+1条。
  分完之后系统会继续去⽐较余下的记录数和未分配的组数能不能进⾏平均分配,若能,则平均分配余下的记录;若不能,则再分出去⼀组,这
个组的记录数也是(总记录数/总组数)+1条。
  然后系统继续去⽐较余下的记录数和未分配的组数能不能进⾏平均分配,若能,则平均分配余下的记录;若还是不能,则再分配出去⼀组,继续⽐较余下的......这样⼀直进⾏下去,直⾄分组完成。
  举个例⼦,将51条记录分配成5组,51%5==1不能平均分配,则先分出去⼀组(51/5)+1=11条记录,
然后⽐较余下的 51-11=40 条记录能否平均分配给未分配的4组,能平均分配,则剩下的4组,每组各40/4=10 条记录,分配完成,分配结果为:11,10,10,10,10,我开始就错误的以为他会分配成 11,11,11,11,7。
  根据上⾯的两个约定,可以得出如下的算法:
//mod表⽰取余,div表⽰取整.
if(记录总数 mod 桶数==0)
{
  recordCount=记录总数 div 桶数;
  //将每桶的记录数都设为recordCount.
}
else
{
  recordCount1=记录总数 div 桶数+1;
  int n=1;//n表⽰桶中记录数为recordCount1的最⼤桶数.
  m=recordCount1*n;
  while(((记录总数-m) mod (桶数- n)) !=0)
  {
    n++;
    m=recordCount1*n;
  }
  recordCount2=(记录总数-m) div (桶数-n);
  //将前n个桶的记录数设为recordCount1.
  //将n+1个⾄后⾯所有桶的记录数设为recordCount2.
}
int recordTotal = 51;//记录总数.
int tcount = 5;//总组数.
string groupResult = "将" + recordTotal + "条记录分成" + tcount + "组,";
int recordCount = 0;//平均分配时每组的记录数.
//不能平均分配
int recordCount1 = 0;//前n个组每组的记录数.
int recordCount2 = 0;//第n+1组⾄后⾯所有组每个组的记录数.
int n = 1;//组中记录数为recordCount1的最⼤组数(前n组).
if (recordTotal % tcount == 0)//能平分.
{
recordCount = recordTotal / tcount;//每组的记录数.
}
else//不能平分.
{
recordCount1 = recordTotal / tcount + 1;//不能平分则先分出⼀组-前n组每组的记录数.
int m = recordCount1 * n;//已分配的记录数.
while ((recordTotal - m) % (tcount - n) != 0)//余下的记录数和未分配的组不能进⾏平分.
{
//还是不能平分,继续分出⼀组.
n++;
m = recordCount1 * n;
}
recordCount2 = (recordTotal - m) / (tcount - n);//余下的记录数和未分配的组能进⾏平分或者只剩下最后⼀组了-第n+1组⾄后⾯所有组每个组的记录数.
}
//输出.
if (recordCount != 0)
{
groupResult += "能平均分配,每组" + recordCount + "个.";
}
else
{
groupResult += "不能平均分配,前" + n + "组,每组" + recordCount1 + "个,";
if (n < tcount - 1)
{
//groupResult += "第" + (groupNumber + 1) + "组⾄后⾯所有组,每组" + recordCount2 + "个.";
groupResult += "第" + (n + 1) + "组⾄第" + tcount + "组,每组" + recordCount2 + "个.";
}
else
{
groupResult += "第" + (n + 1) + "组" + recordCount2 + "个.";
}
}
ViewData["result"] = groupResult;
  根据上⾯的算法,如果总记录数为59,总组数为5,则 n=4 , recordCount1=12 , recordCount2=11,分组结果为:12,12,12,12,11。
  如果总记录数为53,总组数为5,则 n=3 , recordCount1=11 , recordCount2=10,分组结果为:11,11,11,10,10。
  就拿上⾯的例⼦来说,总记录数为6,总组数为4,通过算法得到 n=2 , recordCount1=2 , recordCount2=1,分组结果为:2,2,1,1。select ntile,COUNT([ID]) recordCount from
(
select NTILE(4) OVER(order by [SubTime] desc) as ntile,* from [Order]
) as t
group ile
  运⾏Sql,分组结果如图:
  ⽐对算法与Sql Server的分组结果是⼀致的,说明算法没错。:)
总结:
在使⽤排名函数的时候需要注意以下三点:
  1、排名函数必须有 OVER ⼦句。
  2、排名函数必须有包含 ORDER BY 的 OVER ⼦句。
  3、分组内从1开始排序。