Laravel实现每个分类仅显示一个商品且新增分类排首位
问题
我有products和categories两张表,二者为一对多关系,已在Laravel中创建pivot表及模型关联。需求为:每个分类仅展示一个商品,且新增商品的分类需排在返回数组的首位。当前代码可实现每个分类显示一个商品,但无法满足排序需求。
现有代码
Category::with('products')->orderBy('id', 'desc')->get()->map(function($categories) { $categories->setRelation('products', $categories->products->sortByDesc('id')->take(1)); return $categories; });
当前返回结果
[ { "id": 1, "name": "dairy", "products": { "6": { "id": 4, "name": "yoghurt", "price": "1", "created_at": "2022-09-02T16:45:24.000000Z", "updated_at": "2022-09-02T16:45:24.000000Z", "pivot": { "category_id": 1, "product_id": 4 } } } }, { "id": 2, "name": "fruits", "products": [ { "id": 10, "name": "coconut", "price": "2", "created_at": "2022-09-02T11:28:12.000000Z", "updated_at": "2022-08-15T00:24:00.000000Z", "pivot": { "category_id": 2, "product_id": 10 } } ] } ]
期望返回结果
[ { "id": 2, "name": "fruits", "products": [ { "id": 10, "name": "coconut", "price": "2", "created_at": "2022-09-02T11:28:12.000000Z", "updated_at": "2022-08-15T00:24:00.000000Z", "pivot": { "category_id": 2, "product_id": 10 } } ] }, { "id": 1, "name": "dairy", "products": [ { "id": 4, "name": "yoghurt", "price": "1", "created_at": "2022-09-02T16:45:24.000000Z", "updated_at": "2022-09-02T16:45:24.000000Z", "pivot": { "category_id": 1, "product_id": 4 } } ] } ]
解决方案
核心问题分析
原代码按categories.id倒序排序,但需求是按分类下最新商品的创建时间排序,把有最新商品的分类放在最前面。同时原代码返回的products格式不一致(有的是对象,有的是数组),需要统一成数组格式。
修正代码
use Illuminate\Support\Facades\DB; Category::with(['products' => function ($query) { // 预加载时直接取每个分类下最新的1个商品 $query->latest()->take(1); }]) // 关联子查询获取每个分类下最新商品的创建时间,以此作为排序依据 ->select('categories.*') ->joinSub( Product::select('category_id', DB::raw('MAX(created_at) as latest_product_created_at')) ->groupBy('category_id'), 'latest_products', 'categories.id', '=', 'latest_products.category_id' ) ->orderBy('latest_product_created_at', 'desc') ->get() // 统一products为纯数组格式 ->map(function ($category) { $category->setRelation('products', $category->products->values()); return $category; });
代码说明
- 预加载优化:在
with闭包中直接通过latest()->take(1)获取每个分类的最新商品,避免后续集合层面的排序截取,提升查询性能。 - 排序逻辑修正:通过
joinSub关联子查询,计算每个分类下最新商品的创建时间,再按该时间倒序排序,确保有最新商品的分类排在数组首位。 - 格式统一:使用
values()将Collection转为纯数组,解决原结果中products格式不一致的问题。
简化替代方案(基于模型关联)
如果你的Category模型已定义取最新商品的一对一关联,可以进一步简化:
// 在Category模型中添加关联 public function latestProduct() { return $this->hasOne(Product::class)->latest(); } // 查询代码 use Illuminate\Support\Facades\DB; Category::with('latestProduct') ->select('categories.*') ->joinSub( Product::select('category_id', DB::raw('MAX(created_at) as latest_product_created_at')) ->groupBy('category_id'), 'latest_products', 'categories.id', '=', 'latest_products.category_id' ) ->orderBy('latest_product_created_at', 'desc') ->get() ->map(function ($category) { // 将一对一关联转为数组格式的products字段 $category->setRelation('products', $category->latestProduct ? collect([$category->latestProduct]) : collect()); unset($category->latestProduct); return $category; });
内容的提问来源于stack exchange,提问作者enrestd
相关产品推荐
相关产品推荐

