Laravel多对多关联中优化pivot表最低价格及对应供应商查询
解决多对多关联中重复查询最低价格供应商的问题
核心问题分析
你现在的问题是每次调用cheapest_vendor属性都会触发两次查询,视图里多次调用就会产生大量重复查询——本质是没做实例级缓存,也没利用Laravel的预加载机制避免N+1查询。
方案1:给模型属性加实例级缓存
在Product模型里用私有变量缓存已经查询过的结果,同一产品实例多次调用属性不会重复查库,同时把原来的两次查询合并成一次:
class Product extends Model { // 用私有变量缓存查询结果 private ?object $cheapestVendor = null; public function vendors() { return $this->belongsToMany(Vendor::class) ->withPivot('price', 'off_price'); } public function getCheapestVendorAttribute() { // 如果已经缓存过,直接返回 if ($this->cheapestVendor) { return $this->cheapestVendor; } // 一次查询拿到最低价格的供应商,优先用off_price,没有就用price $this->cheapestVendor = $this->vendors() ->select('vendors.*', 'product_vendor.price', 'product_vendor.off_price') ->orderByRaw('LEAST(COALESCE(off_price, price), price) ASC') ->first(); return $this->cheapestVendor; } }
方案2:预加载所有产品的最低供应商(解决批量产品的N+1问题)
如果是一次性查询多个Product,直接在控制器里预加载每个产品的最低供应商数据,彻底避免后续重复查询:
// 控制器里查询Product时,用withSub预计算每个产品的最低供应商 $products = Product::query() ->withSub([ 'cheapestVendor' => function ($query) { $query->from('vendors') ->join('product_vendor', 'vendors.id', '=', 'product_vendor.vendor_id') ->whereColumn('product_vendor.product_id', 'products.id') ->select('vendors.*', 'product_vendor.price', 'product_vendor.off_price') ->orderByRaw('LEAST(COALESCE(off_price, price), price) ASC') ->limit(1); } ]) ->get();
之后在视图里直接调用$product->cheapestVendor就能拿到预加载好的数据,完全不用再查库。
方案3:自定义关联关系
也可以给Product模型定义一个专门的关联,用来直接获取最低价格的供应商,然后通过预加载使用:
class Product extends Model { public function vendors() { return $this->belongsToMany(Vendor::class) ->withPivot('price', 'off_price'); } // 自定义关联:获取当前产品最低价格的供应商 public function cheapestVendor() { return $this->belongsToMany(Vendor::class) ->withPivot('price', 'off_price') ->orderByRaw('LEAST(COALESCE(off_price, price), price) ASC') ->limit(1); } }
控制器里预加载:
$products = Product::with('cheapestVendor')->get();
视图里调用:$product->cheapestVendor->first()(因为是多对多关联,返回的是集合,取第一个就是目标供应商)
内容的提问来源于stack exchange,提问作者Mohsen
相关产品推荐
相关产品推荐

