必须显式用partition by year(order_date)切分年度并指定rows unbounded preceding帧子句,否则跨年累加失真;先group by年+季度聚合,再对聚合结果开窗累加。

用 SUM() OVER() 按年分区计算季度累加销售额
直接结论:必须显式用 PARTITION BY YEAR(order_date) 切分年度,否则跨年累加会失真。MySQL 8.0+、PostgreSQL、SQL Server 都支持,但 SQLite 不行。
常见错误是只写 ORDER BY quarter 却漏掉 PARTITION BY,结果 2023 Q4 的累加值里混进了 2022 全年数据——因为窗口默认作用于整个结果集。
- 先确保日期字段能提取年份:
YEAR(order_date)(MySQL)、EXTRACT(YEAR FROM order_date)(PostgreSQL)或DATEPART(YEAR, order_date)(SQL Server) - 季度建议统一用数值表示(1–4),避免字符串排序错乱;可用
QUARTER(order_date)或CEIL(MONTH(order_date)/3.0) - 排序必须包含年份+季度组合,否则同季度跨年数据会乱序:
ORDER BY YEAR(order_date), QUARTER(order_date)
SELECT
YEAR(order_date) AS y,
QUARTER(order_date) AS q,
SUM(amount) AS quarterly_sales,
SUM(SUM(amount)) OVER (
PARTITION BY YEAR(order_date)
ORDER BY YEAR(order_date), QUARTER(order_date)
ROWS UNBOUNDED PRECEDING
) AS cumsum_q_sales
FROM orders
GROUP BY y, q
ORDER BY y, q;
为什么 ROWS UNBOUNDED PRECEDING 不能省
省略帧子句(frame clause)时,某些数据库(如 PostgreSQL)默认为 RANGE UNBOUNDED PRECEDING,而 RANGE 对相同排序值会“合并累加”——如果两年的 Q1 排序值都是 (2022,1),它可能把它们当同一组处理,导致跨年干扰。
-
ROWS UNBOUNDED PRECEDING严格按物理行序累加,安全可靠 - MySQL 8.0 默认就是
ROWS模式,但显式写出更可移植 - 别依赖数据库默认行为,尤其在跨库迁移或升级时
GROUP BY 和窗口函数的执行顺序陷阱
窗口函数在 GROUP BY 之后执行,所以 SUM(amount) 要先聚合出季度销售额,再套一层窗口函数累加——不能在窗口里直接对原始明细行做 SUM() OVER(...),否则结果是每个明细行的累加,不是季度维度的。
- 错误写法:
SUM(amount) OVER (PARTITION BY YEAR(order_date) ORDER BY ...)→ 得到每笔订单的累加,非季度粒度 - 正确路径:先
GROUP BY 年, 季度得到quarterly_sales,再对其应用窗口函数 - 若需保留明细行+季度累加,得用子查询或 CTE 先聚合,再 JOIN 回去
季度字段排序不稳时的兼容处理
如果业务中季度用字符串存储(如 'Q1'、'Q2'),直接 ORDER BY quarter 可能按字典序排成 Q1、Q10、Q2 —— 实际并不存在 Q10,但字符串比较会出错。
- 临时转换:在
ORDER BY中用CASE WHEN quarter = 'Q1' THEN 1 ... END - 更稳妥:建计算列或视图,把季度标准化为整数,避免每次查询都重复逻辑
- 注意 NULL 值:季度字段为空时,
ORDER BY默认把它排最前或最后(依数据库而定),可能打乱走势,建议提前过滤或COALESCE(quarter, 0)










