thinkphp6关联查询需分清主表查询与关联筛选:with()闭包仅过滤关联数据,不影响分页总数;wherehas()则筛选主表结果集,分页数严格对应条件;深度关联排序或中间表条件应优先用join。

ThinkPHP6模型关联查询加条件,核心在于区分「查询主表」和「筛选关联数据」两个动作。直接在with()里传条件,或用whereHas(),效果完全不同——一个影响展示内容,一个影响主表结果集。
with() 闭包条件:只过滤关联数据,不改变分页总数
当你想查出用户列表,并只为每个用户加载「已发布的文章」,但又不想漏掉没发布文章的用户,就用with()配闭包:
User::with(['articles' => function ($q) { $q->where('status', 1); }])->paginate(10);- 它会先按用户分页,再对每页用户的
articles单独加WHERE status = 1查子表 - 注意:这种写法下,分页总数仍按用户数算,跟文章状态无关
- 不能写成
with(['articles' => ['status' => 1]]),那会报Relation not exists
whereHas():用关联字段筛选主表,总数与结果严格一致
如果你只想列出「至少有一篇已发布文章」的用户,且分页页码要准确反映这个结果集,必须用whereHas():
User::whereHas('articles', function ($q) { $q->where('status', 1); })->with('articles')->paginate(10);- 它会在主表
SELECT前,先用EXISTS或JOIN检查关联条件,确保每条记录都满足 - 分页总数是「符合条件的用户数」,不是全部用户数
- 若需同时查多个关联条件(如「有发布文章且属于管理员组」),可链式调用
whereHas('articles', ...)->whereHas('roles', ...)
深度关联或排序字段来自关联表时,优先用 JOIN
当你要按文章发布时间倒序分页,或筛选「最近7天有订单的用户」,with()和whereHas()都难以保证排序/分页准确性,此时应显式join:
User::alias('u') ->join('articles a', 'u.id = a.user_id AND a.status = 1') ->field('u.*, a.title as latest_title') ->group('u.id') ->order('a.created_at DESC') ->paginate(10);- 关键点:
AND a.status = 1写在ON里,确保COUNT和SELECT逻辑统一 - 用
group('u.id')防一对多导致行数膨胀;count('u.id')配合distinct才准 - 排序字段
a.created_at必须出现在join中,且该字段建议加索引
多对多中间表条件,别用 with 闭包,改用 whereHas 或手动 join
比如查「所属角色名含 admin 的用户」,中间表是sys_user_role:
- ❌ 错误:
with(['roles' => function ($q) { $q->where('name', 'like', '%admin%'); }])—— 这个闭包实际作用于role表,但中间表sys_user_role没被约束,结果不可靠 - ✅ 正确:
whereHas('roles', function ($q) { $q->where('name', 'like', '%admin%'); })—— 框架会自动把条件下推到中间表+角色表联合查询 - 更复杂场景(如按中间表字段
created_at筛选),建议手写join('sys_user_role sur', 'sur.uid = u.id')→where('sur.created_at', '>=', ...)
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











