窗口函数不能用于游标分页,因其仅附加编号而不支持状态感知偏移;游标分页需基于有序字段比较(如 (created_at, id) > (?, ?))实现精准跳转,而 where rn > n 会报错且逻辑错误。

为什么不能直接用窗口函数做游标分页
窗口函数本身不改变结果集行数,ROW_NUMBER()、RANK() 这类函数只是为每行附加一个计算值,无法跳过前 N 行或限制返回条数——而游标分页依赖「从某条记录之后取下一页」,核心是状态感知和有序偏移,不是编号。
常见错误是写成这样:
SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM orders WHERE rn > 100 AND rn <p>这会报错:<code>Window function is not allowed in WHERE clause</code>。因为窗口函数在逻辑执行顺序中晚于 <code>WHERE</code>,此时 <code>rn</code> 还不存在。</p> <h3>游标分页真正依赖的是 ORDER BY + WHERE 组合</h3> <p>游标分页本质是「基于上一页最后一条记录的排序字段值,查出下一批严格大于它的记录」。它不要求全局编号,只要求排序字段有确定顺序、无重复(或能处理重复)。</p> <p>关键实操点:</p><div class="aritcle_card flexRow artxards"> <div class="artcardd flexRow"> <a class="aritcle_card_img" rel="nofollow" href="/ai/1262" title="CG Faces"><img src="https://img.php.cn/upload/ai_manual/001/431/639/68b6dba8a90c4923.png" alt="CG Faces" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a> <div class="aritcle_card_info flexColumn"> <a rel="nofollow" href="/ai/1262" title="CG Faces" class="overflowclass">CG Faces</a> <p class="overflowclass">CG Faces是一款提供免费高分辨率 AI 生成人像素材的网站。</p> </div> <a rel="nofollow" href="/ai/1262" title="CG Faces" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a> </div> </div>
- 必须有一个确定性、高选择性的排序字段,比如
created_at+id组合(避免时间相同导致游标漂移) - 查询条件要写成
WHERE (created_at, id) > (?, ?)这种行比较语法(PostgreSQL/MySQL 8.0+ 支持),而不是WHERE created_at >= ? AND id > ?—— 后者在边界处容易漏数据或重复 - 首次请求没有游标,用
WHERE 1=1或省略WHERE,但务必加LIMIT - 客户端必须保存上一页最后一条的完整游标值(如
{"created_at": "2024-05-01 10:20:30", "id": 12345}),不能只存单个字段
窗口函数能在游标分页里起什么作用
它不参与分页逻辑,但可用于辅助场景,比如:在返回当前页数据的同时,附带「下一页是否存在」或「是否为最后一页」的提示信息。
例如,查 20 条数据,并多取 1 条用于判断是否还有下一页:
WITH page_plus_one AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY created_at, id) AS rn
FROM orders
WHERE (created_at, id) > ('2024-05-01 10:20:30', 12345)
ORDER BY created_at, id
LIMIT 21
)
SELECT *,
CASE WHEN rn = 21 THEN 'has_next' ELSE 'no_next' END AS pagination_hint
FROM page_plus_one
WHERE rn
<p>注意:<code>ROW_NUMBER()</code> 在这里只是给临时结果编号,真正的分页过滤靠的是外层 <code>WHERE rn ,且这个 CTE 必须配合 <code>ORDER BY</code> 和 <code>LIMIT</code> 才可靠。</code></p>
<h3>不同数据库对游标分页的支持差异</h3>
<p>PostgreSQL 对行比较支持最干净:<code>WHERE (a, b) > ($1, $2)</code> 可直接用;MySQL 8.0+ 也支持,但要注意字符集和 NULL 处理;SQLite 需手动展开为 <code>WHERE a > ? OR (a = ? AND b > ?)</code>;SQL Server 没有原生行比较,得用 <code>OFFSET-FETCH</code>——但这不是游标分页,是基于位置的分页,性能随 offset 增大而下降。</p>
<p>容易被忽略的一点:如果排序字段允许 NULL,所有数据库都会让 NULL 排在最前或最后(取决于 <code>NULLS FIRST/LAST</code>),但默认行为不一致。游标分页必须显式约定,比如统一写成 <code>ORDER BY created_at DESC NULLS LAST, id DESC</code>,并在 WHERE 条件中同步处理 NULL 游标值。</p>










