前言

1、什么是窗口函数

2、窗口函数的语法

3、窗口函数中的元素

1)函数名 window_function_name

2) 分区 partition_defintion

3) 排序 order_definition

4) 框架 frame_definition

4、窗口函数的应用

1)排名函数 rank(),dense_rank()、row_number() 静态窗口(不用frame)

2)聚合函数  avg()、count()、sum()、min()、max()、percent_rank()、cume_dist()

3)取值函数

前后函数lag()、lead()

头尾函数first_val() 、last_val()

其他函数nth_value()、ntile()

参考文献


前言

在我们日常数据分析中,常常遇到要在组内排名、组内取topn值等需求,例如下面的业务需求:

排名问题:每个品牌的商品按销售额来排名
topN问题:找出每个品牌排名前N的商品

面对这类需求,就需要使用sql的高级功能窗口函数了。窗口函数是个神奇的存在,可以把多行代码变一行处理,把复杂的子查询变简单。让我们来一起了解窗口函数吧!

1、什么是窗口函数

窗口函数,也叫OLAP函数(Online Anallytical Processing,联机分析处理),作用于一个数据集合。窗口函数的一个概念就是当前行,当前行属于某个窗口就是从整个数据集选取一部分数据进行聚合/排名等操作。

2、窗口函数的语法

window_function_name(window_name/expression)
OVER ([partition_defintion][order_definition][frame_definition])

窗口数据集由"[partition_defintion]","[order_definition]","[frame_definition]"确定。

3、窗口函数中的元素

1)函数名 window_function_name

静态窗口函数不能用frame子句;滑动窗口函数指加入order by或frame子句后,函数区域变为到当前行的数据集。

静态窗口函数:

排名函数 rank()、dense_rank()、row_number();

滑动窗口函数:

聚合函数 sum、 avg、count、max、min、percent_rank()、cum_dist();

取值函数 first_value()、last_value()、nth_value()、lag()、lead()、ntile()

函数名

参数

描述

rank()

这种排序允许并列,并且保留空缺,两个第1名,没有第2名,排名是1,1,3

dense_rank()

这种排序允许并列,但不留空,两个第1名,接下来就是第2名,排名是1,1,2

row_number()

这种排序不允许并列,且不留空,如果分数是100,100,99 ,这里的排名依旧是1,2,3

sum()

sum(expr)

求和,expr指字段名称或表达式

avg()

avg(expr)

取平均

count()

cout(expr)

统计记录数

min()

min(expr)

取最小值

max()

max(expr)

取最大值

percent_rank()

和之前的rank()函数相关,每条记录按照如下公式计算:(rank-1)/(rows-1)其中,rank为rank()函数产生的序号,rows为当前窗口的记录总行数

cum_dist()

返回一组值中值的累积分布,表示值小于或等于行的值的行数除以总行数的行数 ,返回值大于零且小于或等于1

first_value()

first_value(expr)

返回分组内的第一个值

last_value()

last_value(expr)

返回分组内的最后一个值,与last_value()合称为头尾函数

nth_value()

nth_value(expr,n)

nth_value()功能与first_value()/last_value()类似,返回分组中第N个expr的值,n为正数,若n为空,函数将返回错误,若n大于窗口内的所有函数,函数返回NULL

lag()

lag(expr, [n, [default]])

分区中位于当前行前n行的记录值,若n缺失默认值为1,如果没有返回default,没有default,返回NULL

lead()

lead(expr, [n, [default]])

分区中位于当前行后n行的记录值,函数功能与lag()相反,其余与lag()相同

ntile()

ntile(n)

再平均分配到n个集合中,返回每个记录的分桶号,范围从1到n

2) 分区 partition_defintion

窗口按照指定字段进行分区,分区语句为partition by <指定字段>,窗口函数功能在分区内执行,并在跨越分区边界时重新初始化。如果没有指定 partition by 语句,且没有后面的frame元素限制,就把所有数据当做一整个区。

3) 排序 order_definition

按照指定字段进行排序,排序语句为order by <指定字段>[asc/desc]。和partition by 子句配合使用,就是对分区后的数据进行排序;如果单独使用且没有后面的frame元素的限制,就是对整个区的所有数据进行排序。

4) 框架 frame_definition

窗口框架的作用是对分区进一步细分,框架frame是当前分区的一个子集,在分区里面再进一步细分窗口,子句用来定义子集的规则,通常用来作为滑动窗口使用,某些窗口函数属于静态窗口,frame子句就没有作用。

frame_unit有两种,分别是ROWS和RANGE,ROWS是基于行号,RANGE是基于值的范围。

使用BETWEEN frame_start AND frame_end语法来表示行范围,frame_start和frame_end可以支持如下关键字:

  • CURRENT ROW: 当前行
  • UNBOUNDED PRECEDING:   区间的第一行
  • UNBOUNDED FOLLOWING:区间的最后一行
  • expr  NPRECEDING:   当前行之前的N行,可以是数字,也可以是一个能计算出数字的表达式
  • expr  NFOLLOWING:当前行之后的N行,可以是数字,也可以是一个能计算出数字的表达式

4、窗口函数的应用

接下来,结合实例,介绍以上几种窗口函数的用法

-- 创建成绩表
drop table if exists score;
CREATE TABLE IF NOT EXISTS `score` (
`学号` INT (5) zerofill NOT NULL,
`姓名` VARCHAR (10) NOT NULL,
`课程号` VARCHAR (15) NOT NULL,
`成绩` INT (5) NOT NULL DEFAULT 0
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='成绩表';
-- 查看表结构
DESC score;

接下来插入数据

-- 插入数据
INSERT INTO `score`(`学号`, `姓名`, `课程号`, `成绩`)
VALUES
( '0005', '范若若', '00001', '99' ),( '0005', '范若若', '00002', '81' ),( '0005', '范若若', '00003', '81' ),
( '0001', '范闲',   '00001', '95' ),( '0001', '范闲',   '00002', '91' ),( '0001', '范闲',   '00003', '90' ),
( '0004', '范思辙', '00001', '93' ),( '0004', '范思辙', '00002', '93' ),( '0004', '范思辙', '00003', '97' ),
( '0003', '王启年', '00001', '94' ),( '0003', '王启年', '00002', '84' ),( '0003', '王启年', '00003', '96' ),
( '0002', '林婉儿', '00001', '96' ),( '0002', '林婉儿', '00002', '86' ),( '0002', '林婉儿', '00003', '91' ),
( '0006', '叶轻眉', '00001', '100'),( '0006', '叶轻眉', '00002', '88' ),( '0006', '叶轻眉', '00003', '88' ),
( '0007', '庆帝',   '00001', '98' ),( '0007', '庆帝',   '00002', '88' ),( '0007', '庆帝',   '00003', '93' );
-- 查询成绩表
select * from score order by 学号;

MySQL从8.0版本开始也和Sql Server、Oracle一样支持在查询中使用窗口函数,本文使用 MySQL 8.0.23版本通过实例来介绍窗口函数的应用。

1)排名函数 rank(),dense_rank()、row_number() 静态窗口(不用frame)

-- 按照姓名分区,成绩升序排名。 排名函数 rank(),dense_rank()、row_number()
SELECT 姓名,课程号,成绩,rank() OVER (PARTITION BY 姓名 ORDER BY 成绩) AS r,dense_rank() OVER (PARTITION BY 姓名 ORDER BY 成绩) AS dr,row_number() OVER (PARTITION BY 姓名 ORDER BY 成绩) AS rn
FROM score;

2)聚合函数  avg()、count()、sum()、min()、max()、percent_rank()、cume_dist()

首先介绍常见聚合函数中的avg():求平均、count():统计个数、sum():求和、min():求最小值、max():求最大值,下面使用窗口函数和聚合函数求每个人成绩的均值、个数、总分、最小值和最大值。

-- 求每个人成绩的均值、个数、总分、最小值和最大值
SELECT *,avg(成绩)   over w AS score_avg,count(成绩) over w AS score_count,sum(成绩)   over w AS score_sum,min(成绩)   over w AS score_min,max(成绩)   over w AS score_max
FROM score
WINDOW w AS (partition by 姓名)
order by 学号;

在上面这个例子中,通过对姓名分区后,聚合了各个课程的成绩,例如score_sum列 对每个人分区后,加总各个课程的分数得到总分,但结果中保留了每一行的信息,出现了重复的总分行。我们可以直接使用group by函数得到唯一的的信息,避免重复。

-- 直接使用group by 函数
SELECT 学号,姓名,round(avg(成绩),0)    AS score_avg,count(成绩)  AS score_count,sum(成绩)    AS score_sum,min(成绩)    AS score_min,max(成绩)    AS score_max
FROM score
group by 姓名
order by 学号;

下面介绍不太常用的两个聚合函数percent_rank():累计百分比、cume_dist():累计分布值函数

percent_rank(),和之前的rank()函数相关,每条记录按照如下公式计算:(rank-1)/(rows-1)其中,rank为RANK()函数产生的序号,rows为当前窗口的记录总行数。

cume_dist(),和之前的rank()函数也相关,每条记录按照如下公式计算:相对位置(行排名)/rows

例如:叶轻眉的课程号00002和00003的成绩一样都为88,属于同一个等级1。因此这些行都以第2行为基准:2/3=0.66666,如下图所示:

-- percent_rank累计百分比、cume_dist累计分布值
SELECT *,rank() over w r,percent_rank()  over w AS pr,cume_dist()   over w AS cd
FROM score
WINDOW w AS (partition by 姓名 order by 成绩);

注意:这两个函数不同的地方在于起始点,percent_rank 这个函数从0开始,而cume_list函数从1开始计算百分比。

3)取值函数

  • 前后函数lag()、lead()

用途:分区中位于当前行前n行/后n行的记录值。

使用场景:查询上一个订单距离当前订单的时间间隔等

-- 按照课程号分区,成绩列每条记录的前一行lag()/后一行lead()
SELECT *,lag(成绩, 1) over w as  first_row,lead(成绩,1) over w as  last_row
FROM score
WINDOW w AS (partition by 课程号);

注:lag(expr, [n, [default]])分区中位于当前行前n行的记录值,若n缺失默认值为1,如果没有返回default,没有default,返回NULL;lead()的语法和其相反,其余类似。

  • 头尾函数first_val() 、last_val()

用途:返回分区中的第一个/最后一个记录值

-- 按照课程号分区的第一个和最后一个成绩分数
SELECT *,first_value(成绩) over w as first_values,last_value(成绩) over w as last_values
FROM score
WINDOW w AS (partition by 课程号);

  • 其他函数nth_value()、ntile()

(1)nth_value()

用途:返回窗口中第N个expr的值,expr可以是表达式,也可以是列名;

应用场景:每个用户订单中显示本用户金额排名第二和第三的订单金额,对头尾函数进行补充;


-- 按照课程号分区取每个分区成绩的第2和第3个值
SELECT *,nth_value(成绩, 2) over w as  second_score,nth_value(成绩, 3) over w as  third_score
FROM score
WINDOW w AS (partition by 课程号);

(2)ntile()

用途:将分区中的有序数据分为n个桶,记录桶号;

应用场景:将每个用户的订单按照订单金额分成3组。

此函数在数据分析中应用较多,比如由于数据量大,需要将数据平均分配到N个并行的进程分别计算,此时就可以用NTILE(N)对数据进行分组,由于记录数不一定被N整除,所以数据不一定完全平均。

-- 按照课程号分区并按成绩升序排序,将各分区分为3桶
SELECT *,ntile(3) over w as nt
FROM score
WINDOW w AS (partition by 课程号 order by 成绩 asc);

窗口函数非常有意思,对于一些使用常规思维无法实现的SQL需求,大家可以尝试一下窗口函数,相信会有意想不到的收获。而在MySQL8.0以前的版本上无法使用窗口函数,大家也可以试一试自己写窗口函数。

4)frame语句

ROWS基于行号的应用

  • PRECEDING:   区间的前面
  • FOLLOWING:区间的后面
-- 计算每分区内当前行的前1行(共两行)的平均值以及和,计算成绩的移动平均数与求和。
SELECT 姓名,课程号,成绩,avg(成绩) OVER (PARTITION BY 姓名 ORDER BY 成绩 ASC ROWS 1 preceding) AS moving_avg,sum(成绩) OVER (PARTITION BY 姓名 ORDER BY 成绩 ASC ROWS 1 preceding) AS moving_sum
FROM score;

RANGE是基于值范围的应用

-- 计算每个分区内从第一行到当前行的平均值以及和
SELECT 姓名,课程号,成绩,avg(成绩) OVER (PARTITION BY 姓名 ORDER BY 成绩 ASC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS moving_avg,sum(成绩) OVER (PARTITION BY 姓名 ORDER BY 成绩 ASC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS moving_sum
FROM score;

-- 计算每个分区内从当前行到最后一行的平均值以及和
SELECT 姓名,课程号,成绩,avg(成绩) OVER (PARTITION BY 姓名 ORDER BY 成绩 ASC RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS moving_avg,sum(成绩) OVER (PARTITION BY 姓名 ORDER BY 成绩 ASC RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS moving_sum
FROM score;

参考文献

  1. https://zhuanlan.zhihu.com/p/165210822?utm_source=wechat_session
  2. https://www.cnblogs.com/DataArt/p/9961676.html?ivk_sa=1024320u
  3. https://blog.csdn.net/lkforce/article/details/102938990
  4. https://zhuanlan.zhihu.com/p/111755529

SQL篇-常用窗口函数相关推荐

  1. Oracle 的 Sql*Plus 常用命令介绍

    估计用 Oracle 的几乎一开始都是使用 Sql*Plus 吧, 毕竟这个工具可以运行在任何的 Oracle 运行的平台上, 这个工具呢,您可以在客户端实现安装,也可以在服务端安装, 在客户端的安装 ...

  2. oracle sql常用的函数,界别Oracle和SQL Server常用函数

    区分Oracle和SQL Server常用函数 一.数学函数 1.绝对值 S:select abs(-1) value O:select abs(-1) value from dual 2.取整(大) ...

  3. SQL 中的窗口函数

    SQL 中的窗口函数(Window Functions)是一种特殊的函数,它可以在查询结果的某个区间内执行计算,而不仅仅是对单个行进行计算. 以下是一些常用的窗口函数: 1.ROW_NUMBER() ...

  4. SQL server之窗口函数应用

    1.首先先简单描述一下SQL server的存储过程,通俗的说,就是将一个或多个Transact-SQL语句分组到逻辑单元中,并作为对象存储在数据库服务器中. 当第一次调用存储过程时,SQL Serv ...

  5. 数据清洗必须会的一些方法 - sql篇

    数据清洗必须会的一些方法 - sql篇 介绍 解决质量问题 解决办法 数据的完整性 sql处理方式 数据的唯一性 sql处理方式 数据的权威性 数据的合法性问题 sql处理方式 数据的一致性问题 介绍 ...

  6. 《T-SQL性能调优秘笈——基于SQL Server 2012 窗口函数》——1.7 小结

    本节书摘来自异步社区出版社<T-SQL性能调优秘笈--基于SQL Server 2012 窗口函数>一书中的第1章,第1.7节,作者: [美]Itzik Ben-Gan,更多章节内容可以访 ...

  7. 《T-SQL性能调优秘笈——基于SQL Server 2012 窗口函数》——1.2 使用窗口函数的解决方案简介...

    本节书摘来自异步社区出版社<T-SQL性能调优秘笈--基于SQL Server 2012 窗口函数>一书中的第1章,第1.2节,作者: [美]Itzik Ben-Gan,更多章节内容可以访 ...

  8. 语句 查询每个人每个科目的最高分_转行数据分析师专栏(SQL篇)-05多表查询...

    数据表(4张)及数据情况: 搭建数据表数据参考: 空杯希望:转行数据分析师专栏(SQL篇)-01SQL入门​zhuanlan.zhihu.com 一.表的加法 1.先新建一个表course1(和cou ...

  9. SQL Server常用的系统存储过程应用实例(转)

    摘要:Sql Server自带的系统存储过程有许多,但大部分我们是不常用的.我在实践中根据自己的体会,总结整理了一些比较常用的,加上一些实例介绍给大家.本期介绍: l         sp_attac ...

最新文章

  1. 【 MATLAB 】fliplr 函数介绍(从左到右翻转阵列)
  2. LibSvm python 调试实验
  3. 转: eclipse 快捷键列表(功能清晰版本)
  4. CVPR2021开源项目,带你傲游宇宙!
  5. 使用python实现GBK转unicode码查询表
  6. java零碎要点001--深入理解JVM_Java的堆内存_栈内存_以及运行时数据区的作用
  7. 原生CSS设置网站主题色—CSS变量赋值
  8. linux 设置ssh免密登录
  9. play for scala 通过网易smtp发送邮件
  10. web developer tips (47):脚本远程调试需关闭IE增强安全配置
  11. Windows系统结构
  12. Aspose.PSD for Java 21.6 Crack
  13. 常见图片存储格式文件简介
  14. x战娘2服务器维护,x战娘2变态版攻略
  15. 计算机学科发表顶级期刊论文,计算机类顶级期刊_计算机顶级期刊_mcs数学与计算机期刊...
  16. 生成式对抗网络(GAN)简介
  17. VMware Workstation中安装Windows10系统
  18. jvm.option是什么,它是如何加载的
  19. MySQL之——mysql5.5 uuid做主键与int做主键的性能实测
  20. Gulp 编译Less和Sass

热门文章

  1. Linux安全漏洞如何进行修复?
  2. wireshark分析数据包
  3. 工地人员定位系统解决方案
  4. 蓝桥杯.第几个幸运数字(数学_因子)
  5. 网站安全漏洞检测报告年度安全分析
  6. AS400数据库事务处理机制
  7. 微信小程序中显示使用64位图片
  8. vi笔记 - 使用vi加xxd完成对二进制文件的内容修改
  9. ch340 win7 64位驱动下载
  10. OPPO R9m线刷包_OPPO R9m刷机包_线刷救砖