
本文介绍如何在 Laravel 中避免循环查询,通过单次 SQL 聚合查询将多个 PK 关联的视频表数据(如 video_202108、video_202208)统一按 from_user_id 分组,精准汇总 total_ticket 并获取时间基准。
本文介绍如何在 laravel 中避免循环查询,通过单次 sql 聚合查询将多个 pk 关联的视频表数据(如 `video_202108`、`video_202208`)统一按 `from_user_id` 分组,精准汇总 `total_ticket` 并获取时间基准。
在实际开发中,若需对多个动态分表(如按月命名的 video_202108、video_202208)中的记录按 from_user_id 进行跨表聚合统计,常见的错误做法是使用 for 循环逐个查询再手动合并数组——这不仅性能低下(N+1 查询),还极易引发逻辑混乱与内存溢出。
正确方案:统一查询 + 原生 SQL 聚合
首先明确关键前提:所有目标分表结构一致(含 total_ticket、from_user_id、create_time、pk_id 字段),且 $pks 是待匹配的 pk_id 列表。此时应放弃循环 + 多次 DB::select(),转而构建单条聚合 SQL:
$pks = $request->input('pkids');
// 安全过滤:仅保留合法正整数,防止 SQL 注入
$pks = array_filter(array_map('intval', $pks), function ($v) {
return $v > 0;
});
if (empty($pks)) {
return response()->json(['data' => []]);
}
// 动态拼接所有相关分表(示例:假设已知需查 video_202108 和 video_202208)
$tables = [
'video_202108',
'video_202208',
// 可根据 $pks 对应的 create_time 动态推导,见下方说明
];
// 构建 UNION ALL 查询(推荐方式,兼容跨月分表)
$unionParts = [];
foreach ($tables as $table) {
$unionParts[] = "SELECT total_ticket, from_user_id, create_time FROM {$table} WHERE pk_id IN (" . implode(',', $pks) . ")";
}
$unionSql = implode(' UNION ALL ', $unionParts);
$sql = "SELECT
SUM(total_ticket) AS sum_total_ticket,
MIN(create_time) AS min_create_time,
from_user_id
FROM ({$unionSql}) AS combined
GROUP BY from_user_id";
$results = DB::select($sql);✅ 优势说明:
- 单次数据库往返,消除 N 次网络开销与连接消耗;
- 利用数据库原生
GROUP BY和SUM(),计算高效且结果精确;MIN(create_time)可替换为MAX()或AVG(),满足不同业务时序需求。
⚠️ 重要注意事项:
-
表名动态推导:生产环境不应硬编码
$tables。建议先查询$pks对应的PK记录,提取create_time并生成对应Ym格式表名(如date('Ym', $pk->create_time)),再去重合并; -
SQL 注入防护:绝不可直接拼接用户输入的表名或字段名。本例中
$pks已通过intval+array_filter严格校验,但表名必须来自白名单或服务端可控逻辑; -
Laravel Query Builder 替代方案:若坚持使用 Eloquent/Query Builder,可借助
DB::raw()与union()方法实现相同效果,但原生 SQL 在复杂跨表聚合中更直观可控。
最终 $results 将返回标准对象集合,例如:
[
{"sum_total_ticket": 53, "min_create_time": "2022-08-20 00:00:00", "from_user_id": 1},
{"sum_total_ticket": 224, "min_create_time": "2021-08-20 00:00:00", "from_user_id": 18}
]这种设计兼顾性能、安全与可维护性,是处理分表聚合场景的 Laravel 最佳实践。


















