若觉得本文写得还可以,请多多关注本人所作书籍《C++语法详解》电子工业出版社出版,作者 黄勇,网盘地址:
https://pan.baidu.com/s/1dIxLMN5b91zpJN2sZv1MNg
本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)
三、窗口函数的特点及与分组、子查询的区别
1、窗口函数和聚合函数都是作用于行集的,因此,使用窗口聚合函数能实现的方案,通常可以使用分组查询或子查询替代,但是窗口聚合函数通常优于替代方案,下面对其进行讲解。
2、不同的窗口函数可以使用不同的窗口进行计算,而分组查询的分组是在查询中定义的,所有计算都使用相同的集合进行。比如,
select a,sum( c), avg ( c) from T4 group by a;
以上分组语句中sum 和avg的计算都是在由列a产生的分组上进行计算的
select a,sum( c) over (partition by a),avg( c) over(partition by b) from T4
以上语句中的窗口聚合函数sum是在由列a定义的窗口上进行计算的,而avg是在由列b定义的窗口上进行计算的。
3、窗口函数不会丢失详细信息
窗口函数是根据窗口进行计算的,并且会为查询结果集的每一行返回一个标量结果值,分组查询一个组只返回一个值,因此分组查询会失去细节信息,而窗口函数则不会。比如
select a, sum( c) from T group by a
以上语句对c列求和,由于是仅使用了列a进行分组,因此无法查看到c列各行值的详细信息,若使用窗口函数,则可以查看到c列及其他列各行值的详细信息,比如
select a,c,sum( c) over(partition by a) from T4
当实际应用中需要计算聚合信息和详细信息时,窗口函数比分组查询能更好,详见后文。
4、对使用了相同窗口的窗口函数,只会对窗口内的数据访问一次,即只会对源表进行一次扫描,从而提高性能。从此优点可见,窗口函数的性能明显高于使用子查询替代窗口函数的方案,详见后文的示例
5、窗口是在对表进行了表操作符(如联接)、筛选(比如WHERE子句)、分组之后的结果集基础之上定义的,这样就不用重复设定对外部查询的限定,而子查询是从头开始的查询,需要重复设定对外部查询的限定,因此窗口函数的代码会明显短于使用子查询替代窗口函数的方案,并且可提高性能,比如
select a, sum(c) over (partition by a)from T4 where a=5 --窗口函数
select a,
(select sum(c)from T4 --使用子查询替代窗口函数
where a=5 --外部查询的WHERE子句需要重新限定,增加了代码长度
group by a ) from T4 where a=5
6、若需要对窗口函数的计算结果进行筛选,则可使用公用表表达式CTE来解决,比如
WITH S AS(select a, sum(c) over(partition by a) AS x from T4)
select * from S where x=3
7、注意SELECT语句的同时性概念,比如SELECT a as x, x+b FROM T4;是错误的,因为a as x与x+b是同时执行的,因此,在执行x+b时,列a的别名x还未创建成功。同时性概念意味着,在同一时刻,所有的窗口是同时存在的。
示例:表T4具有(dat,val)两列,其中dat表示日期,val表示当月的销量,试计算当月销量占年总销量的百分比,当月销量占年平均销量的百分比
解:当月销量为详细信息,年总销量和年平均销量是聚合信息,若使用分组查询进行处理,将比较复杂,若使用窗口函数,则比较容易实现。下面为3种实现方法
方法1:使用分组查询
WITH TT as --使用公用表表达式(CTE)存储聚合信息
( select year(dat)AS y,
sum(val)AS yval,avg(val)as v --计算年总销量和年平均销量
from T7 group by year(dat))
select dat,val, --显示详细信息
100.0*val/yval,100.0*val/v --需要求解的数据
from T7 join TT on year(T7.dat)=TT.y --还需要把CTE与表T7进行联接
方法2:使用窗口函数
select dat,val --显示的详细信息
,100.0*val/sum(val) over(partition by year(dat)) --当前月销量与年总销量之比
, 100.0*val/avg(val) over(partition by year(dat)) --当前月销量与年平均销量之比
from T7;
方法3:使用子查询
select dat,val
,100.0*val/(select sum(val) from T7 group by year(dat))
,100.0*val/(select avg(val) from T7 group by year(dat))
由以上示例可见
1)、分组查询需要引入CTE和联接,当需要计算的量越来越多时,就需要更多的CTE和联接,因此分组查询会变得越来越复杂。由此可见,窗口函数可很好的解决这个问题。
2)、子查询有以下问题,第一,代码太长,第二,对于每个子查询,都需要单独扫描一次源表,因此,子查询越多,扫描源表的次数就越多,从而性能就越低,另外,子查询还需要对外部查询的限定(比如WHERE子句)进行重复限定。从以上对窗口函数的讲解可知,窗口函数可很好的解决子查询的这些问题
作者:黄邦勇帅(原名:黄勇)
主要参考文献:
1、C++语法详解 黄勇 编著 电子工业出版社 2017年7月
2、SQL Server 2012 T-SQL基础教程 [美] Itzik Ben-Gan著 张洪举 李联国 张昊天 译 人民邮电出版社 2013年12月
3、T-SQL性能调优秘笈------基于SQL Server 2012窗口函数 [美] Itzik Ben-Gan著 林德玲 方鑫译 人民邮电出版社 2014年8月
4、SQL Server 2005技术内幕:T-SQL查询 [美] Itzik Ben-Gan , Lubor Kollar , Dejan Sarka著 赵立东 唐灿 刘波 译 赵立东 审校 电子工业出版社 出版日期不详
4、SQL Server 2005技术内幕:T-SQL程序设计 [美] Itzik Ben-Gan , Dejan Sarka , Roger Wolter著 赵立东 译 电子工业出版社 出版日期不详
5、Microsoft SQL Server 2008技术内幕:T-SQL语言基础 [美] Itzik Ben-Gan著 成保栋 张昱 译 电子工业出版社 2009年10月
6、Microsoft SQL Server 2008技术内幕:T-SQL查询 [美] Itzik Ben-Gan , Lubor Kollar , Dejan Sarka , Steve Kass著 成保栋 李保强 译 电子工业出版社 2010年9月