Laravel Eloquent中如何在JOIN里使用Scope获取带有效价格的商品
问题描述
我希望通过ProductPrices模型中的Scope获取所有带有有效价格的商品,并同时展示商品及其有效价格。我的Product模型与价格是hasMany关联:
Product.php(模型)
public function prices () { return $this->hasMany(ProductsPrice::class); }
我的ProductsPrices模型中有一个isActive Scope,用于判断当前日期下价格是否有效:
ProductsPrices.php(模型)
public function scopeIsActive($query) { return $query->whereRaw(' timestampdiff(second, start_time, NOW()) >= 0') ->where(function ($query) { $query->whereRaw(' timestampdiff(second, end_time, NOW()) <= 0') ->orWhereNull('end_time'); }); }
我尝试了多种方法但均未成功,以下是报错的写法:
第一种写法
Route::get('/test', function (Request $request) { return Product::join('products_prices', 'products.id', 'products_prices.product_id') ->prices->isActive() ->where('products.is_active', true) ->get(); });
报错信息:
Property [prices] does not exist on the Eloquent builder instance.
第二种写法(test2)
Route::get('/test2', function (Request $request) { $prices = DB::table('products_prices')->select('id'); $product = Product::whereIn('id', $prices)->get(); return $product->prices()->isActive()->get(); });
报错信息:
Method Illuminate\Database\Eloquent\Collection::prices does not exist.
疑问:为何无法在Product模型上调用->prices()方法?是否应该放弃Eloquent改用Laravel查询构造器?
问题分析与解决方案
报错原因
- 第一种写法错误:
Product::join(...)返回的是Eloquent查询构建器实例,而prices是模型定义的关联方法,只能在单个Product模型实例上调用,不能直接在查询构建器上用属性访问的方式调用。 - 第二种写法错误:
Product::whereIn(...)->get()返回的是Eloquent集合(Collection),集合是多个模型实例的集合,不能直接调用prices()关联方法,必须遍历集合中的单个模型实例才能调用。
正确实现方式
不需要放弃Eloquent,有几种优雅的方式实现需求:
方式1:使用with关联预加载+筛选有效价格
通过with()方法预加载关联,并在关联中应用isActive Scope,同时筛选出至少有一个有效价格的商品:
Route::get('/test', function (Request $request) { return Product::where('is_active', true) ->whereHas('prices', function ($query) { // 筛选出存在有效价格的商品 $query->isActive(); }) ->with(['prices' => function ($query) { // 预加载时只加载有效价格 $query->isActive(); }]) ->get(); });
whereHas:确保商品至少存在一个符合条件的价格记录with:预加载关联时只获取有效价格,避免N+1查询问题
方式2:从价格模型反向查询
如果更关注价格数据,也可以从ProductsPrice模型出发,获取有效价格并关联对应的商品:
Route::get('/test', function (Request $request) { return ProductsPrice::isActive() ->whereHas('product', function ($query) { $query->where('is_active', true); }) ->with('product') ->get(); });
需要先在ProductsPrice模型中定义反向关联:
public function product() { return $this->belongsTo(Product::class); }
方式3:自定义查询构造器关联查询
如果需要自定义返回字段,也可以直接用查询构造器关联表查询:
Route::get('/test', function (Request $request) { return Product::where('is_active', true) ->join('products_prices', 'products.id', '=', 'products_prices.product_id') ->whereRaw('timestampdiff(second, products_prices.start_time, NOW()) >= 0') ->where(function ($query) { $query->whereRaw('timestampdiff(second, products_prices.end_time, NOW()) <= 0') ->orWhereNull('products_prices.end_time'); }) ->select('products.*', 'products_prices.*') // 按需选择需要返回的字段 ->get(); });
注意:如果一个商品有多个有效价格,这种方式会返回多条对应记录,可根据需求调整。
关键知识点
prices()是单个模型实例的方法:仅能在单个Product对象上调用,例如$product = Product::find(1); $product->prices()->isActive()->get();- 查询构建器(
Product::where(...))和集合(get()返回的结果)都不是单个模型实例,因此无法直接调用关联方法
内容的提问来源于stack exchange,提问作者St. Jan
相关产品推荐
相关产品推荐

