用窗口函数可单条SELECT实现每日总计与累计求和:先GROUP BY date得daily_total,再用SUM(daily_total) OVER (ORDER BY date)计算running_total;旧版MySQL需变量模拟并强制ORDER BY;补全日期序列防跳变;索引和条件过滤对性能至关重要。

用窗口函数实现每日总计 + 累计求和(MySQL 8.0+ / PostgreSQL / SQL Server)
直接在单条 SELECT 中同时拿到每日原始汇总值和截至当日的累计值,核心靠窗口函数——不是子查询也不是自连接,更稳定、易读、性能好。
典型错误是试图用 SUM() 聚合后直接套 SUM() OVER (),结果报错或逻辑错:聚合函数和窗口函数不能混在同一层无分组上下文中使用。
- 先用
GROUP BY date算出每日总计(比如每日销售额) - 再在外层或同一层用窗口函数基于排序后的日期做累计,如
SUM(daily_total) OVER (ORDER BY date) - 注意:
ORDER BY必须明确,否则累计顺序不确定;若日期有重复,建议加二级排序(如ORDER BY date, id)
SELECT date, SUM(amount) AS daily_total, SUM(SUM(amount)) OVER (ORDER BY date) AS running_total FROM sales GROUP BY date ORDER BY date;
兼容旧版 MySQL(5.7 及以下)的替代写法
没有窗口函数时,不能用 OVER,但也不能简单用自连接暴力关联——数据量稍大就指数级变慢。稳妥做法是变量模拟累计,但必须严格保证执行顺序。
- 必须用
ORDER BY date强制排序,否则用户变量行为不可靠 - 初始化变量要在同一语句中完成,推荐用
(SELECT @rt := 0)子查询方式 - 避免在
WHERE或HAVING中引用计算出的累计列,MySQL 5.7 不允许
SELECT date, daily_total, @rt := @rt + daily_total AS running_total FROM ( SELECT date, SUM(amount) AS daily_total FROM sales GROUP BY date ORDER BY date ) t CROSS JOIN (SELECT @rt := 0) init;
处理空日期(补全连续日期序列)
如果原始表里某天没数据,GROUP BY date 就不会出现那行,导致累计值“跳变”。要真正按日历连续展示,得先生成完整日期序列再 LEFT JOIN。
- PostgreSQL 可用
GENERATE_SERIES() - MySQL 8.0+ 建议用递归 CTE 构造日期表(注意设置
cte_max_recursion_depth) - 补完日期后,
COALESCE(SUM(s.amount), 0)防止NULL中断累计逻辑
漏掉这步,看起来累计值对了,但横轴缺天、业务同学一查就懵。
性能与索引关键点
累计计算本身不重,但底层扫描和排序容易成瓶颈。尤其当 sales 表没索引或数据倾斜时:
- 确保
date字段有索引(单列或作为联合索引最左前缀) - 如果只查最近 30 天,务必加
WHERE date >= CURDATE() - INTERVAL 30 DAY,别依赖外面过滤 - 避免在
ORDER BY的窗口函数中使用表达式(如DATE(created_at)),会无法走索引
累计值看着只是加法,但背后排序和范围扫描的代价常被低估——特别是补全日期 + 窗口函数嵌套时,explain 里多一个 Derived 表就可能慢十倍。

















