Laravel 5.7带关联的Eloquent查询转Query Builder求助
用Query Builder实现Eloquent关联查询的等价方案
核心思路
Eloquent的with()是预加载机制,会执行多条查询;Query Builder要实现等价效果,需通过左连接+聚合函数打包关联数据,或用子查询获取单条关联,最后将结果映射成和Eloquent一致的结构。
分步实现(结合你提到的关联场景)
假设你的模型关联定义如下(匹配你给出的Post/Media/PlanDetail模型):
// PostModel.php public function media() { return $this->hasMany(MediaModel::class, 'post_id'); } public function find_thumbnail_media() { return $this->hasOne(MediaModel::class, 'post_id')->where('is_thumbnail', 1); } public function favorites() { return $this->hasMany(PlanDetailModel::class, 'post_id', 'id'); }
1. 基础连接与查询构造
先通过左连接关联所有需要的表,注意一对一关联(缩略图)的条件:
$query = DB::table('posts') // 左连接media表(一对多) ->leftJoin('media', 'posts.id', '=', 'media.post_id') // 左连接缩略图media(带筛选条件的一对一) ->leftJoin('media as thumbnail_media', function ($join) { $join->on('posts.id', '=', 'thumbnail_media.post_id') ->where('thumbnail_media.is_thumbnail', 1); }) // 左连接favorites表(一对多) ->leftJoin('plan_details as favorites', 'posts.id', '=', 'favorites.post_id');
2. 聚合打包关联数据
直接连接会导致主表数据重复,需按主表主键分组,用JSON聚合函数把关联数据打包成数组:
MySQL版本(5.7+)
$posts = $query ->select([ 'posts.*', // 打包media数据为JSON数组 DB::raw('JSON_ARRAYAGG(JSON_OBJECT( "id", media.id, "url", media.url, "is_thumbnail", media.is_thumbnail )) as media'), // 缩略图为一对一,直接取字段并处理空值 DB::raw('IFNULL(thumbnail_media.id, "") as thumbnail_id'), DB::raw('IFNULL(thumbnail_media.url, "") as thumbnail_url'), // 打包favorites数据为JSON数组 DB::raw('JSON_ARRAYAGG(JSON_OBJECT( "id", favorites.id, "user_id", favorites.user_id, "created_at", favorites.created_at )) as favorites') ]) ->groupBy('posts.id') ->get();
PostgreSQL版本
将JSON_ARRAYAGG替换为json_agg,JSON_OBJECT替换为json_build_object即可。
3. 映射结果结构(对齐Eloquent格式)
Query Builder返回的是StdClass对象,需将字段转换为Eloquent的关联结构:
$formattedPosts = $posts->map(function ($post) { // 处理media:解析JSON并过滤空数据 $post->media = json_decode($post->media, true) ?: []; $post->media = array_filter($post->media, fn($item) => !empty($item['id'])); // 处理find_thumbnail_media:组装成关联对象 $post->find_thumbnail_media = !empty($post->thumbnail_id) ? (object)[ 'id' => $post->thumbnail_id, 'url' => $post->thumbnail_url, 'is_thumbnail' => 1 ] : null; // 处理favorites:解析JSON并过滤空数据 $post->favorites = json_decode($post->favorites, true) ?: []; $post->favorites = array_filter($post->favorites, fn($item) => !empty($item['id'])); // 移除临时字段 unset($post->thumbnail_id, $post->thumbnail_url); return $post; });
性能优化备选方案
如果关联表数据量较大,聚合函数可能产生性能瓶颈,可采用分查询手动匹配(模拟Eloquent预加载逻辑):
// 第一步:查询主表+子查询获取缩略图 $posts = DB::table('posts') ->select([ 'posts.*', '(SELECT url FROM media WHERE post_id = posts.id AND is_thumbnail = 1 LIMIT 1) as thumbnail_url' ]) ->get(); // 第二步:批量查询关联数据 $postIds = $posts->pluck('id')->toArray(); $mediaList = DB::table('media')->whereIn('post_id', $postIds)->get()->groupBy('post_id'); $favoritesList = DB::table('plan_details')->whereIn('post_id', $postIds)->get()->groupBy('post_id'); // 第三步:手动匹配关联数据 $posts->each(function ($post) use ($mediaList, $favoritesList) { $post->media = $mediaList->get($post->id, collect()); $post->find_thumbnail_media = $post->thumbnail_url ? (object)[ 'url' => $post->thumbnail_url, 'is_thumbnail' => 1 ] : null; $post->favorites = $favoritesList->get($post->id, collect()); unset($post->thumbnail_url); });
内容的提问来源于stack exchange,提问作者Ngọc Đồ Đinh
相关产品推荐
相关产品推荐

