1. 窗口函数基础概念解析
窗口函数(Window Function)是SQL中一种强大的分析工具,它能够在保持原始行不变的同时,对一组相关的行执行计算。与常规的聚合函数不同,窗口函数不会将多行合并为单个输出行,而是为每一行返回一个值。
1.1 窗口函数的核心特点
窗口函数的核心特点体现在三个关键方面:
- 保留原始行数据:窗口函数不会像GROUP BY那样合并行,而是为查询结果中的每一行都返回一个值
- 定义计算范围:通过OVER()子句指定计算的行集合(称为"窗口")
- 灵活的分区控制:可以使用PARTITION BY将数据分成多个组,在每个组内独立计算
sql复制-- 基本语法结构
SELECT
column1,
column2,
window_function(column3) OVER (
[PARTITION BY partition_expression]
[ORDER BY sort_expression [ASC | DESC]]
[frame_clause]
) AS result_column
FROM table_name;
1.2 窗口函数与聚合函数的区别
理解窗口函数与普通聚合函数的区别至关重要:
| 特性 | 窗口函数 | 聚合函数 |
|---|---|---|
| 行数变化 | 保持原行数不变 | 合并为少量结果行 |
| 计算范围 | 通过OVER()定义窗口 | 对整个分组计算 |
| 使用场景 | 分析性计算 | 汇总统计 |
| 语法位置 | SELECT子句中 | SELECT或HAVING子句中 |
| 典型函数 | RANK(), LAG(), SUM() OVER() | SUM(), COUNT(), AVG() |
2. 常用窗口函数分类与应用
窗口函数主要分为三大类,每类都有其独特的应用场景和函数集合。
2.1 排名函数(Ranking Functions)
排名函数用于为行分配排名值,特别适用于Top-N查询和数据分析:
sql复制-- RANK(): 相同值相同排名,后续排名跳过并列数量
SELECT
employee_name,
salary,
RANK() OVER (ORDER BY salary DESC) AS salary_rank
FROM employees;
-- DENSE_RANK(): 相同值相同排名,但后续排名不跳过
SELECT
product_name,
sales,
DENSE_RANK() OVER (ORDER BY sales DESC) AS sales_rank
FROM products;
-- ROW_NUMBER(): 为每行分配唯一序号,不考虑相同值
SELECT
student_id,
exam_score,
ROW_NUMBER() OVER (ORDER BY exam_score DESC) AS score_rank
FROM exam_results;
实际应用场景:
- 计算销售人员的业绩排名
- 识别考试成绩前10%的学生
- 分析产品在各地区的销售排名
2.2 分析函数(Analytic Functions)
分析函数提供跨行的计算能力,常用于时间序列和趋势分析:
sql复制-- LAG/LEAD: 访问前后行的数据
SELECT
date,
revenue,
LAG(revenue, 1) OVER (ORDER BY date) AS prev_day_revenue,
revenue - LAG(revenue, 1) OVER (ORDER BY date) AS daily_growth
FROM sales_data;
-- FIRST_VALUE/LAST_VALUE: 获取窗口首尾值
SELECT
department,
employee_name,
salary,
FIRST_VALUE(employee_name) OVER (
PARTITION BY department
ORDER BY salary DESC
) AS highest_paid_employee
FROM employees;
-- NTH_VALUE: 获取指定位置的值
SELECT
month,
product,
sales,
NTH_VALUE(product, 2) OVER (
PARTITION BY month
ORDER BY sales DESC
) AS second_best_seller
FROM monthly_sales;
典型使用案例:
- 计算环比/同比增长率
- 识别销售趋势变化点
- 分析用户行为序列模式
2.3 聚合窗口函数(Aggregate Window Functions)
常规聚合函数与OVER()结合,实现灵活的分区计算:
sql复制-- 计算移动平均值
SELECT
date,
temperature,
AVG(temperature) OVER (
ORDER BY date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS three_day_avg_temp
FROM weather_data;
-- 累计求和
SELECT
month,
revenue,
SUM(revenue) OVER (
ORDER BY month
ROWS UNBOUNDED PRECEDING
) AS cumulative_revenue
FROM monthly_financials;
-- 计算占比
SELECT
category,
product,
sales,
sales / SUM(sales) OVER (PARTITION BY category) * 100 AS category_percentage
FROM product_sales;
实用技巧:
- 使用ROWS或RANGE定义窗口范围
- UNBOUNDED PRECEDING表示从分区开始
- CURRENT ROW表示当前行
- n FOLLOWING表示后续n行
3. 窗口定义与帧规范详解
窗口函数的强大之处在于可以精确控制计算范围,这通过OVER()子句中的窗口规范实现。
3.1 PARTITION BY子句
PARTITION BY将数据分成多个组,窗口函数在每个组内独立计算:
sql复制-- 按部门分区计算薪资排名
SELECT
department_id,
employee_name,
salary,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dept_salary_rank
FROM employees;
注意事项:
- 可以指定多个分区字段(PARTITION BY col1, col2)
- 分区字段的选择直接影响计算结果
- 未指定PARTITION BY时,整个结果集作为一个分区
3.2 ORDER BY子句
ORDER BY确定窗口内行的排序方式,影响排名函数和累积计算:
sql复制-- 按日期排序计算累计销售额
SELECT
order_date,
daily_sales,
SUM(daily_sales) OVER (ORDER BY order_date) AS cumulative_sales
FROM sales_data;
关键点:
- 排序方向(ASC/DESC)会影响结果
- 对于聚合窗口函数,ORDER BY会创建默认的窗口帧
- 在排名函数中,ORDER BY是必需的
3.3 窗口帧规范(Frame Specification)
窗口帧精确控制参与计算的行范围,语法灵活:
sql复制-- 计算3天移动平均(前一天、当天、后一天)
SELECT
date,
temperature,
AVG(temperature) OVER (
ORDER BY date
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
) AS three_day_avg
FROM weather_data;
-- 计算季度累计(从季度开始到当前行)
SELECT
month,
revenue,
SUM(revenue) OVER (
PARTITION BY quarter
ORDER BY month
ROWS UNBOUNDED PRECEDING
) AS quarter_to_date
FROM financials;
帧类型说明:
ROWS:基于物理行偏移RANGE:基于逻辑值偏移GROUPS:基于组偏移(部分数据库支持)
常用帧模式:
UNBOUNDED PRECEDING:分区开始n PRECEDING:前n行CURRENT ROW:当前行n FOLLOWING:后n行UNBOUNDED FOLLOWING:分区结束
4. 高级应用与性能优化
掌握窗口函数的高级用法可以解决复杂分析需求,同时需要注意性能影响。
4.1 多层窗口函数嵌套
窗口函数可以嵌套使用,实现复杂分析逻辑:
sql复制-- 计算部门内薪资排名,然后分析排名变化
SELECT
employee_id,
department,
salary,
current_rank,
prev_rank,
current_rank - prev_rank AS rank_change
FROM (
SELECT
employee_id,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS current_rank,
LAG(RANK() OVER (PARTITION BY department ORDER BY salary DESC), 1)
OVER (PARTITION BY employee_id ORDER BY year) AS prev_rank
FROM employee_history
WHERE year = 2023
) t;
4.2 动态窗口与条件聚合
结合CASE表达式实现条件窗口计算:
sql复制-- 计算过去30天内的高价值客户交易总额
SELECT
customer_id,
transaction_date,
amount,
SUM(CASE WHEN amount > 1000 THEN amount ELSE 0 END) OVER (
PARTITION BY customer_id
ORDER BY transaction_date
RANGE BETWEEN INTERVAL '30' DAY PRECEDING AND CURRENT ROW
) AS high_value_tx_30d
FROM transactions;
4.3 性能优化技巧
窗口函数可能成为查询性能瓶颈,需注意:
-
分区策略优化:
- 选择合适的分区字段,避免创建过多小分区
- 分区字段应该有良好的选择性
-
排序成本控制:
- 确保ORDER BY字段有索引支持
- 避免在窗口函数中使用复杂表达式排序
-
帧范围选择:
- 尽量缩小窗口帧范围(如3行而非整个分区)
- 使用ROWS而非RANGE(前者通常性能更好)
-
执行计划分析:
sql复制-- 查看执行计划,关注窗口函数操作
EXPLAIN ANALYZE
SELECT ... window_function() OVER(...) ...
FROM ...;
典型优化案例:
sql复制-- 优化前:全分区排序计算
SELECT
user_id,
SUM(revenue) OVER (ORDER BY user_id)
FROM users;
-- 优化后:添加合理分区
SELECT
user_id,
SUM(revenue) OVER (PARTITION BY user_segment ORDER BY user_id)
FROM users;
4.4 跨数据库兼容性
不同数据库对窗口函数的支持有差异:
| 功能 | PostgreSQL | MySQL | Oracle | SQL Server | SQLite |
|---|---|---|---|---|---|
| 基本窗口函数 | 完全支持 | 8.0+ | 完全 | 完全 | 3.25+ |
| RANGE帧 | 支持 | 支持 | 支持 | 支持 | 支持 |
| GROUPS帧 | 11+ | 不支持 | 不支持 | 2012+ | 不支持 |
| EXCLUDE子句 | 支持 | 不支持 | 不支持 | 不支持 | 不支持 |
| 窗口函数嵌套 | 有限支持 | 有限 | 支持 | 支持 | 有限 |
实际开发中,对于需要跨数据库的应用,建议:
- 使用最通用的窗口函数语法
- 避免使用数据库特有的高级功能
- 在应用层处理复杂的分析逻辑
