ROWS BETWEEN 必须配合 OVER() 中 ORDER BY 和窗口函数使用,缺 ORDER BY 会报错或结果不可预测;MySQL 仅支持按行数的 7 行移动平均,PostgreSQL/BigQuery 支持按日历天的 RANGE + INTERVAL。

ROWS BETWEEN 是窗口函数的帧定义语法,不是独立函数
直接写 ROWS BETWEEN 不会生效,它必须配合 OVER() 子句中的 ORDER BY 和窗口函数(如 AVG())一起用。缺少 ORDER BY 会导致 SQL 报错或结果不可预测——因为窗口需要明确的排序依据才能确定“前7行”是谁。
- 必须有
ORDER BY,且该列应代表时间顺序(如date、created_at) -
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW表示包含当前行及往前6行,共7行 - 若日期不连续(比如跳过周末),这个写法是按行数算,不是按日历天数算;要严格7日需改用
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW(仅 PostgreSQL / BigQuery 支持,MySQL 8.0+ 不支持RANGE+INTERVAL)
MySQL 8.0+ 中正确写法(按行数的7日移动平均)
MySQL 不支持 RANGE 搭配日期间隔,所以只能用 ROWS + 预先补全日期(否则工作日数据会导致“7行 ≠ 7天”)。假设表叫 sales,有 sale_date 和 amount:
SELECT
sale_date,
amount,
ROUND(AVG(amount) OVER (
ORDER BY sale_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
), 2) AS moving_avg_7d
FROM sales
ORDER BY sale_date;
- 注意:如果某天无销售记录,这天不会出现在结果里,导致窗口跨度过大(例如周一数据会拉到上上周二)
- 解决办法是先用递归 CTE 或日历表
LEFT JOIN补齐所有日期,再计算 -
ROUND(..., 2)推荐加上,避免浮点精度干扰可读性
PostgreSQL / BigQuery 中按真实日历天计算(推荐场景)
用 RANGE + INTERVAL 才能保证“过去7天内所有记录”,自动跳过空日期:
SELECT
sale_date,
amount,
ROUND(AVG(amount) OVER (
ORDER BY sale_date
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW
), 2) AS moving_avg_7d
FROM sales
ORDER BY sale_date;
- PostgreSQL 要求
ORDER BY列是 date/timestamp 类型,不能是字符串 - BigQuery 中
INTERVAL '6 day'(单数 day)才合法,写days会报错Invalid INTERVAL value - 此写法在数据稀疏时更鲁棒,但性能略低于
ROWS(需按时间范围索引扫描)
常见错误与调试提示
执行后结果异常?大概率卡在这几个点:
- 忘记
ORDER BY:MySQL 报错This function requires an ORDER BY clause;PostgreSQL 允许但结果无意义 - 误写成
ROWS BETWEEN 7 PRECEDING AND CURRENT ROW:实际取了8天(0~7共8行),应是6 PRECEDING - 字段类型不匹配:SQL Server 要用
DATEADD(day, -6, sale_date)模拟,不能直接写INTERVAL - NULL 值影响:
AVG()自动忽略 NULL,但如果amount是 NULL 占比高,移动平均会变短;必要时加WHERE amount IS NOT NULL
真正难的不是写对语法,而是确认你的“7日”到底指日历跨度还是样本数量——这个业务定义没理清,后面所有优化都白搭。

















