LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

SQL 窗口函数,别再死记硬背了:一篇彻底讲透 OVER / PARTITION BY / ROW_NUMBER

admin
2026年4月13日 22:37 本文热度 13

如果你学 SQL 时总觉得窗口函数“看起来懂了,一写就废”,那不是你笨,是很多教程都没讲人话。

它们喜欢先甩给你一串语法:OVER(PARTITION BY ... ORDER BY ...),然后再贴几个例子,最后你记住了 ROW_NUMBER(),却还是不知道什么时候该用、为什么好用。

这一篇不绕弯,直接把 SQL 窗口函数最常用、最容易卡壳的部分讲透。


一、窗口函数到底是干嘛的?

先说人话版定义:

窗口函数 = 在“不压缩结果行数”的前提下,按某个范围对数据做计算。

这是它和 GROUP BY 最大的区别。

1. GROUP BY 会把多行压成一行

比如你按部门统计员工人数:

SELECT department, COUNT(*) AS emp_count
FROM
 employees
GROUP
 BY department;

结果是每个部门只剩一行。

这很正常,但问题来了:

如果你既想保留每个员工这一行,又想知道他所在部门有多少人,怎么办?

这时候 GROUP BY 就不顺手了,窗口函数就是来干这个的。

2. 窗口函数保留明细,同时做聚合

SELECT
  employee_id,
  name,
  department,
  salary,
  COUNT
(*) OVER(PARTITION BY department) AS dept_emp_count
FROM
 employees;

你会得到这样的效果:

  • • 每个员工仍然保留一行
  • • 同时多出一个字段:这个员工所在部门的人数

这就是窗口函数最核心的价值:

明细不丢,统计也有。

这四个字,值千金。


二、先把最关键的语法拆开

窗口函数常见写法长这样:

函数名() OVER(
  PARTITION
 BY ...
  ORDER
 BY ...
)

别一看括号多就头大,其实就三层意思。

1. OVER():声明“我要按窗口算”

没有 OVER(),很多函数就是普通聚合函数。

比如:

SUM(salary)

这是普通聚合。

而:

SUM(salary) OVER()

这是窗口函数,表示:

在整个结果集这个窗口里,计算总工资,但不要把行合并掉。


2. PARTITION BY:先分组,但不折叠行

它很像 GROUP BY,但只是在逻辑上分区,不会把数据压缩。

SUM(salary) OVER(PARTITION BY department)

意思是:

  • • 以部门为单位划分窗口
  • • 在每个部门窗口内计算工资总和
  • • 结果回填到该部门的每一行

你可以把它理解成:

 是“分组统计”,但保留每条明细。


3. ORDER BY:定义窗口内的顺序

很多窗口函数都和“顺序”强相关,比如:

  • • 排名
  • • 累计值
  • • 上一行 / 下一行

这时候就必须指定顺序:

ROW_NUMBER() OVER(PARTITION BY department ORDER BY salary DESC)

意思是:

  • • 先按部门分区
  • • 再在每个部门内部,按工资从高到低排序
  • • 给每个人编号

三、先学会最常用的 3 个排名函数

这是窗口函数入门里最实用的一组。

假设有一张成绩表:

student_scores
-------------------------

student_name | class | score
张三         | A     | 95
李四         | A     | 95
王五         | A     | 90
赵六         | A     | 88

1. ROW_NUMBER():强行排唯一名次

SELECT
  student_name,
  class,
  score,
  ROW_NUMBER
() OVER(PARTITION BY class ORDER BY score DESC) AS rn
FROM
 student_scores;

结果可能是:

  • • 张三 1
  • • 李四 2
  • • 王五 3
  • • 赵六 4

即使张三和李四同分,ROW_NUMBER() 也会硬分先后。

适用场景:

  • • 每组取 Top 1 / Top N
  • • 去重保留一条
  • • 分页编号

2. RANK():并列占同名,后面跳号

SELECT
  student_name,
  class,
  score,
  RANK
() OVER(PARTITION BY class ORDER BY score DESC) AS rk
FROM
 student_scores;

结果会变成:

  • • 张三 1
  • • 李四 1
  • • 王五 3
  • • 赵六 4

因为前两个人并列第一,所以第三个人的名次直接变成 3。

适用场景:

  • • 正常比赛排名
  • • 允许并列且保留“跳号”语义的榜单

3. DENSE_RANK():并列占同名,但不跳号

SELECT
  student_name,
  class,
  score,
  DENSE_RANK
() OVER(PARTITION BY class ORDER BY score DESC) AS dr
FROM
 student_scores;

结果是:

  • • 张三 1
  • • 李四 1
  • • 王五 2
  • • 赵六 3

适用场景:

  • • 分层级
  • • 分档次
  • • 想体现并列,但不想出现名次断层

四、SQL 面试和实战都爱考:每组 Top N

这题你应该见过:

查询每个部门工资最高的 3 个人。

很多人第一反应是子查询套子查询,写得像意大利面。

其实窗口函数是标准答案。

SELECT *
FROM
 (
  SELECT

    employee_id,
    name,
    department,
    salary,
    ROW_NUMBER
() OVER(PARTITION BY department ORDER BY salary DESC) AS rn
  FROM
 employees
) t
WHERE
 rn <= 3;

为什么这么写?

第一层:

  • • 每个部门内部按工资倒序编号

第二层:

  • • 取编号前 3 的记录

这就是窗口函数最经典的应用之一。

顺手提醒一个坑

如果你想保留并列,比如第三名有两个人同薪,别用 ROW_NUMBER(),要用 RANK() 或 DENSE_RANK()

不然你会“误杀”一个本该入榜的人。


五、累计求和:窗口函数的另一个高频场景

假设你有每日销售额表:

daily_sales
----------------------

sale_date   | amount
2026
-03-01  | 100
2026
-03-02  | 150
2026
-03-03  | 120
2026
-03-04  | 180

现在你想知道:

  • • 每天销售额是多少
  • • 截止到当天累计卖了多少

窗口函数一把梭:

SELECT
  sale_date,
  amount,
  SUM
(amount) OVER(ORDER BY sale_date) AS running_total
FROM
 daily_sales;

这会得到:

  • • 3 月 1 日:100
  • • 3 月 2 日:250
  • • 3 月 3 日:370
  • • 3 月 4 日:550

这就是累计求和

如果想按门店分别累计?

SELECT
  store_id,
  sale_date,
  amount,
  SUM
(amount) OVER(PARTITION BY store_id ORDER BY sale_date) AS running_total
FROM
 daily_sales;

意思很简单:

  • • 每个门店是一个窗口
  • • 门店内部按日期排序
  • • 分别计算各自累计值

这类写法在下面场景非常常见:

  • • 日活 / 周活累计
  • • 财务累计收入
  • • 用户增长曲线
  • • 库存变化趋势

六、LAG 和 LEAD:查上一行、下一行,真的太好用

很多新手写 SQL 时,一遇到“和上一天比较”,就想自连接。

说实话,能写,但丑。

窗口函数里有更直接的:

  • • LAG():看上一行
  • • LEAD():看下一行

1. 看上一天销售额

SELECT
  sale_date,
  amount,
  LAG
(amount, 1) OVER(ORDER BY sale_date) AS prev_amount
FROM
 daily_sales;

这里:

  • • LAG(amount, 1) 表示取前 1 行的 amount
  • • 如果前面没有行,就返回 NULL

2. 直接算日增长

SELECT
  sale_date,
  amount,
  LAG
(amount, 1) OVER(ORDER BY sale_date) AS prev_amount,
  amount - LAG(amount, 1) OVER(ORDER BY sale_date) AS diff_amount
FROM
 daily_sales;

这就能直接算出和上一天相比增减了多少。

3. 再进一步,算增长率

SELECT
  sale_date,
  amount,
  LAG
(amount, 1) OVER(ORDER BY sale_date) AS prev_amount,
  ROUND(
    (amount - LAG(amount, 1) OVER(ORDER BY sale_date))
    /
 NULLIF(LAG(amount, 1) OVER(ORDER BY sale_date), 0),
    2

  ) AS growth_rate
FROM
 daily_sales;

这个场景在业务分析里非常常见:

  • • 环比增长
  • • 次日留存对比
  • • 用户行为变化
  • • 价格波动分析

七、很多人学不会窗口函数,不是因为难,是因为脑子里没画面

你可以用这个心智模型记:

普通聚合函数

像把很多行扔进榨汁机,最后变成一杯。

窗口函数

像你把每一行放在原位,只是给它发一张“统计结果小纸条”。

比如这一行员工数据旁边贴一张:

  • • 部门平均工资
  • • 部门排名
  • • 上一位同事工资
  • • 截至当前的累计销售额

行没消失,信息却更丰富了。

这就是窗口函数的本质。


八、再讲一个关键点:窗口函数常见组合拳

实战里,你不是只会写一个 ROW_NUMBER() 就完事,更多时候是组合使用。

场景 1:找每个用户最近一次登录

SELECT *
FROM
 (
  SELECT

    user_id,
    login_time,
    ROW_NUMBER
() OVER(PARTITION BY user_id ORDER BY login_time DESC) AS rn
  FROM
 user_login_log
) t
WHERE
 rn = 1;

场景 2:计算每个部门的平均工资,并看员工是否高于部门均值

SELECT
  employee_id,
  name,
  department,
  salary,
  AVG
(salary) OVER(PARTITION BY department) AS dept_avg_salary,
  CASE

    WHEN
 salary > AVG(salary) OVER(PARTITION BY department) THEN &#x27;高于均值&#x27;
    ELSE
 &#x27;不高于均值&#x27;
  END
 AS salary_level
FROM
 employees;

场景 3:找连续上涨的数据趋势

SELECT
  sale_date,
  amount,
  LAG
(amount, 1) OVER(ORDER BY sale_date) AS prev_amount,
  CASE

    WHEN
 amount > LAG(amount, 1) OVER(ORDER BY sale_date) THEN &#x27;上涨&#x27;
    WHEN
 amount < LAG(amount, 1) OVER(ORDER BY sale_date) THEN &#x27;下降&#x27;
    ELSE
 &#x27;持平&#x27;
  END
 AS trend
FROM
 daily_sales;

这些需求,如果不用窗口函数,不是不能做,是会写得更丑、更绕、更难维护。


九、初学者最容易踩的 5 个坑

1. 把 PARTITION BY 当成 GROUP BY

记住:

  • • GROUP BY:会减少结果行数
  • • PARTITION BY:不会减少结果行数

别混。

2. 排名函数没写 ORDER BY

像 ROW_NUMBER()RANK() 这种函数,不写排序基本等于耍流氓。

你不指定顺序,数据库怎么知道谁第一?

3. 取 Top N 时选错函数

  • • 只要唯一编号:ROW_NUMBER()
  • • 允许并列且跳号:RANK()
  • • 允许并列且不跳号:DENSE_RANK()

别无脑都写 ROW_NUMBER()

4. 忘了窗口函数是在 SELECT 结果阶段计算

所以你通常不能直接在  里过滤窗口函数结果,要包一层子查询或用 QUALIFY(如果数据库支持)。

错误示意:

SELECT
  name,
  ROW_NUMBER
() OVER(PARTITION BY department ORDER BY salary DESC) AS rn
FROM
 employees
WHERE
 rn <= 3;

这在很多数据库里会报错。

正确方式:

SELECT *
FROM
 (
  SELECT

    name,
    ROW_NUMBER
() OVER(PARTITION BY department ORDER BY salary DESC) AS rn
  FROM
 employees
) t
WHERE
 rn <= 3;

5. 不理解默认窗口范围

尤其是 SUM()AVG() 这种带 ORDER BY 的窗口函数,很多数据库会有默认 frame 行为。

你如果发现结果和预期不一致,就要看看是不是该显式写:

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

对于初学者来说,这一层先记住一句就够了:

只要你在做累计值,最好知道数据库默认 frame 是怎么处理的。


十、一套最实用的学习路径,别乱学

如果你想真正掌握 SQL 窗口函数,建议按下面顺序学:

第一步:先理解它和 GROUP BY 的区别

如果这一步不清楚,后面都会糊。

第二步:先练 ROW_NUMBER()RANK()DENSE_RANK()

因为这是最容易出现在题目和业务里的。

第三步:练累计求和

把 SUM() OVER(ORDER BY ...) 写顺手。

第四步:练 LAG() / LEAD()

这是分析类 SQL 的核心能力之一。

第五步:再去碰 frame、滑动窗口这些高级用法

比如:

  • • 最近 7 天移动平均
  • • 最近 3 条记录求均值
  • • 滚动统计

这些更进阶,但前面没打牢,学它们就是硬啃。


十一、给你一份窗口函数速记表

排名类

  • • ROW_NUMBER():唯一编号,不管并列
  • • RANK():并列占同名,后面跳号
  • • DENSE_RANK():并列占同名,后面不跳号

聚合类

  • • SUM() OVER(...):窗口求和
  • • AVG() OVER(...):窗口平均值
  • • COUNT() OVER(...):窗口计数
  • • MAX() OVER(...) / MIN() OVER(...):窗口最大最小值

偏移类

  • • LAG():取前 N 行
  • • LEAD():取后 N 行

常见结构

-- 分组排名
ROW_NUMBER
() OVER(PARTITION BY department ORDER BY salary DESC)

-- 分组累计

SUM
(amount) OVER(PARTITION BY store_id ORDER BY sale_date)

-- 取上一行

LAG
(amount, 1) OVER(ORDER BY sale_date)

建议你把这几种写法手打 5 遍,比看 20 遍教程有用。


十二、最后一句:窗口函数不是高级炫技,是实战刚需

很多人学 SQL,总爱把窗口函数当成“高级语法”,仿佛会了就显得厉害。

其实不是。

它不是炫技工具,而是分析 SQL 的基本功。

你一旦开始做下面这些事,就根本绕不开它:

  • • 排名
  • • Top N
  • • 去重保留最新记录
  • • 同比 / 环比
  • • 累计值
  • • 趋势分析
  • • 分组统计但保留明细

说白了:

不会窗口函数,你写 SQL 只能算“能查数据”;会窗口函数,你才开始真正“分析数据”。

这个差距,不小。


结尾

如果你现在对窗口函数还有点模糊,不用慌。

先把这篇文章里的几个核心例子亲手敲一遍:

  1. 1. ROW_NUMBER() 做每组 Top N
  2. 2. SUM() OVER(ORDER BY ...) 做累计求和
  3. 3. LAG() 做和上一条记录对比

把这三类写顺了,你对窗口函数的理解会立刻从“看懂”升级到“会用”。

真正学 SQL,别只背语法。要建立场景感。

窗口函数这东西,一旦开窍,后面很多分析题你都会觉得:

就这?原来以前是在绕远路。


阅读原文:原文链接


该文章在 2026/4/14 16:14:52 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-2  粤公网安备44030602007207号