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 by t.ntile
运行Sql,分组结果如图:
比对算法与Sql Server的分组结果是一致的,说明算法没错。:)