本文详解如何利用 Lara vel 的查询构建器和 Carbon 库,从 exercises 表的日期字段中提取月份编号(如 1–12),实现按月聚合统计用户训练天数,并找出训练最频繁的月份。
在 Lara vel 开发中,按时间维度对数据进行聚合分析可以说是家常便饭。比如,你已经会用 whereMonth() 和 whereYear() 来获取当前月的训练天数,但问题是:如何从历史数据中找出训练天数最多的那个月份?关键是——得从存储的日期字段里动态提取月份信息并做分组统计,而不是依赖额外的辅助表。
这里有个省心又高效的做法:直接利用 Lara vel 查询构建器的 groupByRaw() 配合 MySQL 原生 MONTH() 函数。你完全不必手动解析日期字符串,数据库自己就能搞定。推荐写法长这样:
use Illuminate\Support\Facades\DB;
$topMonth = DB::table('exercises')
->selectRaw('MONTH(day) as month_num, COUNT(DISTINCT DATE(day)) as workout_days')
->where('user_id', auth()->id())
->groupByRaw('MONTH(day)')
->orderByDesc('workout_days')
->limit(1)
->first();
if ($topMonth) {
echo "训练最多的月份是:{$topMonth->month_num}月(共{$topMonth->workout_days}天)";
}
这段代码有几个隐藏的妙处:
MONTH(day)直接在数据库层面执行,效率高,而且跨时区场景下也足够安全;COUNT(DISTINCT DATE(day))能确保同一天内的多次训练只算一次——这个细节才真正贴合“训练日”的概念;groupByRaw('MONTH(day)')按月份分组,避免了在 PHP 层写循环来手动聚合;- 最终返回的月份编号(比如 6 代表六月)可以直接塞给前端展示,或者传给后续业务逻辑使用。
⚠️ 有几个注意事项值得提一下:
- 如果项目要兼容 PostgreSQL 或 SQLite,记得把
MONTH(day)换成EXTRACT(MONTH FROM day)或strftime('%m', day); - 别拿 Carbon 的
parse来干这个活——虽然Carbon::parse($date)->month对单条数据挺好用,但一旦用到聚合场景,它会一股脑儿把所有数据拉到 PHP 内存里,性能直接跳水; - 确保
day字段是 DATE 或 DATETIME 类型,并且在(user_id, day)上建好联合索引——这个组合索引能瞬间提升查询效率。
说到底,提取月份的核心原则就是:让数据库来干聚合的活,别在应用层一条条手动解析。用 groupByRaw 配合 MONTH(),不仅能干净利落地搞定“月度训练热度分析”,还能很轻松地扩展成年度对比、趋势图表等等高级功能。
