Laravel 10中如何通过Eloquent关联获取订单商品的最新库存?
用Eloquent关联实现订单商品对应店铺的最新库存(解决N+1问题)
问题场景
在Laravel 10项目中,存在三张数据表:
orders(订单表):包含id、shop_id字段orders_products(订单商品表):包含order_id、product_id字段remainings(库存表):包含date、shop_id、product_id、remaining字段
需要在OrdersProduct模型中定义Eloquent关联,获取对应product_id、对应店铺(通过orders表关联shop_id)的最新日期库存。原hasOneThrough写法会触发N+1查询问题,现需改用支持预加载的Eloquent关联实现。
原代码问题分析
原hasOneThrough关联中使用了whereProductId($this->product_id),该条件依赖当前模型实例的属性,导致Laravel无法将其合并到预加载查询中,只能在每个OrdersProduct实例单独调用时触发子查询,进而引发N+1问题。
解决方案:可预加载的Eloquent关联
方案一:调整hasOneThrough关联,使用字段级条件
在OrdersProduct模型中定义如下关联,通过whereColumn关联表字段而非实例属性,确保关联支持预加载:
use Illuminate\Database\Eloquent\Relations\HasOneThrough; public function latestRemaining(): HasOneThrough { return $this->hasOneThrough( Remaining::class, Order::class, 'id', // 中间表orders的外键,对应orders_products的order_id 'shop_id', // 目标表remainings的外键,对应orders的shop_id 'order_id', // 当前表orders_products的本地键,对应orders的id 'shop_id' // 中间表orders的本地键,对应remainings的shop_id )->whereColumn('remainings.product_id', 'orders_products.product_id') ->orderByDesc('date'); }
方案二:使用hasOne结合关联查询
如果觉得hasOneThrough的外键映射容易混淆,也可以用hasOne直接关联Remaining,并通过join关联orders表匹配店铺:
use Illuminate\Database\Eloquent\Relations\HasOne; public function latestRemaining(): HasOne { return $this->hasOne(Remaining::class) ->join('orders', 'orders.shop_id', '=', 'remainings.shop_id') ->whereColumn('orders.id', '=', 'orders_products.order_id') ->whereColumn('remainings.product_id', '=', 'orders_products.product_id') ->orderByDesc('remainings.date') ->limit(1); }
使用方式
通过with()预加载关联,彻底避免N+1查询:
$orderProducts = OrdersProduct::with('latestRemaining')->get(); foreach ($orderProducts as $orderProduct) { // 获取最新库存值,注意处理null情况 $latestRemaining = $orderProduct->latestRemaining?->remaining; }
执行时只会生成两条查询:一条获取所有订单商品,另一条批量获取对应商品和店铺的最新库存记录。
内容的提问来源于stack exchange,提问作者user618383
相关产品推荐
相关产品推荐

