Laravel 9查询嵌套关联时仍返回无tiers的PingtreeEntry记录问题
Laravel 9 关联查询问题:如何排除无tiers关联的PingtreeEntry记录
我在Laravel 9项目中开发,需要仅展示存在深层关联(即tiers)的记录。当前查询仍会返回tiers为空的pingtree_entries,想请教问题出在哪里?
我的顶层查询代码如下:
$pingtree = Pingtree::where('company_id', $company_id) ->where('id', $id) ->has('pingtree_entries.tiers') ->with('pingtree_entries.tiers') ->first();
预期逻辑是:获取指定company_id和id的Pingtree,仅包含关联的存在tiers的pingtree_entries。
Pingtree模型定义的关联:
/** * Get the pingtrees that the model has. */ public function pingtree_entries() { return $this->hasMany(PingtreeEntry::class); }
PingtreeEntry模型定义的关联:
/** * Get the buyer tier that the model has. */ public function tiers() { return $this->hasMany(BuyerTier::class, 'id', 'buyer_tier_id'); }
通过Postman返回的结果中,存在一个无tiers的PingtreeEntry记录(如下方JSON所示),我希望完全排除这类记录。我也尝试过使用whereHas,但问题依旧:
$pingtree = Pingtree::where('company_id', $company_id) ->where('id', $id) ->whereHas('pingtree_entries.tiers') ->with('pingtree_entries.tiers.buyer') ->first();
返回结果示例:
{ "model": { "id": 1, "user_id": 1, "company_id": 1, "pick_chance": 4, "name": "omnis iusto consequatur", "description": "Hic nihil suscipit error.", "is_enabled": false, "created_at": "2023-01-27T14:15:26.000000Z", "updated_at": "2023-01-27T14:15:26.000000Z", "deleted_at": null, "is_deleting": false, "pingtree_entries": [ { "id": 1, "user_id": 1, "company_id": 1, "buyer_id": 2, "buyer_tier_id": 4, "pingtree_id": 1, "pingtree_group_id": null, "processing_order": 1, "is_enabled": true, "created_at": "2023-01-27T14:15:26.000000Z", "updated_at": "2023-01-27T14:15:26.000000Z", "deleted_at": null, "tiers": [ { "id": 4, "user_id": 1, "company_id": 1, "buyer_id": 2, "country_id": 2, "product_id": 3, "name": "dignissimos voluptas et", "description": "Dolore tempora et maxime nam.", "processing_class": "et", "is_default": false, "is_enabled": false, "created_at": "2023-01-27T14:15:25.000000Z", "updated_at": "2023-01-27T14:15:25.000000Z", "deleted_at": null, "is_deleting": false } ] }, { "id": 3, "user_id": 1, "company_id": 1, "buyer_id": null, "buyer_tier_id": null, "pingtree_id": 1, "pingtree_group_id": 1, "processing_order": 1, "is_enabled": false, "created_at": "2023-01-27T14:15:26.000000Z", "updated_at": "2023-01-27T14:15:26.000000Z", "deleted_at": null, "tiers": [] } ] } }
问题原因及解决方案
1. 关联关系定义错误
从PingtreeEntry表的buyer_tier_id字段来看,每个PingtreeEntry应该对应单个BuyerTier,而不是多个。当前用hasMany是错误的,应该改为belongsTo:
// PingtreeEntry模型修正后的关联 public function tiers() { return $this->belongsTo(BuyerTier::class, 'buyer_tier_id'); }
(如果确实是一对多关系,外键应该在BuyerTier表中,比如pingtree_entry_id,但从现有数据结构判断,一对一更合理)
2. 预加载未做过滤
原来的has('pingtree_entries.tiers')或whereHas('pingtree_entries.tiers')只是筛选存在符合条件关联的Pingtree模型,不会过滤Pingtree下的pingtree_entries集合。要排除无tiers的pingtree_entries,需要在预加载时添加约束:
$pingtree = Pingtree::where('company_id', $company_id) ->where('id', $id) // 可选:确保Pingtree至少有一个符合条件的PingtreeEntry ->has('pingtree_entries.tiers') ->with([ 'pingtree_entries' => function ($query) { // 过滤出存在tiers关联的PingtreeEntry $query->has('tiers'); }, 'pingtree_entries.tiers.buyer' ]) ->first();
为什么之前的代码无效?
has('pingtree_entries.tiers')的作用是:只要当前Pingtree有至少一个pingtree_entry关联了tiers,就会选中这个Pingtree,但不会修改预加载的pingtree_entries集合,所以会返回所有关联的pingtree_entries,包括无tiers的。- 必须在
with的闭包内对pingtree_entries单独过滤,才能让预加载的集合只保留符合条件的记录。
内容的提问来源于stack exchange,提问作者Ryan H
相关产品推荐
相关产品推荐

