窗口函数适用于保留原始行并按逻辑分组计算的场景,如在门店明细旁附加城市总销量;若需压缩成新行(如仅城市月度汇总),应使用GROUP BY。

窗口函数不是万能的分段聚合工具——它只在“保留原始行 + 按逻辑分组计算”场景下真正高效。如果你需要把数据彻底压缩成新行(比如只留每个城市的月度汇总),GROUP BY仍是首选;窗口函数适合的是“既要明细,又要分段统计”的中间态需求。
分段聚合 ≠ GROUP BY,关键看是否要保留原始行
很多人一看到“按城市算总销量”,第一反应写 GROUP BY city,结果发现丢了门店名、单日销售额这些字段。这时候不是 SQL 写错了,而是需求理解偏了:你要的是“每个门店旁边附上它所在城市的总销量”,不是“只要城市销量”。窗口函数的 PARTITION BY city 正是干这个的。
-
PARTITION BY不合并行,只是划出计算边界;GROUP BY会强制折叠,丢失非聚合字段 - 如果后续还要用
store_name或sale_date做过滤或排序,用GROUP BY就得套子查询或JOIN回原表,多一层嵌套就多一分出错可能 - PostgreSQL 和 MySQL 8.0+ 都支持,但 SQLite 目前不支持窗口函数,别在轻量环境里默认假设可用
ROWS BETWEEN 是控制“分段”粒度的核心开关
默认的 OVER(PARTITION BY ... ORDER BY ...) 窗口范围是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,也就是从分区第一行累加到当前行。这在算累计值时没问题,但做“最近7天滚动总和”或“本季度内平均”就必须显式写 ROWS BETWEEN。
- 想算每个门店过去3天(含当天)的销售额总和:
SUM(sales) OVER(PARTITION BY store_name ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) - 注意:用
ROWS是按物理行数滑动,RANGE是按排序值(如日期)滑动;日期有缺失时,RANGE 3 PRECEDING更可靠,但部分数据库(如 MySQL)对RANGE的日期支持有限 - MySQL 8.0 对
ROWS BETWEEN N PRECEDING支持良好,但不支持RANGE BETWEEN INTERVAL 7 DAY PRECEDING这类语法,得靠变量或自连接补位
多个分段维度嵌套时,PARTITION BY 顺序影响结果可读性
业务常提“每个城市每季度的销量中位数”,看起来要两层分组。但窗口函数不支持嵌套 PARTITION BY,只能靠组合字段或预处理。
- 直接写
PARTITION BY city, QUARTER(sale_date)可以,但中位数要用PERCENTILE_CONT(0.5)(PostgreSQL)或近似函数(MySQL 8.0 无原生中位数,得用ROW_NUMBER做中间计算) - 如果字段组合复杂(比如 “华东大区-上海-2026-Q1”),建议先用子查询生成分组键,再在外部用窗口函数,避免
OVER子句过长难维护 - 别在
PARTITION BY里用函数表达式(如PARTITION BY YEAR(sale_date), MONTH(sale_date)),MySQL 会拒绝;应提前算好列,或用生成列(Generated Column)
性能陷阱:ORDER BY 在窗口函数里不是可选的“锦上添花”
很多初学者以为 ORDER BY 只影响排名类函数,其实只要用了 ROWS BETWEEN 或累计类聚合(SUM、AVG),ORDER BY 就是必需的。漏写会导致结果不可预测,甚至报错。
- MySQL 8.0 报错示例:
SELECT city, sales, SUM(sales) OVER(PARTITION BY city ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM store_sales;→ 缺少ORDER BY,直接报Window 'w' lacks an ORDER BY clause - 即使你只想要“城市总销量”这种静态聚合,也得写
ORDER BY sales(哪怕逻辑上无关),否则窗口范围默认行为不明确 - 真实业务中,
ORDER BY字段最好有索引,否则大表上窗口计算会全表扫描,比等价的GROUP BY还慢
最易被忽略的一点:窗口函数的执行时机在 WHERE 之后、SELECT 列计算之中,但早于 HAVING 和 GROUP BY(如果存在)。这意味着你不能用窗口函数结果做 WHERE 过滤——得包一层子查询或用 CTE,否则会报错或逻辑错乱。










