备注 | 修改日期 | 修改人 |
修改标题 | 2019-04-17 17:56:16[当前版本] | 系统管理员 |
调整分类 | 2019-03-28 15:04:58 | 系统管理员 |
记录一个分组取前N条数据的SQL:
SELECT *
FROM ( SELECT ROW_NUMBER() OVER ( PARTITION BY
t1.X ORDER BY t1.Y ) AS RNUM ,
*
FROM Table1 t1
) AS T
WHERE T.RNUM = N
注:
X:分组的字段;
Y:排序的字段;
N:第N条
实例整理:
转自:http://www.cnblogs.com/isun/archive/2012/12/22/2829528.html
示例表结构说明
表SectionTransactionLog,用来记录各部门各项活动的日志表
SectionId,部门Id
SectionTransactionType,活动类型
TotalTransactionValue,活动花费
TransactionDate,活动时间
我们设定的场景为:选出每部门(SectionId)最近两次举行的活动。
笔者用来测试的SectionTransactionLog表中数据超3,000,000。
一、 嵌套子查询方式
SELECT * FROM SectionTransactionLog mLog
where
(select COUNT(*) from SectionTransactionLog
subLog
where subLog.SectionId = mLog.SectionId and
subLog.TransactionDate >= mLog.TransactionDate)<=2
order by SectionId, TransactionDate desc
运行时间:34秒
该方式原理较简单,只是在子查询中确定该条记录是否是其Section中新近发生的2条之一。
2
1SELECT*FROMSectionTransactionLogmLog
2wheremLog.Idin
3(selecttop2Id
fromSectionTransactionLogsubLog
wheresubLog.SectionId=mLog.SectionId
orderbyTransactionDatedesc)
orderbySectionId,TransactionDatedesc
运行时间:1分25秒
在子查询中使用TransactionDate排序,取top 2。并应用in关键字确定记录是否符合该子查询。
二、 自联接方式
SELECT * FROM SectionTransactionLog mLog
where mLog.Id
in
(select top 2 Id
from SectionTransactionLog
subLog
where subLog.SectionId = mLog.SectionId
order by TransactionDate desc)
order by SectionId,
TransactionDate desc
运行时间:56秒
该实现方式较为巧妙,但较之之前方法也稍显复杂。其中,以SectionTransactionLog表自联接为基础而构造出的subLog部分为每一活动(以Id标识)计算出其在Section内部的排序rankNum(按时间TransactionDate)。
在自联接条件rankLeft.SectionId=rankRight.SectionIdandrankLeft.TransactionDate(=rankRight.TransactionDate的筛选下,查询结果中对于某一活动(以Id标识)而言,与其联接的只有同其在一Section并晚于或与其同时发生活动(当然包括其自身)。下图为Id=1的活动自联接示意:
从上图中一目了然可以看出,基于此结果的count计算,便为Id=1活动在Section 9022中的排次rankNum。
而后having COUNT(*) (= 2选出排次在2以内的,再做一次联接select出所需信息。
三、 应用ROW_NUMBER()(SQL SERVER 2005及之后)
select * from
(
select *, ROW_NUMBER() over(partition
by SectionId order by TransactionDate desc) as rowNum
from
SectionTransactionLog
) ranked
where ranked.rowNum <=
2
order by ranked.SectionId, ranked.TransactionDate desc
运行时间:20秒
这是截至目前效率最高的实现方式。ROW_NUMBER() over(partition bySectionIdorder byTransactionDatedesc)完成了分组、排序、取行号的整个过程。
效率思考
下面我们对上述的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 byTransactionDatedesc子句(当然结果不正确),其耗时仅为8秒。
添加有效索引可提高该查询方法的性能。