SQL中UNBOUNDED PRECEDING如何定义窗口起点?
作者:技术拾光时间:2026-06-23 08:53:13浏览:488
对于刚开始接触窗口函数的朋友来说,`UNBOUNDED PRECEDING` 这个概念确实容易让人有点困惑。它到底是个什么东西?真的能“从起点开始”吗? 先聊第一个问题。`UNBOUNDED PRECEDING` 不是指整个表物理上的第一行,而是指当前这个窗口框架(frame)在逻辑上能回溯到的最远
对于刚开始接触窗口函数的朋友来说,`UNBOUNDED PRECEDING` 这个概念确实容易让人有点困惑。它到底是个什么东西?真的能“从起点开始”吗?
先聊第一个问题。`UNBOUNDED PRECEDING` 不是指整个表物理上的第一行,而是指当前这个窗口框架(frame)在逻辑上能回溯到的最远边界。它的起点在哪里,完全由 `ORDER BY` 子句和分区(`PARTITION BY`)共同决定。没写 `PARTITION BY` 的话,那它回看的就是整个表;写了,就只看当前分区内部,从该分区排好序的第一行算起。
一个很常见的误解是,只要加了 `UNBOUNDED PRECEDING`,窗口就会自动累积所有的历史数据。结果跑出来一看,发现根本没累加起来,问题多半就出在漏写了 `ORDER BY`。按照 SQL 标准,如果没写 `ORDER BY`,窗口帧的默认定义是 `ROWS BETWEEN CURRENT ROW AND CURRENT ROW`,这时候 `UNBOUNDED PRECEDING` 其实是不起作用的。
所以这里有几个关键点需要记住:
* **必须搭配 `ORDER BY`**:这是激活帧定义的前提,缺了它就没法用。
* **分区的影响**:如果用了 `PARTITION BY order_date`,那 `UNBOUNDED PRECEDING` 的“起点”就是该 `order_date` 分区内排好序的第一行。
* **兼容性**:PostgreSQL、Snowflake、BigQuery、Spark SQL 都支持;MySQL 8.0+ 开始支持,5.7 不行。

怎么写一个真正从分区首行累加的 SUM 窗口?
再来看看一个典型的场景:按用户分组,按时间排序,计算每个订单的累计金额。写对的关键不只是写上 `UNBOUNDED PRECEDING`,而是整个帧子句要写完整:SELECT
user_id,
order_time,
amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY order_time
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumsum
FROM orders;
这里需要留意三点:
1. **`ROWS BETWEEN ...` 要显式写出来**:有些数据库方言(比如 BigQuery)允许省略 `ROWS`,但语义可能会降级为 `RANGE`,导致重复值被合并计算,结果就不对了。
2. **`ORDER BY` 决定了“首行”是谁**:如果 `order_time` 有重复值,建议加个二级排序(比如 `ORDER BY order_time, order_id`),避免得到不稳定的结果。
3. **用 `ROWS` 而不是 `RANGE`**:除非你确实需要把相同 `order_time` 的多行看作一个整体,否则 `RANGE` 会把它们捆在一起计算,很可能不符合我们期望的“逐行累加”逻辑。
