
本文介绍如何在 laravel 中基于原始 sql 实现按日期分组后的累计求和(cumulative sum),解决 eloquent 原生不支持窗口函数(如 sum() over())时的统计需求,并提供安全、可维护的替代方案。
本文介绍如何在 laravel 中基于原始 sql 实现按日期分组后的累计求和(cumulative sum),解决 eloquent 原生不支持窗口函数(如 sum() over())时的统计需求,并提供安全、可维护的替代方案。
在 Laravel 中,若需对聚合结果(如每日下载量)计算累计总和(cumulative sum),直接使用 Eloquent 构建复杂窗口逻辑较为困难——尤其在 MySQL 5.7 或更早版本中不支持标准 SUM(...) OVER (ORDER BY ...) 语法时。此时,推荐采用原生 SQL + 用户变量的方式高效完成累计计算。
以下是一个经过优化、安全可用的实现方案:
use Illuminate\Support\Facades\DB;
$table = Downloads::getTableName();
$downloads = DB::select(<<<SQL
SELECT
date,
total_count,
@running_total := @running_total + total_count AS cumm_total_count
FROM (
SELECT
DATE(created_at) AS date,
COUNT(*) AS total_count
FROM `$table`
GROUP BY DATE(created_at)
ORDER BY date ASC
) AS grouped
CROSS JOIN (SELECT @running_total := 0) AS init;
SQL
);✅ 关键说明:
- 使用 DATE(created_at) 替代 DATE_FORMAT(..., '%Y-%m-%d') 更简洁且索引友好;
- CROSS JOIN (SELECT @running_total := 0) 确保变量初始化,避免会话残留影响;
- 显式 ORDER BY date ASC 保证累计顺序正确(时间升序是累计逻辑前提);
- 外层查询直接复用内层聚合结果,结构清晰、性能可控。
⚠️ 注意事项:
- 此方案依赖 MySQL 用户变量,在高并发场景下不保证绝对线程安全(但单次查询内可靠);
- Laravel 8+ 及 MySQL 8.0+ 用户可优先考虑标准窗口函数写法(更健壮):
SELECT DATE(created_at) AS date, COUNT(*) AS total_count, SUM(COUNT(*)) OVER (ORDER BY DATE(created_at) ROWS UNBOUNDED PRECEDING) AS cumm_total_count FROM `downloads` GROUP BY DATE(created_at) ORDER BY date;对应 Laravel 调用:DB::select($sql) 即可。
? 总结:
当需要在数据库层高效完成累计统计时,应优先利用数据库原生能力(窗口函数 > 用户变量 > 应用层循环累加)。本方案兼顾兼容性与可读性,适用于大多数 Laravel + MySQL 项目;若技术栈允许,升级至 MySQL 8.0 并采用 SUM() OVER() 是更现代、更可维护的选择。


















