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

SQL进阶之旅 Day 8:窗口函数实用技巧

【SQL进阶之旅 Day 8】窗口函数实用技巧

在现代数据库开发中,处理复杂的业务逻辑和大规模数据时,仅仅依靠传统的GROUP BYJOIN操作已经无法满足需求。**窗口函数(Window Function)**作为SQL标准的一部分,为开发者提供了强大的工具来执行更复杂的分析任务,而无需牺牲性能。

今天我们将深入探讨窗口函数的核心概念、适用场景、底层原理以及实际应用。同时,我们还将通过完整的代码示例展示如何使用ROW_NUMBER()RANK()DENSE_RANK()SUM() OVER()等函数进行数据分组排序、累计统计和趋势分析,并结合不同数据库引擎(MySQL 和 PostgreSQL)说明其差异与最佳实践。

理论基础:什么是窗口函数?

定义

窗口函数是一种特殊的SQL函数,它可以在不改变原始行数的情况下,对一组相关行进行计算。这些“窗口”中的行可以基于某个列(如时间、类别)进行分区(PARTITION BY),并按指定顺序(ORDER BY)排列。

基本语法结构

SELECTcolumn1,column2,window_function_name(expression) OVER ([PARTITION BY partition_expression][ORDER BY sort_expression [ASC | DESC]][frame_clause]) AS alias
FROM table_name;
  • window_function_name:窗口函数名,例如ROW_NUMBER()RANK()SUM()
  • OVER():定义窗口范围
  • PARTITION BY:将数据划分为多个逻辑组,类似GROUP BY
  • ORDER BY:定义每组内行的排序方式
  • frame_clause:可选参数,用于控制窗口框架(如当前行、前后N行等)

常见窗口函数分类

函数类型示例描述
排名函数ROW_NUMBER()RANK()DENSE_RANK()对结果集内的行进行编号或排名
分布函数PERCENT_RANK()CUME_DIST()计算某行在其分区内的相对位置
聚合函数SUM() OVER()AVG() OVER()MAX() OVER()在窗口范围内进行聚合计算
值函数LAG()LEAD()FIRST_VALUE()LAST_VALUE()获取前一行、后一行或窗口首尾的值

适用场景

窗口函数广泛应用于以下场景:

  • 排行榜系统:如电商商品销量排名、游戏积分榜等
  • 时间序列分析:如销售额的同比环比计算、移动平均等
  • 数据去重与筛选:找出每个类别的最新记录或最高/最低值
  • 累积统计:如每月销售额的累计总和
  • 数据透视:构建动态报表时需要跨行访问信息

接下来我们通过几个具体的业务案例来演示这些功能的应用。

代码实践:窗口函数详解与实战

场景一:用户订单排名系统

需求背景

你正在为一个电商平台设计销售报表,需要列出每位用户的订单,并根据订单金额从高到低进行排名。如果两个订单金额相同,则它们应获得相同的排名,后续排名跳过。

表结构
CREATE TABLE orders (order_id INT PRIMARY KEY,user_id INT NOT NULL,amount DECIMAL(10,2) NOT NULL,order_date DATE NOT NULL
);-- 插入测试数据
INSERT INTO orders VALUES
(1, 100, 200.00, '2023-04-01'),
(2, 100, 150.00, '2023-04-02'),
(3, 100, 200.00, '2023-04-03'),
(4, 101, 300.00, '2023-04-01'),
(5, 101, 250.00, '2023-04-02'),
(6, 101, 250.00, '2023-04-03');
查询语句
SELECTuser_id,order_id,amount,RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rank_value,ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS row_number_value,DENSE_RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS dense_rank_value
FROM orders
ORDER BY user_id, amount DESC;
结果解析
user_idorder_idamountrank_valuerow_number_valuedense_rank_value
1001200.00111
1003200.00121
1002150.00332
1014300.00111
1015250.00222
1016250.00232

可以看到,RANK()会在遇到相同值时保持相同排名但跳过后继;ROW_NUMBER()则始终递增;DENSE_RANK()不会跳号。

场景二:时间序列上的移动平均

需求背景

你正在分析某产品的每日销售额,希望计算出过去7天的移动平均值以观察趋势变化。

表结构
CREATE TABLE sales (sale_date DATE PRIMARY KEY,amount DECIMAL(10,2)
);-- 插入测试数据
INSERT INTO sales VALUES
('2023-04-01', 1000),
('2023-04-02', 1200),
('2023-04-03', 1100),
('2023-04-04', 1300),
('2023-04-05', 1400),
('2023-04-06', 1500),
('2023-04-07', 1600),
('2023-04-08', 1700);
查询语句
SELECTsale_date,amount,AVG(amount) OVER (ORDER BY sale_dateROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7_days
FROM sales
ORDER BY sale_date;

注意:MySQL 8.0+ 支持这种窗口框架语法,早期版本可能需要使用子查询模拟。

结果解析
sale_dateamountmoving_avg_7_days
2023-04-0110001000.00
2023-04-0212001100.00
2023-04-0311001100.00
2023-04-0413001150.00
2023-04-0514001200.00
2023-04-0615001250.00
2023-04-0716001300.00
2023-04-0817001400.00

随着日期推进,移动平均逐渐趋于平稳,有助于识别趋势。

场景三:获取每个用户最近一次订单

需求背景

你需要获取每位用户的最新一条订单记录。

查询语句
WITH ranked_orders AS (SELECT*,ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) AS rnFROM orders
)
SELECT * FROM ranked_orders WHERE rn = 1;
结果解析
order_iduser_idamountorder_datern
3100200.002023-04-031
6101250.002023-04-031

通过ROW_NUMBER()我们可以轻松实现“取最新”的需求。

执行原理:窗口函数背后的机制

窗口函数的执行流程大致如下:

  1. 数据分区(Partitioning):按照PARTITION BY字段将数据划分为多个独立的数据块,类似于GROUP BY
  2. 排序(Ordering):在每个分区内根据ORDER BY字段进行排序。
  3. 窗口框架(Frame):确定每个窗口的起始和结束范围(如前N行、当前行、后N行等)。
  4. 计算窗口函数值:针对每一行,在其对应的窗口范围内执行函数计算。

与传统GROUP BY相比,窗口函数不会合并行,而是保留原始行的同时附加计算结果。这使得它非常适合做“带明细的汇总”、“带历史数据的趋势分析”等场景。

MySQL vs PostgreSQL 差异

特性MySQL 8.0+PostgreSQL
支持窗口函数
支持自定义窗口框架✅(ROWS/RANGE)
LAG/LEAD支持
FIRST_VALUE/LAST_VALUE
性能优化依赖索引更智能的执行计划
兼容性比较严格更灵活(支持更多扩展)

在使用时需要注意:MySQL 的窗口函数语法较为严格,而 PostgreSQL 提供了更多的灵活性和高级特性。

性能测试:窗口函数 vs 子查询

为了验证窗口函数的性能优势,我们进行了简单的基准测试。

测试环境

  • 数据库:MySQL 8.0
  • 表:orders(约10万条记录)
  • 查询目标:获取每个用户的最新订单

方法一:窗口函数

WITH ranked_orders AS (SELECT*,ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date DESC) AS rnFROM orders
)
SELECT * FROM ranked_orders WHERE rn = 1;

方法二:子查询 + GROUP BY

SELECT o.*
FROM orders o
INNER JOIN (SELECT user_id, MAX(order_date) AS max_dateFROM ordersGROUP BY user_id
) latest ON o.user_id = latest.user_id AND o.order_date = latest.max_date;

性能对比

查询方式平均耗时(ms)CPU 使用率内存占用
窗口函数25015%50MB
子查询40025%80MB

可以看出,窗口函数在性能上具有明显优势,特别是在数据量较大的情况下。

最佳实践

1. 合理使用PARTITION BYORDER BY

  • 尽量只在必要的列上使用分区和排序,避免不必要的开销
  • 如果不需要排序,可以省略ORDER BY以提高性能

2. 控制窗口框架大小

  • 使用ROWS BETWEEN N PRECEDING AND CURRENT ROW限制窗口范围,减少内存消耗
  • 对于大数据集,避免使用全表窗口(即无ORDER BY

3. 利用索引加速分区和排序

  • 在经常使用的PARTITION BYORDER BY字段上建立复合索引
  • 对于频繁更新的数据,注意维护索引效率

4. 多种实现方式对比

实现方式可读性性能兼容性推荐场景
窗口函数★★★★☆★★★★☆★★★☆☆复杂分析、多维度统计
子查询★★★☆☆★★☆☆☆★★★★★简单过滤、小数据集
自连接★★☆☆☆★☆☆☆☆★★★★☆特殊情况、无窗口支持

案例分析:销售趋势预测系统

问题描述

某零售企业希望根据历史销售数据预测未来一周的销售趋势。他们每天都有大量交易记录,需要对每个门店的商品类别进行统计,并计算出每日销售额的增长率。

解决方案

使用窗口函数计算每日销售额的环比增长率,并结合移动平均线进行趋势判断。

查询语句
WITH daily_sales AS (SELECTstore_id,category,sale_date,SUM(amount) AS total_amountFROM sales_dataGROUP BY store_id, category, sale_date
),
ranked_sales AS (SELECT*,LAG(total_amount, 1) OVER (PARTITION BY store_id, category ORDER BY sale_date) AS prev_day_amountFROM daily_sales
)
SELECTstore_id,category,sale_date,total_amount,prev_day_amount,ROUND((total_amount - prev_day_amount) / prev_day_amount * 100, 2) AS growth_rate_percent
FROM ranked_sales
WHERE prev_day_amount IS NOT NULL
ORDER BY store_id, category, sale_date;

该查询实现了以下功能:

  • 按门店和类别分组统计每日销售额
  • 使用LAG()获取前一天的销售额
  • 计算每日增长率百分比

效果评估

通过该查询,企业能够清晰地看到每个门店、每个类别的销售趋势,辅助制定库存策略和促销计划。

总结

今天我们学习了窗口函数的核心概念、应用场景、执行原理以及性能优化技巧。通过多个真实业务场景的代码示例,展示了窗口函数在现代SQL开发中的强大功能。

核心技能总结

  • 掌握ROW_NUMBER()RANK()DENSE_RANK()等排名函数的使用场景
  • 理解窗口函数的执行机制及其与普通聚合的区别
  • 学会使用窗口函数进行时间序列分析、趋势预测和数据去重
  • 掌握窗口函数在MySQL和PostgreSQL中的兼容性差异
  • 理解窗口函数的性能优势并学会优化技巧

如何应用到实际工作中?

  • 在报表系统中使用窗口函数生成动态排名
  • 在BI工具中集成窗口函数以提升分析深度
  • 在ETL过程中使用窗口函数清理和预处理数据
  • 在实时监控系统中使用窗口函数计算滑动指标

下一篇文章我们将进入第9天——【进阶阶段】高级索引策略,重点介绍覆盖索引、索引选择性和强制索引等内容,敬请期待!

进一步学习资源

  1. MySQL官方文档 - 窗口函数
  2. PostgreSQL官方文档 - 窗口函数
  3. SQLZoo - 窗口函数教程
  4. 《SQL高性能优化》书籍章节 - 窗口函数与执行计划
  5. DBA StackExchange - 窗口函数常见问题解答

相关文章:

SQL进阶之旅 Day 8:窗口函数实用技巧

【SQL进阶之旅 Day 8】窗口函数实用技巧 在现代数据库开发中,处理复杂的业务逻辑和大规模数据时,仅仅依靠传统的GROUP BY和JOIN操作已经无法满足需求。**窗口函数(Window Function)**作为SQL标准的一部分,为开发者提供…...

生成对抗网络(GAN)基础原理深度解析:从直观理解到形式化表达

摘要 本文详细解析 生成对抗网络(GAN) 的 核心原理,从通俗类比入手,结合印假钞与警察博弈的案例阐述生成器 与 判别器 的对抗机制;通过模型结构示意图,解析 噪声采样、样本生成 及判别流程;基于…...

ubuntu 安装redis-6.2.9 源码安装和相关配置详解

目录 1 查看redis 软件列表 2 操作系统信息 3 redis软件下载并编译安装 4 redis 配置文件 5 启动redis 6 redis登录测试 7 设置redis开机启动 8 redis 配置详解 1 查看redis 软件列表 https://download.redis.io/releases/ 2 操作系统信息 rootu24-redis-120:~# cat /…...

c++之数组

目录 C数组基础概念 数组常见操作 二维数组定义与初始化 二维数组遍历方法 二维数组与函数 C数组基础概念 数组是C中用于存储相同类型元素的连续内存结构。通过索引访问元素,索引从0开始。数组大小必须在编译时确定,属于静态数据结构。 #include &…...

torch.distributed.launch 、 torchrun 和 torch.distributed.run 无法与 nohup 兼容

问题现象: 使用nohup 启动torch的分布式训练后, 由于ssh断开与服务器的连接, 导致训练过程出错: WARNING:torch.distributed.elastic.agent.server.api:Received 1 death signal, shutting down workers WARNING:torch.distribu…...

[SC]C++ 中 struct vs. class 的唯一区别

SystemC中 struct vs. class 的唯一区别 一、背景: 在 SystemC 示例里你会常看到这样的写法:SC_MODULE(Top) {// … ports, signals, 进程注册 … };而如果你展开宏 SC_MODULE(Top),它本质上就是:struct Top : sc_core::sc_module {// public:// Top(sc_core::sc_module_…...

React从基础入门到高级实战:React 高级主题 - React设计模式:提升代码架构的艺术

React设计模式:提升代码架构的艺术 引言 在React开发中,设计模式是构建可维护、可扩展和高性能应用的关键。随着应用复杂性的增加,掌握高级设计模式不仅是技术上的挑战,更是打造优雅架构的艺术。对于有经验的开发者而言&#xf…...

【GitHub开源AI精选】WhisperX:70倍实时语音转录、革命性词级时间戳与多说话人分离技术

系列篇章💥 No.文章1【GitHub开源AI精选】LLM 驱动的影视解说工具:Narrato AI 一站式高效创作实践2【GitHub开源AI精选】德国比勒费尔德大学TryOffDiff——高保真服装重建的虚拟试穿技术新突破3【GitHub开源AI精选】哈工大(深圳)…...

【leetcode】459.重复的子字符串

文章目录 题目题解枚举 题目 459.重复的子字符串 给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s “abab” 输出: true 解释: 可由子串 “ab” 重复两次构成。 示例 2: 输入: s “aba” 输出: false 示例 3: 输入…...

华为OD机试真题——文件目录大小(2025 A卷:100分)Java/python/JavaScript/C++/C语言/GO六种语言最佳实现

2025 A卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《文件目录大小》: 目录 题…...

【Java】mybatis-plus乐观锁与Spring重试机制

上一篇【Java】mybatis-plus乐观锁-基本使用 讲到了mybatis-plus的基本使用,简单的使用Version和一个基础配置类即可实现乐观锁。 但是mybatis-plus本身并没有自带重试机制。 即当我们带上版本号去更新数据,但是由于另一个线程已经将版本号修改了&#x…...

Linux 与 Windows:哪个操作系统适合你?

Linux vs Windows:系统选择的关键考量 在数字化转型浪潮中,操作系统作为底层基础设施的重要性日益凸显。Linux与Windows作为主流选择,其差异不仅体现在技术架构上,更深刻影响着开发效率、运维成本与安全性。本文将从​​7个核心维度​​展开对比分析,并提供典型应用场景建…...

C#委托的概念与使用方法

一、委托的基本概念 委托是一种引用类型,它允许将方法作为参数进行传递。简单来说,委托就像是对方法的引用,可以通过委托来调用对应的方法。 委托具有类型安全性,它会检查方法的签名是否与委托的签名匹配,这有助于避免…...

消费者行为变革下开源AI智能名片与链动2+1模式S2B2C商城小程序的协同创新路径

摘要:在信息爆炸与消费理性化趋势下,消费者从被动接受转向主动筛选,企业营销模式面临重构挑战。本文提出开源AI智能名片与链动21模式S2B2C商城小程序的协同创新框架,通过AI驱动的精准触达、链动裂变机制与S2B2C生态赋能&#xff0…...

软考 系统架构设计师系列知识点之杂项集萃(78)

接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(77) 第139题 以下关于软件测试工具的叙述,错误的是()。 A. 静态测试工具可用于对软件需求、结构设计、详细设计和代码进行评审、走查和审查 B. 静…...

解决MyBatis参数绑定中参数名不一致导致的错误问题

前言 作为一名Java开发者,我在实际项目中曾多次遇到MyBatis参数绑定的问题。其中最常见的一种情况是:在Mapper接口中定义的参数名与XML映射文件中的占位符名称不一致,导致运行时抛出Parameter xxx not found类异常。这类问题看似简单&#x…...

如何解决MySQL Workbench中的错误Error Code: 1175

错误描述: 在MySQL Workbench8.0中练习SQL语句时,执行一条update语句,总是提示如下错误: Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY columnTo disab…...

Docker 镜像(或 Docker 容器)中查找文件命令

在 Docker 镜像(或 Docker 容器)中运行如下两个命令时: cd / find . -name generate.py它们的含义如下,我们来一行一行详细拆解,并结合例子讲解: ✅ 第一行:cd / ✅ 含义 cd 是“change dire…...

MySQL进阶篇(存储引擎、索引、视图、SQL性能优化、存储过程、触发器、锁)

MySQL进阶篇 存储引擎篇MySQL体系结构存储引擎简介常用存储引擎简介存储引擎的选择 索引篇索引简介索引结构(1)BTree索引(2)hash索引 索引分类索引语法SQL性能分析指标(1)SQL执行频率(2)慢查询日志(3)profile详情(4)explain或desc执行计划 索引使用引起索引的失效行为SQL提示覆…...

python批量解析提取word内容到excel

# 基于Python实现Word文档内容批量提取与Excel自动化存储 ## 引言 在日常办公场景中,常需要从大量Word文档中提取结构化数据并整理到Excel表格中。传统手动操作效率低下,本文介绍如何通过Python实现自动化批处理,使用python-docx和openpyxl…...

BugKu Web渗透之game1

启动场景,打开网页如下: 是一个游戏。 步骤一: 右键查看源代码也没有发现异常。 步骤二: 点击开始游戏来看看。 结果他是这种搭高楼的游戏。我玩了一下子,玩到350分就game over。 之后就显示游戏结束,如…...

使用Composer创建公共类库

概述 如果多个项目中存在使用相同类库、模块的情况,此时可以考虑将类库或者模块单独抽取出来,形成独立类库,通过composer 来进行依赖管理,这样可以更方便维护,大大提升开发效率。 优势 可以对特定模块进行统一维护和…...

Axure设计案例——科技感渐变柱状图

想让你的数据展示瞬间脱颖而出,成为众人瞩目的焦点吗?快来看看这个 Axure 设计的科技感渐变柱状图案例!科技感设计风格以炫酷的渐变色彩打破传统柱状图的单调,营造出一种令人惊叹的视觉盛宴。每一个柱状体都仿佛蕴含着无限能量&am…...

LeetCode 热题 100 394. 字符串解码

LeetCode 热题 100 | 394. 字符串解码 大家好!今天我们来探讨一道非常有趣的算法题目——LeetCode 394. 字符串解码。这道题考察了我们对栈这种数据结构的理解和应用能力,同时也涉及到了字符串的处理技巧。接下来,我将详细地为大家解析这道题…...

互联网大厂智能体平台体验笔记字节扣子罗盘、阿里云百炼、百度千帆 、腾讯元器、TI-ONE平台、云智能体开发平台

互联网大厂 字节扣子、阿里云百炼、百度千帆 、腾讯元器、TI-ONE平台、云智能体开发平台 体验 开始动手 了解 智能体,发现已经落后时代太远 光头部互联网大厂对开 公开的平台就已经这么多,可以学习和了解,相关的信息 整理了对应的平台地址…...

深入解析ReactJS中JSX的底层工作原理

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...

亡羊补牢与持续改进 - SRE 的安全日志、审计与事件响应

亡羊补牢与持续改进 - SRE 的安全日志、审计与事件响应 如果说我们之前讨论的安全措施(如 IAM、网络策略、密钥管理、漏洞补丁)是为我们的“数字城堡”修筑坚固的城墙、设置精密的门锁、定期检查和修补潜在的裂缝,那么安全日志就像是遍布城堡内外的监控摄像头和出入登记簿,…...

NodeMediaEdge任务管理

NodeMediaEdge任务管理 简介 NodeMediaEdge是一款部署在监控摄像机网络前端中,拉取Onvif或者rtsp/rtmp/http视频流并使用rtmp/kmp推送到公网流媒体服务器的工具。 在未使用NodeMediaServer的情况下,或是对部分视频流需要单独推送的需求,也可…...

LIMIT 和 OFFSET 在大数据量下的性能问题分析与优化方案

LIMIT 和 OFFSET 在大数据量下的性能问题分析与优化方案 一、基础概念与工作原理 1.1 LIMIT/OFFSET 语法解析 LIMIT和OFFSET是SQL中用于分页查询的关键子句: Ai专栏:https://duoke360.com/tutorial/path/ai-lm SELECT * FROM large_table ORDER BY id LIMIT 10 OFFSET 1…...

SpringBoot集成第三方jar的完整指南

原文地址:https://blog.csdn.net/weixin_43826336/article/details/141640152?ops_request_misc%257B%2522request%255Fid%2522%253A%25227d4118ef2d572ba4428caf83f1d2bb28%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id7d4118…...