Flink SQL 中的 OVER 聚合——为每一行算“窗口统计

1. OVER 聚合和 GROUP BY 聚合到底有什么不一样?

在 Flink SQL 里,最常见的聚合其实有两大类:

  1. GROUP BY 聚合

    • 一组输入行 → 一行聚合结果
    • 典型场景:按用户汇总、按订单分组统计
  2. OVER 聚合(窗口函数聚合)

    • 一行输入 → 一行输出 + 一个"附加指标"
    • 每一行都能得到一个"基于窗口的统计值"

简单说:

  • GROUP BY压缩行数(很多行 → 一行)
  • OVER保留行数(每行都保留,只是在行上多了一些"统计字段")

举个简单例子:

"我想知道每一条订单,过去一小时内同一个商品的总成交金额是多少?"

OVER 写起来刚好就很自然:

sql 复制代码
SELECT
  order_id,
  order_time,
  product,
  amount,
  SUM(amount) OVER (
    PARTITION BY product
    ORDER BY order_time
    RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW
  ) AS one_hour_prod_amount_sum
FROM Orders;

这里:

  • 每一行订单仍然只对应一行结果;
  • 但会多出一个字段 one_hour_prod_amount_sum
  • 表示:这条订单的视角下,回顾过去 1 小时该商品的累计金额

你可以把它理解为一种"按时间滑动的明细 + 统计"组合。

2. OVER 的基本语法:PARTITION、ORDER、RANGE / ROWS

一个典型的 OVER 聚合写法长这样:

sql 复制代码
agg_func(agg_col) OVER (
  [PARTITION BY col1, col2, ...]
  ORDER BY time_col | non_time_col
  RANGE ... | ROWS ...
)

几个关键点:

  1. ORDER BY 是必须的

    因为 OVER 是"在一段有顺序的行范围上做聚合",而表本身是无序的,所以必须指定一个排序字段。

  2. 流式查询中,ORDER BY 有限制:

    • 只能按时间属性升序非时间字段升序
    • 不支持各种复杂排序组合。
  3. PARTITION BY 的语义和 GROUP BY 类似:

    • 在每个 partition 内单独做窗口聚合;
    • 常规用法:按用户、按商品、按租户分区,互不影响。
  4. RANGE / ROWS 是窗口范围的两种定义方式:

    • RANGE:按值(时间或非时间字段)定义范围;
    • ROWS:按行数定义范围。

3. RANGE:按时间 / 值做"区间窗口"

3.1 时间 RANGE:过去 30 分钟的滑动窗口

最常见的写法是基于时间的 RANGE,例如:

sql 复制代码
SUM(amount) OVER (
  PARTITION BY product
  ORDER BY order_time
  RANGE BETWEEN INTERVAL '30' MINUTE PRECEDING AND CURRENT ROW
)

含义:

  • 对当前行而言,把"当前时间之前 30 分钟内的所有行 + 当前行"全部纳入窗口;
  • 然后在这个窗口上做 SUM(amount)

每一行的窗口都不一样,是一个移动的时间窗口

适用场景:

  • 实时看某个用户最近 30 分钟的活跃度;
  • 实时看某个商品最近 1 小时的销量;
  • 实时看某个账号近 5 分钟的转账金额总和,用于风控。
3.2 非时间 RANGE:按排序键做"从头到当前"的累积

对于非时间字段,比如自增 ID 或某个数值,也可以用 RANGE:

sql 复制代码
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

意味着:

  • 从当前 partition 内最早的那一行
  • 一直到当前行,
  • 都纳入窗口。

典型用法就是做累计值,比如"从用户注册到当前为止的总消费"。

4. ROWS:按行数做"最近 N 行"的滑动窗口

ROWS 是完全按行数来定义窗口范围:

sql 复制代码
ROWS BETWEEN 10 PRECEDING AND CURRENT ROW

语义:

  • 当前行之前的 10 行;
  • 再加上当前行;
  • 总共 11 行作为一个窗口。

特点:

  • 不看时间,只看"相对行数";
  • 适合那种"最近 N 条记录"的统计。

典型场景:

  • 股票/行情:最近 5 根 K 线的平均价格;
  • 日志分析:最近 100 条日志中 error 比例。

5. 流式查询中的一个限制:多个 OVER 必须用相同窗口

文档里有一个容易忽略的限制:

在流式查询中,如果一个 SELECT 里有多个 OVER 聚合,所有 OVER 窗口定义必须完全相同

例如,这样是可以的:

sql 复制代码
SELECT
  order_id,
  SUM(amount) OVER w AS sum_amount,
  AVG(amount) OVER w AS avg_amount
FROM Orders
WINDOW w AS (
  PARTITION BY product
  ORDER BY order_time
  RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW
);

但如果你这样写:

sql 复制代码
SUM(amount) OVER (RANGE 1 HOUR ...)
AVG(amount) OVER (RANGE 2 HOUR ...)

在流模式下就不被支持,因为目前运行时代价较高,Flink 要求这些 OVER 聚合共享同一个窗口定义,才能共享状态 / 运算。

所以实战中的建议是:

  • 多个 OVER 聚合 → 尽量抽到同一个 WINDOW 子句里复用;
  • 若确实需要不同窗口,通常考虑拆成多个算子 / 多个 SQL。

6. WINDOW 子句:让 SQL 更干净、更可复用

当你有多个 OVER 聚合时,把窗口定义写在 WINDOW 里会清爽很多:

sql 复制代码
SELECT
  order_id,
  order_time,
  amount,
  SUM(amount) OVER w AS sum_amount,
  AVG(amount) OVER w AS avg_amount
FROM Orders
WINDOW w AS (
  PARTITION BY product
  ORDER BY order_time
  RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW
);

好处有三个:

  1. 语义更清晰

    • SELECT 里只看得到"我用哪个窗口 w",
    • 窗口如何定义单独在下面声明。
  2. 便于修改

    • 窗口范围改成 2 小时,只用改一处 WINDOW w
  3. 满足流式多 OVER 同窗的限制

    • 所有 OVER 都挂在 w 上,自然是同一个窗口定义。

7. 什么时候该用 OVER,而不是 GROUP BY / Window TVF?

一个简单的经验法则:

  • 只要你还想保留明细行,同时又想附加"基于窗口的统计值",就适合用 OVER;
  • 如果你只需要"窗口级别的汇总结果",那用 Window TVF + GROUP BY 更合适。

对比一下几个典型需求:

  1. 需求 A:每 10 分钟统计一次某商品的销售额

    • TUMBLE(TABLE ..., INTERVAL '10' MINUTES) GROUP BY window_start, window_end 更自然。
  2. 需求 B:在订单明细表中,想看到每条订单在过去 1 小时内的累计金额

    • SUM(amount) OVER (PARTITION BY product ORDER BY order_time RANGE 1 HOUR) 非常契合。
  3. 需求 C:表格中每一行都显示"截止当前为止累计值"和"最近 10 行平均值"

    • OVER + RANGE / ROWS 的典型使用场景。

在流式业务里,OVER 很适合做:

  • 实时看板上的"当前订单 + 最近 N 分钟统计";
  • 明细列表页里的"当前用户历史累计";
  • 需要点状视角(行)+ 区间视角(窗口)的混合场景。

8. 小结

这一节的重点可以压缩成 3 句话:

  1. OVER 聚合不会减少行数,只是给每一行多算一个基于窗口的统计值,适合"明细 + 统计"的场景;
  2. 它依赖 PARTITION BY + ORDER BY + RANGE/ROWS 定义窗口,在流式场景下多个 OVER 必须用同一个窗口;
  3. 对只关心窗口级汇总的场景,还是优先考虑 Window TVF Aggregation;对需要在每一行旁边展示"过去一段时间统计"的场景,用 OVER 会很优雅。
相关推荐
JIngJaneIL3 小时前
基于Java旅游信息推荐系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·旅游
LSL666_3 小时前
mybatisplus入门案例
数据库·mysql·mybatisplus
ZePingPingZe3 小时前
DriverManager、DataSource、数据库驱动以及数据库连接池的关系
android·数据库·adb
诗旸的技术记录与分享3 小时前
Flink-1.19.0源码详解10-Flink计算资源的申请与调度
大数据·flink
Loiioฅ3 小时前
ctfshow-web入门-sql注入-171-186
数据库·sql
思成不止于此3 小时前
【MySQL 零基础入门】DML 核心语法全解析:表数据的增删改操作篇
数据库·笔记·sql·学习·mysql
yy17962610014 小时前
mysql基本结构及操作
数据库
todoitbo4 小时前
时序数据库选型实战指南:国产时序数据库Apache IoTDB的技术对比与实践
数据库·apache·时序数据库·iotdb·国产
赵渝强老师4 小时前
【赵渝强老师】TiDB的备份恢复策略
数据库·mysql·oracle·tidb