导读:本期聚焦于小伙伴创作的《SQL怎么用ROW_NUMBER窗口函数查询每个分组的前N条记录》,敬请观看详情。在报表统计里经常要取每个分类下销量最高的三款商品,或者每个用户最近五笔订单。传统写法靠子查询和计数字段,逻辑绕且大数据量下很慢。ROW_NUMBER是SQL标准里的窗口函数,它能在不破坏原行的前提下,按指定分区和排序给每行编序号。借助PARTITION BY划分分组、ORDER BY定排序规则,再套一层过滤序号小于等于N,就能干净地拿到分组前N条。下面从语法结构、可运行示例到与RANK类函数差异逐步说明,并给出常见误用与索引优化思路。

在数据库查询中,经常遇到“按某个字段分组,再取每组里面排在最前面的几条记录”这类需求。比如按部门取工资最高的三人,按城市取气温最低的两天。使用ROW_NUMBER窗口函数可以把这类问题写得非常直观,而且大多数主流数据库都支持。

SQL怎么用ROW_NUMBER窗口函数查询每个分组的前N条记录

一、ROW_NUMBER窗口函数基本语法

ROW_NUMBER是一个窗口函数,它为查询结果集中的每一行分配一个唯一的连续整数,整数的分配规则由OVER子句里的PARTITION BY和ORDER BY决定。PARTITION BY用来划分子集,相当于分组;ORDER BY决定每个子集内部的排序方向。编号从1开始,同一个分区内不会重复。

基本写法如下:

SELECT
  row_number() OVER (
    PARTITION BY department_id
    ORDER BY salary DESC
  ) AS rn,
  employee_id,
  department_id,
  salary
FROM employee;

上面这段SQL会把员工按部门分开,每个部门里工资从高到低编上号。有了这个序号,只需要外层再筛选rn小于等于N,就能拿到每个部门的前N条。注意ROW_NUMBER本身不做聚合,不会减少行数,它只是多算出一列编号。

二、查询每个分组前N条记录的完整写法

最常见做法是把窗口函数放在子查询或者公用表表达式(CTE)里,然后在外层加WHERE条件。以下示例取每个部门工资前三名的员工:

WITH ranked_emp AS (
  SELECT
    row_number() OVER (
      PARTITION BY department_id
      ORDER BY salary DESC
    ) AS rn,
    employee_id,
    department_id,
    salary
  FROM employee
)
SELECT
  employee_id,
  department_id,
  salary
FROM ranked_emp
WHERE rn <= 3
ORDER BY department_id, rn;

这段代码先通过CTE算出编号,再过滤rn小于等于3。逻辑清晰,也方便数据库优化器处理。如果不用CTE,也可以写成派生表:

SELECT
  employee_id,
  department_id,
  salary
FROM (
  SELECT
    row_number() OVER (
      PARTITION BY department_id
      ORDER BY salary DESC
    ) AS rn,
    employee_id,
    department_id,
    salary
  FROM employee
) t
WHERE t.rn <= 3;

两种写法在多数数据库里性能接近。使用CTE通常可读性更好,也更容易在复杂查询中复用中间结果。无论哪种方式,核心都是“先编号,后过滤”。

三、ROW_NUMBER与RANK、DENSE_RANK的区别

初学者容易混淆ROW_NUMBER、RANK和DENSE_RANK。假设某部门工资为9000、9000、8000、7000,ORDER BY salary DESC时:

  • ROW_NUMBER无论值是否相同,都给1、2、3、4,相同工资谁排前取决于数据库实现或额外排序列。
  • RANK会给两个9000都标1,下一个8000标3,出现名次跳跃。
  • DENSE_RANK给两个9000标1,8000标2,名次连续不跳。

如果业务要求“前N条”严格限制行数,比如只要三行,就用ROW_NUMBER。如果要求“前N名”且并列都算,比如工资前三名可能返回四行,则用RANK或DENSE_RANK再配合过滤。以下示例展示RANK写法:

SELECT
  employee_id,
  department_id,
  salary
FROM (
  SELECT
    rank() OVER (
      PARTITION BY department_id
      ORDER BY salary DESC
    ) AS rk,
    employee_id,
    department_id,
    salary
  FROM employee
) t
WHERE t.rk <= 3;

可以看到,只是把函数名换了,其它结构完全一致。选择哪个函数取决于业务对“并列”的处理规则,而不是性能差异,三者底层都是窗口排序。

四、常见错误与优化建议

一个常见错误是在WHERE里直接写row_number() OVER(...) <= N,这是不允许的,因为窗口函数不能出现在WHERE子句中,必须先算出再过滤。另一个错误是ORDER BY列不够唯一,导致同分区同值行的编号不稳定,每次执行可能顺序不同。解决方法是增加第二排序列,例如ORDER BY salary DESC, employee_id ASC。

在性能方面,PARTITION BY和ORDER BY用到的列最好有复合索引,例如对employee表的(department_id, salary DESC)建索引,能让数据库避免额外排序。数据量极大时,也可以考虑先按分区字段过滤再算编号,减少处理行数。以下为建索引示例:

CREATE INDEX idx_emp_dept_sal
ON employee (department_id, salary DESC);

最后提醒,窗口函数不改变原表行数,也不会像GROUP BY那样折叠数据,因此它特别适合既要分组前N条、又要保留明细字段的场景。掌握ROW_NUMBER后,类似“最新订单”“最高分记录”等需求都能用统一模式快速解决。

SQLROW_NUMBER窗口函数修改时间:2026-08-01 10:42:25

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。