You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
});

代码说明

  1. 预加载优化:在with闭包中直接通过latest()->take(1)获取每个分类的最新商品,避免后续集合层面的排序截取,提升查询性能。
  2. 排序逻辑修正:通过joinSub关联子查询,计算每个分类下最新商品的创建时间,再按该时间倒序排序,确保有最新商品的分类排在数组首位。
  3. 格式统一:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 09:30:51