You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 23:20:42