
本文教你使用 laravel 的 query builder 正确实现多表关联 + 分组聚合,解决“查询每个产品名称及其入库总数量”这一常见需求,避免重复数据与 null 值干扰。
在实际开发中,我们常需基于一对多关系(如 products 表与 ins 入库记录表)统计聚合值。你当前的代码执行了 JOIN,但未进行分组(GROUP BY),也未显式调用聚合函数(如 SUM())配合分组——这导致数据库返回每条匹配记录的原始行(例如 Aerobat 出现 7 次),而非每个产品的唯一汇总结果。
✅ 正确做法需同时满足三点:
- 使用 GROUP BY 按产品维度分组(此处为 Products.name);
- 对数值字段使用聚合函数(如 SUM(ins.amount)),并处理可能的 NULL;
- 在 SELECT 中明确声明分组字段与聚合字段,避免 SQL 错误(尤其在严格模式下)。
以下是优化后的完整示例(含健壮性增强):
public function index()
{
$productsWithTotal = DB::table('products')
->leftJoin('ins', 'products.id', '=', 'ins.products_id') // 改用 LEFT JOIN 确保无入库记录的产品也不丢失
->select(
'products.name',
DB::raw('COALESCE(SUM(ins.amount), 0) as total_amount') // SUM 处理 NULL → 0
)
->groupBy('products.id', 'products.name') // 推荐按主键 + 业务字段双重分组,避免歧义
->get();
return response()->json($productsWithTotal);
}
? 关键说明与注意事项:
- 必须加 GROUP BY:仅写 SUM() 而不 GROUP BY 会导致 MySQL 报错(sql_mode=only_full_group_by 启用时)。Laravel 不自动添加,需手动指定。
- 推荐 LEFT JOIN:若某产品暂无入库记录(ins 表无对应行),INNER JOIN 会将其过滤掉;LEFT JOIN 则保留该产品,并通过 COALESCE(SUM(…), 0) 将其 total_amount 设为 0。
- 分组字段要完整:GROUP BY products.id, products.name 比仅 GROUP BY products.name 更安全(防止同名不同产品被错误合并)。
- 避免 ->get() 后再 PHP 聚合:数据库层面聚合(SUM + GROUP BY)性能远高于 PHP 循环累加,尤其在大数据量时。
最终输出将符合预期格式:
[
{"name": "Aerobat", "total_amount": 70},
{"name": "god of war", "total_amount": 30},
{"name": "Rs537", "total_amount": 0}
]
掌握此模式后,你可轻松扩展至其他聚合场景(如 COUNT(*) 统计入库次数、AVG() 计算平均单次入库量等)。
