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

Laravel多对多关系下按最新状态筛选订单并实现分页

解决Laravel多对多关系下筛选订单最新状态并分页的问题

你的核心问题在于:直接用with('latestStatuses')预加载关联无法实现基于最新状态的精准筛选,且如果在加载后过滤会破坏分页逻辑(分页是基于未过滤的全量订单计算的)。要解决这个问题,需要在主查询中直接关联并筛选每个订单的最新状态记录,确保分页是基于筛选后的结果。

解决方案:子查询关联筛选最新状态

方法1:使用whereNotExists获取最新状态记录

通过子查询找到每个订单的最新状态变更记录(即不存在比它更晚的状态记录),再将其与订单表关联,筛选目标状态ID:

$targetStatusIds = [1, 2];
$pageSize = 10;

// 子查询:获取每个订单的最新状态ID
$latestStatusSubquery = DB::table('order_status as os1')
    ->select('os1.order_id', 'os1.status_id')
    ->whereNotExists(function ($query) {
        $query->from('order_status as os2')
            ->whereColumn('os2.order_id', 'os1.order_id')
            ->where('os2.created_at', '>', 'os1.created_at');
    });

// 关联子查询并筛选,同时执行分页
$orders = Order::query()
    ->joinSub(
        $latestStatusSubquery,
        'latest_status',
        'latest_status.order_id',
        '=',
        'orders.id'
    )
    ->whereIn('latest_status.status_id', $targetStatusIds)
    ->select('orders.*') // 仅选择订单表字段,避免字段冲突
    ->latest('orders.id')
    ->paginate($pageSize);

// 预加载最新状态数据(可选,用于后续业务逻辑)
$orders->load('latestStatuses');

方法2:使用窗口函数(MySQL 8.0+ 支持)

利用ROW_NUMBER()窗口函数给每个订单的状态变更记录按创建时间倒序排名,取排名为1的最新记录:

$targetStatusIds = [1, 2];
$pageSize = 10;

$orders = Order::query()
    ->joinSub(
        DB::table('order_status')
            ->select(
                'order_id',
                'status_id',
                DB::raw('ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY created_at DESC) as rn')
            ),
        'status_ranked',
        'status_ranked.order_id',
        '=',
        'orders.id'
    )
    ->where('status_ranked.rn', 1) // 仅取最新状态
    ->whereIn('status_ranked.status_id', $targetStatusIds)
    ->select('orders.*')
    ->latest('orders.id')
    ->paginate($pageSize);

$orders->load('latestStatuses');

为什么原方法失效?

你定义的latestStatuses关联用了limit(1),它仅用于预加载单个最新状态数据,但无法用于筛选:

  • 如果用whereHas('latestStatuses', function($q) use ($targetStatusIds) { ... }),逻辑是判断订单是否存在任意一个状态在目标集合中,而非最新的那个状态在集合中。
  • 若先查询所有订单再过滤最新状态,分页会基于全量订单计算,导致分页结果不准确(比如每页10条,过滤后可能只剩5条,分页逻辑混乱)。

优化模型关联(可选)

如果需要频繁获取订单最新状态,可以在Order模型中定义一个单数关联latestStatus,直接关联最新状态:

public function latestStatus()
{
    return $this->hasOne(Status::class)
        ->joinSub(
            DB::table('order_status')
                ->select('order_id', 'status_id')
                ->whereNotExists(function ($query) {
                    $query->from('order_status as os2')
                        ->whereColumn('os2.order_id', 'order_status.order_id')
                        ->where('os2.created_at', '>', 'order_status.created_at');
                }),
            'latest_os',
            function ($join) {
                $join->on('latest_os.order_id', '=', 'orders.id')
                    ->on('latest_os.status_id', '=', 'statuses.id');
            }
        );
}

之后可以简化查询:

$orders = Order::query()
    ->with('latestStatus')
    ->whereHas('latestStatus', function($q) use ($targetStatusIds) {
        $q->whereIn('id', $targetStatusIds);
    })
    ->latest('id')
    ->paginate($pageSize);

内容的提问来源于stack exchange,提问作者Jafrul Hossain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 05:13:10