在 Laravel 模型中,whereHas
和 with
都是处理关联关系的重要方法,但它们的作用和使用场景有明显区别:
1. whereHas
方法
作用:用于筛选出满足关联条件的主模型数据,本质是对主模型进行过滤。
原理:
- 会生成 SQL 的
WHERE EXISTS
子句或INNER JOIN
,通过关联表的条件来限制主模型的查询结果。 - 不会预加载关联数据,只用于筛选主模型。
示例:
假设存在 Post
模型(文章)和 Comment
模型(评论),关联关系为 Post
有多个 Comment
:
// 筛选出有评论的文章(主模型是Post)
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', '%laravel%'); // 评论内容包含laravel
})->get();
上面代码会查询出所有包含“laravel”评论的文章,但不会获取这些评论的具体数据。
2. with
方法
作用:用于预加载关联数据,解决“N+1 查询问题”,提高查询效率。
原理:
- 会先查询主模型数据,再根据主模型的关联外键批量查询关联数据(通常是 2 条 SQL)。
- 不会筛选主模型,只是提前加载关联数据,避免后续访问关联时重复查询数据库。
示例:
// 预加载文章的所有评论(不会筛选文章,只是加载关联)
$posts = Post::with('comments')->get();
// 访问关联数据时不会再查询数据库
foreach ($posts as $post) {
echo $post->comments; // 已预加载,直接使用
}
核心区别
维度 | whereHas | with |
---|---|---|
作用 | 筛选主模型(基于关联条件) | 预加载关联数据(不筛选主模型) |
生成的 SQL | 带 WHERE EXISTS 或关联条件的查询 | 主模型查询 + 关联数据批量查询 |
结果影响 | 影响主模型的查询结果(数量可能减少) | 不影响主模型数量,仅附加关联数据 |
典型场景 | 需要根据关联数据过滤主模型时 | 需要高效访问关联数据时 |
结合使用
两者可以结合使用,实现“筛选主模型 + 预加载关联数据”:
// 筛选有符合条件评论的文章,并预加载这些评论
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', '%laravel%');
})->with(['comments' => function ($query) {
$query->where('content', 'like', '%laravel%'); // 只加载符合条件的评论
}])->get();
简单来说:whereHas
是“筛选主模型”,with
是“预加载关联”,两者常配合使用但职责不同。
那是否可以将whereHas去掉,只保留with
因为两者的使用场景不一样,因此能不能直接去掉 whereHas
只保留 with
,要根据使用场景实际决定,因为两者的作用完全不同,去掉后会导致查询结果发生变化。
具体分析:
-
保留
whereHas
+with
的效果:whereHas
会筛选出有“包含laravel的评论”的文章(主模型被过滤)。with
会为这些筛选后的文章,只加载“包含laravel的评论”(关联数据被过滤)。- 最终结果:只有符合条件的文章,且文章中只包含符合条件的评论。
-
去掉
whereHas
只保留with
的效果:// 只保留with的情况 $posts = Post::with(['comments' => function ($query) { $query->where('content', 'like', '%laravel%'); }])->get();
- 此时不会筛选文章(所有文章都会被查询出来,包括没有任何评论的文章)。
- 对于每篇文章,只加载符合条件的评论(如果文章没有符合条件的评论,
comments
会是一个空集合)。 - 最终结果:所有文章,且每篇文章中只包含符合条件的评论(可能为空)。
结论:
如果你的需求是 “只获取有符合条件评论的文章,并且只加载这些符合条件的评论” ,就必须保留 whereHas
。
如果去掉 whereHas
,会导致结果中包含所有文章(包括没有符合条件评论的文章),这与原代码的逻辑不符。
两者的配合是“先筛选主模型,再过滤关联数据”,缺一不可。