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

分页场景下如何实现ROW_NUMBER函数的分组连续排序效果?

分页时保留全局分组的连续序号(按category_id分组、created_at排序)

需求是按category_id分组,统计每组内按created_at排序的行序号。常规使用ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY created_at)能得到正确的全局分组序号,但启用分页后,序号会基于当前页的子集重新计算,导致序号重置(比如每页4条时第二页序号错误),需要实现分页时仍保留全局的分组连续序号。

示例数据与结果对比

原表格

id | category_id | item_id | created_at
---|-------------|---------|-----------
 7 |      11     |   106   | 2024-05-06
 6 |      3      |   102   | 2024-05-06
 5 |      11     |   101   | 2024-05-05
 4 |      9      |   98    | 2024-05-04
 3 |      3      |   97    | 2024-05-03
 2 |      1      |   91    | 2024-05-02
 1 |      11     |   89    | 2024-05-01

常规查询正确结果

id | category_id | item_id | created_at | order
---|-------------|---------|------------|-------
 7 |      11     |   106   | 2024-05-06 |    3
 6 |      3      |   102   | 2024-05-06 |    2
 5 |      11     |   101   | 2024-05-05 |    2
 4 |      9      |   98    | 2024-05-04 |    1
 3 |      3      |   97    | 2024-05-03 |    1
 2 |      1      |   91    | 2024-05-02 |    1  
 1 |      11     |   89    | 2024-05-01 |    1

分页(每页4条)第二页错误结果

id | category_id | item_id | created_at | order
---|-------------|---------|------------|-------
 7 |      11     |   106   | 2024-05-06 |    2
 6 |      3      |   102   | 2024-05-06 |    1
 5 |      11     |   101   | 2024-05-05 |    1

期望结果

id | category_id | item_id | created_at | order
---|-------------|---------|------------|-------
 7 |      11     |   106   | 2024-05-06 |    3
 6 |      3      |   102   | 2024-05-06 |    2
 5 |      11     |   101   | 2024-05-05 |    2

问题原因

常规分页逻辑是先通过LIMIT/OFFSET截取当前页数据,再对这个子集计算ROW_NUMBER(),窗口函数仅作用于当前页的少量数据,自然会导致序号重置。要解决这个问题,必须先计算全局所有数据的分组序号,再对包含序号的完整结果集执行分页。


解决方案

1. 原生SQL实现

通过子查询先完成全局分组序号的计算,外层再执行分页操作:

SELECT *
FROM (
    SELECT 
        `table`.*, 
        ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY created_at) AS `order`
    FROM `table`
) AS ranked_table
ORDER BY created_at DESC -- 此处排序需和分页逻辑一致,保证分页数据的连贯性
LIMIT 4 OFFSET 4; -- 第二页,每页4条数据

2. Laravel 查询构造器实现

利用fromSub先构造包含全局序号的子查询,再基于该子查询进行分页:

$perPage = 4;
$page = 2;

// 第一步:构建计算全局序号的子查询
$rankedQuery = DB::table('table')
    ->selectRaw('*, ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY created_at) AS `order`');

// 第二步:基于子查询执行分页
$result = DB::table($rankedQuery, 'ranked_table')
    ->orderBy('created_at', 'desc')
    ->forPage($page, $perPage)
    ->get();

如果需要使用Laravel自带的分页器(返回带分页参数的集合),可以用闭包写法:

$result = DB::table(function ($query) {
    $query->from('table')
          ->selectRaw('*, ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY created_at) AS `order`');
}, 'ranked_table')
->orderBy('created_at', 'desc')
->paginate($perPage, ['*'], 'page', $page);

验证结果

执行上述代码后,分页返回的结果会和期望结果完全一致,分组序号保持全局连续,不会因分页重置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 10:55:56