row_number()必须配合partition by和order by才能实现分组排序,仅用order by会导致全表连续编号;正确语法为row_number() over(partition by 字段 order by 字段),且需用子查询或cte封装后过滤。

ROW_NUMBER() 必须配合 PARTITION BY 和 ORDER BY 才能分组取前N
单独写 ROW_NUMBER() 不会自动分组,不加 PARTITION BY 就是全表连续编号,根本不是“每组”。常见错误是只写 ORDER BY,结果发现所有记录排成一列,完全没分组效果。
正确结构必须是:ROW_NUMBER() OVER (PARTITION BY 分组字段 ORDER BY 排序字段)。比如按部门分组、按薪资降序取前3人,就得写 PARTITION BY dept_id ORDER BY salary DESC。
-
PARTITION BY决定“哪些行属于同一组”,可以多个字段,如PARTITION BY region, product_type -
ORDER BY在组内决定优先级,NULLS LAST建议显式加上,避免不同数据库对 NULL 排序行为不一致 - 别在
WHERE里直接过滤ROW_NUMBER()别名——它不能在 WHERE 中使用,得用子查询或 CTE 包一层
CTE 或子查询是绕不开的封装步骤
因为窗口函数计算发生在 WHERE 和 GROUP BY 之后,但又要在 SELECT 中生成序号,所以必须先算出序号,再在外面筛选。直写 WHERE rn 会报错 “column "rn" does not exist”。
推荐用 CTE,语义清晰且可读性好:
WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY price DESC) AS rn FROM products ) SELECT id, name, category, price FROM ranked WHERE rn
- 子查询写法等价但嵌套深,容易漏括号;CTE 更易调试和复用
- 如果只是临时取数,不用建视图,CTE 完全够用
- 注意 CTE 后面不能加分号(除非前面有其他语句),否则 PostgreSQL/SQL Server 会报错
ROW_NUMBER() vs RANK() vs DENSE_RANK():选错就拿不到“前N条”
三者都生成序号,但处理并列的方式完全不同。想严格取“最多N条”,必须用 ROW_NUMBER();用错会漏数据或超量。
-
ROW_NUMBER():严格递增,相同值也不同号(1,2,3,4…)→ 确保正好 N 条 -
RANK():并列同号,跳号(1,1,3,3,5…)→ 可能只返回2条就跳到第5名,导致实际不足N条 -
DENSE_RANK():并列同号,不跳号(1,1,2,2,3…)→ 可能返回超过N条(比如前3名里有4个并列第1)
例如某组有5条记录价格全是100,用 DENSE_RANK() ORDER BY price DESC 全是1,WHERE dr 会把5条全捞出来——这不是“前3条”,是“所有并列第一的记录”。
性能敏感场景要警惕 ORDER BY 字段没索引
窗口函数本身不慢,但 ORDER BY 字段若无索引,PostgreSQL/MySQL 8.0+/SQL Server 都会触发大量排序,尤其数据量大时延迟明显。
- 检查执行计划,确认是否出现
Sort节点且占成本高 - 复合索引建议按
(分组字段, 排序字段)建,比如CREATE INDEX idx_dept_salary ON employees(dept_id, salary DESC) - MySQL 8.0.20+ 支持窗口函数下推,但旧版本仍可能 Materialize 整个中间结果,慎用于千万级表
真正难的不是写对语法,而是理解 PARTITION BY 的边界在哪、ORDER BY 的稳定性是否可控、以及索引能不能兜住实时查询压力——这些不跑真实数据看不出问题。










