当前位置: 首页 > web >正文

浅谈 SQL 窗口函数:ROW_NUMBER() 与聚合函数的妙用

在日常开发中,我们经常会遇到这样的需求:既要保留明细数据,又要对数据进行排名、累计、分区统计。如果仅依赖传统的 GROUP BY,往往需要做多次子查询或者复杂的 JOIN,既繁琐又低效。

窗口函数(Window Function) 就是为了解决这类问题而生的。它的最大特点是:保留所有行,并在每一行的基础上增加一个“运算结果列”。


窗口函数的执行过程

可以把窗口函数的执行理解为以下三个步骤:

  1. 得到基础结果集
    先执行 FROMWHEREGROUP BYHAVING 等,形成基础结果集。此时还没有窗口函数的列。

  2. 在结果集上运算
    根据 OVER (PARTITION BY ... ORDER BY ...) 指定的分区规则和排序规则,对结果集的一组行进行计算。

  3. 合并结果
    把窗口函数的计算值作为新列,附加到结果集的每一行。最终输出就是“原始列 + 窗口函数列”。

⚡ 和 GROUP BY 的最大区别在于:

  • GROUP BY压缩行,一组只保留一行。
  • 窗口函数会保留所有行,只是多了一列运算结果。

实验准备

首先,我们创建两张用于实验的数据库表。

CREATE TABLE students (id     INT PRIMARY KEY,name   VARCHAR(50) NOT NULL,score  INT NOT NULL
);-- 插入示例数据
INSERT INTO students (id, name, score) VALUES
(1, '张三', 95),
(2, '李四', 88),
(3, '王五', 88),
(4, '赵六', 72);CREATE TABLE sales (id       INT PRIMARY KEY,region   VARCHAR(20) NOT NULL,salesman VARCHAR(50) NOT NULL,amount   INT NOT NULL
);-- 插入示例数据
INSERT INTO sales (id, region, salesman, amount) VALUES
(1, '东区', '张三', 1000),
(2, '东区', '李四', 1200),
(3, '西区', '王五', 800),
(4, '西区', '赵六', 950),
(5, '西区', '孙七', 700);

例子一:ROW_NUMBER() 全局排序

students 表信息如下:

idnamescore
1张三95
2李四88
3王五88
4赵六72

我们想给所有学生按成绩排名:

SELECT id, name, score,ROW_NUMBER() OVER (ORDER BY score DESC, id ASC) AS rn
FROM students;

结果:

idnamescorern
1张三951
2李四882
3王五883
4赵六724

其中

  • ROW_NUMBER() → 为每行分配一个顺序号;
  • ORDER BY score DESC, id ASC → 排序规则,分数高的排前面;
  • 最终每一行都多了一个 rn 列,表示它的名次。

例子二:ROW_NUMBER() 分区内排序

再来看sales 表中的信息:

idregionsalesmanamount
1东区张三1000
2东区李四1200
3西区王五800
4西区赵六950
5西区孙七700

需求:在每个区域内,按销售额从高到低排名。

SELECTid,region,salesman,amount,ROW_NUMBER() OVER (PARTITION BY regionORDER BY amount DESC) AS rn
FROM sales;

结果:

idregionsalesmanamountrn
2东区李四12001
1东区张三10002
4西区赵六9501
3西区王五8002
5西区孙七7003

其中

  • PARTITION BY region → 按区域分区,每个区域单独排名。
  • ROW_NUMBER() → 每个区域内部从 1 开始编号。
  • 最终结果集仍然包含所有行,只是多了一个“区域内排名”的列。

例子三:SUM() OVER 分区聚合

继续使用 sales 表。
需求:在保留明细行的同时,显示该销售人员所在区域的总销售额。

SELECTid,region,salesman,amount,SUM(amount) OVER (PARTITION BY region) AS region_total
FROM sales;

结果:

idregionsalesmanamountregion_total
1东区张三10002200
2东区李四12002200
3西区王五8002450
4西区赵六9502450
5西区孙七7002450

其中

  • SUM(amount) OVER (PARTITION BY region) → 在每个区域内计算总额;
  • 结果仍然返回 5 行,只是每行多了一个“区域总额”的列。

窗口函数 vs GROUP BY

特性窗口函数 (Window Functions)GROUP BY 聚合 (Aggregate)
是否保留明细行✅ 保留所有行,只是在每行后面加一个新列❌ 会压缩行,每个分组只保留一行
典型用途排名 (ROW_NUMBER)、分区统计 (SUM OVER)、累计值、移动平均等分组统计 (SUM、AVG、COUNT、MAX、MIN 等)
是否依赖 PARTITION BY可选。PARTITION BY 决定分区范围,不写则针对全表计算必须分组,语义上天然就是“按分组聚合”
ORDER BY 的作用在窗口内排序,影响计算结果(如行号、累计和)在最终结果集中排序,对聚合计算无影响
返回结果行数与原始结果集相同行数 = 分组数(通常远少于原始行数)
复杂度一般更直观,避免子查询和 JOIN,常用于分析型 SQL用于汇总报表,逻辑简单,但不能同时保留明细数据
举例SUM(amount) OVER (PARTITION BY region)SELECT region, SUM(amount) FROM sales GROUP BY region;

对比一下 GROUP BY

SELECT region, SUM(amount)
FROM sales
GROUP BY region;

结果只有 2 行:

regionsum
东区2200
西区2450

可以看到:

  • GROUP BY 会把多行压缩成一行。
  • 窗口函数则保留明细,把聚合结果“合并回去”。

所以:

  • GROUP BY:适合做汇总统计,结果行数减少。
  • 窗口函数:适合做分析计算,保留明细又能展示分组/累计/排名结果。

总结

  1. 窗口函数执行顺序:先生成基础结果集,再对结果集进行分区/排序运算,最后把结果加到每行。

  2. 保留所有行:窗口函数不会减少行数,只会增加新列。

  3. 典型应用场景

    • 排名:ROW_NUMBER()RANK()DENSE_RANK()
    • 分区统计:SUM() OVER (PARTITION BY ...)
    • 累计计算:SUM() OVER (ORDER BY ...)
    • 移动平均、窗口滑动分析

掌握窗口函数,可以极大简化 SQL 写法,让我们的代码更直观、更高效。

http://www.xdnf.cn/news/19581.html

相关文章:

  • 机器视觉opencv教程(三):形态学变换(腐蚀与膨胀)
  • 利用爬虫获取淘宝商品信息,参数解析
  • 基于单片机停车场管理系统/车位管理/智慧停车系统
  • 小迪自用web笔记22
  • Java线程池使用入门
  • uvm验证环境中struct(结构体)和class的区别与联系
  • 基于单片机老人防丢失防摔倒系统/老人健康状态检测系统
  • CMake⼯程指南-3
  • [光学原理与应用-361]:ZEMAX - 分析 - 像差分析
  • KingbaseES V009版本发布:国产数据库的新飞跃
  • 基于全参考图的质量评价均方误差MSE、峰值信噪比PSNR
  • [特殊字符] Rust概述:系统编程的革命者
  • 力扣(LeetCode) ——101. 对称二叉树(C语言)
  • Vue Router 嵌套路由与布局系统详解:理解 component = router-view 的核心概念
  • 接口测试总结-含接口测试和前端测试的区别与比较
  • Matlab自学笔记六十六:求解带参数的不等式
  • 国庆福建霞浦游
  • Linux 启动传参
  • 使用AdaLoRA 自适应权重矩阵微调大模型介绍篇
  • Docker一小时快速上手(附报错解决方式)
  • 【MLLM】具有长期记忆的多模态智能体框架M3-Agent
  • 《信息学奥林匹克辞典》中的一个谬误
  • Java异常处理完全指南:从入门到精通
  • 安装proteus,并实现stm32仿真
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘pydantic’问题
  • 从 ETL 到 ELT 再到 EAI:AI 如何重塑数据处理
  • 小迪安全v2023学习笔记(七十五讲)—— 验证码安全插件识别攻击利用宏命令
  • 设计模式在Java中的应用:从单例模式到工厂模式的全面解析!
  • 计算机网络总览
  • 使用 GLSL 实现真实自然的纹理混合技术详解