窗口进阶|分区排序、累计求和,搞定复杂统计

发布时间:2026/6/10 23:56:16

窗口进阶|分区排序、累计求和,搞定复杂统计 前言上一篇我们入门了三大排名类窗口函数ROW_NUMBER/RANK/DENSE_RANK掌握了全局排名、分组 TopN 等基础场景。本篇继续深挖 MySQL 8.0 窗口函数的进阶用法聚焦窗口帧Frame、聚合型窗口函数、行间取值函数三大核心能力。在报表统计、时序数据分析、业务同比 / 环比计算中累计求和、移动平均、前后行数据对比是刚需。传统写法需要大量子查询、变量拼接代码臃肿且性能差而窗口函数结合分区、排序、窗口帧一行代码就能实现复杂统计逻辑。本篇结合电商销售、员工薪资、时序数据等真实场景从语法、规则、案例、坑点逐层拆解同时对比传统写法帮你彻底吃透窗口函数进阶能力一、本章知识点汇总窗口函数完整结构分区 排序 窗口帧Frame全解析窗口帧核心关键字ROWS/RANGE、PRECEDING/FOLLOWING/CURRENT ROW聚合函数作为窗口函数SUM/AVG/COUNT/MAX/MIN进阶用法主流实战场景全局累计、分组累计、移动求和、移动平均行间取值函数LAG/LEAD上下行数据提取环比 / 同比必备FIRST_VALUE/LAST_VALUE窗口首尾数据获取窗口函数默认帧规则与手动帧改写窗口函数与传统子查询 / 变量写法性能对比综合实战销售报表全套统计累计 均值 行间对比避坑指南 课后练习题二、各知识点详解1. 窗口函数完整语法新增窗口帧上一篇我们使用了PARTITION BY分区和ORDER BY排序完整窗口函数还包含窗口帧Frame用于精准划定当前行参与计算的数据范围这是进阶核心。sql函数名() OVER ( [PARTITION BY 字段1, 字段2] -- 分区横向拆分数据集 [ORDER BY 排序字段 ASC|DESC] -- 分区内排序 [帧单位 BETWEEN 帧起始 AND 帧结束] -- 窗口帧纵向划定计算行范围 ) AS 别名;1.1 窗口帧基础关键字表格关键字含义ROWS按物理行定位逐行计算最常用RANGE按数值范围定位同值行会合并极少用UNBOUNDED PRECEDING分区内第一行无上限n PRECEDING当前行往前 n 行CURRENT ROW当前行n FOLLOWING当前行往后 n 行UNBOUNDED FOLLOWING分区内最后一行无下限1.2 默认帧规则高频易错点若OVER()内无 ORDER BY默认帧 UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING整个分区所有行参与计算若OVER()内有 ORDER BY默认帧 UNBOUNDED PRECEDING AND CURRENT ROW分区首行到当前行累计场景默认规则排名类函数ROW_NUMBER/RANK忽略窗口帧设置帧不生效MySQL。2. 聚合窗口函数SUM/AVG/COUNT/MAX/MIN普通聚合函数搭配OVER()后从 “整组合并” 变为保留所有明细行同时附加聚合结果分为两大使用模式全分区聚合无窗口帧计算分区整体统计值逐行累计聚合搭配窗口帧实现累计、移动计算。2.1 SUM 累计求和职场最高频分为全局累计、分组累计、移动求和三大场景是日报、流水报表核心用法。全局累计全表按时间逐行累加分组累计每个分区内独立累加如每个用户、每个品类单独累计移动求和限定前后行数计算区间和如近 3 天销售额。2.2 AVG 移动平均基于窗口帧限定行范围计算周期平均值常用于股价、销量、流量等时序数据分析。2.3 COUNT/MAX/MIN 窗口用法COUNT累计计数、分区总行数统计MAX/MIN逐行获取分区内最大值、最小值如每个阶段历史最高值。3. 行间取值函数LAG / LEAD专门用于获取当前行的上一行 / 下一行数据是实现环比、同比、前后数据对比的核心函数。3.1 LAG () 语法sqlLAG(字段, 偏移量1, 默认值NULL) OVER([分区][排序])作用取当前行往前偏移 N 行的数据默认偏移量为 1上一行无数据返回 NULL可自定义默认值。3.2 LEAD () 语法sqlLEAD(字段, 偏移量1, 默认值NULL) OVER([分区][排序])作用取当前行往后偏移 N 行的数据常用于查看下一期数据、预测参考。4. 首尾取值函数FIRST_VALUE / LAST_VALUE用于提取窗口内第一行、最后一行的数据注意LAST_VALUE受默认窗口帧影响必须手动指定帧范围才能获取分区最后一行。FIRST_VALUE(字段)分区排序后取第一行数据LAST_VALUE(字段)默认仅取 “当前行”需手动设置帧为UNBOUNDED FOLLOWING才能取分区末行。5. 拓展窗口函数 VS 传统写法以 “累计求和” 为例传统写法变量 子查询代码冗长、可读性差、大表性能低仅兼容 MySQL 5.7 及以下窗口函数语法简洁、逻辑清晰、MySQL 底层优化优先推荐。三、实战环境准备我们创建电商销售表包含日期、品类、单日销售额覆盖累计、移动平均、行间对比全场景所有案例共用此表sql-- 电商每日销售表 CREATE TABLE daily_sales ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, sale_date DATE NOT NULL COMMENT 销售日期, category VARCHAR(20) NOT NULL COMMENT 商品品类, sale_amount DECIMAL(10,2) NOT NULL COMMENT 单日销售额 ) COMMENT 每日销售统计表; -- 插入测试数据2个品类连续日期模拟时序数据 INSERT INTO daily_sales (sale_date, category, sale_amount) VALUES (2026-05-01,数码,1200.00), (2026-05-02,数码,1800.00), (2026-05-03,数码,1500.00), (2026-05-04,数码,2100.00), (2026-05-01,服饰,800.00), (2026-05-02,服饰,950.00), (2026-05-03,服饰,1100.00), (2026-05-04,服饰,780.00); -- 基础查询 SELECT * FROM daily_sales ORDER BY category, sale_date;四、应用案例及结果分析案例 1SUM 全局累计求和默认窗口帧需求按日期排序计算全平台销售额逐行累计总额。sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 单日销售额, -- 全局累计默认帧 首行 - 当前行 SUM(sale_amount) OVER (ORDER BY sale_date) AS 全局累计销售额 FROM daily_sales ORDER BY sale_date;结果分析无PARTITION BY整张表为一个窗口开启ORDER BY触发默认帧规则从最早日期逐行累加每一行都会展示截止到当天的总销售额流水报表标准写法。案例 2SUM 分组累计求和PARTITION BY 累计需求按品类分区每个品类内部按日期独立累计销售额数码、服饰分开统计。sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 单日销售额, -- 分区内累计每个品类单独累加 SUM(sale_amount) OVER (PARTITION BY category ORDER BY sale_date) AS 品类累计销售额 FROM daily_sales ORDER BY category, sale_date;结果分析PARTITION BY category将数据拆分为数码、服饰两个独立窗口两个品类各自从 0 开始累计互不干扰适用场景分部门、分产品线、分区域独立统计累计数据。案例 3手动窗口帧 - 移动求和近 2 日销售额需求计算当天 前 1 天的两日移动销售额手动定义窗口帧。sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 单日销售额, -- 窗口帧往前1行 ~ 当前行共2行 SUM(sale_amount) OVER ( PARTITION BY category ORDER BY sale_date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) AS 两日移动销售额 FROM daily_sales ORDER BY category, sale_date;结果分析第一行无前置数据仅计算当前行第二行及之后自动累加前 1 行 当前行扩展ROWS BETWEEN 2 PRECEDING AND CURRENT ROW可实现近 3 日求和。案例 4AVG 移动平均时序数据分析需求计算每个品类连续 3 日移动平均销售额前 1 行 当前行 后 1 行。sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 单日销售额, ROUND( AVG(sale_amount) OVER ( PARTITION BY category ORDER BY sale_date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING ), 2 ) AS 三日移动平均值 FROM daily_sales ORDER BY category, sale_date;结果分析首尾行因缺少前置 / 后置数据仅计算现有行均值ROUND(数值,2)保留 2 位小数报表展示必备适用场景商品销量趋势、网站流量分析、金融数据统计。案例 5LAG / LEAD 行间取值环比计算基础需求查询每个品类每日销售额并获取前一日销售额、后一日销售额。sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 当日销售额, -- 获取上一行数据前1天销售额 LAG(sale_amount, 1, 0) OVER (PARTITION BY category ORDER BY sale_date) AS 前一日销售额, -- 获取下一行数据后1天销售额 LEAD(sale_amount, 1, 0) OVER (PARTITION BY category ORDER BY sale_date) AS 后一日销售额 FROM daily_sales ORDER BY category, sale_date;结果分析分区内按日期排序LAG提取上一行首行无数据填充默认值 0LEAD提取下一行末行无数据填充 0延伸基于此结果可直接计算环比增长率 (当日 - 前一日)/ 前一日。案例 6FIRST_VALUE / LAST_VALUE 首尾数据提取需求每个品类展示每日销售额、品类首日销售额、品类末日销售额。sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 单日销售额, -- 分区第一行首日销售额 FIRST_VALUE(sale_amount) OVER (PARTITION BY category ORDER BY sale_date) AS 品类首日销售额, -- 手动指定窗口帧获取分区最后一行末日销售额 LAST_VALUE(sale_amount) OVER ( PARTITION BY category ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS 品类末日销售额 FROM daily_sales ORDER BY category, sale_date;结果分析FIRST_VALUE不受默认帧影响直接取分区排序后首行LAST_VALUE必须手动设置窗口帧为全分区否则仅返回当前行高频坑点。案例 7综合实战 - 完整销售报表需求输出全维度销售报表包含单日销售额、品类累计、两日移动和、前一日销售额、品类总销售额。sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 单日销售额, -- 品类累计销售额 SUM(sale_amount) OVER (PARTITION BY category ORDER BY sale_date) AS 品类累计, -- 两日移动求和 SUM(sale_amount) OVER (PARTITION BY category ORDER BY sale_date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS 两日移动和, -- 前一日销售额 LAG(sale_amount,1,0) OVER (PARTITION BY category ORDER BY sale_date) AS 前一日销售额, -- 品类整体总销售额无排序全分区聚合 SUM(sale_amount) OVER (PARTITION BY category) AS 品类总销售额 FROM daily_sales ORDER BY category, sale_date;结果分析一条 SQL 实现 5 类统计明细 多维度聚合共存代码极简是企业运营报表标准写法。五、注意事项避坑指南牢记默认窗口帧规则有ORDER BY默认是「首行到当前行」LAST_VALUE必须手动扩展帧范围否则取值错误。ROWS 优先于 RANGE99% 业务场景使用ROWS物理行RANGE按数值分组仅特殊时序场景使用。LAG/LEAD 偏移量规范偏移量为非负整数偏移行数超过分区数据量时返回默认值建议设置 0 或 “无”。窗口函数不能用于 WHERE执行顺序限制过滤统计结果必须嵌套子查询 / CTE。分区 排序字段建议建索引大表千万级数据下复合索引可大幅提升窗口函数执行速度。区分累计与全分区聚合无ORDER BY的聚合窗口函数计算分区整体值有ORDER BY为逐行累计值。兼容版本限制所有进阶窗口函数仅 MySQL 8.0 支持5.7 及以下版本需改用变量、子查询。移动计算帧范围书写规范移动平均 / 移动求和严格使用BETWEEN 前N行 AND 后N行避免范围混乱。六、核心总结窗口函数完整结构分区 (PARTITION BY) → 排序 (ORDER BY) → 窗口帧 (ROWS/RANGE)三层控制计算范围。窗口帧核心有排序默认首行到当前行累计专用无排序默认整个分区整体聚合专用移动计算手动指定前后行范围。聚合窗口函数SUM/AVG累计、移动求和、移动平均时序报表核心COUNT/MAX/MIN累计计数、分区极值。行间取值函数LAG取上一行LEAD取下一行环比、同比计算必备FIRST_VALUE取分区首行LAST_VALUE 需手动改帧取末行。职场选型时序统计、多维度报表、行间对比一律使用窗口函数替代传统子查询与变量。一句话记忆分区横向切数据排序纵向定顺序窗口帧划计算区ROWS 行级最常用SUM 累计 LAG 取前移动平均改帧线LAST_VALUE 要注意全帧设置才靠谱。七、练习题基于daily_sales销售表完成练习贴合数据分析真实场景。题目 1按日期全局排序计算近 3 日移动平均销售额当前行 前 2 行结果保留 2 位小数。题目 2使用LAG函数计算每个品类每日销售额的环比差值当日销售额 - 前一日销售额。题目 3查询所有数据同时展示单日销售额、该品类历史最高销售额MAX 窗口函数。题目 4简述LAST_VALUE函数的使用坑点以及对应的解决方案。参考答案思路题目 1sqlSELECT sale_date AS 销售日期, sale_amount AS 单日销售额, ROUND( AVG(sale_amount) OVER ( ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ), 2 ) AS 三日移动平均 FROM daily_sales ORDER BY sale_date;题目 2sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 当日销售额, LAG(sale_amount,1,0) OVER (PARTITION BY category ORDER BY sale_date) AS 前一日销售额, sale_amount - LAG(sale_amount,1,0) OVER (PARTITION BY category ORDER BY sale_date) AS 环比差值 FROM daily_sales ORDER BY category, sale_date;题目 3sqlSELECT sale_date AS 销售日期, category AS 品类, sale_amount AS 单日销售额, MAX(sale_amount) OVER (PARTITION BY category ORDER BY sale_date) AS 品类历史最高销售额 FROM daily_sales ORDER BY category, sale_date;题目 4 参考答案坑点当OVER()子句包含ORDER BY时窗口帧默认为「分区首行到当前行」导致LAST_VALUE只能获取当前行数据无法拿到分区最后一行。 解决方案手动设置窗口帧为ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING将计算范围扩展到整个分区。

相关新闻