
本文详解如何在 laravel 8 中联查 sales 和 purchases 表,按商品(product)分组汇总数量,并支持筛选重复记录,涵盖正确 join 策略、聚合查询写法、避免笛卡尔积陷阱及实际应用注意事项。
在实际库存管理场景中,常需将采购入库量与销售出库量统一归集到同一商品维度下,展示每个商品的总采购量、总销售量及净库存(或仅总量)。但直接对 products 表同时 LEFT JOIN purchases 和 sales,再使用 GROUP BY products.id,极易因一对多关系引发笛卡尔积膨胀——例如某商品有 3 笔采购 + 4 笔销售,将生成 12 条中间记录,导致 SUM() 结果严重失真。
✅ 正确解法是:先分别聚合采购与销售,再通过产品主表关联合并。以下是推荐的三步实现方案:
✅ 步骤一:分别聚合采购与销售数据(子查询)
use Illuminate\Support\Facades\DB;
// 聚合采购数据:每个 product_id 对应总采购量
$purchasesAgg = DB::table('purchases')
->select('product_id', DB::raw('SUM(quantity) as total_purchased'))
->groupBy('product_id');
// 聚合销售数据:每个 product_id 对应总销售量
$salesAgg = DB::table('sales')
->select('product_id', DB::raw('SUM(quantity) as total_sold'))
->groupBy('product_id');✅ 步骤二:主表 LEFT JOIN 两个聚合结果
$stockSummary = DB::table('products')
->select(
'products.id',
'products.name as product_name',
'p.total_purchased',
's.total_sold',
DB::raw('COALESCE(p.total_purchased, 0) - COALESCE(s.total_sold, 0) as net_stock')
)
->leftJoinSub($purchasesAgg, 'p', 'p.product_id', '=', 'products.id')
->leftJoinSub($salesAgg, 's', 's.product_id', '=', 'products.id')
->get();? 输出示例:[ { "id": 1, "product_name": "Laptop", "total_purchased": 50, "total_sold": 23, "net_stock": 27 } ]
⚠️ 关键注意事项
- 勿用 INNER JOIN:若某商品仅有采购无销售(或反之),INNER JOIN 会丢失该记录;务必使用 LEFT JOIN 或 LEFT JOIN SUB 保证产品主数据完整性。
- 避免原始写法中的错误:题干中 ->join('purchases')→join('sales')→groupBy('product_id') 会导致数量被交叉放大,不可用于求和场景。
-
如需筛选“存在重复采购/销售记录的商品”(即同一商品有多条采购单或销售单),可对聚合后的结果加 havingRaw:
$duplicates = DB::table('products') ->select('products.id', 'products.name', 'p.count as purchase_count', 's.count as sale_count') ->leftJoinSub( DB::table('purchases')->select('product_id', DB::raw('COUNT(*) as count'))->groupBy('product_id'), 'p', 'p.product_id', '=', 'products.id' ) ->leftJoinSub( DB::table('sales')->select('product_id', DB::raw('COUNT(*) as count'))->groupBy('product_id'), 's', 's.product_id', '=', 'products.id' ) ->whereRaw('(p.count > 1 OR s.count > 1)') ->get(); - 性能优化建议:为 purchases.product_id 和 sales.product_id 字段添加数据库索引,显著提升 JOIN 与 GROUP BY 效率。
综上,Laravel 8 中实现跨表聚合的核心原则是:先降维(子查询聚合),再升维(主表关联)。该模式逻辑清晰、结果精准,适用于库存统计、财务对账、报表分析等强一致性要求场景。










