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

SQL示例:为什么普通聚合比窗口函数更高效?

SQL聚合查询优化分析:比较普通聚合与窗口函数在统计岗位简历数量时的性能差异。


普通聚合(GROUP BY)只需一次数据扫描和计算,内存占用低(仅维护不同job的哈希表),直接输出分组结果(如10行)。


窗口函数(OVER)虽然计算结果相同,但必须为每行数据(如100万行)计算并维护窗口聚合,需要额外排序和内存开销,最后还需DISTINCT去重。


测试表明,对于简单分组统计,普通聚合效率更高,因其避免冗余计算和大量中间结果处理。


窗口函数适用于需要保留明细数据的场景,而普通聚合更适合纯汇总需求。


SQL示例


题目

SQL283 实习广场投递简历分析(一)


描述

在牛客实习广场有很多公司开放职位给同学们投递,同学投递完就会把简历信息存到数据库里。

现在有简历信息表(resume_info),部分信息简况如下:

idjobdatenum
1C++2025-01-0253
2Python2025-01-0223
3Java2025-01-0212
4Java2025-02-0324
5C++2025-02-0323
6Python2025-02-0334
7Python2025-03-0454
8C++2025-03-0465
9Java2025-03-0492
10Java2026-01-04230

第1行表示,在2025年1月2号,C++岗位收到了53封简历

。。。

最后1行表示,在2026年1月4号,Java岗位收到了230封简历

请你写出SQL语句查询在2025年内投递简历的岗位和数量,并且按数量降序排序,以上例子查询结果如下:

jobcnt
C++141
Java128
Python111

注意:MySQL用YEAR(date),Oracle用TO_CHAR(date,'YYYY') = '2025'


解答

select distinct job, sum(num) over(partition by job) as cnt from resume_info where year(date)=2025 order by cnt desc; --使用普通聚合(更高效) SELECT job, SUM(num) AS cnt FROM resume_info WHERE YEAR(date) = 2025 GROUP BY job ORDER BY cnt DESC;

为什么普通聚合比窗口函数更高效?


普通聚合比窗口函数更高效,主要有以下几个原因:

1.只需一次数据扫描和一次计算

普通聚合

sql

SELECT job, SUM(num) AS cnt FROM resume_info WHERE YEAR(date) = 2025 GROUP BY job
  • 数据扫描1次

  • 每个job只计算1次SUM

  • 输出结果行数 = 不重复的job数量(如10行)

窗口函数

sql

SELECT DISTINCT job, SUM(num) OVER(PARTITION BY job) AS cnt FROM resume_info WHERE YEAR(date) = 2025
  • 数据扫描1次

  • 但需要为每一行都计算和维护窗口聚合

  • 中间结果行数 = 原始数据行数(如100万行)

  • 最后再用DISTINCT去重

2.内存和排序开销不同

普通聚合

  • 使用Hash Aggregate(哈希聚合)

  • 内存中维护一个哈希表,key是job

  • 直接累加,无需排序

  • 内存占用:O(不同job数量)

窗口函数

  • 需要先按PARTITION BY字段排序(隐式或显式)

  • 维护窗口缓冲区

  • 内存占用:O(数据总量) 或需要磁盘溢出

  • 额外排序开销:O(n log n)

3.实际执行计划对比

普通聚合执行计划

text

-> HashAggregate (job, SUM(num)) -> Table Scan (filter: YEAR=2025)
  • 简单直接,无额外操作

窗口函数执行计划

text

-> Unique (DISTINCT去重) ← 额外操作 -> WindowAgg (SUM OVER) ← 为每行计算 -> Sort (按job排序) ← 额外排序 -> Table Scan

4.数据量对比示例

假设有100万条简历数据,10种不同的job:

指标普通聚合窗口函数
中间结果行数10行100万行
排序开销O(100万 log 100万)
内存占用~KB级别~MB到GB级别
最终输出10行10行(去重后)

5.使用窗口函数的场景

窗口函数虽然低效,但在需要保留明细行时不可替代

sql

-- 需要每个岗位的平均薪资,同时保留每个人原数据 SELECT name, job, salary, AVG(salary) OVER(PARTITION BY job) AS avg_job_salary FROM employees

总结

  • 普通聚合:只关心每个组的汇总结果 → 用GROUP BY

  • 窗口函数:需要同时看到明细和汇总 → 用OVER()

对于你的需求(统计每个job的总num),普通聚合是正确且高效的选择。


窗口函数需要为每一行都计算和维护窗口聚合,计算结果不是相同的吗,为什么需要多次计算


这确实是个容易混淆的地方。让我详细解释:


窗口函数的核心特性

计算结果确实相同,但数据库无法提前知道每行的计算结果是否相同,所以必须为每一行都计算一次。

为什么不能只计算一次?

1.窗口函数设计为返回行级结果

sql

SELECT name, -- 张三 job, -- 工程师 salary, -- 30000 SUM(salary) OVER(PARTITION BY job) AS total_salary -- 工程师组总计: 90000 FROM employees;

预期输出:

text

name job salary total_salary 张三 工程师 30000 90000 ← 第1行 李四 工程师 35000 90000 ← 第2行(相同值,但必须出现) 王五 工程师 25000 90000 ← 第3行(相同值,但必须出现) 赵六 销售 40000 40000 ← 第4行(不同组)

数据库的问题是:它不知道最终需要输出多少行,只能老老实实地为每一行计算。

2.PARTITION BY可以非常复杂

sql

-- 滑动窗口:每组内计算累加和 SELECT date, sales, SUM(sales) OVER(PARTITION BY product ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS rolling_sum FROM sales;

这种场景下,每行的计算结果都不同,所以必须逐行计算。

3.数据库的通用实现策略

数据库的优化器为了处理各种复杂场景,采用了统一的执行模型

text

原始数据 (100万行) ↓ 【排序】按PARTITION BY字段排序 ↓ 【窗口聚合】为每一行计算 - 维护当前窗口的状态 - 每读一行,更新聚合值 - 为这一行输出结果 ↓ 输出 100万行 (每行都带有窗口聚合结果)

理论上可以优化吗?

可以,但数据库一般不做这个优化,因为:

优化方案的复杂性

sql

-- 如果数据库想做优化 -- 1. 先分析窗口函数:SUM(num) OVER(PARTITION BY job) -- 2. 发现没有ORDER BY,窗口固定 -- 3. 改为:先聚合出每个job的SUM,再回连原表 -- 等价于: WITH job_total AS ( SELECT job, SUM(num) AS cnt FROM resume_info GROUP BY job ) SELECT r.job, j.cnt FROM resume_info r JOIN job_total j ON r.job = j.job;

为什么不自动做这个优化?

  • 可能改变语义(如果有DISTINCT或复杂WHERE条件)

  • 优化器需要额外开销来判断是否值得

  • 窗口函数语法保证行为一致,优化可能引入bug

实际测试对比

假设一个简单的场景:

sql

-- 100万行数据,10个分组 SELECT job, SUM(num) OVER(PARTITION BY job) FROM resume_info;

理论上:

  • 理想优化:计算10次SUM,输出100万行相同值

  • 实际执行:扫描100万行,在内存中维护10个累加器,为100万行都输出一次

虽然结果是100万行相同的数字,但数据库必须产生100万行输出,这个过程本身就比只输出10行要慢得多。

总结

你的理解是对的:值相同,但行数不同

  • 普通聚合:输出10行,每行一个结果

  • 窗口函数:输出100万行,每行都附加一个结果(虽然很多行结果相同)

这就是为什么窗口函数更慢的原因:不是计算本身慢,而是要为每一行都产生输出,这些输出在CPU、内存、网络传输上都是成本。

http://www.jsqmd.com/news/779743/

相关文章:

  • 物联网系统设计实战:从安全架构到低功耗优化的工程实践
  • 开源大模型机器人操作评估框架:从仿真到真实世界的AI动手能力测评
  • 2026年现阶段伊宁免砸砖防水服务深度解析:一城一家防水补漏何以成为优选? - 2026年企业推荐榜
  • 泡沫动力学揭示AI学习数学原理
  • MindNLP实战:零代码迁移HuggingFace模型至昇腾NPU与MindSpore
  • 锁相环(PLL)核心原理、设计调试与应用场景全解析
  • 2026年5月新消息:昆明五华区珠宝鉴定机构专业度深度**——以昆明佳德盛奢侈品为例 - 2026年企业推荐榜
  • AI对话备份工具convx:基于Git的本地化版本控制实践
  • Python 函数签名检测:inspect 模块深度应用
  • ARM调试寄存器BRP原理与多线程调试实践
  • 2026年当下长沙推拉门夹丝夹胶玻璃采购指南:湖南福湘钢化玻璃有限公司深度解析 - 2026年企业推荐榜
  • 利用 workbuddy 小龙虾 对CodeBuddy开发历史对话 进行开发总结 提高以后的开发效果
  • 别再只接SWDIO和SWCLK了!STM32 SWD下载电路完整接线指南(含NRST、3.3V、GND详解)
  • 一种不用任何编译器和编辑器写代码方式
  • Cursor IDE AI助手深度定制:利用.mdc规则与Agent配置打造专属开发伙伴
  • AI领域工作与入门指南
  • GPAK5混合信号可编程器件:重塑嵌入式设计的硬件协处理器
  • copy-fail和dirty-frag漏洞
  • 别再只用默认密码了!手把手教你用Hydra和Burp Suite搞定SSH、Web后台的弱口令检测(附实战避坑指南)
  • 【GitHub】skillshare:一条命令同步所有 AI CLI 工具 Skills 的神器
  • 2026年5月江苏废电瓶回收行业盘点与顶尖服务商推荐 - 2026年企业推荐榜
  • 网站安全验证机制与Cloudflare防护技术
  • 锂电池装配产线机械臂路径规划与碰撞检测方法【附程序】
  • 从DeepFuse到Diffusion:7年图像融合顶会论文演进,我的私房笔记与代码实战
  • Digi ConnectCore MP13 SoM:工业级嵌入式系统模块解析
  • 2026年5月更新:河北沧州盐山陶瓷贴片耐磨管道厂家选择,实地考察这几点是关键 - 2026年企业推荐榜
  • 避开这些坑:在Slim Bootloader中集成Intel FSP时的常见配置错误与排查指南
  • 四川盛世钢联国际贸易有限公司钢管频道 -无缝管|焊管|镀锌管|直缝管 - 四川盛世钢联营销中心
  • 告别抓包失败!在雷电模拟器Android 7.1.2上搞定Xposed框架与JustTrustMe模块的保姆级教程
  • 2026年EPC合同纠纷激增,如何选择律师?上海嘉隆所王彦民成行业焦点 - 2026年企业推荐榜