Laravel:如何基于自定义模型方法排序后再对记录分页?
问题描述
我有products和variants两张表,二者为一对多关系(一个产品对应多个变体)。products表无价格字段,价格字段存储在variants表中。后台中产品的价格由其变体的最低价格表示。
需要在后台实现产品按价格从低到高排序并分页的功能。目前我通过模型的自定义属性方法获取变体最低价格,代码如下:
protected function price(): Attribute { return new Attribute(get: function(){ return $this->variants()->get()->min('price'); }); }
尝试过使用sortBy方法,但它仅适用于集合,无法直接分页,且大数据量下集合操作会造成严重内存损耗。
核心问题:如何在Eloquent Builder中基于自定义模型方法(price)的结果进行排序并实现分页?
解决方案
自定义属性是模型实例化后计算的结果,无法直接用于Eloquent Builder的数据库层面排序。要实现需求,必须把“获取最低变体价格”的逻辑移到数据库查询中,让排序和分页在数据库层面完成,避免内存损耗。
方法1:子查询添加最低价格字段后排序分页
通过子查询为每个产品计算最低变体价格,生成min_price字段,再基于该字段排序分页:
use Illuminate\Database\Eloquent\Builder; $products = Product::query() ->select('products.*') ->selectSub(function (Builder $query) { $query->selectRaw('MIN(price)') ->from('variants') ->whereColumn('variants.product_id', 'products.id'); }, 'min_price') ->orderBy('min_price', 'asc') ->paginate(15); // 替换为你需要的分页条数
这种方式直接在SQL层面完成最低价格计算与排序,分页逻辑由数据库处理,性能远优于集合操作。
方法2:JOIN+分组查询计算最低价格
通过关联variants表并分组,计算每个产品的最低价格:
use Illuminate\Support\Facades\DB; $products = Product::query() ->join('variants', 'products.id', '=', 'variants.product_id') ->select('products.*', DB::raw('MIN(variants.price) as min_price')) ->groupBy('products.id') ->orderBy('min_price', 'asc') ->paginate(15);
如果你的MySQL开启了ONLY_FULL_GROUP_BY模式,需要调整select子句,确保非聚合字段符合分组规则,比如使用ANY_VALUE()函数:
$products = Product::query() ->join('variants', 'products.id', '=', 'variants.product_id') ->select( DB::raw('ANY_VALUE(products.id) as id'), DB::raw('ANY_VALUE(products.name) as name'), // 按需添加products表的其他字段 DB::raw('MIN(variants.price) as min_price') ) ->groupBy('products.id') ->orderBy('min_price', 'asc') ->paginate(15);
优化模型属性(可选)
如果需要继续使用模型的price属性,可以修改实现逻辑,复用查询生成的min_price字段,避免重复查询变体表:
protected function price(): Attribute { return new Attribute(get: function(){ // 优先使用查询时已计算好的min_price,无则查询数据库 return $this->min_price ?? $this->variants()->min('price'); }); }
内容的提问来源于stack exchange,提问作者Melly
相关产品推荐
相关产品推荐

