Laravel关联表自定义orderBy排序实现方案咨询
问题与解决方案
问题描述
环境:Laravel 9.52.9,PHP 8.2.4
需求:仅展示分组名为sponsor、featured、boost的文章列表,排序优先级为featured(最高)→boost→sponsor,同时按approved_at倒序排列。目前已通过join实现查询,希望改用模型关联的方式完成排序需求。
现有代码
模型关联代码
// Post Model public function group() { return $this->belongsTo(Group::class); } // Group Model public function posts() { return $this->hasMany(Post::class); }
现有Join查询代码
Post::query() ->select([ 'posts.id', 'posts.slug', 'posts.created_at', 'group_id', 'title', 'thumbnail', 'approved_at', 'groups.name as group_name' ]) ->selectRaw("LENGTH(content) - LENGTH(REPLACE(content, ' ', '')) + 1 AS total_content_word_count") ->without('permissions') ->join('groups', 'posts.group_id', '=', 'groups.id') ->whereIn('groups.name', ['sponsor', 'featured', 'boost']) ->where('ads', true) ->published() ->orderByRaw("FIELD(groups.name, 'featured','boost','sponsor') ASC") ->latest('approved_at') ->paginate(30);
解决方案
方案1:结合模型关联与Join(性能优先)
通过模型的getTable()方法避免硬编码表名,同时用whereHas复用关联逻辑过滤分组,既符合模型关联设计,又保证查询性能:
Post::query() ->select([ 'posts.id', 'posts.slug', 'posts.created_at', 'group_id', 'title', 'thumbnail', 'approved_at', 'groups.name as group_name' ]) ->selectRaw("LENGTH(content) - LENGTH(REPLACE(content, ' ', '')) + 1 AS total_content_word_count") ->without('permissions') // 利用模型获取表名,避免硬编码 ->join(Group::getTable(), 'posts.group_id', '=', Group::getTable() . '.id') // 用whereHas复用关联逻辑过滤分组 ->whereHas('group', function ($query) { $query->whereIn('name', ['sponsor', 'featured', 'boost']); }) ->where('ads', true) ->published() // 按分组优先级排序,再按approved_at倒序 ->orderByRaw("FIELD(" . Group::getTable() . ".name, 'featured','boost','sponsor') ASC") ->latest('approved_at') ->paginate(30);
方案2:子查询获取分组名称(无Join写法)
无需手动写Join,通过子查询从关联模型中获取分组名称,再基于该字段排序,代码更贴近模型关联的使用习惯:
Post::query() ->select([ 'posts.id', 'posts.slug', 'posts.created_at', 'group_id', 'title', 'thumbnail', 'approved_at', // 子查询关联分组表获取名称 Group::select('name') ->whereColumn('id', 'posts.group_id') ->limit(1) ->as('group_name') ]) ->selectRaw("LENGTH(content) - LENGTH(REPLACE(content, ' ', '')) + 1 AS total_content_word_count") ->without('permissions') ->whereHas('group', function ($query) { $query->whereIn('name', ['sponsor', 'featured', 'boost']); }) ->where('ads', true) ->published() // 基于子查询得到的group_name排序 ->orderByRaw("FIELD(group_name, 'featured','boost','sponsor') ASC") ->latest('approved_at') ->paginate(30);
说明
- 方案1性能更优,适合数据量较大的场景,Join操作能减少数据库查询次数;
- 方案2无需手动维护Join关系,代码更简洁,但子查询在数据量大时性能略低于Join。
内容的提问来源于stack exchange,提问作者Moxley
相关产品推荐
相关产品推荐

