whereHas()适用于关联表多条件查询,需将主表条件写在外层、关联表条件写在闭包内;join更适合跨表联合筛选且只取主模型字段,须加表前缀和索引优化。

直接用 whereHas() 就能解决大多数关联多条件查询,但必须把主表条件和关联表条件分开写,混在一起就查不到数据。
whereHas 闭包里只能写关联表字段
比如要查「职业是 teacher 且 salary 不为空」的用户,userAttr 是 User 的 hasOne 关联:
-
whereHas('userAttr', function ($q) { $q->where('job', 'teacher')->whereNotNull('salary'); })✅ 正确:闭包中所有字段都属于user_attrs表 -
whereHas('userAttr', function ($q) { $q->where('users.name', 'like', '%John%'); })❌ 错误:users.name不在user_attrs表里,SQL 报错或空结果 - 主表条件(如
name、status)必须写在whereHas外层,不能塞进闭包
join 更适合跨表联合筛选且只取主模型字段
当目标是获取 Post 记录,同时要按 users.account_type 和 posts.type 一起过滤时,join 比 whereHas 更高效、更可控:
Post::join('users', 'posts.user_id', '=', 'users.id')→where('posts.type', 'politics')→where('users.account_type', 'active')→select('posts.*')→get()- 必须加
select('posts.*'),否则同名字段(如id)会被覆盖,模型实例构造失败 - 用
join而非leftJoin,因为业务要求“必须是活跃用户发布的”,内连接天然保证数据一致性 - 记得给
posts.user_id和users.id加索引,否则 JOIN 性能会断崖式下跌
动态条件别忘了显式判 null
用 when() 构建搜索时,PHP 的松散判断容易漏掉有效值:
-
->when(request('keyword'), fn ($q, $v) => $q->where('title', 'like', "%{$v}%"))✅ 安全:空字符串、null自动跳过 -
->when(request('status') !== null, fn ($q, $v) => $q->where('status', $v))✅ 状态字段允许值为'0'时必须这么写 - 不要用
empty()或isset()直接包request(),它们对'0'、'false'等字符串返回true
最常被忽略的是字段作用域——whereHas 的闭包只作用于关联表,join 后所有字段必须带表前缀,连 select 都不能偷懒写 *。











