SQL Server 分组后取Top N
SQL Server 分组后取Top N(转)
近日,工作中突遇一需求:将一数据表分组,而后取出每组内按一定规则排列的前N条数据。乍想来,这本是寻常查询,无甚难处。可提笔写来,终究是困住了笔者好一会儿。冥思苦想,遍查网络,不曾想这竟然是SQL界的一个经典话题。今日将我得来的若干方法列出,抛砖引玉,以期与众位探讨。
正文之前,对示例表结构加以说明。

表SectionTransactionLog,用来记录各部门各项活动的日志表 SectionId,部门Id SectionTransactionType,活动类型 TotalTransactionValue,活动花费 TransactionDate,活动时间
我们设定的场景为:选出每部门(SectionId)最近两次举行的活动。
笔者用来测试的SectionTransactionLog表中数据超3,000,000。
一、 嵌套子查询方式
1
1 SELECT * FROM SectionTransactionLog mLog
2 where
3 (select COUNT(*) from SectionTransactionLog subLog
4 where subLog.SectionId = mLog.SectionId and subLog.TransactionDate >= mLog.TransactionDate)<=2
5 order by SectionId, TransactionDate desc
运行时间:34秒
该方式原理较简单,只是在子查询中确定该条记录是否是其Section中新近发生的2条之一。
2

1 SELECT * FROM SectionTransactionLog mLog
2 where mLog.Id in
3 (select top 2 Id
4 from SectionTransactionLog subLog
5 where subLog.SectionId = mLog.SectionId
6 order by TransactionDate desc)
7 order by SectionId, TransactionDate desc

运行时间:1分25秒
在子查询中使用TransactionDate排序,取top 2。并应用in关键字确定记录是否符合该子查询。
二、 自联接方式

1 select mLog.* from SectionTransactionLog mLog
2 inner join
3 (SELECT rankLeft.Id, COUNT(*) as rankNum FROM SectionTransactionLog rankLeft
4 inner join SectionTransactionLog rankRight
5 on rankLeft.SectionId = rankRight.SectionId and rankLeft.TransactionDate <= rankRight.TransactionDate
6 group by rankLeft.Id
7 having COUNT(*) <= 2) subLog on mLog.Id = subLog.Id
8 order by mLog.SectionId, mLog.TransactionDate desc

运行时间:56秒
该实现方式较为巧妙,但较之之前方法也稍显复杂。其中,以SectionTransactionLog表自联接为基础而构造出的subLog部分为每一活动(以Id标识)计算出其在Section内部的排序rankNum(按时间TransactionDate)。
在自联接条件rankLeft.SectionId = rankRight.SectionId and rankLeft.TransactionDate <= rankRight.TransactionDate的筛选下,查询结果中对于某一活动(以Id标识)而言,与其联接的只有同其在一Section并晚于或与其同时发生活动(当然包括其自身)。下图为Id=1的活动自联接示意:

从上图中一目了然可以看出,基于此结果的count计算,便为Id=1活动在Section 9022中的排次rankNum。
而后having COUNT(*) <= 2选出排次在2以内的,再做一次联接select出所需信息。
三、 应用ROW_NUMBER()(SQL SERVER 2005及之后)

1 select * from
2 (
3 select *, ROW_NUMBER() over(partition by SectionId order by TransactionDate desc) as rowNum
4 from SectionTransactionLog
5 ) ranked
6 where ranked.rowNum <= 2
7 order by ranked.SectionId, ranked.TransactionDate desc

运行时间:20秒
这是截至目前效率最高的实现方式。ROW_NUMBER() over(partition by SectionId order by TransactionDate desc)完成了分组、排序、取行号的整个过程。
效率思考
下面我们对上述的4种方法做一个效率上的统计。
| 方法 | 耗时(秒) | 排名 |
| 应用ROW_NUMBER() | 20 | 1 |
| 嵌套子查询方式1 | 34 | 2 |
| 自联接方式 | 56 | 3 |
| 嵌套子查询方式2 | 85 | 4 |
4种方法中,嵌套子查询2所用时最长,其效率损耗在什么地方了呢?难道果真是使用了in关键字的缘故?下图为其执行计划(execute plan):

从图中,我们可以看出优化器将in解析为了Left Semi Join, 其损耗极低。而该查询绝大部分性能消耗在子查询的order by处(Top N Sort)。果然,若删掉子查询中的order by TransactionDate desc子句(当然结果不正确),其耗时仅为8秒。
添加有效索引可提高该查询方法的性能。
SQL Server 分组后取Top N的更多相关文章
- 记一次有意思的 SQL 实现 → 分组后取每组的第一条记录
开心一刻 今天,朋友气冲冲的走到我面前 朋友:我不是谈了个女朋友,谈了三个月嘛,昨天我偷看她手机,你猜她给我备注什么 我:备注什么? 朋友:舔狗 2 号! 我一听,气就上来了,说道:走,找她去,这婆娘 ...
- SQL获取分组后取某字段最大一条记录(求每个类别中最大的值的列表)
获取分组后取某字段最大一条记录 方法一:(效率最高) select * from test as a where typeindex = (select max(b.typeindex) from t ...
- SQL数据分组后取最大值或者取前几个值(依照某一列排序)
今日做项目的时候,项目中遇到须要将数据分组后,分组中的最大值,想了想,不知道怎么做.于是网上查了查,最终找到了思路,经过比較这个查询时眼下用时最快的,事实上还有别的方法,可是我认为我们仅仅掌握最快的方 ...
- sql server 分组,取每组的前几行数据
sql中group by后,获取每组中的前N行数据,目前我知道的有2种方法 比如有个成绩表: 里面有字段学生ID,科目,成绩.我现在想取每个科目的头三名. 1. 子查询 select * from ...
- SQL之分组排序取top n
转自:http://blog.csdn.net/wguangliang/article/details/50167283 要求:按照课程分组,查找每个课程最高的两个成绩. 数据文件如下: 第一列no为 ...
- sql server 分组后字段拼接
- Sql语句groupBY分组后取最新一条记录的SQL
一.问题 groupBY分组后取最新一条记录的SQL的解决方案. 二.解决方案 select Message,EventTime from PT_ChildSysAlarms as a where E ...
- CASE函数 sql server——分组查询(方法和思想) ref和out 一般处理程序结合反射技术统一执行客户端请求 遍历查询结果集,update数据 HBuilder设置APP状态栏
CASE函数 作用: 可以将查询结果集的某一列的字段值进行替换 它可以生成一个新列 相当于switch...case和 if..else 使用语法: case 表达式/字段 when 值 then ...
- MSSQL—按照某一列分组后取前N条记录
以前在开发的时候遇到过一个需求,就是要按照某一列进行分组后取前几条数据,今天又有同事碰到了,帮解决了之后顺便写一篇博客记录一下. 首先先建一个基础数据表,代码如下: IF OBJECT_ID(N'Te ...
随机推荐
- 使用飞信api接口实现短信发送(只能发送好友)
找了很久才找到一个能用的飞信API接口(http://quanapi.sinaapp.com/fetion.php?u=飞信登录手机号&p=飞信登录密码&to=接收飞信的手机号& ...
- C# ORM—Entity Framework 之Database first(数据库优先)&Model First(模型优先)(一)
一.什么是Entity Framework 1.1 实体框架(EF)是一个对象关系映射器,使.NET开发人员使用特定于域的对象与关系数据.它消除了需要开发人员通常需要编写的大部分数据访问代码.简化了原 ...
- TCP\UDP链接的异同
简单的说TCP与UDP的区别是有无连接状态.TCP是有连接状态的,而UDP没有,所以TCP是一种比较安全的通讯协议,而UDP则比较方便 TCP 127.0.0.1:1026 0.0.0.0:0 LIS ...
- Spark笔记--使用Maven编译Spark源码(windows)
1. 官网下载源码 source code,地址: http://spark.apache.org/downloads.html 2. 使用maven编译: 注意在编译之前,需要设置java堆大小以及 ...
- Scheme是什么、怎么自定义Scheme、JLRoutes的使用-备
转到移动端开发后居然现在才用到Scheme真是惭愧惭愧. URL Scheme是什么 相信大家都知道URL. http://www.apple.com就是一个URL. 而://之前的部分就称为Sche ...
- 点击itemView选中checkbox
在Listview中如果item中含有checkbox会使itemview的setonitemchecklistingner失效,我们可以通过设置checkbox的clickbale的值为false来 ...
- vijosP1499炸毁燃料库
背景 某天,外星人展开了对地球的侵略,OIer们开始与之周旋... 描述 外星人派出了172849个外星人乘着UFO来到地球,curimit神new带领着OIer们奋力抵抗.curimit神new觉得 ...
- 实现zbar扫描二维码的时候就把照片存储出来的办法
业务场景 当实现二维码扫描的时候(用的扫码库是zbar),有时候需要悄悄的整个扫描的照片存储下来,(charles有部分变态业务就是这样)就是说给扫描的图片照个全景照片. 那么代码如何实现 ...
- HDOJ 1010
深度搜索,注意要剪枝 1.奇偶剪枝 可以把map看成这样: 0 1 0 1 0 1 1 0 1 0 1 0 0 1 0 1 0 1 1 0 1 0 1 0 0 1 0 1 0 1 从为 0 的格子走一 ...
- 关于FIN_WAIT1
前些天,一堆人在 TCPCopy 社区里闲扯蛋,有人提了一个问题:FIN_WAIT1 能持续多久?引发了一场讨论,期间我得到斌哥和多位朋友的点化,受益良多. 让我们热热身,通过一张旧图来回忆一下 TC ...