理解OVER子句
简介
Over子句在SQLServer 2005中回归,并且在2012中得到了扩展。这个功能主要结合窗口函数来使用;也可以在序列函数“NEXT VALUE FOR”使用。OVER子句确定哪些来自查询的列被应用到函数中,在函数中这些列被如何排序,并且何时重启函数计算。由于篇幅限制,本篇仅仅就OVER子句讨论,不再深入各种函数了(提供几个2014中新增的函数)。
语法:
<function> OVER ( [PARTITION BY clause] [ORDER BY clause] [ROWS or RANGE clause])
这个语法中,显示所有的子句都是可选的,实际上,每个函数使用OVER子句的函数都能确定哪个子句被允许哪个被需要。下图是展示那些函数是允许或者需要的:
R-需要, O-可选, X-不允许
PARTITION BY子句用来区分查询结果集到数据子集中,或者分区。如果不使用PARTITION BY子句,整个来自查询的结果集都将被使用。窗口函数被应用到每个独立的分区数据,并且每个函数对于每个分区都是重新运算。通过定义一套确定分区的值来区分查询到子集,这些值可以使列,标量函数,子查询或者变量
举例如下:
SELECT COUNT(*) FROM [msdb].sys.indexes;
查询结果如下:
这种情况下查询仅仅返回一个数字,这就是msdb数据库的索引的数量。现在让我们加入OVER子句到这个查询中:
1 2 | SELECT object_id, index_id, COUNT(*) OVER () FROM [msdb].sys.indexes; |
结果集如下:
这个查询返回每个索引的对象ID和索引ID,并且还有结果集的索引总数。由于没使用PARTITION BY子句,整个结果集都被当做一个分区。
现在我们加入PARTITION BY子句来看看结果如何改变:
SELECT object_id, index_id, COUNT(*) OVER (PARTITION BY object_id)
FROM [msdb].sys.indexes;
返回结果如下:
查询返回每个索引的行数,但是现在查询指定子句按照object_id 列来分区,因此count函数返回的是按object_id 分组的索引的数量。ORDER BY子句来控制排序。ROWS 或者 RANGE子句可以决定在分区内部的行数的子集。当使用ROWS 和 RANGE的时候,可以指定窗口函数的开始和结束点 ,如下图所示:
有两种语法指定窗口函数的范围:
BETWEEN <beginning frame> AND <ending frame> <beginning frame>
如果只有“开始点”,默认结束点为CURRENT ROW。
UNBOUNDED 关键字指定分区开端或者结束。CURRENT ROW 指定当前行是否是窗口的开始或者结束,这取决于窗口使用的位置。上图中的“N”指定了之前当前列的或之后的行数。
下面是有效规范的窗口函数:
1 2 3 4 5 6 7 8 9 10 11 12 | -- 从分区中指定整个结果集 BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -- 指定五行,并且在当前行的前四行 BETWEEN 4 PRECEDING AND CURRENT ROW -- 指定当前行到分区结束的所有行 BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING -- 指定从分区开始到当前行的所有行 UNBOUNDED PRECEDING |
为了展示以上说法,我们创建一些测试数据:两个账户,每个账户四个日期,以及四个金额。然后执行查询展示前面提到的语法的不同使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | DECLARE @Test TABLE ( Account INTEGER , TranDate DATE , TranAmount NUMERIC (5,2)); INSERT INTO @Test (Account, TranDate, TranAmount) VALUES (1, '2015-01-01' , 50.00), (1, '2015-01-15' , 25.00), (1, '2015-02-01' , 50.00), (1, '2015-02-15' , 25.00), (2, '2015-01-01' , 50.00), (2, '2015-01-15' , 25.00), (2, '2015-02-01' , 50.00), (2, '2015-02-15' , 25.00); SELECT Account, TranDate, TranAmount, COUNT (*) OVER (PARTITION BY Account ORDER BY TranDate ROWS UNBOUNDED PRECEDING) AS RowNbr, COUNT (*) OVER (PARTITION BY TranDate) AS DateCount, COUNT (*) OVER (PARTITION BY Account ORDER BY TranDate ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS Last2Count FROM @Test ORDER BY Account, TranDate; |
查询返回如下结果:
“RowNbr”列使用了count 函数返回分区后有多少行。这个分区是按照TranDate进行排序的,然后我们指定从分区的开始到当前行的窗口。对于第一行,‘2015-01-01’是第一行,座椅返回值就是1,然后第二行就是就是‘2015-01-15’,就是2,以此类推,其他这个账户的行往下排序。由于PARTITION BY 指定了Account 列,当Account 改变后这个函数被重置,于是可以看到Account 为2的时候RowNbr重新开始排序。
“DateCount”列根据“date”分组分区展示有多少个相同的date值。例子中每个交易的日期都有两个所以该列值都是2。与group by 相似,不同点是总的返回行数。尤其当计算当前行所占的总行数的百分比的时候应用比较多。
“Last2Count” 列表示在分区内对于当前行和其前面一行的行数。有点拗口,具体点就是对于每个Account 最小date的数据就是第一行,那么对于第一行距离第一行的计数就是1,其他行和都是计算它和它前面一行的数值都是2。比较常见的应用就是计算最近两个月销售的情况来计算奖金。
此时,我们已经展示了ROWS的子句。我们通过下面的例子可以快速理解两者的不同(注意4和5行以及12和13行是相同的值,此处产生不同):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | SELECT FName, Salary, SumByRows = SUM (Salary) OVER ( ORDER BY Salary ROWS UNBOUNDED PRECEDING), SumByRange = SUM (Salary) OVER ( ORDER BY Salary RANGE UNBOUNDED PRECEDING) FROM ( VALUES (1, 'George' , 800), (2, 'Sam' , 950), (3, 'Diane' , 1100), (4, 'Nicholas' , 1250), (5, 'Samuel' , 1250), (6, 'Patricia' , 1300), (7, 'Brian' , 1500), (8, 'Thomas' , 1600), (9, 'Fran' , 2450), (10, 'Debbie' , 2850), (11, 'Mark' , 2975), (12, 'James' , 3000), (13, 'Cynthia' , 3000), (14, 'Christopher' , 5000) ) dt(RowID, FName, Salary); |
查询结果如下:
后两列的OVER子句除了ROWS/RANGE 的子句不同以外完全相同,注意,结束两个的结束点都没有指定,默认就是当前行。SumByRows 列通过计算第一行到当前行的所有行的值作为总数,而RANGE子句是计算到排序字段(SALARY)的值相同的列的所有值得总和。所以当有重复薪水值得时候就发现了两者的不同,如上所示。
重要提示:ORDER BY在OVER子句中只控制在窗口函数中使用分区行的顺序,而不控制最终结果集的顺序。如果需要制定结果集顺序,还要在查询后加上ORDER BY 语句。
下面介绍几种2014加入的新的窗口函数,以便我们使用,个人觉得很有帮助,性能非常不错。
1.LAG() and LEAD()
–向前或者向后N行
2.FIRST_VALUE() 与 LAST_VALUE()
–第一行或最后一行
3.PERCENT_RANK() 与 CUME_DIST()
–计算排序(统计分析常用)
4.PERCENTILE_DISC() 与 PERCENTILE_CONT()
–根据百分比取值(统计分析)
总结:
本篇主要介绍了OVER的用法,通过对比不同关键字的对比展示如何使用。在很多缺少排序和分区分组的条件下,能够简化t-sql语句提高语句效率。希望对大家的使用有帮助。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?