文章目录
MySQL窗口函数(Window Functions)是MySQL 8.0版本引入的一项强大功能,它允许用户在查询结果集中执行复杂的分析操作,而不需要通过自连接或者子查询来实现。窗口函数可以对一组行的集合进行计算,并返回每个行对应的计算结果。这个“窗口”就是指定的行集范围。
一、基础概念及应用
窗口函数的主要特点
- 无需GROUP BY:与聚合函数不同,使用窗口函数时,不会将多行合并为一行输出,而是保留原始结果集中的每一行。
- 灵活定义窗口:可以通过PARTITION BY、ORDER BY和窗口框架(ROWS或RANGE)来自定义窗口。
- 多种函数支持:包括但不限于排名函数、聚合函数等。
常见窗口函数类型
-
排名函数
ROW_NUMBER()
: 返回当前行在其分区内的顺序编号。RANK()
: 返回当前行在其分区内的排名,存在相同排名时会跳过后续排名。DENSE_RANK()
: 类似于RANK()
,但是不会跳过后续排名。NTILE(n)
: 将分区划分为n个桶,并返回每行所属的桶号。
-
聚合函数作为窗口函数
SUM()
,AVG()
,COUNT()
,MIN()
,MAX()
等传统聚合函数也可以用作窗口函数,它们会在指定的窗口内执行聚合操作。
-
其他函数
LAG()
: 访问前一行的数据。LEAD()
: 访问后一行的数据。FIRST_VALUE()
: 获取窗口框架中第一个值。LAST_VALUE()
: 获取窗口框架中最后一个值。
使用示例
假设有一个员工表employees
,包含以下字段:
id
: 员工IDdepartment
: 部门名称salary
: 工资
如果想要计算每个部门内部按工资降序排列的员工薪资排名,可以使用如下SQL语句:
SELECT
id,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS salary_rank
FROM employees;
这里,PARTITION BY department
定义了窗口边界,即每个部门作为一个独立的窗口;ORDER BY salary DESC
指定了窗口内数据的排序规则;RANK()
则用于计算每个员工在其所在部门内的薪资排名。
窗口函数极大地增强了SQL处理复杂数据分析的能力,使得一些原本需要复杂嵌套查询或临时表的任务变得简单直接。
接下来让我们深入探讨更多关于如何使用这些函数的例子以及更高级的用法。
计算累积和
如果你想计算每个部门内员工工资的累积和,可以使用SUM()
作为窗口函数:
SELECT
id,
department,
salary,
SUM(salary) OVER (PARTITION BY department ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_salary
FROM employees;
在这个例子中,ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
定义了一个窗口框架,它从分区的开始一直到当前行。这意味着对于每一行,SUM(salary)
将累加从分区开始到当前行的所有薪资。
比较当前行与前一行的数据
假设你想比较一个员工的工资与他/她前一位同事的工资差异,可以使用LAG()
函数:
SELECT
id,
department,
salary,
LAG(salary, 1) OVER (PARTITION BY department ORDER BY id) AS previous_salary,
salary - LAG(salary, 1) OVER (PARTITION BY department ORDER BY id) AS salary_difference
FROM employees;
这里,LAG(salary, 1)
会获取当前行之前的第一行(即“上一行”)的工资值。通过这种方式,你可以轻松地计算出工资差异。
动态分区
有时你可能需要根据某些条件动态定义分区。虽然直接在PARTITION BY
中使用子查询或复杂表达式是不可行的,但你可以通过创建一个临时表或使用CTE(公用表表达式)来实现这一点。
例如,假设你想基于员工入职年份动态分区:
WITH employee_year AS (
SELECT id, department, salary, YEAR(hire_date) as hire_year
FROM employees
)
SELECT
id,
department,
salary,
hire_year,
AVG(salary) OVER (PARTITION BY hire_year ORDER BY hire_year) AS avg_salary_by_year
FROM employee_year;
这里,我们首先使用CTE计算每个员工的入职年份,然后在主查询中基于这个字段进行分区。
使用RANGE框架
除了ROWS
之外,你还可以使用RANGE
来定义窗口框架。这对于基于值范围而不是物理行位置的情况非常有用。
例如,如果你想找到每个部门内工资高于当前行工资的所有员工的平均工资:
SELECT
id,
department,
salary,
AVG(salary) OVER (PARTITION BY department RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS avg_higher_salaries
FROM employees
ORDER BY department, salary DESC;
在这个例子中,RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
意味着对于每一行,它将计算从当前行到该分区末尾所有工资高于当前行的员工的平均工资。
结合JOIN使用窗口函数
在实际应用中,你可能会遇到需要结合多个表的数据并使用窗口函数分析的情况。这时,你可以先执行JOIN操作,然后再应用窗口函数。
例如,如果你有一个包含部门信息的另一个表departments
,并且想根据这些信息对员工数据进行分析:
SELECT e.id, e.department, e.salary, d.manager_id,
RANK() OVER (PARTITION BY e.department ORDER BY e.salary DESC) AS salary_rank
FROM employees e
JOIN departments d ON e.department = d.name;
这将允许你在联合查询的结果上应用窗口函数。
二、应用案例
,我们将创建一个场景:一家公司希望分析其销售数据以做出更好的业务决策。该公司有一个销售记录表sales_records
,包含以下字段:
id
: 销售记录IDemployee_id
: 销售员IDsale_amount
: 销售金额sale_date
: 销售日期product_category
: 产品类别
我们的目标是通过窗口函数来回答以下几个问题:
- 每个销售人员的累计销售额是多少?
- 相对于每个销售人员的前一笔销售,本次销售的增长或下降百分比是多少?
- 在每个月份内,每个产品类别的总销售额排名如何?
数据准备
首先,确保你的数据库中有相应的sales_records
表,并且已经填充了适当的数据。
SQL查询实现
1. 每个销售人员的累计销售额
SELECT
employee_id,
sale_date,
sale_amount,
SUM(sale_amount) OVER (PARTITION BY employee_id ORDER BY sale_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_sales
FROM sales_records;
此查询计算每个销售人员从开始到当前日期的累计销售额。
2. 相对增长/下降百分比
要计算相对于每个销售人员的前一笔销售的增长或下降百分比,我们可以使用LAG()
函数来获取前一笔销售的金额,然后计算百分比变化。
SELECT
employee_id,
sale_date,
sale_amount,
LAG(sale_amount, 1) OVER (PARTITION BY employee_id ORDER BY sale_date) AS previous_sale,
CASE
WHEN LAG(sale_amount, 1) OVER (PARTITION BY employee_id ORDER BY sale_date) IS NULL THEN NULL
ELSE (sale_amount - LAG(sale_amount, 1) OVER (PARTITION BY employee_id ORDER BY sale_date)) / LAG(sale_amount, 1) OVER (PARTITION BY employee_id ORDER BY sale_date) * 100
END AS percentage_change
FROM sales_records;
这里我们使用了LAG()
来获取前一笔销售金额,并计算了百分比变化。注意处理首次销售时的情况(即没有前一笔销售),我们返回NULL
作为结果。
3. 每个月份内,每个产品类别的总销售额排名
为了完成这个任务,我们需要首先提取年和月的信息,然后按月份和产品类别进行分组并计算总销售额,最后根据这些信息进行排名。
WITH monthly_sales AS (
SELECT
product_category,
DATE_FORMAT(sale_date, '%Y-%m') AS year_month,
SUM(sale_amount) AS total_sales
FROM sales_records
GROUP BY product_category, year_month
)
SELECT
product_category,
year_month,
total_sales,
RANK() OVER (PARTITION BY year_month ORDER BY total_sales DESC) AS sales_rank
FROM monthly_sales;
在这个例子中,我们首先使用CTE(公用表表达式)monthly_sales
来计算每个月份和每个产品类别的总销售额。然后,在主查询中,我们使用RANK()
函数为每个月份内的产品类别按销售额降序排列。
通过上述示例,我们可以看到窗口函数在处理复杂的业务分析需求时的强大功能。无论是计算累计值、比较历史数据的变化情况,还是进行分类排名,窗口函数都提供了灵活而强大的解决方案。这种能力使得它们成为现代数据分析不可或缺的一部分。
————————————————
最后我们放松一下眼睛