当前位置:首页 > 问答 > 正文

树叶云带你了解OceanBase窗口函数那些事,简单说说怎么用和注意点

树叶云带你了解OceanBase窗口函数那些事,简单说说怎么用和注意点 来源:树叶云技术分享)

今天咱们就来聊聊OceanBase数据库里的窗口函数,你可能听过这个名字,但觉得它有点高级,有点复杂,其实不然,窗口函数就像是给你的SQL查询装上一个“滑动窗口”,让你能在不改变原来行数的情况下,对每一行数据进行一些额外的计算和观察,比如排名、累加、移动平均等等,它和普通的聚合函数(比如SUM、AVG)最大的不同在于,聚合函数会把多行数据压成一行结果,而窗口函数会为每一行都返回一个结果,原表有多少行,结果就有多少行,非常方便进行行级别的分析。

窗口函数长什么样?基本语法速览

一个典型的窗口函数调用看起来是这样的:

<窗口函数> OVER ([PARTITION BY <列名>] [ORDER BY <列名> [ASC|DESC]] [frame_clause])

别被这一串吓到,我们拆开看就很清楚了。

  1. <窗口函数>:这是核心,就是你想要做什么计算,主要分三大类:

    • 排名函数ROW_NUMBER()(行号)、RANK()(排名)、DENSE_RANK()(密集排名),这些常用于TOP N分析、排名场景。
    • 聚合函数:没错,普通的SUM(), AVG(), MAX(), MIN(), COUNT()也可以当窗口函数用,比如计算每个人的销售额占部门总额的百分比。
    • 取值函数LAG()(取前一行的值)、LEAD()(取后一行的值)、FIRST_VALUE()(取窗口内第一个值),这些常用于计算环比、同比。
  2. OVER子句:这是定义“窗口”的关键,窗口就是函数计算时参考的数据范围。

    • PARTITION BY:相当于分组GROUP BY,但不像GROUP BY那样会合并行,它只是把数据分成不同的组(分区),然后窗口函数会在每个分区内独立进行计算,比如PARTITION BY department,就是按部门分区,计算在每个部门内部进行,这个是可选的,如果省略,就是对整个结果集当做一个大分区。
    • ORDER BY:它决定了窗口内数据的排序方式,这对于排名函数和计算累加和(running total)至关重要,比如ORDER BY sales DESC,就是按销售额降序排,这个也是可选的。
    • frame_clause(窗口框架):这个稍微进阶一点,它定义了在分区内,对于当前行,计算所覆盖的精确行范围,从分区的第一行到当前行”(用于累加),或者“当前行前后各一行”(用于移动平均),语法像ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING,如果省略,默认框架通常取决于是否使用了ORDER BY

举几个栗子,一看就懂

假设我们有一张销售表sales_data,有员工姓名name、部门department、月份month、销售额sales

  • 例子1:给每个部门的员工按销售额排名

    SELECT name, department, sales,
           ROW_NUMBER() OVER (PARTITION BY department ORDER BY sales DESC) as dept_rank
    FROM sales_data;

    解读PARTITION BY department保证了排名是在每个部门内部进行的。ORDER BY sales DESC表示销售额高的排前面。ROW_NUMBER()会给出唯一的连续排名(1,2,3...),这样结果里,每个部门都会有自己的第一名、第二名。

  • 例子2:计算每个员工销售额的累计和(到当前月份)

    SELECT name, month, sales,
           SUM(sales) OVER (PARTITION BY name ORDER BY month) as running_total
    FROM sales_data;

    解读PARTITION BY name是按每个员工分区,看他一年的数据。ORDER BY month按月份排序。SUM(sales)作为窗口函数,在没有指定框架的情况下,默认框架是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,即“从分区第一行到当前行”,所以它计算的就是累计到当前月的总和。

  • 例子3:查看每个员工本月销售额比上个月增长了多少

    SELECT name, month, sales,
           LAG(sales, 1) OVER (PARTITION BY name ORDER BY month) as prev_month_sales,
           sales - LAG(sales, 1) OVER (PARTITION BY name ORDER BY month) as growth
    FROM sales_data;

    解读LAG(sales, 1)表示获取当前行之前第1行sales值。PARTITION BY nameORDER BY month保证了是在每个员工的时间序列里,取上一个月的值,这样就能轻松算出环比增长。

使用时的注意点(坑别踩)

  1. 执行顺序:窗口函数是在SQL查询中几乎最后一步执行的,是在WHERE、GROUP BY、HAVING这些子句都处理完之后,你不能在WHERE条件里直接引用窗口函数的结果别名,如果想过滤窗口函数的结果,需要用到子查询或者公共表表达式(CTE)。

    • 错误示范SELECT ..., ROW_NUMBER() OVER() as rn FROM ... WHERE rn = 1 (会报错,因为WHERE执行时还不知道rn是什么)
    • 正确做法SELECT * FROM (SELECT ..., ROW_NUMBER() OVER() as rn FROM ...) t WHERE rn = 1
  2. 性能考量:窗口函数很强大,但如果数据量巨大,并且分区和排序的列上没有合适的索引,可能会导致查询变慢,特别是当PARTITION BYORDER BY的列不同时,数据库可能需要进行额外的排序操作,要关注执行计划。

  3. 理解默认窗口框架:这是最容易混淆的地方之一,当OVER子句中有ORDER BY没有显式指定frame_clause时,默认框架是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,这个RANGEROWS有细微差别,RANGE会把ORDER BY值相同的行都视为同一行(peer)一起计算,而ROWS是物理行,在绝大多数情况下,你可能想要的是ROWS的行为,所以为了结果清晰,建议在需要精确控制时,显式写出frame_clause`。

  4. NULL值的处理:窗口函数计算时,通常不会忽略NULL值(除非你用的窗口函数本身如MAX()会忽略),NULL值在排序时,默认会被视为最小值(在ASC排序时排在最前面),这可能会影响LAGLEAD和排名的结果,需要根据业务逻辑留意。

OceanBase的窗口函数是一个非常实用的数据分析利器,刚开始可以从简单的ROW_NUMBER()SUM() ... OVER用起,慢慢再尝试LAG/LEAD和窗口框架,多写几个例子跑一跑,感受一下那个“滑动窗口”的范围,很快就能上手了,用它来做数据对比、趋势分析、排名统计,会让你的SQL能力提升一个档次。

树叶云带你了解OceanBase窗口函数那些事,简单说说怎么用和注意点