一.窗口函数简介

窗口函数又名开窗函数,属于分析函数的一种。用于解决复杂报表统计需求的功能强大的函数。窗口函数用于计算基于组的某种聚合值,它和聚合函数的不同之处是:对于每个组返回多行,而聚合函数对于每个组只返回一行。
开窗函数指定了分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变化而变化。

二.窗口函数基本语句形式

Function(arg1 , ……) over(partition by query_patition_clause order by order_by_clause Window_clause )

  • Window_clause : rows | range between start_expr and end_expr
  • Start_expr : unbounded preceding | current row | n preceding | n following
  • End_expr : unbounded following | current row | n preceding | n following

窗口函数带有一个开窗函数over(),包含三个分析子句:

  • 分组(partition by),即query_patition_clause
  • 排序(order by),即order_by_clause
  • 窗口(rows),即Window_clause

三.可使用窗口的函数

不是所有的函数都支持开窗函数。目前支持的窗口函数可结合的函数有:

  • Row_number;
  • 和排名函数(rank、dense_rank)连用;
  • 和错行函数(lead、lag)联合使用;
  • First_value和last_value;
  • NTILE;
  • Ratio_to_report;
  • 和统计函数(max、min、avg、sum)等连用;

先构建测试表:

create table test(id int,name varchar(10),sale int);
insert into test values(1,’aaa’,100);
insert into test values(1,’bbb’,200);
insert into test values(1,’ccc’,200);
insert into test values(1,’ddd’,300);
insert into test values(2,’eee’,400);
insert into test values(2,’fff’,200);
select * from test;
Ln ID NAME SALE
1 1 aaa 100
2 1 bbb 200
3 1 ccc 200
4 1 ddd 300
5 2 eee 400
6 2 fff 200

下面依次介绍支持的窗口函数。
3.1 ROW_NUMBER

ROW_NUMBER() OVER(partition by col1 order by col2)
表示根据col1分组,在分组内部根据col2排序,而此函数计算的值就表示每组内部排序后的顺序编号(组内是连续且唯一的)。

select t.*,row_number() over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 1
2 1 bbb 200 2
3 1 ccc 200 3
4 1 ddd 300 4
5 2 fff 200 1
6 2 eee 400 2

当query_patition_clause没有时,将视全部记录为一个分组。使用row_number(),order_by_clause必须有,否则报错。


select t.*,row_number() over(order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 1
2 2 fff 200 2
3 1 bbb 200 3
4 1 ccc 200 4
5 1 ddd 300 5
6 2 eee 400 6

另外,如果在over中已经添加了order by…,就不建议在from后面再添加相同的order
by。二者一致的话还好(效果一样,没有差别),但不一致时结果有时就可能令人费解了。

3.2 RANK与DENSE_RANK
rank 和 dense_rank 主要的功能是计算一组数值中的排序值。其语法格式如下:

RANK() OVER ([query_partition_clause] order_by_clause)

select t.*,rank() over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 1
2 1 bbb 200 2
3 1 ccc 200 2
4 1 ddd 300 4
5 2 fff 200 1
6 2 eee 400 2

select t.*,dense_rank() over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 1
2 1 bbb 200 2
3 1 ccc 200 2
4 1 ddd 300 3
5 2 fff 200 1
6 2 eee 400 2

rank()是跳跃排序,有两个第二名时接下来就是第四名,不会出现第三名;dense_rank()是连续排序,有两个第二名时仍然跟着第三名。

3.3 LEAD与LAG
这两个函数是偏移量函数,可以查出一个字段的上一个值或者下一个值。lead函数是向下取值,即当前行是当前分区内最后一条则显示null;lag函数是向上取值,即当前行是当前分区内第一条则显示null。其语法如下:

lead(EXPR,<OFFSET>,<DEFAULT>)
lag(EXPR,<OFFSET>,<DEFAULT>)
  • EXPR通常是直接是列名,也可以是从其他行返回的表达式;
  • OFFSET是默认为1,表示在当前分区内基于当前行的偏移行数;
  • DEFAULT是在OFFSET指定的偏移行数超出了分组的范围时(此时会返回null),可以通过设置这个字段来返回一个默认值来替代null。

 select t.*,lead(sale) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 200
2 1 bbb 200 200
3 1 ccc 200 300
4 1 ddd 300 NULL
5 2 fff 200 400
6 2 eee 400 NULL

select t.*,lag(sale) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 NULL
2 1 bbb 200 100
3 1 ccc 200 200
4 1 ddd 300 200
5 2 fff 200 NULL
6 2 eee 400 200

select t.*,lead(sale,2) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 200
2 1 bbb 200 300
3 1 ccc 200 NULL
4 1 ddd 300 NULL
5 2 fff 200 NULL
6 2 eee 400 NULL

select t.*,lead(sale,2,500) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 200
2 1 bbb 200 300
3 1 ccc 200 500
4 1 ddd 300 500
5 2 fff 200 500
6 2 eee 400 500

select t.*,lead(2) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 2
2 1 bbb 200 2
3 1 ccc 200 2
4 1 ddd 300 NULL
5 2 fff 200 2
6 2 eee 400 NULL

如果只指定一个常量,则将该值直接作为结果返回(偏移行数默认为1),但分区内最后一行仍然为null(同理,lag时分区内第一行也是null)。


select t.*,lead(2,2) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 2
2 1 bbb 200 2
3 1 ccc 200 NULL
4 1 ddd 300 NULL
5 2 fff 200 NULL
6 2 eee 400 NULL

如果指定2个常量,也是将该值直接作为结果返回(第一个参数为偏移行数),这样分区内最后两行仍然为null(同理,lag时分区内前两行也是null)。


select t.*,lead(2,2,2) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 2
2 1 bbb 200 2
3 1 ccc 200 2
4 1 ddd 300 2
5 2 fff 200 2
6 2 eee 400 2

如果是3个参数都是常量,则会把最后一个常量当作默认值替代null。(这两个函数的确挺有趣的!)

3.4 FIRST_VALUE与LAST_VALUE
FIRST_VALUE返回一组排序值后的第一个值,LAST_VALUE返回一组排序值后的最后一个值。其语法如下:

FIRST_VALUE( expr ) OVER ( analytic_clause )
LAST_VALUE( expr ) OVER ( analytic_clause )


select t.*,first_value(sale) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 100
2 1 bbb 200 100
3 1 ccc 200 100
4 1 ddd 300 100
5 2 fff 200 200
6 2 eee 400 200

select t.*,first_value(2) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 2
2 1 bbb 200 2
3 1 ccc 200 2
4 1 ddd 300 2
5 2 fff 200 2
6 2 eee 400 2

可以接收一个常量参数,效果等同于直接将该值作为结果返回。


select t.*,last_value(sale) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 100
2 1 bbb 200 200
3 1 ccc 200 200
4 1 ddd 300 300
5 2 fff 200 200
6 2 eee 400 400

这两个函数可以不跟order by子句,也可以指定窗口上下界。

3.5 NTILE
NTILE函数对一个数据分区中的有序结果集进行划分,将其分组到各个桶,并为每个小组分配一个唯一的组编号。这个函数在统计分析中是很有用的。例如,如果想移除异常值,我们可以将它们分组到顶部或底部的桶中,然后在统计分析的时候将这些值排除。在统计信息收集可以使用NTILE函数来计算直方图信息边界。在统计学术语中,NTILE函数创建等宽直方图信息。其语法如下:

NTILE(ntile_num) OVER ( analytic_clause )

其中ntile_num不能小于等于0。analytic_clause中必须有order by子句。

select t.*,ntile(3) over(order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 1
2 2 fff 200 1
3 1 bbb 200 2
4 1 ccc 200 2
5 1 ddd 300 3
6 2 eee 400 3

3.6 RATIO_TO_REPORT
ratio_to_report函数用来计算总数百分比。通常计算百分比的方法是在总计报告的子查询中使用SUM函数总计报告,然后把那个结果放到细节表中相除来计算百分比。还可以用一个子查询作为SELECT语句表达式。RATIO_TO_REPORT函数使得这种类型的查询更容易编写。其语法如下:

RATIO_TO_REPORT (expr) OVER (query_partition_clause)

select t.*,ratio_to_report(sale) over(partition by id) rn from tes t;
Ln ID NAME SALE RN
1 1 aaa 100 0.125
2 1 bbb 200 0.25
3 1 ccc 200 0.25
4 1 ddd 300 0.375
5 2 fff 200 0.333
6 2 eee 400 0.667

此函数不能跟order by子句。

3.7统计函数(MAX、MIN、AVG、SUM、COUNT)
统计函数最为常见,这里只列举例子,语法形式如下:
SUM[MAX][MIN][AVG] OVER (query_partition_clause)

select t.*,sum(sale) over(partition by id order by sale) rn from test t;
select t.*,sum(sale) over(partition by id order by sale range between unbounded preceding and current row) rn from test t;

(注:以上二者是等效的)

Ln ID NAME SALE RN
1 1 aaa 100 100
2 1 bbb 200 500
3 1 ccc 200 500
4 1 ddd 300 800
5 2 fff 200 200
6 2 eee 400 600

select t.*,sum(sale) over(partition by id order by sale rows between unbounded preceding and current row) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 100
2 1 bbb 200 300
3 1 ccc 200 500
4 1 ddd 300 800
5 2 fff 200 200
6 2 eee 400 600

select t.*,max(sale) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 100
2 1 bbb 200 200
3 1 ccc 200 200
4 1 ddd 300 300
5 2 fff 200 200
6 2 eee 400 400

select t.*,min(sale) over(partition by id order by sale) rn from test t;
Ln ID NAME SALE RN
1 1 aaa 100 100
2 1 bbb 200 100
3 1 ccc 200 100
4 1 ddd 300 100
5 2 fff 200 200
6 2 eee 400 200

四.Window_clause窗口子句

本章详细介绍下Window_clause。即窗口子句,它标识了在分组上的一个范围。其中,

  • between…and用来指定窗口的起始点和终结点;
  • Unbounded preceding指明窗口开始于分组的第一行;
  • Current row作为起始点,指明窗口开始于当前行或当前值;作为终结点,指明窗口结束于当前行或当前值;
  • Unbounded following指明窗口结束于分组的最后一行;

range是逻辑窗口,是指定当前行对应值的范围取值,行数不固定,只要行值在范围内,对应行都包含在内。rows是物理窗口,即根据order by 子句排序后,取的前N行及后N行的数据计算(与当前行的值无关,只与排序后的行号相关)。

Oracle对窗口子句的逻辑限定:
*Cause: If the window specification is specified
using RANGE option and there are multiple ORDER BY expressions, then
the aggregation group cannot contain any expression (It can only
have CURRENT ROW, UNBOUNDED PRECEDING, or UNBOUNDED FOLLOWING).
First end point (bound) cannot be UNBOUNDED FOLLOWING and second end
point cannot be UNBOUNDED PRECEDING. If the first end point is
CURRENT ROW, then second end point can only be CURRENT ROW or
/UNBOUNDED FOLLOWING. If the first end point is FOLLOWING,
then second end point can only be /UNBOUNDED FOLLOWING.

即,如果使用窗口说明中使用了range/rows以及order by子句时,聚合组里只能含有CURRENT ROW, UNBOUNDED PRECEDING或UNBOUNDED FOLLOWING。
下界点(the first end point)不能是UNBOUNDED FOLLOWING;上界点(second end point)不能是UNBOUNDED PRECEDING。
如果下界点是CURRENT ROW,那么上界点只能是CURRENT ROW或者UNBOUNDED FOLLOWING或者 FOLLOWING;如果下界点是 FOLLOWING,那么上界点只能是 / UNBOUNDED FOLLOWING。

编者按:窗口说明中起始点与终结点之间排列组合顺序(由下限到上限)依次为:
UNBOUNDED PRECEDING, PRECEDING,CURRENT ROW, FOLLOWING,UNBOUNDED FOLLOWING。
因此只允许BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,而不允许BETWEEN CURRENT ROW AND UNBOUNDED PRECEDING。其他情况相同。
目前除了聚合函数可以指定窗口上下界之外,非聚合函数只有first_value和last_value函数可以指定。其他的函数都不能指定。

五.补充说明

  • 当sql语句中的order by子句里的内容和开窗函数over()中的order by子句里的内容一样,那么sql语句中的排序将先执行,分析函数在分析时就不必再排序;order by 字段两者不一致时,那么sql语句中的排序将最后在分析函数分析结束后执行排序。所以如果在over中已经添加了order by…,就不建议在from后面再添加相同的order by。二者一致的话还好(效果一样,没有差别),但不一致时结果有时就可能令人费解了。
  • 窗口子句必须和order by 子句同时使用,且如果指定了order by 子句未指定窗口子句,则默认为RANGE BETWEEN unbounded preceding AND CURRENT ROW。
  • 如果分析函数没有指定ORDER BY子句,也就不存在ROWS/RANGE窗口的计算。

窗口函数的基本使用方法相关推荐

  1. opencv4.4.0函数手册_实战 MySQL8.0 窗口函数的巧妙应用

    大家好,我是知数堂SQL 优化班老师 网名:骑龟的兔子 今天给大家带来的是 窗口函数的应用 因为需求希望我,提供脚本,直接上脚本 WITH t AS ( SELECT 1 rn, 3.0 v FROM ...

  2. MariaDB Window Functions窗口函数分组取TOP N记录

    窗口函数在MariaDB10.2版本里实现,其简化了复杂SQL的撰写,提高了可读性. 在某些方面,窗口函数类似于聚集函数, 但它不像聚集函数那样每组只返回一个值,窗口函数可以为每组返回多个值. 作为一 ...

  3. sql之窗口函数(一)

    更新了 一.窗口函数 1. 概述 MySQL8.0版本之后,加入了窗口函数功能,简化了数据分析工作中查询语句的书写. 窗口函数是数据分析工作中必须掌握的工具,在SQL笔试中也是高频考点 窗口函数是类似 ...

  4. mysql中还有窗口函数?这是什么东西?

    什么是窗口函数? 在mysql8.0的版本中,新增了一个窗口函数,用他可以实现很多新的查询方式.窗口函数类似于sun().count()那样的集合函数,但它并不会将多行查询结果合并为一行,而是将结果放 ...

  5. 2021-11-07大数据学习日志——MySQL进阶——窗口函数

    01_窗口函数概述 学习目标 了解窗口函数的优点 1.1 窗口函数 接下来的课程中我们来介绍窗口函数window functions. MYSQL 8.0 之后,加入了窗口函数功能,简化了数据分析工作 ...

  6. 关联子查询实现窗口函数

    有窗口函数,我这一篇博客就是不用,诶,就是玩 前提: 在本篇博客中,将会用其他的方式来实现窗口函数的功能,用普普通通的方式完成窗口函数干的事. 需求: 和过去最邻近的时间进行比较,找出和当前年份销售值 ...

  7. js使用showModalDialog,弹出一个自适应大小窗口

    showModalDialog(sURL [, vArguments] [, sFeatures]) 是参数形式,后面2个参数可以缺省.其中 sFeatures就是dialog的显示控制.如果要实现自 ...

  8. Oracle SQL 基础要点

    Oracle SQL 基础要点 本文是学习<程序员的SQL金典>时的读书摘要,记录一些自己不太熟悉或者很重要的知识点.方便后期对照复习. 1.各种主流数据库的优缺点比较 - DB2由IBM ...

  9. mysql8.0 MySQL函数

    PART1. MySQL函数介绍 函数表示对输入参数值返回一个具有特定关系的值,MySQL提供了大量丰富的函数,在进行数据库管理以及数据的查询和操作时将会经常用到各种函数.各类函数从功能方面主要分为数 ...

最新文章

  1. 重磅消息:蚂蚁金服推出RPC框架
  2. C#中Attribute的继承
  3. Python3压缩和解压缩实现
  4. [react] 状态管理器解决了什么问题?什么时候用状态管理器?
  5. C# Winform 窗体美化(六、双层窗体)
  6. 硅谷对“元宇宙”一无所知
  7. 合并excel文件 C语言,多个Excel文件中的多个Sheet合并到一个Excel文件中两个函数...
  8. asp.net使用httpModule来实现一个反向代理
  9. AirServer for mac如何实现无线投屏
  10. 代码走查,使用插件findbugs(eclipse)
  11. 1到20阶乘倒数之和
  12. 易支付源码第四方支付接口
  13. 关于redis客户端连接不上
  14. 金蝶KIS记账王账务处理常见问题解决方案
  15. Microsoft edge升级之后收藏夹内容丢失_等了这么久,Edge 终于成为我的默认浏览器...
  16. 如此优秀,这18个 Python 高效编程技巧真的太香了
  17. VS2008 中无法使用ACTIVEX控件的解决
  18. Java编程----函数
  19. 设计并测试一个椭圆类
  20. python- 按指定列值筛选数据

热门文章

  1. NNLM神经网络语言模型简单实现词语预测(含python代码详解)
  2. 华为ct5000瘦客户机_第八集[达特分享]华为CT5000一键刷BIOS安装OpenWRT制作软路由...
  3. 职中计算机专业入门基础知识,浅谈职业中学《计算机应用基础》的教法论文
  4. 【博客430】overlay网络与underlay网络
  5. WebService是什么?他究竟和WebSocket有什么关系?
  6. 9.9元的ST-LINK(V2.J40.M27)制作并附带刷写DAPLinkV2文件
  7. 微信小程序云开发实战:网上商城(五)
  8. python如何缩放图片
  9. w7重启计算机打印机无法使用,电脑打印提示由于打印机的当前设置有问题,Windows无法打印怎么解决...
  10. IPGeo从捕捉的网络流量文件中快速提取IP地址