Laravel9 按关联模型日期排序查询并获取关联数据方法
问题原因
你的两种写法不符合需求的核心原因如下:
- 第一种
whereHas写法仅做关联存在性校验:只会筛选出「至少有1篇已发布Post」的用户,完全不会根据Post的更新时间对用户排序,最终取4个用户时默认按用户表主键排序,和「拥有最新Post的用户」筛选逻辑完全无关。 - 第二种join写法未做用户去重:连表后每一行结果对应一篇Post,按Post更新时间倒序后,若最新的4篇Post都属于同一个用户,取前4条自然全是同一个用户的重复记录;另外你在
with闭包中直接调用get()是错误写法,会提前触发SQL执行,破坏预加载逻辑。
正确实现代码
实现逻辑拆为两步:先从已发布Post中按更新时间倒序,取出不重复的前4个用户ID;再查询对应ID的用户,同时预加载每个用户的2篇最新已发布Post即可。
// 1. 取出拥有最新已发布Post的4个不重复用户ID,顺序和Post更新时间倒序一致 $targetUserIds = Post::query() ->where('state', PostStateEnum::PUBLISHED) ->orderByDesc('updated_at') ->distinct() ->pluck('user_id') ->take(4); // 2. 查询目标用户,同时预加载每个用户的2篇最新已发布Post $users = User::query() ->whereIn('id', $targetUserIds) // 保证用户顺序和Post最新时间排序一致 ->orderByRaw('FIELD(id, ' . $targetUserIds->join(',') . ')') ->with([ 'posts' => function ($query) { $query->where('state', PostStateEnum::PUBLISHED) ->orderByDesc('updated_at'); } ]) ->get() // 每个用户只保留2篇最新Post,兼容所有Laravel版本,无需额外扩展 ->each(fn ($user) => $user->setRelation('posts', $user->posts->take(2)));
优化说明
- 上述写法没有在预加载闭包中直接写
take(2),是因为原生Eloquent预加载时的limit是针对整个关联查询的,会导致所有用户总共只查出2条Post,而非每个用户2条。用查询后集合截取的方式兼容所有Laravel版本,不需要安装额外扩展包。 - 如果你的项目使用Laravel 10+,或者安装了
staudenmeir/eloquent-eager-limit扩展包,可以直接在User模型中定义专门的关联来实现预加载阶段的limit,减少内存占用:
// User模型内新增关联 public function latestPublishedPosts() { return $this->hasMany(Post::class) ->where('state', PostStateEnum::PUBLISHED) ->orderByDesc('updated_at') ->limit(2); }
查询时直接加载该关联即可:
$users = User::query() ->whereIn('id', $targetUserIds) ->orderByRaw('FIELD(id, ' . $targetUserIds->join(',') . ')') ->with('latestPublishedPosts') ->get();
内容的提问来源于stack exchange,提问作者Andre
相关产品推荐
相关产品推荐

