如何使用Laravel Eloquent按30分钟区间分组获取单日数据记录
基于Laravel Eloquent的N分钟区间分组实现方案
完全可以通过Eloquent配合原生SQL片段实现全数据库侧运算,无需PHP侧循环计算时间区间,适配大表性能要求,具体实现如下:
核心思路
通过SQL原生时间函数,直接在查询层为每条记录计算所属的时间区间标签,后续分组/聚合逻辑全部在数据库侧完成,PHP仅负责接收查询结果。
MySQL环境实现示例
场景1:获取每个区间的聚合统计结果
如果只需要统计每个区间的col1/col2/col3的聚合值(如总和、列表、计数等),可直接用以下Eloquent查询,全程无PHP侧额外处理:
// 自定义区间分钟数 $interval = 30; $groupedData = \App\Models\YourModel::selectRaw(" -- 计算区间起始时间 DATE_FORMAT( DATE_ADD( DATE(created_at), INTERVAL FLOOR(TIME_TO_SEC(TIME(created_at)) / (60 * ?)) * 60 * ? SECOND ), '%H:%i:%s' AS interval_start, -- 计算区间结束时间 DATE_FORMAT( DATE_ADD( DATE(created_at), INTERVAL (FLOOR(TIME_TO_SEC(TIME(created_at)) / (60 * ?)) + 1) * 60 * ? - 1 SECOND ), '%H:%i:%s' AS interval_end, -- 自定义聚合字段,可根据需求调整 SUM(col1) AS col1_total, GROUP_CONCAT(col2 SEPARATOR ',') AS col2_list, AVG(col3) AS col3_avg, COUNT(*) AS record_count ") // 限定单日数据范围,可替换为指定日期 ->whereDate('created_at', '2024-01-01') ->groupBy('interval_start', 'interval_end') ->orderBy('interval_start') // 绑定参数,避免SQL注入 ->setBindings(array_fill(0, 4, $interval) + ['2024-01-01']) ->get();
场景2:获取每个区间的原始记录
如果需要保留每个区间下的完整原始记录,数据库侧无法直接返回嵌套结构,最优方案是先在SQL层为每条记录打区间标签,再用Laravel集合做一次O(n)复杂度的分组,性能远高于PHP侧计算时间区间的方案:
$interval = 30; $groupedRecords = \App\Models\YourModel::selectRaw(" *, DATE_FORMAT( DATE_ADD( DATE(created_at), INTERVAL FLOOR(TIME_TO_SEC(TIME(created_at)) / (60 * ?)) * 60 * ? SECOND ), '%H:%i:%s' AS interval_tag ") ->whereDate('created_at', '2024-01-01') ->orderBy('created_at') ->setBindings([$interval, $interval]) ->get() ->groupBy('interval_tag');
性能优化建议
- 给时间字段(如
created_at)添加索引,查询时严格限定日期范围,避免全表扫描。 - 百万级以上单日数据的场景,建议使用数据库临时表预先生成所有时间区间,左连业务表即可同时补全无数据的空区间,全程无需PHP侧补数。
- 若使用PostgreSQL等其他数据库,仅需替换SQL片段中的时间函数为对应数据库语法,Eloquent整体结构无需调整。
内容的提问来源于stack exchange,提问作者farshidpg
相关产品推荐
相关产品推荐

