不能。ROWS BETWEEN 后的偏移量必须是常量整数,不支持变量、参数、子查询或函数;动态窗口需通过自连接、LAG/LEAD 或应用层计算等替代方案实现。

ROWS BETWEEN 能不能指定变量或表达式?
不能。ROWS BETWEEN 后面的偏移量必须是常量整数,不支持变量、参数、子查询或函数(比如 ROW_NUMBER() 或 @n)。你写 ROWS BETWEEN 1 PRECEDING AND CURRENT ROW 没问题,但写成 ROWS BETWEEN @start PRECEDING AND @end FOLLOWING 会直接报错:Incorrect syntax near '@start'。
常见误操作是试图用动态 SQL 拼接窗口大小再执行 —— 这确实能绕过语法限制,但代价是失去执行计划复用、增加注入风险,且无法在视图或内联表值函数中使用。
如何实现“动态”移动窗口的等效效果?
所谓“动态”,实际是指窗口大小依赖于某列值(如每行的 days_lookback)或业务逻辑。这时得放弃纯 OVER(ROWS BETWEEN ...),改用以下策略:
- 用
LAG()/LEAD()手动展开固定最大偏移(适合偏移范围小且有上限的场景) - 用自连接 +
BETWEEN条件模拟滑动范围(例如按日期字段关联前 N 天记录) - 在应用层分批拉取数据后计算(当窗口逻辑复杂、数据库难以表达时更可控)
例如,要对每行计算「过去 lookback_days 天内销售额总和」,不能写:
SELECT sale_date, amount,
SUM(amount) OVER (
ORDER BY sale_date
ROWS BETWEEN ? PRECEDING AND CURRENT ROW -- ❌ ? 不被允许
) AS rolling_sum
FROM sales;
而应改用自连接:
SELECT s1.sale_date, s1.amount,
(SELECT SUM(s2.amount)
FROM sales s2
WHERE s2.sale_date BETWEEN DATEADD(day, -s1.lookback_days, s1.sale_date)
AND s1.sale_date) AS dynamic_rolling_sum
FROM sales s1;
ROWS BETWEEN 和 RANGE BETWEEN 的关键区别在哪?
这是最容易混淆也最影响结果准确性的点:
-
ROWS BETWEEN纯按物理行序计数,无视排序键值是否重复或间隔多大 -
RANGE BETWEEN按排序键的**逻辑值范围**划分窗口,相同键值的行会被打包进同一窗口(可能引发意外聚合膨胀)
比如按 order_date 排序,若某天有 50 笔订单,RANGE BETWEEN 1 PRECEDING AND CURRENT ROW 会把「前一天所有订单 + 今天全部 50 笔」全算进来;而 ROWS BETWEEN 1 PRECEDING AND CURRENT ROW 只取紧邻的上一行(即前一笔订单),不管日期是否相同。
所以,只要排序键存在重复值,又需要严格按“最近 N 行”而非“最近 N 天”,就必须用 ROWS —— 且必须接受它不能动态化这个事实。
有没有折中方案兼顾灵活性与性能?
有,但需接受权衡:
- 预定义几档常用窗口大小(如 7/30/90 天),用多个
CASE+ 固定ROWS窗口并行计算,再用CASE WHEN选其一 - 用
NTILE()或PERCENT_RANK()分桶后,在每个桶内用固定窗口近似动态行为 - 在 ETL 阶段用游标或递归 CTE 预计算动态窗口指标,存为宽表供查询使用
这些都不是运行时真正“动态”,而是把动态逻辑前置或离散化。真正想在单条 SELECT 里让 ROWS BETWEEN 吃变量,SQL Server、PostgreSQL、MySQL(8.0+)目前都做不到 —— 不是配置问题,是语法层面的硬性约束。










