Mysql窗口函数
Mysql窗口函数
一、什么是窗口函数?
窗口函数是一种在查询结果的"窗口"上执行计算的函数,它不会像常规聚合函数那样将多行合并为一行,而是为每一行返回一个值,同时保持原始行数不变。
基本语法
1<窗口函数>(<参数>) OVER (
2[PARTITION BY <分区表达式>]
3[ORDER BY <排序表达式> [ASC | DESC]]
4[ROWS/RANGE <窗口范围>]
5)
- <窗口函数>:可以是聚合函数(如SUM、AVG)或专用函数(如ROW_NUMBER、RANK)。
- OVER():必需子句,定义窗口框架。
- PARTITION BY:可选,用于分组数据;若省略,窗口覆盖整个结果集。
- ORDER BY:可选,指定窗口内行的排序顺序。
- 窗口范围:可选,默认ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。
二、 准备测试数据
1-- 创建销售数据表
2CREATE TABLE sales (
3 sale_id INT PRIMARY KEY,
4 salesperson VARCHAR(50),
5 region VARCHAR(50),
6 sale_date DATE,
7 amount DECIMAL(10,2)
8);
9
10INSERT INTO sales VALUES
11(1, '张三', '北京', '2023-01-15', 5000),
12(2, '李四', '上海', '2023-01-16', 8000),
13(3, '王五', '北京', '2023-01-17', 6000),
14(4, '张三', '北京', '2023-02-01', 7000),
15(5, '李四', '上海', '2023-02-02', 9000),
16(6, '赵六', '广州', '2023-02-03', 4000),
17(7, '王五', '北京', '2023-02-04', 5500),
18(8, '张三', '北京', '2023-03-01', 8500);
19
20-- 创建员工薪资表
21CREATE TABLE employee_salary (
22 emp_id INT,
23 name VARCHAR(50),
24 department VARCHAR(50),
25 salary DECIMAL(10,2),
26 hire_date DATE
27);
28
29INSERT INTO employee_salary VALUES
30(1, '张三', '技术部', 15000, '2020-01-15'),
31(2, '李四', '技术部', 12000, '2021-03-20'),
32(3, '王五', '销售部', 8000, '2022-06-10'),
33(4, '赵六', '销售部', 7500, '2022-08-05'),
34(5, '钱七', '技术部', 13000, '2020-11-30'),
35(6, '孙八', '人事部', 9000, '2021-09-15'),
36(7, '周九', '技术部', 16000, '2019-05-20');
1. 排名函数
ROW_NUMBER()
1SELECT
2 salesperson,
3 region,
4 amount,
5 ROW_NUMBER() OVER (PARTITION BY region ORDER BY amount DESC) as row_num
6FROM sales;
结果:为每个地区的销售按金额降序分配唯一序号
RANK() 和 DENSE_RANK()
1SELECT
2 name,
3 department,
4 salary,
5 RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank_pos,
6 DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dense_rank_pos
7FROM employee_salary;
区别:
RANK(): 相同值相同排名,后续排名跳过 (1,2,2,4)
DENSE_RANK(): 相同值相同排名,后续排名不跳过 (1,2,2,3)
NTILE() - 数据分桶
1SELECT
2 name,
3 salary,
4 NTILE(4) OVER (ORDER BY salary DESC) as quartile
5FROM employee_salary;
结果:将员工按薪资分为4个分组,用于四分位分析
2. 分布函数
PERCENT_RANK()
1SELECT
2 name,
3 salary,
4 ROUND(PERCENT_RANK() OVER (ORDER BY salary) * 100, 2) as percentile
5FROM employee_salary;
结果:计算每个薪资在总体中的百分比排名 (0-1之间)
CUME_DIST()
1SELECT
2 name,
3 salary,
4 ROUND(CUME_DIST() OVER (ORDER BY salary) * 100, 2) as cumulative_dist
5FROM employee_salary;
结果:计算累计分布(小于等于当前值的行数比例)
3. 前后行函数
LAG() 和 LEAD()
1SELECT
2 salesperson,
3 sale_date,
4 amount,
5 LAG(amount, 1) OVER (PARTITION BY salesperson ORDER BY sale_date) as prev_amount,
6 LEAD(amount, 1) OVER (PARTITION BY salesperson ORDER BY sale_date) as next_amount,
7 amount - LAG(amount, 1) OVER (PARTITION BY salesperson ORDER BY sale_date) as growth
8FROM sales;
应用:计算环比增长、访问前后期数据
FIRST_VALUE() 和 LAST_VALUE()
1SELECT
2 salesperson,
3 sale_date,
4 amount,
5 FIRST_VALUE(amount) OVER (PARTITION BY salesperson ORDER BY sale_date) as first_sale,
6 LAST_VALUE(amount) OVER (PARTITION BY salesperson ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as last_sale
7FROM sales;
注意:LAST_VALUE需要指定正确的窗口框架
4. 聚合窗口函数
累计计算
1SELECT
2 salesperson,
3 sale_date,
4 amount,
5 SUM(amount) OVER (PARTITION BY salesperson ORDER BY sale_date) as running_total,
6 AVG(amount) OVER (PARTITION BY salesperson ORDER BY sale_date) as running_avg
7FROM sales;
移动平均
1SELECT
2 sale_date,
3 amount,
4 AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as moving_avg_3,
5 AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as moving_avg_7
6FROM sales;
5. 窗口框架详解
框架语法
1ROWS BETWEEN frame_start AND frame_end
常用框架范围
1SELECT
2 salesperson,
3 sale_date,
4 amount,
5 -- 从开始到当前行
6 SUM(amount) OVER (PARTITION BY salesperson ORDER BY sale_date ROWS UNBOUNDED PRECEDING) as total_to_date,
7
8 -- 前1行到当前行
9 AVG(amount) OVER (PARTITION BY salesperson ORDER BY sale_date ROWS 1 PRECEDING) as avg_last_2,
10
11 -- 前1行到后1行
12 SUM(amount) OVER (PARTITION BY salesperson ORDER BY sale_date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as sum_3_rows
13FROM sales;
6. 高级窗口函数应用
复杂业务分析
1-- 销售员绩效综合分析
2WITH sales_stats AS (
3 SELECT
4 salesperson,
5 region,
6 sale_date,
7 amount,
8 -- 排名分析
9 ROW_NUMBER() OVER (PARTITION BY region ORDER BY amount DESC) as region_rank,
10 -- 累计分析
11 SUM(amount) OVER (PARTITION BY salesperson ORDER BY sale_date) as ytd_sales,
12 -- 趋势分析
13 LAG(amount, 1) OVER (PARTITION BY salesperson ORDER BY sale_date) as prev_month,
14 -- 占比分析
15 amount * 100.0 / SUM(amount) OVER (PARTITION BY salesperson) as pct_of_total
16 FROM sales
17)
18SELECT *,
19 CASE
20 WHEN region_rank = 1 THEN '地区冠军'
21 WHEN region_rank <= 3 THEN '地区前三'
22 ELSE '其他'
23 END as performance_level
24FROM sales_stats;
员工薪资深度分析
1SELECT
2 name,
3 department,
4 salary,
5 -- 部门内分析
6 AVG(salary) OVER (PARTITION BY department) as dept_avg,
7 salary - AVG(salary) OVER (PARTITION BY department) as diff_from_dept_avg,
8
9 -- 公司整体分析
10 AVG(salary) OVER () as company_avg,
11 salary - AVG(salary) OVER () as diff_from_company_avg,
12
13 -- 排名分析
14 RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dept_rank,
15 RANK() OVER (ORDER BY salary DESC) as company_rank,
16
17 -- 分布分析
18 PERCENT_RANK() OVER (PARTITION BY department ORDER BY salary) as dept_percentile,
19 CUME_DIST() OVER (ORDER BY salary) as company_cume_dist
20FROM employee_salary
21ORDER BY department, salary DESC;
7. 性能优化技巧
使用窗口函数替代复杂子查询
不推荐:
1SELECT
2 s1.salesperson,
3 s1.amount,
4 (SELECT COUNT(*)
5 FROM sales s2
6 WHERE s2.region = s1.region AND s2.amount > s1.amount) + 1 as rank
7FROM sales s1;
推荐:
1SELECT
2 salesperson,
3 amount,
4 RANK() OVER (PARTITION BY region ORDER BY amount DESC) as rank
5FROM sales;
合理使用PARTITION BY
1-- 只在需要时使用PARTITION BY
2SELECT
3 salesperson,
4 region,
5 amount,
6 -- 需要分区:按销售员计算累计
7 SUM(amount) OVER (PARTITION BY salesperson ORDER BY sale_date) as personal_running_total,
8 -- 不需要分区:总体排名
9 RANK() OVER (ORDER BY amount DESC) as overall_rank
10FROM sales;
8. 实际业务场景
场景1:销售排行榜
1SELECT
2 salesperson,
3 region,
4 total_sales,
5 region_rank,
6 company_rank
7FROM (
8 SELECT
9 salesperson,
10 region,
11 SUM(amount) as total_sales,
12 RANK() OVER (PARTITION BY region ORDER BY SUM(amount) DESC) as region_rank,
13 RANK() OVER (ORDER BY SUM(amount) DESC) as company_rank
14 FROM sales
15 GROUP BY salesperson, region
16) ranked_sales
17WHERE region_rank <= 3 OR company_rank <= 10;
场景2:员工薪资调整分析
1SELECT
2 name,
3 department,
4 salary,
5 dept_avg,
6 CASE
7 WHEN salary < dept_avg * 0.9 THEN '低于平均-建议调整'
8 WHEN salary > dept_avg * 1.2 THEN '高于平均-表现优秀'
9 ELSE '在正常范围内'
10 END as adjustment_recommendation
11FROM (
12 SELECT
13 name,
14 department,
15 salary,
16 AVG(salary) OVER (PARTITION BY department) as dept_avg
17 FROM employee_salary
18) with_avg;
三、总结
窗口函数是MySQL中极其强大的功能,它能够:
简化复杂查询,避免多重子查询
提高查询性能
实现高级分析功能
保持数据的原始粒度
掌握窗口函数能够显著提升数据分析和报表开发的效率
发表评论