01_窗口函数概述
学习目标
- 了解窗口函数的优点
1.1 窗口函数
接下来的课程中我们来介绍窗口函数window functions.
- MYSQL 8.0 之后,加入了窗口函数功能,简化了数据分析工作中查询语句的书写
- 在没有窗口函数之前,我们需要通过定义临时变量和大量的子查询才能完成的工作,使用窗口函数实现起来更加简洁高效
- 窗口函数是数据分析工作中必须掌握的工具,在SQL笔试中也是高频考点
- 为什么说窗口函数可以使复杂的查询变得更加简单方便?
1.1.1 窗口函数示例
示例:聚合函数 VS 窗口函数
1)假设我们有如下这个 students
表格:
2)如果要按性别获取平均GPA,可以使用聚合函数并运行以下查询:
SELECT
Gender,
AVG(GPA) AS avg_gpa
FROM students
GROUP BY Gender;
结果如下:
现在我们想得到如下结果:
3)我们当然可以用我们刚刚提到的聚合函数,然后再将结果join到初始表,但这需要两个步骤。
SELECT
s.*,
g.avg_gpa
FROM students s
JOIN (
SELECT
Gender,
AVG(GPA) AS avg_gpa
FROM students
GROUP BY Gender
) g
ON s.Gender=g.Gender;
4)但如果我们使用窗口函数,我们则可以一步到位,并得到相同的结果:
SELECT
*,
AVG(GPA) OVER (PARTITION BY Gender) as avg_gpa
FROM students;
通过上面的查询,就按性别对数据进行划分,并计算每种性别的平均GPA。然后,它将创建一个称为avg_gpa
的新列,并为每行附加关联的平均GPA
1.1.2 窗口函数的优点
1)简单
- 窗口函数更易于使用。在上面的示例中,与使用聚合函数然后合并结果相比,使用窗口函数的 SQL 语句更加简单。
2)快速
- 这一点与上一点相关,使用窗口函数比使用替代方法要快得多。当你处理成百上千个千兆字节的数据时,这非常有用。
3)多功能性
- 最重要的是,窗口函数具有多种功能,比如:添加移动平均线、添加行号和滞后数据等等。
02_窗口函数基本用法
学习目标
- 掌握窗口函数的基本语法和OVER()的使用方法
2.1 OVER() 关键字
2.1.1 数据集介绍
本小节我们先介绍窗口函数中最重要的关键字 OVER()
在介绍具体内容之前先熟悉一下要用到的数据,我们选择了很常见的业务来介绍窗口函数的使用
- 三张表:员工表、部门表、采购表
- 员工表:员工id、姓名、员工所属部门id(
department_id
)、工资(salary
)、工龄(years_worked
) - 部门表:部门id、部门名称
- 采购表:每个部门(
department_id
)、采购的物品明细(item
)、物品价格(price
)
员工表(employee)
部门表(department)
采购表(purchase)
2.1.2 什么是窗口函数?
窗口函数是对表中一组数据进行计算的函数,一组数据跟当前行相关
例如:想计算每三天的销售总金额,就可以使用窗口函数,以当前行为基准,选前一行,后一行,三行一组如下图所示:
之所以称之为窗口函数,是因为好像有一个固定大小的窗框划过数据集,滑动一次取一次,对窗口内的数据进行处理。
窗口函数的语法:
<window_function> OVER (...)
<window_function>
这里可以是我们之前已经学过的聚合函数,比如(COUNT()
、SUM()
、AVG()
等)- 也以是其他函数,比如ranking 排序函数、分析函数等,后面的课程中会介绍
OVER(...)
窗口函数的窗口通过OVER(...)
子句定义,窗口函数中很重要的部分就是通过OVER(...)
定义窗口 (开窗方式和大小)
2.1.3 OVER()基本用法
练习1
需求:创建报表,除了查询每个人的工资之外,还要统计出公司每月的工资总支出
查询结果字段:
- first_name、last_name、salary(工资)、sum(公司每月工资总支出)
SELECT
first_name,
last_name,
salary,
SUM(salary) OVER() AS `sum`
FROM employee;
OVER()
意思是所有的数据都在窗口中SUM(salary)
意思是要计算工资的和,加上OVER()
意味着对全部数据进行计算,所以就是在计算所有人的工资之和,也就是公司每月的工资总支出
查询结果
练习2
需求:统计采购表中的平均采购价格,并与明细一起显示(每件物品名称,价格)
查询结果字段:
- item(物品名称)、price、avg(平均采购价格)
SELECT
item,
price,
AVG(price) OVER() as `avg`
FROM purchase;
查询结果
2.1.4 将OVER()的结果用于进一步计算
通常,
OVER()
用于将当前行与一个聚合值进行比较。
练习3
需求:创建报表统计每个员工的工资和平均工资之间的差。
查询结果字段:
- first_name、last_name、salary(工资)、avg(平均工资)、difference(工资和平均工资的差值)
SELECT
first_name,
last_name,
salary,
AVG(salary) OVER() AS `avg`,
salary - AVG(salary) OVER() AS `difference`
FROM employee;
查询结果:
上面查询结果的最后一列显示了每名员工的工资和平均工资之间的差额,这就是窗口函数的典型应用场景:将当前行与一组数据的聚合值进行比较
练习4
需求:统计id为2的部门所采购的所有商品,并将计算了每项支出占总采购金额的占比
查询结果字段:
- id、item(物品名称)、price(采购价格)、percent(支出占采购总金额的占比)
SELECT
id,
item,
price,
price / SUM(price) OVER() AS `percent`
FROM purchase
WHERE department_id=2;
查询结果
2.1.5 OVER()的作用范围
窗口函数是可以与 WHERE 一起使用的,但是 WHERE 与窗口函数那个先执行呢?
练习5
需求:创建报表统计 id 为1的部门中每个员工的工资和该部门平均工资之差
查询结果字段:
- first_name、last_name、salary(工资)、avg(平均工资)、difference(工资和部门平均工资的差值)
SELECT
first_name,
last_name,
salary,
AVG(salary) OVER() AS `avg`,
salary - AVG(salary) OVER() AS `difference`
FROM employee
WHERE department_id=1;
查询结果
在上面的SQL中,我们通过WHERE department_id=1,过滤出部门ID为1的数据,窗口函数只作用于id = 1的部门
需要记住,窗口函数在
WHERE
子句后执行!
练习6
需求:查询部门id为1、2、3三个部门员工的姓名,薪水,和这三个部门员工的平均工资
查询结果字段:
- department_id、first_name、last_name、salary(工资)、avg(三个部门的平均工资)
SELECT
department_id,
first_name,
last_name,
salary,
AVG(salary) OVER() as `ave`
FROM employee
WHERE department_id IN (1, 2, 3);
查询结果
2.1.6 在过滤条件中使用OVER()
看下面的SQL,我们打算查询所有员工中,薪资高于平均薪资的员工:
SELECT
first_name,
last_name,
salary,
AVG(salary) OVER() AS `avg`
FROM employee
WHERE salary > AVG(salary) OVER();
上面的 SQL 能否正确执行?
- 执行上面的SQL时会返回错误信息:
3593 - You cannot use the window function 'avg' in this context.
- 原因是:窗口函数在WHERE子句之后执行,把窗口函数写在WHERE子句中会导致循环依赖
03_PARTITION BY
学习目标
- 掌握 OVER(PARTITION BY)的使用方法
3.1 PARTITION BY基本用法
3.1.1 PARTITION BY简介
接下来,我们来介绍如何在OVER()
中添加 PARTITION BY
,基本的语法如下:
<window_function> OVER(PARTITION BY column1, column2 ... column_n)
PARTITION BY
的作用与 GROUP BY
类似:将数据按照传入的列进行分组,与 GROUP BY
的区别是, PARTITION BY
不会改变结果的行数
PARTITION BY与GROUP BY区别:
1)普通的聚合函数用GROUP BY分组,每个分组返回一个统计值;而分析函数采用PARTITION BY分组,并且每组每行都可以返回一个统计值
2)在执行顺序上:
FROM > WHERE > GROUP BY > HAVING
PARTITION BY应用在以上关键字之后,可以简单理解为就是在执行完 SELECT 之后,在所得结果集之上进行PARTITION BY分组
3.1.2 数据集介绍
不同型号火车基本信息表(train)
first_class_places:一等座,second_class_places:二等座
运营线路表(route)
票价表(ticket)
时刻表(journey)
3.1.3 PARTITION BY基本使用
练习1
需求:查询每种类型火车的ID,型号,一等座数量,同型号一等座数量总量
查询结果字段:
- id、model(火车型号)、first_class_places(一等座数量)、sum(同型号一等座数量总量)
GROUP BY实现:
SELECT
id,
t.model,
first_class_places,
c.sum
FROM train t
JOIN (
SELECT
model,
SUM( first_class_places ) AS sum
FROM train
GROUP BY model
) c
ON t.model=c.model;
窗口函数实现:
SELECT
id,
model,
first_class_places,
SUM(first_class_places) OVER (PARTITION BY model) AS `sum`
FROM train;
如果用
GROUP BY
实现上面的需求,我们需要通过子查询将结果与原始表JOIN
, 对比起来,使用窗口函数的实现更加简洁
练习2
需求:查询每天的车次数量
查询结果字段:
- id(时刻表车次ID)、date(日期)、count(每天的车次数量)
SELECT
id,
date,
COUNT(id) OVER(PARTITION BY date) as `count`
FROM journey;
查询结果
练习3
需求:按车型分组,每组中满足一等座>30,二等座>180的有几条记录
查询结果字段:
- id、model(车型)、first_class_places(一等座数量)、second_class_places(二等座数量)、count(该车型中一等座>30,二等座>180的有几条记录)
SELECT
id,
model,
first_class_places,
second_class_places,
COUNT(id) OVER (PARTITION BY model) AS `count`
FROM train
WHERE first_class_places > 30 AND second_class_places > 180;
查询结果
3.1.4 PARTITION BY传入多列
练习4
需求:查询每天、每条线路速的最快车速
查询结果字段:
- route_id(线路ID)、date(日期)、model(车型)、max_speed(该车型最大车速)、max(每天相同线路最大车速)
SELECT
j.route_id,
j.date,
t.model,
t.max_speed,
MAX(max_speed) OVER(PARTITION BY route_id, date) AS `max`
FROM journey j
JOIN train t
ON j.train_id=t.id;
查询结果
练习5
需求:查询票价表信息,并统计每天的在售车票的平均价格,每天的在售车票种类。(注意:不统计运营车辆id为5的数据)
查询结果字段: