分析SQL窗口函数之聚合窗口函数


Posted in Oracle onApril 21, 2022

关于窗口函数的基础,请看文章详解SQL的窗口函数

许多常见的聚合函数也可以作为窗口函数使用,包括AVG()、SUM()、COUNT()、MAX()以及MIN()等函数。

案例分析

案例使用的示例表

下面的查询中会用到两个表,其中sales_monthly表中存储了不同产品(苹果、香蕉、橘子)每个月的销售额情况。以下是该表中的部分数据:

分析SQL窗口函数之聚合窗口函数

transfer_log表中记录了一些银行账户的交易日志。以下是该表中的部分数据: 

分析SQL窗口函数之聚合窗口函数

该表中的字段分别表示交易日志编号、交易时间、交易发起账户、交易接收账户、交易类型以及交易金额。这两个表的初始化脚本可以在文章底部获取。

1.移动平均值

AVG函数在作为窗口函数使用时,可以用于计算随着当前行移动的窗口内数据行的平均值。

例如,以下语句用于查找不同产品每个月以及截至当前月最近3个月的平均销售额

SELECT m.product,m.ym,m.amount,
AVG(m.amount) OVER(
 PARTITION BY m.product
 ORDER BY m.ym
 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
)
FROM sales_monthly m
ORDER BY m.product,m.ym

AVG函数OVER子句中的PARTITION BY选项表示按照产品进行分区。

ORDER BY选项表示按照月份进行排序;ROWS BETWEEN 2 PRECEDING AND CURRENT ROW表示窗口从当前行的前2行开始,直到当前行结束。该查询返回的结果如下:

分析SQL窗口函数之聚合窗口函数

对于“橘子”:

第一个月的分析窗口只有1行数据,因此平均销售额为“10154”。

第二个月的分析窗口为第1行和第2行数据,因此平均销售额为“10168.5”((10154+10183)/2)。

第三个月的分析窗口为第1行到第3行数据,因此平均销售额为“10194”((10154+10183+10245)/3)。

依此类推,直到计算完“橘子”所有月份的平均销售额,然后开始计算其他产品的平均销售额。

2.累计求和(ROW)

SUM函数作为窗口函数时,可以用于统计指定窗口内的累计值。

例如,以下语句用于查找不同产品截至当前月份的累计销售额:

SELECT m.product,m.ym,m.amount,
SUM(m.amount) OVER(
 PARTITION BY m.product
 ORDER BY m.ym
 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
)
FROM sales_monthly m
ORDER BY m.product,m.ym

SUM函数OVER子句中的PARTITION BY选项表示按照产品进行分区。

ORDER BY选项表示按照月份进行排序。

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW表示窗口从当前分区第1行开始,直到当前行结束。

该查询返回的结果如下:

分析SQL窗口函数之聚合窗口函数

对于“橘子”:

第一个月的分析窗口只有1行数据,因此累计销售额为“10154”。

第二个月的分析窗口为第1行和第2行数据,因此累计销售额为“20337”(10154+10183)。

第三个月的分析窗口为第1行到第3行数据,因此累计销售额为“30582”(10154+10183+10245)。

依此类推,直到计算完“橘子”所有月份的累计销售额,然后开始计算其他产品的累计销售额。

提示:对于聚合窗口函数,如果我们没有指定ORDER BY选项,默认的窗口大小就是整个分区。

如果我们指定了ORDER BY选项,默认的窗口大小就是分区的第一行到当前行。

因此,以上示例语句中的ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW选项可以省略。省略后的语句:

分析SQL窗口函数之聚合窗口函数

 执行结果与上面相同。

如果去掉ORDER BY选项,查询的窗口大小就是整个分区,如下图所示:

分析SQL窗口函数之聚合窗口函数

这时,合计值就变成了分区内所有记录的合计。

3.累计求和(RANGE)

除使用ROWS关键字以数据行为单位指定窗口的偏移量外,我们也可以使用RANGE关键字以数值为单位指定窗口的偏移量。

例如,以下语句用于查找短期之内(5天)累计转账超过100万元的账户:

SELECT log_ts,from_user,total_amount FROM (
    SELECT to_char(t.log_ts,'yyyy-mm-dd hh24:mi:ss') log_ts,t.from_user,t.amount,
      SUM(t.amount) OVER(
        PARTITION BY t.from_user
        ORDER BY t.log_ts
        RANGE INTERVAL '5' DAY PRECEDING
      ) AS total_amount
    FROM transfer_log t 
    WHERE t.type = '转账'
)
WHERE total_amount >= 1000000;

其中,SUM函数OVER子句中的RANGE选项指定了一个5天之内的时间窗口。该查询返回的结果如下:

分析SQL窗口函数之聚合窗口函数

截至2021年1月10日7时46分02秒,账户“62221234567890”在最近5天之内累计转账105万元。

示例表和脚本

-- 创建销量表sales_monthly
-- product表示产品名称,ym表示年月,amount表示销售金额(元)
CREATE TABLE sales_monthly(product VARCHAR(20), ym VARCHAR(10), amount NUMERIC(10, 2));
 
-- 生成测试数据
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201801',10159.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201802',10211.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201803',10247.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201804',10376.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201805',10400.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201806',10565.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201807',10613.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201808',10696.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201809',10751.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201810',10842.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201811',10900.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201812',10972.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201901',11155.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201902',11202.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201903',11260.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201904',11341.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201905',11459.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('苹果','201906',11560.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201801',10138.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201802',10194.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201803',10328.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201804',10322.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201805',10481.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201806',10502.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201807',10589.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201808',10681.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201809',10798.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201810',10829.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201811',10913.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201812',11056.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201901',11161.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201902',11173.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201903',11288.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201904',11408.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201905',11469.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('香蕉','201906',11528.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201801',10154.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201802',10183.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201803',10245.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201804',10325.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201805',10465.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201806',10505.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201807',10578.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201808',10680.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201809',10788.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201810',10838.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201811',10942.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201812',10988.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201901',11099.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201902',11181.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201903',11302.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201904',11327.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201905',11423.00);
INSERT INTO sales_monthly (product,ym,amount) VALUES ('桔子','201906',11524.00);
 
 
-- 创建银行交易日志表transfer_log
-- Oracle、MySQL、PostgreSQL以及SQLite
CREATE TABLE transfer_log
( log_id    INTEGER NOT NULL PRIMARY KEY, -- 交易日志编号
  log_ts    TIMESTAMP NOT NULL, -- 交易时间
  from_user VARCHAR(50) NOT NULL, -- 交易发起账号
  to_user   VARCHAR(50), -- 交易接收账号
  type      VARCHAR(10) NOT NULL, -- 交易类型
  amount    NUMERIC(10) NOT NULL -- 交易金额(元)
);
 
 
-- 生成测试数据
-- Oracle 需要执行以下ALTER语句
ALTER SESSION SET nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS';
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (1,'2021-01-02 10:31:40','62221234567890',NULL,'存款',50000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (2,'2021-01-02 10:32:15','62221234567890',NULL,'存款',100000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (3,'2021-01-03 08:14:29','62221234567890','62226666666666','转账',200000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (4,'2021-01-05 13:55:38','62221234567890','62226666666666','转账',150000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (5,'2021-01-07 20:00:31','62221234567890','62227777777777','转账',300000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (6,'2021-01-09 17:28:07','62221234567890','62227777777777','转账',500000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (7,'2021-01-10 07:46:02','62221234567890','62227777777777','转账',100000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (8,'2021-01-11 09:36:53','62221234567890',NULL,'存款',40000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (9,'2021-01-12 07:10:01','62221234567890','62228888888881','转账',10000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (10,'2021-01-12 07:11:12','62221234567890','62228888888882','转账',8000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (11,'2021-01-12 07:12:36','62221234567890','62228888888883','转账',5000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (12,'2021-01-12 07:13:55','62221234567890','62228888888884','转账',6000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (13,'2021-01-12 07:14:24','62221234567890','62228888888885','转账',7000);
INSERT INTO transfer_log (log_id,log_ts,from_user,to_user,type,amount) VALUES (14,'2021-01-21 12:11:16','62221234567890','62228888888885','转账',70000);

到此这篇关于SQL窗口函数之聚合窗口函数的使用的文章就介绍到这了!

Oracle 相关文章推荐
使用springboot暴露oracle数据接口的问题
May 07 Oracle
Oracle创建只读账号的详细步骤
Jun 07 Oracle
ORACLE查看当前账号的相关信息
Jun 18 Oracle
oracle连接ODBC sqlserver数据源的详细步骤
Jul 25 Oracle
详解Oracle块修改跟踪功能
Nov 07 Oracle
Oracle 触发器trigger使用案例
Feb 24 Oracle
Oracle数据库中通用的函数实例详解
Mar 25 Oracle
instantclient客户端 连接oracle数据库
Apr 26 Oracle
oracle设置密码复杂度及设置超时退出的功能
Jun 28 Oracle
Oracle中日期的使用方法实例
Jul 07 Oracle
Oracle查看表空间使用率以及爆满解决方案详解
Jul 23 Oracle
详解SQL的窗口函数
排查并解决Oracle sysaux表空间异常增长
Oracle使用别名的好处
Oracle 多表查询基本语法实例
Apr 18 #Oracle
Lakehouse数据湖并发控制陷阱分析
Oracle数据库中通用的函数实例详解
详细聊聊Oracle表碎片对性能有多大的影响
Mar 19 #Oracle
You might like
第六节--访问属性和方法
2006/11/16 PHP
两个强悍的php 图像处理类1
2009/06/15 PHP
php jquery 实现新闻标签分类与无刷新分页
2009/12/18 PHP
ThinkPHP3.1新特性之多数据库操作更加完善
2014/06/19 PHP
CI映射(加载)数据到view层的方法
2016/03/28 PHP
php文件上传的两种实现方法
2016/04/04 PHP
Yii2创建控制器(createController)方法详解
2016/07/23 PHP
input 高级限制级用法
2009/03/26 Javascript
js鼠标滑轮滚动事件绑定的简单实例(兼容主流浏览器)
2014/01/14 Javascript
jQuery实现预加载图片的方法
2015/03/17 Javascript
jquery实现简单的表单验证
2015/11/17 Javascript
jquery关于事件冒泡和事件委托的技巧及阻止与允许事件冒泡的三种实现方法
2015/11/27 Javascript
轻松学习jQuery插件EasyUI EasyUI实现拖动基本操作
2015/11/30 Javascript
基于node实现websocket协议
2016/04/25 Javascript
网络传输协议(http协议)
2016/11/18 Javascript
详解微信小程序开发之下拉刷新 上拉加载
2016/11/24 Javascript
利用JS判断鼠标移入元素的方向
2016/12/11 Javascript
webpack打包js文件及部署的实现方法
2017/12/18 Javascript
React-native桥接Android原生开发详解
2018/01/17 Javascript
[03:04]DOTA2超级联赛专访ZSMJ “莫名其妙”的逆袭
2013/05/23 DOTA
[02:17]2016国际邀请赛中国区预选赛VG战队领队采访
2016/06/26 DOTA
python中使用OpenCV进行人脸检测的例子
2014/04/18 Python
Flask数据库迁移简单介绍
2017/10/24 Python
使用Django2快速开发Web项目的详细步骤
2019/01/06 Python
基于numpy中的expand_dims函数用法
2019/12/18 Python
Python气泡提示与标签的实现
2020/04/01 Python
python学生管理系统的实现
2020/04/05 Python
python字典key不能是可以是啥类型
2020/08/04 Python
详解CSS3中强大的filter(滤镜)属性
2017/06/29 HTML / CSS
HTML5 Canvas 绘图——使用 Canvas 绘制图形图文教程 使用html5 canvas 绘制精美的图
2015/08/31 HTML / CSS
canvas小画板之平滑曲线的实现
2020/08/12 HTML / CSS
大学新生欢迎词
2014/01/10 职场文书
大学生职业生涯规划书汇总
2014/03/20 职场文书
爱护花草树木的标语
2014/06/11 职场文书
会议欢迎标语
2014/06/30 职场文书
离婚财产分配协议书
2014/10/21 职场文书