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

Laravel使用whereHas实现多层关联非空筛选

需求场景

项目包含三类核心模型:countries(国家)、cities(城市)、shops(商铺),需要筛选并返回所有满足以下条件的城市:

  • 城市存在关联商铺
  • 关联商铺存在有效datas(数据)
  • 商铺的员工关联数据不为NULL

当前未做正确筛选的返回结果如下,其中id=13的城市无关联商铺,id=19的城市存在带员工的关联商铺:

[
    {
    "id": 1,
    "created_at": "2022-06-02T06:07:31.000000Z",
    "updated_at": "2022-06-02T06:07:31.000000Z",
    "name": "Niue",
    "cities": [
                {
                "id": 13,
                "created_at": "2022-06-02T06:07:44.000000Z",
                "updated_at": "2022-06-02T06:07:44.000000Z",
                "country_id": 1,
                "name": "North Michealbury",
                "shops": [] //empty
                },

                {
                "id": 19,
                "created_at": "2022-06-02T06:07:44.000000Z",
                "updated_at": "2022-06-02T06:07:44.000000Z",
                "country_id": 1,
                "name": "Millsmouth",
                "shops": [
                            {
                              "id": 1,
                              "created_at": "2022-06-02T06:14:37.000000Z",
                              "updated_at": "2022-06-02T06:14:37.000000Z",
                              "city_id": 19,
                              "name": "Roberts-Okuneva",
                              "employees_count": 146
                            }
                        ]
                }
            ]
    }
]
现有代码问题

当前控制器代码未实现正确筛选,目标是仅返回存在带员工商铺的城市,现有代码如下:

return Country::with(['cities','cities.shops'=>function($employee){
    $employee->withCount('employees')->where('city_id',19);
}])->whereHas('cities',function($city){
    $city->where('cities.id',19);
})->get();

现有代码存在两个核心问题:

  • 硬编码city_id=19,仅会查询指定id的城市,无法通用筛选所有符合条件的城市
  • 仅做了一层whereHas校验,没有嵌套校验商铺下的员工关联,也没有对预加载的关联数据做过滤,导致不符合条件的城市、商铺仍然会被返回
已定义模型关联

各模型的关联定义如下,关联关系本身配置正确:

  • Country模型
public function Cities(){
    return $this->hasMany(City::class);
}

注:Laravel关联方法建议使用小驼峰命名,此处Cities()首字母大写不符合框架约定,建议改为cities()避免关联匹配异常。

  • City模型
public function shops(){
    return $this->hasMany(Shop::class);
}
  • Shop模型
public function employees(){
    return $this->belongsToMany(User::class,'shop_employees','shop_id','employee_id');
}
正确实现代码

多层关联筛选需要同时做两层处理:一是用嵌套whereHas逐层校验父模型是否存在符合条件的关联,过滤掉不符合要求的国家、城市;二是给预加载的关联加同样的筛选条件,避免返回无效的关联数据(比如空商铺、无员工的商铺)。

具体实现代码如下:

return Country::with([
    // 预加载符合条件的城市
    'cities' => function($cityQuery) {
        $cityQuery
            // 筛选存在符合条件商铺的城市
            ->whereHas('shops', function($shopQuery) {
                $shopQuery
                    // 筛选存在关联员工、且数据有效的商铺
                    ->whereHas('employees')
                    ->whereNotNull('datas')
                    ->withCount('employees');
            })
            // 预加载城市下符合条件的商铺
            ->with([
                'shops' => function($shopQuery) {
                    $shopQuery
                        ->whereHas('employees')
                        ->whereNotNull('datas')
                        ->withCount('employees');
                }
            ]);
    }
])
// 最外层过滤:仅返回存在符合条件城市的国家
->whereHas('cities', function($cityQuery) {
    $cityQuery->whereHas('shops', function($shopQuery) {
        $shopQuery->whereHas('employees')->whereNotNull('datas');
    });
})
->get();

逻辑说明

  • 嵌套whereHas的校验逻辑为:国家下存在满足要求的城市 → 城市下存在满足要求的商铺 → 商铺存在绑定员工、且datas字段非空
  • 预加载阶段加相同筛选条件,是因为whereHas仅负责过滤父模型,不会自动过滤预加载的关联数据,不加这层筛选的话,不符合条件的城市、商铺仍然会出现在返回结果中
  • 如果不需要统计员工数量,可以去掉withCount('employees')部分

内容的提问来源于stack exchange,提问作者Ray Paras

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 16:31:06