Laravel实现hasMany关联数据拆分为独立记录并支持分页查询
Laravel 实现方案
核心思路
使用 UNION 合并两类数据集,完全在数据库层面完成查询逻辑,天然支持原生分页,性能远高于 ORM 读取后手动拼装:
- 第一类是所有 Post 本身的记录,评论相关字段统一设为 null
- 第二类是所有关联了 Post 的 Comment 记录,同步携带对应 Post 的公共字段
具体代码实现
首先引入依赖门面:
use Illuminate\Support\Facades\DB;
编写查询逻辑:
// 子查询1:所有Post本身的记录,评论字段置空 $postRecords = Post::select( 'post_id', DB::raw('NULL as comment_id'), 'name', 'owner_name', DB::raw('NULL as comment') ); // 子查询2:关联了Post的评论记录,携带Post公共字段 $commentRecords = Comment::join('posts', 'comments.post_id', '=', 'posts.post_id') ->select( 'posts.post_id', 'comments.comment_id', 'posts.name', 'posts.owner_name', 'comments.comment' ); // 合并两个查询并排序,保证同一个Post的自身记录排在对应评论前 $mergedQuery = $postRecords->unionAll($commentRecords) ->orderBy('post_id') ->orderByRaw('ISNULL(comment_id) DESC') ->orderBy('comment_id'); // 直接使用Laravel原生分页,每页10条可自定义 $perPage = 10; $paginatedResult = $mergedQuery->paginate($perPage);
如果需要将查询结果转为 Post 模型实例,可补充调用 hydrate 方法:
$modelList = Post::hydrate($paginatedResult->items());
注意事项
- 两个子查询的 select 字段数量、顺序、字段类型必须完全一致,否则 UNION 会报错
- 优先使用
unionAll代替union,两类数据不存在重复,unionAll无需去重性能更高 - 如果需要添加过滤条件(如筛选指定 owner 的内容),需要在两个子查询中同时添加相同的过滤规则,避免结果遗漏
- 若遇到 UNION 分页自动计算总数错误的情况,可手动传入总条数:
$total = $postRecords->count() + $commentRecords->count(); $paginatedResult = $mergedQuery->paginate($perPage, ['*'], 'page', request('page', 1), $total);
内容的提问来源于stack exchange,提问作者madusanka
相关产品推荐
相关产品推荐

