Laravel关联预加载中获取唯一行的问题:belongsToMany关联下如何获取含唯一产品的集合
你遇到的核心问题是中间表(pivot)的size字段导致同一个Product在同一个Collection下生成了多条关联记录——只要size不同,Eloquent就会把它们当作独立的关联结果返回,这就是重复数据出现的原因。你之前用的->unique('products.id')没生效,大概率是参数写错了,不过更高效的方式是从数据库查询层面就避免冗余数据,下面给你几个可行的方案:
方案1:数据库查询层面去重(推荐)
直接在with的关联查询闭包里添加distinct('products.id')或者groupBy('products.id'),让数据库直接返回去重后的Product记录:
$collections = Collection::with(['products' => function ($q) use ($sizes) { $q->whereIn('size', $sizes) ->distinct('products.id'); // 按Product ID去重 }])->whereHas('products', function ($q) use ($sizes) { $q->whereIn('size', $sizes); })->active()->get();
或者用groupBy实现:
$q->whereIn('size', $sizes) ->groupBy('products.id');
注意:如果使用groupBy,需要确保你的数据库SQL模式没有强制ONLY_FULL_GROUP_BY(Laravel默认可能开启,你可以在config/database.php的mysql配置里调整modes项,或者仅选择必要的字段进行分组)。
方案2:集合层面处理并保留size信息
如果你需要保留每个Product对应的所有符合条件的size值,而不是只返回单条关联记录,可以在查询完成后通过集合处理,把同一个Product的size合并成数组:
$collections = Collection::with(['products' => function ($q) use ($sizes) { $q->whereIn('size', $sizes); }])->whereHas('products', function ($q) use ($sizes) { $q->whereIn('size', $sizes); })->active()->get()->map(function ($collection) { // 按Product ID分组,合并对应的size $uniqueProducts = $collection->products->groupBy('id')->map(function ($productGroup) { $product = $productGroup->first(); // 收集该Product在当前Collection下的所有有效size $product->available_sizes = $productGroup->pluck('pivot.size')->unique()->values(); // 移除单个pivot信息(可选操作) unset($product->pivot); return $product; })->values(); // 更新collection的products关联为去重后的结果 $collection->setRelation('products', $uniqueProducts); return $collection; });
处理后每个Product只会出现一次,同时你能拿到它在当前Collection下所有符合筛选条件的size值。
补充:为什么之前的unique没生效?
你用的->unique('products.id')参数有误,集合里的每个元素是Product模型实例,它的ID属性是直接的id,不是products.id,正确用法应该是->unique('id')。不过这种方式是在查询完所有数据后再在内存中去重,效率不如直接在数据库层面处理。
内容的提问来源于stack exchange,提问作者Jamie

