网站首页 > 技术文章 正文
在SQL中处理数据时,我们会经常用到窗口函数和临时表,但用法思路不一样。窗口函数就像“实时计算器”,它不会改动原始数据的行数和结构,能直接在现有数据上,针对某一部分数据(如:按地区分的组)做计算,如:想给每笔销售标上它在本地区的排名,又不想丢掉任何一笔销售记录。这时候,用窗口函数就很合适。临时表则像“临时文件夹”,它用来存中间结果。遇到复杂计算时,如:先筛选出有效订单,再统计每个品类销量,就可以先把筛选结果存进临时表,后面直接用这个表算,不用反复查原始数据,步骤更清楚。由此可见,虽然窗口函数和临时表都是用来处理数据的,但一个是有特殊作用的函数,擅长“在原数据上直接分析”,一个是表,擅长“存中间结果”用来分步算。下面我们一起来梳理梳理窗口函数和临时表,想到哪梳到那。
一、窗口函数
1、窗口函数(Window Functions)基本概念
我们知道,窗口函数是SQL中一种特殊的函数,它能够在保持查询结果集内所有原始行的同时,即:允许你在不破坏原始行结构的情况下,对数据集的特定子集(称为"窗口")执行计算。这一点,与聚合函数(如:SUM、AVG)不同,聚合函数会将多行数据合并为单行结果,而窗口函数则为每行数据返回一个计算结果,同时保留所有原始行。聚合函数通过GROUP BY将组内多行合并为一行,窗口函数通过PARTITION BY划分“窗口”,但每行仍单独返回结果。
窗口函数基本语法结构如下:
-- 窗口函数基本语法
窗口函数名(参数) OVER(窗口定义)
语法元素解析:
- 窗口函数名:可以是内置的窗口函数(如:ROW_NUMBER、RANK、DENSE_RANK),也可以是聚合函数(如:SUM、AVG、COUNT)用作窗口函数
- 参数:函数所需的输入参数,不同函数有不同的参数要求
- OVER():关键字,用于定义窗口
- 窗口定义:指定如何划分和排序数据以形成窗口
2、窗口函数OVER()子句解析
OVER()子句是窗口函数核心,它定义了如何将数据集划分为不同窗口,并且可以基于分区、排序或特定的框架来定义窗口。
完整OVER()子句语法
OVER (
[PARTITION BY 列名1, 列名2, ...] -- 可选:将数据分区
[ORDER BY 列名1 [ASC|DESC], 列名2 [ASC|DESC], ...] -- 可选:在每个分区内排序
[ROWS | RANGE BETWEEN 起始点 AND 终点] -- 可选:定义窗口框架
)
语法元素解析:
- PARTITION BY:将整个数据集划分为多个分区(类似GROUP BY),窗口函数在每个分区内独立计算
- ORDER BY:指定每个分区内数据的排序方式,影响依赖顺序的窗口函数(如:RANK、ROW_NUMBER)的结果
- ROWS | RANGE BETWEEN ... AND ...:定义窗口框架,即:每个行的计算窗口包含哪些行
- UNBOUNDED PRECEDING:从分区第一行开始
- CURRENT ROW:当前行
- UNBOUNDED FOLLOWING:到分区最后一行结束
- ROWS:基于物理行数定义范围
- RANGE:基于逻辑范围定义(如:数值范围)
- 常用范围定义:
3、窗口函数的特点
- 保留原始行:与聚合函数不同,窗口函数不会减少结果集中的行数。每条记录都会保留在输出中,但可能会添加新的计算列。
- 灵活的数据分析:支持多种类型计算,包括但不限于聚合计算(如:SUM(), AVG())、排名计算(如:ROW_NUMBER(), RANK())、分布计算(如:CUME_DIST(), PERCENT_RANK())等等。
- 不需要GROUP BY:可以在不对数据进行分组的情况下执行复杂的分析操作。
4、窗口函数应用示例
假设我们有一个销售数据表sales,结构如下:
sale_id | product | region | sale_date | amount |
1 | A | North | 2023-01-01 | 100 |
2 | B | North | 2023-01-02 | 150 |
3 | A | South | 2023-01-03 | 200 |
4 | B | South | 2023-01-04 | 50 |
5 | A | North | 2023-01-05 | 300 |
6 | B | North | 2023-01-06 | 250 |
7 | A | South | 2023-01-07 | 150 |
8 | B | South | 2023-01-08 | 200 |
下面展示多种窗口函数用法:
SELECT
sale_id,
product,
region,
sale_date,
amount,
-- 1.不分区,计算所有销售总额
SUM(amount) OVER() AS total_all_sales,
-- 2.按地区分区,计算每个地区销售总额
SUM(amount) OVER(PARTITION BY region) AS total_region_sales,
-- 3.按产品分区并按日期排序,计算累计销售额
SUM(amount) OVER(
PARTITION BY product
ORDER BY sale_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_product_sales,
-- 4.按地区分区并按金额排序,计算每个销售在本地区排名
RANK() OVER(
PARTITION BY region
ORDER BY amount DESC
) AS region_sale_rank,
-- 5.按产品分区,计算每个产品平均销售额
AVG(amount) OVER(PARTITION BY product) AS avg_product_sales,
-- 6.计算每个销售与上一个销售金额差(按日期排序)
amount - LAG(amount, 1, 0) OVER(ORDER BY sale_date) AS amount_diff_from_prev,
-- 7.计算移动平均值(包含当前行和前1行、后1行)
AVG(amount) OVER(
ORDER BY sale_date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS moving_avg
FROM sales
ORDER BY region, product, sale_date;
查询结果说明:
- total_all_sales:所有销售记录总金额,每行都显示相同的值
- total_region_sales:每个地区总销售额,同一地区的行显示相同的值
- cumulative_product_sales:按产品分组,按日期排序累计销售额
- region_sale_rank:在每个地区内按销售金额降序排列排名
- avg_product_sales:每个产品平均销售额
- amount_diff_from_prev:当前销售与上一笔销售金额差
- moving_avg:包含当前行和相邻两行的移动平均值
二、临时表
1、临时表定义与用途
临时表(Temporary Tables)是数据库系统提供的一种特殊表结构,用于存储查询过程中的临时数据,核心价值在简化复杂查询逻辑,提升处理效率,并避免对永久表的频繁操作。
在实际应用中,当我们需要处理多层嵌套查询、复杂的数据转换或多步骤计算时。这时候,临时表就可以作为中间数据的"中转站",暂存中间结果。例如:在生成月度报表时,可能需要先过滤出有效数据,再进行聚合计算,最后与其他表关联。这时,可以用临时表存储过滤后的中间结果,使整个逻辑更清晰,同时减少重复计算。
不同数据库系统创建临时表的语法略有差异,以下是主流数据库的实现方式:
-- MySQL 创建临时表
CREATE TEMPORARY TABLE temp_sales_data (
sale_id INT,
product_name VARCHAR(50),
sale_date DATE,
amount DECIMAL(10,2)
);
-- PostgreSQL 创建临时表
CREATE TEMPORARY TABLE temp_sales_data (
sale_id INT,
product_name VARCHAR(50),
sale_date DATE,
amount DECIMAL(10,2)
) ON COMMIT PRESERVE ROWS; -- 事务提交后保留数据
-- SQL Server 创建临时表
CREATE TABLE #temp_sales_data ( -- 使用#前缀标识本地临时表
sale_id INT,
product_name VARCHAR(50),
sale_date DATE,
amount DECIMAL(10,2)
);
-- Oracle 创建临时表
CREATE GLOBAL TEMPORARY TABLE temp_sales_data (
sale_id INT,
product_name VARCHAR2(50),
sale_date DATE,
amount NUMBER(10,2)
) ON COMMIT DELETE ROWS; -- 事务提交后删除数据
语法元素解析:
- CREATE TEMPORARY TABLE(MySQL/PostgreSQL):标准的临时表创建语句
- #前缀(SQL Server):特殊标识,#表示本地临时表,##表示全局临时表
- GLOBAL TEMPORARY(Oracle):Oracle中全局临时表声明方式
- ON COMMIT子句:定义事务提交时临时表数据处理策略
- PRESERVE ROWS:保留数据
- DELETE ROWS:删除数据
2、临时表的生命周期
临时表的生命周期与创建它的数据库会话紧密绑定,只在创建它的会话期间存在,这是其临时表与永久表的最大区别。当会话结束(正常断开连接或异常终止)时,临时表会被数据库自动清理,无需手动删除。
以下示例展示的是临时表在会话中的生命周期:
-- 会话1:创建临时表并插入数据
CREATE TEMPORARY TABLE temp_user_sessions (
session_id VARCHAR(36) PRIMARY KEY,
user_id INT,
login_time DATETIME,
ip_address VARCHAR(45)
);
-- 向临时表插入数据
INSERT INTO temp_user_sessions
VALUES
('a1b2c3d4', 1001, '2023-10-01 08:30:00', '192.168.1.1'),
('e5f6g7h8', 1002, '2023-10-01 09:15:00', '192.168.1.2');
-- 查询临时表数据(此时可以正常查询)
SELECT * FROM temp_user_sessions;
-- 会话1结束(断开数据库连接)
-- 临时表temp_user_sessions被自动删除
-- 新会话2:尝试查询已删除的临时表
SELECT * FROM temp_user_sessions;
-- 结果:会抛出"表不存在"的错误
会话隔离性示例:
-- 会话A创建并插入数据
CREATE TEMPORARY TABLE temp_scores (student_id INT, score INT);
INSERT INTO temp_scores VALUES (1, 90), (2, 85);
-- 会话B尝试访问会话A的临时表
SELECT * FROM temp_scores;
-- 结果:会抛出"表不存在"的错误(临时表仅对创建它的会话可见)
语法元素解析:
- 会话隔离性:临时表具有严格的会话隔离,其他会话无法访问
- 自动清理机制:无需DROP TABLE语句,数据库会在会话结束时自动清理
- 生命周期控制:
- 会话级临时表:随会话结束而删除(大多数数据库默认)
- 事务级临时表:随事务结束而删除(需显式指定,如:Oracle的ON COMMIT DELETE ROWS)
3、临时表的特点
- 持久化中间结果:临时表允许你将查询的部分结果存储在一个表中,以便后续查询可以使用这个结果。这在处理复杂查询时非常有用,特别是当查询涉及多个步骤时。
- 完整的表功能:临时表具有普通表的所有特性,比如:索引、约束等等(大多数数据库中,临时表支持索引和约束,功能与永久表一致),因此可以对其进行任何你可以对普通表执行的操作。
- 生命周期短:仅存在于创建它的数据库会话中,会话结束后自动删除。
4、临时表应用示例:电商订单数据分析
下面展示如何在复杂查询中使用临时表:
-- 场景:分析2023年第四季度各品类top3畅销商品
-- 1. 创建临时表存储第四季度订单数据
CREATE TEMPORARY TABLE temp_q4_orders (
order_id INT,
product_id INT,
category_id INT,
product_name VARCHAR(100),
quantity INT,
order_date DATE,
total_amount DECIMAL(12,2)
);
-- 2. 向临时表插入筛选后的数据
INSERT INTO temp_q4_orders
SELECT
o.order_id,
oi.product_id,
p.category_id,
p.product_name,
oi.quantity,
o.order_date,
oi.quantity * oi.unit_price AS total_amount
FROM
orders o
JOIN
order_items oi ON o.order_id = oi.order_id
JOIN
products p ON oi.product_id = p.product_id
WHERE
o.order_date BETWEEN '2023-10-01' AND '2023-12-31'
AND o.status = 'completed'; -- 只考虑已完成订单
-- 3. 创建第二个临时表存储品类销售汇总
CREATE TEMPORARY TABLE temp_category_sales (
category_id INT,
product_id INT,
product_name VARCHAR(100),
total_quantity INT,
total_revenue DECIMAL(12,2)
);
-- 4. 计算各商品销售数据
INSERT INTO temp_category_sales
SELECT
category_id,
product_id,
product_name,
SUM(quantity) AS total_quantity,
SUM(total_amount) AS total_revenue
FROM
temp_q4_orders
GROUP BY
category_id, product_id, product_name;
-- 5. 使用临时表结果获取最终分析报告
SELECT
c.category_name,
t.product_name,
t.total_quantity,
t.total_revenue,
RANK() OVER (PARTITION BY t.category_id ORDER BY t.total_revenue DESC) AS sales_rank
FROM
temp_category_sales t
JOIN
categories c ON t.category_id = c.category_id
QUALIFY
sales_rank <= 3; -- 获取每个品类的前三名
-- 会话结束后,上述两个临时表自动删除,无需手动清理
以上展示了临时表在复杂数据分析中的应用:通过分步处理,将复杂查询分解为多个简单步骤,每个步骤的结果存储在临时表中,既提高了查询可读性,也可能提升执行效率(数据库可以对临时表进行针对性优化)。
三、窗口函数与临时表的主要区别
特性 | 窗口函数 | 临时表 |
数据处理 | 在现有数据上执行额外计算而不改变行结构 | 创建一个新的表来存储和处理数据 |
行数变化 | 不减少结果集内的行数 | 临时表本身的行数由插入的中间结果决定,可能与原始表行数不同(可多可少),而最终结果集的行数由使用临时表的查询决定 |
使用场景 | 需要在保持所有原始数据的同时添加汇总信息 | 当需要保存中间结果以供后续查询使用时 |
生命周期 | 查询级(仅在当前查询有效):若窗口函数用于子查询或CTE中,其作用范围限于该子查询/CTE 的执行周期,随查询结束而失效 | 会话级(在整个会话期间都存在) |
目标 | 提供更复杂的数据分析能力 | 存储中间结果以便于管理和优化查询 |
总的来说,窗口函数以 “不改变原始数据结构” 为核心优势,擅长实时多维分析;临时表则靠 “持久化中间结果” 简化复杂流程,适合分步处理。实际应用中,二者并非对立,可结合在一起使用(如:临时表存储基础数据,窗口函数做深度分析)。所以,最后来一枚大彩蛋:以下是一个窗口函数与临时表结合使用的具体示例,展示两者如何协同工作解决复杂的数据处理问题。
四、窗口函数和临时表的协同工作示例
假设我们需要分析某电商平台2023年各季度的销售数据,包括每个产品的季度销售额、累计销售额、季度排名以及与上一季度的增长率。
-- 场景:电商平台销售数据分析,需计算各产品季度销售情况、累计销售及增长率
-- 1.创建临时表存储基础销售数据(第一阶段:数据提取与清洗)
CREATE TEMPORARY TABLE temp_quarterly_sales (
product_id INT,
product_name VARCHAR(100),
quarter INT, -- 1-4表示四个季度
total_sales DECIMAL(12,2), -- 季度销售总额
sale_count INT -- 销售数量
);
-- 2.向临时表插入预处理数据
INSERT INTO temp_quarterly_sales
SELECT
p.product_id,
p.product_name,
EXTRACT(QUARTER FROM o.order_date) AS quarter,
SUM(oi.quantity * oi.unit_price) AS total_sales,
COUNT(DISTINCT o.order_id) AS sale_count
FROM
products p
JOIN
order_items oi ON p.product_id = oi.product_id
JOIN
orders o ON oi.order_id = o.order_id
WHERE
EXTRACT(YEAR FROM o.order_date) = 2023 -- 只分析2023年数据
AND o.status = 'completed' -- 只考虑已完成订单
GROUP BY
p.product_id, p.product_name, EXTRACT(QUARTER FROM o.order_date)
ORDER BY
p.product_id, quarter;
-- 3.使用窗口函数对临时表数据进行多维度分析(第二阶段:深度分析)
SELECT
product_id,
product_name,
quarter,
total_sales,
sale_count,
-- 计算每个产品的累计销售额(按季度累加)
SUM(total_sales) OVER(
PARTITION BY product_id
ORDER BY quarter
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_sales,
-- 计算每个季度内产品的销售额排名
RANK() OVER(
PARTITION BY quarter
ORDER BY total_sales DESC
) AS quarterly_rank,
-- 计算与上一季度相比的销售额增长率
ROUND(
(total_sales - LAG(total_sales, 1) OVER(
PARTITION BY product_id
ORDER BY quarter
)) / LAG(total_sales, 1) OVER(
PARTITION BY product_id
ORDER BY quarter
) * 100, 2
) AS growth_rate_percent,
-- 计算每个产品在全年总销售额中的占比
total_sales / SUM(total_sales) OVER(PARTITION BY product_id) * 100 AS quarterly_contribution
FROM
temp_quarterly_sales
ORDER BY
product_id, quarter;
-- 4.会话结束后,临时表temp_quarterly_sales自动删除
示例解析
这个示例展示了临时表与窗口函数的结合使用:
1、临时表的作用:
- 首先通过temp_quarterly_sales临时表存储预处理后的季度销售数据
- 将复杂的多表连接和过滤操作结果持久化,简化后续分析步骤
- 避免重复计算相同的基础数据,提高查询效率
2、窗口函数的作用:
- 在临时表基础上进行多维度分析,无需再次关联原始表
- 使用SUM() OVER()计算累计销售额,追踪产品全年销售趋势
- 用RANK() OVER()实现季度内的产品排名,识别各季度畅销产品
- 通过LAG()函数计算环比增长率,分析销售波动情况
- 计算每个季度销售额占全年的比例,评估季度贡献度
3、协同优势:
- 临时表解决了"数据预处理和复用"问题
- 窗口函数解决了"在保留原始数据结构的同时进行复杂计算"的问题
- 两者结合使代码逻辑清晰,易于维护和扩展
- 相比纯嵌套查询,这种方式更高效且可读性更强
由此可见,我们用SQL做复杂数据分析时,临时表和窗口函数是好搭档。临时表能暂存中间结果,避免重复计算;窗口函数则能实时算出排名、累计值等。比如:分析销量,先存筛选后的临时表,再用窗口函数算各区域排名,又快又清晰,轻松搞定复杂需求。
猜你喜欢
- 2025-09-04 技术栈:一文进阶SQL三值逻辑:TRUE、FALSE、UNKNOWN和NULL
- 2025-09-04 阿里云国际站DBFS:如何通过DBFS提升数据库应用的文件处理能力?
- 2025-09-04 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
- 2024-11-10 数据库的存储结构 数据库的存储结构分为哪两种
- 2024-11-10 mysql文件结构及InnoDB引擎表空间整理
- 2024-11-10 临时表空间的实时监控,百度99%的搜索结果都是错误的
- 2024-11-10 一文看懂postgresql表空间--概念、用途、分类、相关命令等
- 2024-11-10 Oracle 表空间管理 oracle11g表空间管理方式
你 发表评论:
欢迎- 09-0613.通过Excel导出数据库中的维值_数据库exp导入导出数据
- 09-06做数据分析时,SQL需要达到以下水平
- 09-06Java开发指南:JDK21下载、安装及目录解析,轻松开启编程之旅
- 09-06hive存储过程_hive存储过程环境变量
- 09-06Maven常用命令_maven常用命令有哪些
- 09-06JDK从8升级到21的问题集_jdk更新到几了
- 09-06Oracle狂刷存在感 NRF展会惊艳四座
- 09-06哪些软件支持UDI标签的生成与验证
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- Oracle RAC (76)
- oracle恢复 (77)
- oracle 删除表 (52)
- oracle 用户名 (80)
- oracle 工具 (55)
- oracle 内存 (55)
- oracle 导出表 (62)
- oracle约束 (54)
- oracle 中文 (51)
- oracle链接 (54)
- oracle的函数 (58)
- oracle面试 (55)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)