直接用ROW_NUMBER()分页会导致重复,因相同排序值的行顺序随机;须先按业务逻辑去重(如GROUP BY或PARTITION BY+rn=1),再编号分页,且跨页应采用键集分页避免OFFSET缺陷。
当你对含重复数据的表(比如多条记录共享同一 user_id)直接套 ROW_NUMBER() OVER (ORDER BY ...) 分页时,相同排序值的行会被随机打乱顺序,导致第 1 页出现的某条 user_id = 100 记录,在第 2 页又因窗口函数重排而再次出现——本质是去重逻辑没落在分页之前。
DISTINCT 和 ROW_NUMBER() 不能共存于同一层 SELECT(语法报错),强行在子查询中 SELECT DISTINCT ... FROM t 后再套 ROW_NUMBER(),会丢失原始行信息(比如你本想取每用户最新一条订单,但 DISTINCT user_id 不知道哪条是最新)。
SELECT *, ROW_NUMBER() OVER (ORDER BY user_id) rn FROM (SELECT DISTINCT user_id FROM orders) t
order_time、amount 等关键字段,无法支撑“每个用户取最新订单”这类真实需求核心是把“去重逻辑”显式表达为聚合或优先级选择,再编号。常见两种路径:
GROUP BY,用 MAX(order_time) 或 MAX(id) 拿最新行,再对结果集编号:SELECT *, ROW_NUMBER() OVER (ORDER BY latest_time DESC) rn FROM ( SELECT user_id, MAX(order_time) AS latest_time, MAX(amount) AS amount FROM orders GROUP BY user_id) t
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) 先标出每组第一条,再外层筛选 rn = 1,最后重新编号分页:SELECT *, ROW_NUMBER() OVER (ORDER BY user_id) rn FROM ( SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) rn_inner FROM orders ) t WHERE rn_inner = 1) t2
ROW_NUMBER() 的 ORDE
R BY 必须和分页意图一致(比如按创建时间倒序),否则页与页之间顺序不稳用 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY 看似简洁,但底层仍要扫描前 30 行;如果去重后总行数少(比如 25 条),第 3 页就为空——而用户可能以为数据丢了。更稳的方式是用键集分页(Keyset Pagination),即记住上一页最后一条的 user_id 和 latest_time,下一页查:
WHERE (latest_time, user_id) < (‘2024-05-01’, ‘u999’) ORDER BY latest_time DESC, user_id DESC LIMIT 10
真正难的不是写对语法,而是把“去重策略”和“分页稳定性”绑在一起设计;一旦 PARTITION BY 字段和 ORDER BY 字段没对齐,或者没处理好 NULL 值排序优先级,跨页就会漏数或重复。