Laravel API中如何对不同表的字段求和并按采购记录汇总总成本
看起来你现在的问题主要出在两个地方:一是多表左连接产生了笛卡尔积(一个采购对应多条车辆/引擎记录时,会生成重复的组合数据),导致后续计算总和时重复累加;二是你在集合处理上用错了方法,get()已经返回Laravel集合了,不需要再调用collect(),而且直接sum()会把所有数值字段加总,不是每条采购记录的成本。
我给你两种解决方案,优先推荐第一种,因为在数据库层面处理聚合效率更高:
方案一:数据库层面聚合计算(推荐)
我们先通过子查询分别计算每个采购记录对应的车辆总价和引擎总价,再将两者相加得到总成本,同时处理没有车辆/引擎记录的情况(用COALESCE把null转为0):
$purchases = DB::table('purchases') // 关联供应商表获取供应商名称 ->leftJoin('suppliers', 'suppliers.id', '=', 'purchases.supplier_id') // 子查询:计算每个采购的车辆采购总价 ->leftJoinSub( DB::table('vehicles') ->select('purchase_id', DB::raw('SUM(buying_price) as total_vehicle_cost')) ->groupBy('purchase_id'), 'vehicle_totals', 'vehicle_totals.purchase_id', '=', 'purchases.id' ) // 子查询:计算每个采购的引擎采购总价 ->leftJoinSub( DB::table('engines') ->select('purchase_id', DB::raw('SUM(price) as total_engine_cost')) ->groupBy('purchase_id'), 'engine_totals', 'engine_totals.purchase_id', '=', 'purchases.id' ) ->select( 'purchases.id', 'suppliers.name as supplier_name', 'purchases.purchase_date', // 单独展示车辆和引擎的总价(可选) DB::raw('COALESCE(total_vehicle_cost, 0) as total_vehicle_cost'), DB::raw('COALESCE(total_engine_cost, 0) as total_engine_cost'), // 计算最终总成本,null值用0代替 DB::raw('COALESCE(total_vehicle_cost, 0) + COALESCE(total_engine_cost, 0) as total_cost') ) ->get(); return $purchases;
这个方法的优势是:
- 数据库直接完成聚合,避免了PHP处理大量重复数据,性能更好
- 用
COALESCE确保即使某个采购没有车辆或引擎记录,也不会出现null + 数值得到null的情况 - 最终结果每条采购记录对应一行,清晰展示总成本和分项成本
方案二:Laravel集合层面处理(适合小数据量)
如果因为某些原因不想在数据库层处理,也可以先获取所有关联数据,再通过集合分组计算:
// 先获取所有关联数据(注意:这里会产生笛卡尔积重复数据) $rawRecords = DB::table('purchases') ->leftJoin('vehicles', 'vehicles.purchase_id', '=', 'purchases.id') ->leftJoin('engines', 'engines.purchase_id', '=', 'purchases.id') ->leftJoin('suppliers', 'suppliers.id', '=', 'purchases.supplier_id') ->select( 'purchases.id', 'suppliers.name', 'purchases.purchase_date', 'vehicles.buying_price', 'engines.price' ) ->get(); // 按采购ID分组,计算每个采购的总成本 $processedPurchases = $rawRecords->groupBy('id')->map(function ($group) { // 计算该采购下所有车辆的总价,过滤null值后求和 $totalVehicles = $group->pluck('buying_price')->filter()->sum() ?? 0; // 计算该采购下所有引擎的总价,过滤null值后求和 $totalEngines = $group->pluck('price')->filter()->sum() ?? 0; // 取分组内第一条记录的基础信息(因为分组后采购信息是重复的) $baseInfo = $group->first(); return [ 'id' => $baseInfo->id, 'supplier_name' => $baseInfo->name, 'purchase_date' => $baseInfo->purchase_date, 'total_vehicle_cost' => $totalVehicles, 'total_engine_cost' => $totalEngines, 'total_cost' => $totalVehicles + $totalEngines ]; }); return $processedPurchases;
为什么你的原代码会报错?
->collect('vehicles.buying_price','engines.price')是错误用法:get()已经返回了Laravel的Collection实例,不需要再调用collect(),而且collect()的参数不是字段名。- 直接
->sum()会把集合中所有数值类型的字段(buying_price和price)全部累加,得到一个全局总和,而不是每条采购记录的成本。 - 多表左连接产生的笛卡尔积会让同一条采购记录重复出现多次,导致计算结果严重失真。
内容的提问来源于stack exchange,提问作者Nour Hambarosh
相关产品推荐
相关产品推荐

