Laravel 9指定关联模型时将数组转为单个对象的实现方案
Laravel 9 关联查询返回单个对象及查询优化方案
一、让products返回单个对象的方案
方案1:在关联查询闭包中追加first()
直接在with的products闭包末尾添加->first(),Eloquent会返回单个AffiliateProduct模型而非集合,这样就能直接通过$affiliate->products->country访问:
$affiliate = Affiliate::where('aff_id', $request->input('AffId')) ->where('allow_submission', true) ->where('is_enabled', true) ->with([ 'products' => function ($query) use ($productSlug, $countrySlug) { $query->whereHas('product', function ($q) use ($productSlug) { $q->where('slug', $productSlug) ->orWhere('is_default', true) ->where('is_enabled', true); })->whereHas('country', function ($q) use ($countrySlug) { $q->where('slug', $countrySlug) ->orWhere('is_default', true) ->where('is_enabled', true); }) ->first(); // 追加此方法返回单个模型 }, 'products.product', 'products.country' ]) ->first();
注意:如果没有匹配的记录,$affiliate->products会是null,需要提前做好判空处理。
方案2:定义专属hasOne关联
如果这个查询场景是业务中的常用逻辑,建议在Affiliate模型中新增一个hasOne关联,替代原有的hasMany:
// Affiliate模型代码 public function currentProduct() { return $this->hasOne(AffiliateProduct::class); }
查询时直接使用新关联并添加筛选条件:
$affiliate = Affiliate::where('aff_id', $request->input('AffId')) ->where('allow_submission', true) ->where('is_enabled', true) ->with([ 'currentProduct' => function ($query) use ($productSlug, $countrySlug) { $query->whereHas('product', function ($q) use ($productSlug) { $q->where('slug', $productSlug) ->orWhere('is_default', true) ->where('is_enabled', true); })->whereHas('country', function ($q) use ($countrySlug) { $q->where('slug', $countrySlug) ->orWhere('is_default', true) ->where('is_enabled', true); }); }, 'currentProduct.product', 'currentProduct.country' ]) ->first();
之后通过$affiliate->currentProduct->country访问,语义更清晰。
方案3:使用模型访问器处理
在Affiliate模型中定义访问器,自动将products集合转为单个对象:
// Affiliate模型代码 public function getProductsAttribute($value) { return is_array($value) && count($value) > 0 ? $value[0] : $value; }
⚠️ 注意:此方法会全局修改products关联的返回形式,如果其他业务场景需要集合形式,不建议使用。
二、查询语句优化建议
1. 修复orWhere逻辑分组问题
原代码中orWhere与后续where未分组,会导致逻辑不符合预期。正确的写法应该用闭包分组,确保条件逻辑正确:
// 以product筛选为例 $q->where(function ($subQ) use ($productSlug) { $subQ->where('slug', $productSlug) ->orWhere(function ($innerQ) { $innerQ->where('is_default', true) ->where('is_enabled', true); }); });
这样能保证逻辑是:(slug匹配) 或者 (是默认且启用的产品),避免出现逻辑歧义。
2. 替换whereHas为关联查询,减少子查询开销
whereHas会生成子查询,改用join直接关联筛选可以提升查询性能:
$affiliate = Affiliate::where('aff_id', $request->input('AffId')) ->where('allow_submission', true) ->where('is_enabled', true) ->with([ 'products' => function ($query) use ($productSlug, $countrySlug) { $query->join('products', 'affiliate_products.product_id', '=', 'products.id') ->join('countries', 'affiliate_products.country_id', '=', 'countries.id') ->where(function ($q) use ($productSlug) { $q->where('products.slug', $productSlug) ->orWhere(function ($subQ) { $subQ->where('products.is_default', true) ->where('products.is_enabled', true); }); }) ->where(function ($q) use ($countrySlug) { $q->where('countries.slug', $countrySlug) ->orWhere(function ($subQ) { $subQ->where('countries.is_default', true) ->where('countries.is_enabled', true); }); }) ->select('affiliate_products.*') // 确保只返回关联表字段 ->first(); }, 'products.product', 'products.country' ]) ->first();
3. 添加数据库索引提升查询速度
为以下字段创建复合索引或单字段索引:
affiliates表:(aff_id, allow_submission, is_enabled)复合索引products表:(slug, is_default, is_enabled)复合索引countries表:(slug, is_default, is_enabled)复合索引affiliate_products表:affiliate_id、product_id、country_id单字段索引
4. 只查询需要的字段
避免查询全量字段,用select指定业务所需字段,减少数据传输开销:
$affiliate = Affiliate::select('id', 'aff_id', 'allow_submission', 'is_enabled') // 按需添加字段 ->where('aff_id', $request->input('AffId')) ->where('allow_submission', true) ->where('is_enabled', true) ->with([ 'products' => function ($query) use ($productSlug, $countrySlug) { $query->select('id', 'affiliate_id', 'product_id', 'country_id') // 按需添加 ->whereHas(...) ->first(); }, 'products.product' => function ($q) { $q->select('id', 'name', 'slug'); // 按需添加 }, 'products.country' => function ($q) { $q->select('id', 'name', 'currency'); // 按需添加 } ]) ->first();
5. 用when动态添加条件
如果productSlug或countrySlug可能为空,用when动态生成筛选条件,避免无效逻辑:
'products' => function ($query) use ($productSlug, $countrySlug) { $query->whereHas('product', function ($q) use ($productSlug) { $q->where('is_enabled', true) ->when($productSlug, function ($subQ) use ($productSlug) { $subQ->where('slug', $productSlug) ->orWhere('is_default', true); }, function ($subQ) { $subQ->where('is_default', true); }); })->whereHas('country', function ($q) use ($countrySlug) { $q->where('is_enabled', true) ->when($countrySlug, function ($subQ) use ($countrySlug) { $subQ->where('slug', $countrySlug) ->orWhere('is_default', true); }, function ($subQ) { $subQ->where('is_default', true); }); })->first(); }
内容的提问来源于stack exchange,提问作者Ryan H
相关产品推荐
相关产品推荐

