SQL窗口函数用ROWS BETWEEN 2 PRECEDING AND CURRENT ROW可等价Pandas rolling(3).mean(),但必须显式ORDER BY且避免重复值;RANGE不支持时间类型滑动,缺失值处理需手动CASE判断。

窗口函数能实现 Pandas 的 rolling() 效果,但语法和行为有关键差异:SQL 没有“自动对齐时间序列”的概念,必须显式定义 ORDER BY 和 ROWS BETWEEN,否则结果不可靠。
用 ROWS BETWEEN 模拟 rolling().mean()
核心是把 Pandas 的 window=3 翻译成 SQL 的 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW(注意:Pandas 默认包含当前行,且向前取 2 行,共 3 行)。
常见错误是写成 ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING —— 这是中心对齐,不是左对齐滑动,和 Pandas 默认行为不一致。
-
ORDER BY必须存在且唯一(建议加id或timestamp),否则窗口边界不确定 - 如果排序字段有重复值,需额外加
id做二级排序,否则同序号行可能被随机打乱 - MySQL 8.0+、PostgreSQL、SQL Server 2012+、BigQuery 都支持;SQLite 仅从 3.25.0 开始支持完整窗口函数
SELECT
date,
value,
AVG(value) OVER (
ORDER BY date, id
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS rolling_mean_3
FROM sales;
RANGE BETWEEN 不等于 Pandas 的时间窗口
想按“过去 7 天”做滑动计算?别直接用 RANGE BETWEEN INTERVAL '6 DAYS' PRECEDING AND CURRENT ROW —— 这只在 PostgreSQL 支持,且要求 date 是 DATE 或 TIMESTAMP 类型;MySQL 不支持 RANGE 与时间类型的组合,会报错 ERROR 3589 (HY000): Window 'w' with RANGE frame cannot contain a non-numeric ORDER BY expression。
立即学习“Python免费学习笔记(深入)”;
更稳妥的做法是用自连接或 LATERAL(PostgreSQL)/ JOIN + 子查询模拟时间范围,或者先生成日期序列再 LEFT JOIN。
- PostgreSQL 可用:
RANGE BETWEEN '6 days' PRECEDING AND CURRENT ROW(需ORDER BY date) - MySQL 必须改写为:
WHERE date >= t1.date - INTERVAL 6 DAY配合子查询或 CTE - BigQuery 支持
RANGE BETWEEN 6 PRECEDING AND CURRENT ROW仅限数值列,时间列仍需转换为天数差
处理缺失值和边界:SQL 没有 min_periods
Pandas 的 rolling(..., min_periods=2) 允许前两行返回 NaN 而非 NULL,SQL 窗口函数默认在行数不足时返回 NULL,且无法跳过或降级计算。
若需要类似行为,只能手动补足逻辑:
- 用
CASE WHEN COUNT(*) OVER (...) >= 2 THEN AVG(...) OVER (...) END控制输出 - 用
COALESCE(AVG(...) OVER (...), 0)替换NULL,但注意这会掩盖真实数据缺失 - 滚动计数可用
COUNT(*) OVER (...)单独提取,再参与条件判断
SELECT
date,
value,
CASE
WHEN COUNT(*) OVER (
ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) >= 2
THEN AVG(value) OVER (
ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
)
END AS rolling_mean_min2
FROM sales;真正麻烦的不是写法,而是排序键的稳定性 —— 一旦 ORDER BY 字段有重复或空值,同一查询多次执行可能返回不同结果,这点比 Pandas 的 sort_index 更难调试。


















