Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >SQL 窗口函数的优化和执行

SQL 窗口函数的优化和执行

作者头像
SQL数据库开发
发布于 2024-04-24 09:10:39
发布于 2024-04-24 09:10:39
22600
代码可运行
举报
文章被收录于专栏:SQL数据库开发SQL数据库开发
运行总次数:0
代码可运行

https://ericfu.me/sql-window-function/

窗口函数(Window Function)是 SQL2003 标准中定义的一项新特性,并在 SQL2011、SQL2016 中又加以完善,添加了若干处拓展。窗口函数不同于我们熟悉的普通函数和聚合函数,它为每行数据进行一次计算:输入多行(一个窗口)、返回一个值。在报表等分析型查询中,窗口函数能优雅地表达某些需求,发挥不可替代的作用。

本文首先介绍窗口函数的定义及基本语法,之后将介绍在 DBMS 和大数据系统中是如何实现高效计算窗口函数的,包括窗口函数的优化、执行以及并行执行。

什么是窗口函数?

窗口函数出现在 SELECT 子句的表达式列表中,它最显著的特点就是 OVER 关键字。语法定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
window_function (expression) OVER (
   [ PARTITION BY part_list ]
   [ ORDER BY order_list ]
   [ { ROWS | RANGE } BETWEEN frame_start AND frame_end ] )

其中包括以下可选项:

  • PARTITION BY 表示将数据先按 part_list 进行分区
  • ORDER BY 表示将各个分区内的数据按 order_list 进行排序

Figure 1. 窗口函数的基本概念

最后一项表示 Frame 的定义,即:当前窗口包含哪些数据?

  • ROWS 选择前后几行,例如 ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING 表示往前 3 行到往后 3 行,一共 7 行数据(或小于 7 行,如果碰到了边界)
  • RANGE 选择数据范围,例如 RANGE BETWEEN 3 PRECEDING AND 3 FOLLOWING 表示所有值在 [c−3,c+3][c−3,c+3] 这个范围内的行,cc 为当前行的值

Figure 2. Rows 窗口和 Range 窗口

逻辑语义上说,一个窗口函数的计算“过程”如下:

  1. 按窗口定义,将所有输入数据分区、再排序(如果需要的话)
  2. 对每一行数据,计算它的 Frame 范围
  3. 将 Frame 内的行集合输入窗口函数,计算结果填入当前行

举个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SELECT dealer_id, emp_name, sales,
       ROW_NUMBER() OVER (PARTITION BY dealer_id ORDER BY sales) AS rank,
       AVG(sales) OVER (PARTITION BY dealer_id) AS avgsales 
FROM sales

上述查询中,rank 列表示在当前经销商下,该雇员的销售排名;avgsales 表示当前经销商下所有雇员的平均销售额。查询结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
+------------+-----------------+--------+------+---------------+
| dealer_id  | emp_name        | sales  | rank | avgsales      |
+------------+-----------------+--------+------+---------------+
| 1          | Raphael Hull    | 8227   | 1    | 14356         |
| 1          | Jack Salazar    | 9710   | 2    | 14356         |
| 1          | Ferris Brown    | 19745  | 3    | 14356         |
| 1          | Noel Meyer      | 19745  | 4    | 14356         |
| 2          | Haviva Montoya  | 9308   | 1    | 13924         |
| 2          | Beverly Lang    | 16233  | 2    | 13924         |
| 2          | Kameko French   | 16233  | 3    | 13924         |
| 3          | May Stout       | 9308   | 1    | 12368         |
| 3          | Abel Kim        | 12369  | 2    | 12368         |
| 3          | Ursa George     | 15427  | 3    | 12368         |
+------------+-----------------+--------+------+---------------+

注:语法中每个部分都是可选的:

  • 如果不指定 PARTITION BY,则不对数据进行分区;换句话说,所有数据看作同一个分区
  • 如果不指定 ORDER BY,则不对各分区做排序,通常用于那些顺序无关的窗口函数,例如 SUM()
  • 如果不指定 Frame 子句,则默认采用以下的 Frame 定义:
    • 若不指定 ORDER BY,默认使用分区内所有行 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
    • 若指定了 ORDER BY,默认使用分区内第一行到当前值 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

最后,窗口函数可以分为以下 3 类:

  • 聚合(Aggregate)AVG(), COUNT(), MIN(), MAX(), SUM()...
  • 取值(Value)FIRST_VALUE(), LAST_VALUE(), LEAD(), LAG()...
  • 排序(Ranking)RANK(), DENSE_RANK(), ROW_NUMBER(), NTILE()...

受限于篇幅,本文不去探讨各个窗口函数的含义,有兴趣的读者可以参考这篇文档。

https://drill.apache.org/docs/sql-window-functions-introduction/#types-of-window-functions

注:Frame 定义并非所有窗口函数都适用,比如 ROW_NUMBER()RANK()LEAD() 等。这些函数总是应用于整个分区,而非当前 Frame。

窗口函数 VS. 聚合函数

聚合这个意义上出发,似乎窗口函数和 Group By 聚合函数都能做到同样的事情。但是,它们之间的相似点也仅限于此了!这其中的关键区别在于:窗口函数仅仅只会将结果附加到当前的结果上,它不会对已有的行或列做任何修改。而 Group By 的做法完全不同:对于各个 Group 它仅仅会保留一行聚合结果。

有的读者可能会问,加了窗口函数之后返回结果的顺序明显发生了变化,这不算一种修改吗?因为 SQL 及关系代数都是以 multi-set 为基础定义的,结果集本身并没有顺序可言ORDER BY 仅仅是最终呈现结果的顺序。

另一方面,从逻辑语义上说,SELECT 语句的各个部分可以看作是按以下顺序“执行”的:

Figure 3. SQL 各部分的逻辑执行顺序

注意到窗口函数的求值仅仅位于 ORDER BY 之前,而位于 SQL 的绝大部分之后。这也和窗口函数只附加、不修改的语义是呼应的——结果集在此时已经确定好了,再依此计算窗口函数。

窗口函数的执行

窗口函数经典的执行方式分为排序函数求值这 2 步。

Figure 4. 一个窗口函数的执行过程,通常分为排序和求值 2 步

窗口定义中的 PARTITION BYORDER BY 都很容易通过排序完成。例如,对于窗口 PARTITION BY a, b ORDER BY c, d,我们可以对输入数据按 (a,b,c,d)(a,b,c,d) 或 (b,a,c,d)(b,a,c,d) 做排序,之后数据就排列成 Figure 1 中那样了。

接下来考虑:如何处理 Frame?

  • 对于整个分区的 Frame(例如 RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),只要对整个分区计算一次即可,没什么好说的;
  • 对于逐渐增长的 Frame(例如 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),可以用 Aggregator 维护累加的状态,这也很容易实现;
  • 对于滑动的 Frame(例如 ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING)相对困难一些。一种经典的做法是要求 Aggregator 不仅支持增加还支持删除(Removable),这可能比你想的要更复杂,例如考虑下 MAX() 的实现。

窗口函数的优化

对于窗口函数,优化器能做的优化有限。这里为了行文的完整性,仍然做一个简要的说明。

通常,我们首先会把窗口函数从 Project 中抽取出来,成为一个独立的算子称之为 Window。

Figure 5. 窗口函数的优化过程

有时候,一个 SELECT 语句中包含多个窗口函数,它们的窗口定义(OVER 子句)可能相同、也可能不同。显然,对于相同的窗口,完全没必要再做一次分区和排序,我们可以将它们合并成一个 Window 算子。

对于不同的窗口,最朴素地,我们可以将其全部分成不同的 Window,如上图所示。实际执行时,每个 Window 都需要先做一次排序,代价不小。

那是否可能利用一次排序计算多个窗口函数呢?某些情况下,这是可能的。例如本文例子中的 2 个窗口函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
... ROW_NUMBER() OVER (PARTITION BY dealer_id ORDER BY sales) AS rank,
    AVG(sales) OVER (PARTITION BY dealer_id) AS avgsales ...

虽然这 2 个窗口并非完全一致,但是 AVG(sales) 不关心分区内的顺序,完全可以复用 ROW_NUMBER() 的窗口。这篇论文 提供了一种启发式的算法,能尽可能利用能够复用的机会。

窗口函数的并行执行

现代 DBMS 大多支持并行执行。对于窗口函数,由于各个分区之间的计算完全不相关,我们可以很容易地将各个分区分派给不同的节点(线程),从而达到分区间并行

但是,如果窗口函数只有一个全局分区(无 PARTITION BY 子句),或者分区数量很少、不足以充分并行时,怎么办呢?上文中我们提到的 Removable Aggregator 的技术显然无法继续使用了,它依赖于单个 Aggregator 的内部状态,很难有效地并行起来。

TUM 的这篇论文中提出使用线段树(Segment Tree)实现高效的分区内并行。线段树是一个 N 叉树数据结构,每个节点包含当前节点下的部分聚合结果。

下图是一个使用二叉线段树计算 SUM() 的例子。例如下图中第三行的 1212,表示叶节点 5+75+7 的聚合结果;而它上方的 2525 表示叶节点 5+7+3+105+7+3+10 的聚合结果。

Figure 6. 使用线段树计算给定范围的总和

假设当前 Frame 是第 2 到第 8 行,即需要计算 7+3+10+...+47+3+10+...+4 区间之和。有了线段树以后,我们可以直接利用 7+13+207+13+20 (图中红色字体)计算出聚合结果。

线段树可以在 O(nlogn)O(nlog⁡n) 时间内构造,并能在 O(logn)O(log⁡n) 时间内查询任意区间的聚合结果。更棒的是,不仅查询可以多线程并发互不干扰,而且线段树的构造过程也能被很好地并行起来。

References

  1. Efficient Processing of Window Functions in Analytical SQL Queries - Leis, Viktor, et al. (VLDB'15)
  2. Optimization of Analytic Window Functions - Cao, Yu, et al. (VLDB'12)
  3. SQL Window Functions Introduction - Apache Drill
  4. PostgreSQL 11 Reestablishes Window Functions Leadership
  5. [Window Functions in SQL Server
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-02-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 SQL数据库开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
数据库:SQL 窗口函数知识介绍
窗口函数(Window Function) 是 SQL2003 标准中定义的一项新特性,并在 SQL2011、SQL2016 中又加以完善,添加了若干处拓展。窗口函数不同于我们熟悉的普通函数和聚合函数,它为每行数据进行一次计算:输入多行(一个窗口)、返回一个值。在报表等分析型查询中,窗口函数能优雅地表达某些需求,发挥不可替代的作用。
小明互联网技术分享社区
2021/05/14
7560
数据库:SQL 窗口函数知识介绍
SQL 窗口函数的优化和执行
窗口函数(Window Function)是 SQL2003 标准中定义的一项新特性,并在 SQL2011、SQL2016 中又加以完善,添加了若干处拓展。窗口函数不同于我们熟悉的普通函数和聚合函数,它为每行数据进行一次计算:输入多行(一个窗口)、返回一个值。在报表等分析型查询中,窗口函数能优雅地表达某些需求,发挥不可替代的作用。
大数据老哥
2021/03/08
1.9K0
SQL 窗口函数的优化和执行
大数据快速入门(10):Hive窗口函数
首先,需要认识到,窗口函数并不是只有 hive 才有的,SQL 语法标准中,就有窗口函数。
kk大数据
2020/11/11
2.6K0
MySQL8新特性窗口函数详解
本文博主给大家详细讲解一波 MySQL8 的新特性:「窗口函数」,相信大伙看完一定能有所收获。
wayn
2023/06/14
4790
MySQL8新特性窗口函数详解
sparksql比hivesql优化的点(窗口函数)
有时候,一个 select 语句中包含多个窗口函数,它们的窗口定义(OVER 子句)可能相同、也可能不同。
数据仓库践行者
2020/04/18
1.5K0
Hive SQL 大厂必考常用窗口函数及相关面试题
二、窗口函数的基本用法 1.基本语法 2.设置窗口的方法 1)window_name 2)partition by 子句 3) order by子句 4)rows 指定窗口大小 3.开窗函数中加order by 和 不加 order by的区别
王知无-import_bigdata
2022/11/11
3.8K0
Hive SQL 大厂必考常用窗口函数及相关面试题
spark、hive中窗口函数实现原理复盘
这篇文章从一次业务中遇到的问题出发,深入聊了聊hsql中窗口函数的数据流转原理,在文章最后针对这个问题给出解决方案。
数据仓库践行者
2020/04/20
3.2K0
Hive补充之窗口函数
窗口函数 1、hive窗口函数语法 hive中的窗口函数over() ,over()窗口函数的语法结构
Maynor
2021/04/09
1.1K0
不要到处翻了 | Hive开窗函数总结与实践
平常我们使用 hive或者 mysql时,一般聚合函数用的比较多。但对于某些偏分析的需求,group by可能很费力,子查询很多,这个时候就需要使用窗口分析函数了~ 注:hive、oracle提供开窗函数,mysql8之前版本不提供,但Oracle发布的 MySQL 8.0版本支持窗口函数(over)和公用表表达式(with)这两个重要的功能!
大数据真好玩
2021/01/26
6.3K0
mysql中分组排序_oracle先分组后排序
​ 窗口函数(window functions),也被称为 “开窗函数”,也叫OLAP函数(Online Anallytical Processing,联机分析处理),可对数据库数据进行实时分析处理。它是数据库的标准功能之一,主流的数据库比如Oracle,PostgreSQL都支持窗口函数功能,MySQL 直到 8.0 版本才开始支持窗口函数。
全栈程序员站长
2022/11/10
8K0
SQL窗口函数概述
在应用WHERE、GROUP by和HAVING子句之后,窗口函数对SELECT查询选择的行进行操作。
用户7741497
2022/03/27
2.5K1
Hive 窗口函数最全讲解和实战
在深入研究Over字句之前,一定要注意:在SQL处理中,窗口函数都是最后一步执行,而且仅位于Order by子句之前 可以想象成sql的输出结果,就是窗口函数输入的结果。
kk大数据
2019/12/18
2.1K0
MySQL 窗口函数详解:分析性查询的强大工具
MySQL 窗口函数提供了一种灵活的方式来处理 SQL 查询中的数据,它们允许你在不需要对数据进行分组的情况下对行集进行分析。窗口函数最常用于分析性操作,比如计算排名、累计和、移动平均值等。MySQL 从版本 8.0 开始支持窗口函数。以下是窗口函数的几个关键概念和常见用法:
科技新语
2024/12/17
1650
MySQL 窗口函数详解:分析性查询的强大工具
深入MySQL窗口函数:原理和应用
窗口函数(Window Functions)是SQL标准中的一个高级特性,它允许用户在不改变查询结果集行数的情况下,对每一行执行聚合计算或其他复杂的计算。这些计算是基于当前行与结果集中其他行之间的关系进行的。窗口函数特别适用于需要执行跨多行的计算,同时又想保持原始查询结果集的行数不变的场景。
公众号:码到三十五
2024/03/19
2.9K0
深入MySQL窗口函数:原理和应用
大数据学习之数据仓库代码题总结上
请编写 SQL 查询,计算从注册当天开始的每个用户在注册后第1天、第3天、第7天的学习留存率。留存率的计算方式是在注册后的特定天数内继续学习的用户数除以当天注册的用户总数。结果应包含日期、留存天数和留存率。
bxia的厨房_公众号
2024/03/05
2551
大数据学习之数据仓库代码题总结上
MySQL窗口函数,你最熟悉的陌生人~
  这三个点虽然平时用得少,但在面试中却常被问到。值得一提的是,很多面试官对问题竟然也是一知半解。。
陈哈哈
2021/12/31
1.1K0
MySQL窗口函数,你最熟悉的陌生人~
SparkSql窗口函数源码分析(第一部分)
WindowExpression :描述该expression是一个windowExpression,继承BinaryLike,是一个二元树。
数据仓库践行者
2022/11/25
1.2K0
SparkSql窗口函数源码分析(第一部分)
MySQL8新特性窗口函数详解
MySQL8 窗口函数是一种特殊的函数,它可以在一组查询行上执行类似于聚合的操作,但是不会将查询行折叠为单个输出行,而是为每个查询行生成一个结果。窗口函数可以用来处理复杂的报表统计分析场景,例如计算移动平均值、累计和、排名等。其中博主认为它展现的主要威力在于「它能够让我们在不修改原有语句输出结果的基础上,直接添加新的聚合字段」。
wayn
2023/08/28
3040
MySQL8新特性窗口函数详解
详解spark开窗函数
窗口函数(Window functions)又称分析函数或开窗函数,它允许你在不改变原始行的情况下,对一组相关的行(称为“窗口”)进行计算和分析。与普通的聚合函数(如SUM、AVG等)不同,窗口函数不会将多行合并为一行,而是为每一行返回一个计算结果,同时保留原始行的详细信息。通常写法为func()over(),详细语法如下:
数据仓库晨曦
2025/02/26
1211
详解spark开窗函数
Hive常用窗口函数实战
本文介绍了Hive常见的序列函数,排名函数和窗口函数。结合业务场景展示了Hive分析函数的使用
Eights
2020/07/13
2.8K0
相关推荐
数据库:SQL 窗口函数知识介绍
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验