分析函数
这里的分析函数也就是我们常说的开窗函数,通常有两类:
一类是聚合开窗函数(SUM、AVG、MAX、MIN、COUNT等),
一类是排序开窗函数(ROW_NUMBER、DENSE_RANK、RANK等)。
本文主要内容转自《高效使用Greenplum》一书。
分析函数是Greenplum数据库管理系统自带函数中的一种专门解决具有复杂统计需求的函数,它可以对数据分组,然后基于组中数据进行分析统计,最后在每组数据集的每一行返回这个统计值。
分析函数不同于分组统计(Group By),分组统计只能按照分组字段返回一个固定的统计值,不能在原来的数据行上附带这个统计值,而分析函数正式专门解决这类统计需求所开发出来的函数。分析函数已经逐步称为SQL标准的一部分,有越来越多的数据库系统开始支持分析函数。
开窗函数和聚合函数的区别如下:
- SQL 标准允许将所有聚合函数用作开窗函数,用OVER 关键字区分开窗函数和聚合函数。
- 聚合函数每组只返回一个值,开窗函数每组可返回多个值。
分析函数的语法结构如下:
SELECT table.column,
analysis_function() OVER ( [PARTITION BY 字符] [ORDER BY 字段 [rows]] ) as 统计值
FROM table
其中:
analysis_function():指定分析函数名称,常用的分析函数有sum、max、first_value、last_value、lag、lead、rank、desn_rank、row_number等。
OVER():开窗函数名,PARTITION BY指定进行数据分组的字段,ORDER BY指定进行排序的字段,ROWS指定数据窗口(即指定分析函数要操作的行数),语法形式为OVER(PARTITION BY xxx ORDER BY yyy ROWS BETWEEN zzz)。
这里的ROWS有多个范围值(一般情况下会省略),具体如下:
- unbounded preceding:无限/不限定往前的范围;
- n preceding:往前统计n行;
- unbounded following:无限/不限定往后的范围;
- n following:往后统计n行;
- current row:当前行;
案例:下图是员工表和部门表数据内连接后的结果集。
select b.dept_id,b.dept_name,t.emp_id,t.emp_name,t.age,t.salary
from emp t,dept b
where t.dept_id = b.dept_id
dept_id |
dept_name |
emp_id |
emp_name |
age |
salary |
1100 |
销售部 |
3 |
Jack |
40 |
15000 |
1100 |
销售部 |
4 |
Michael |
36 |
9800 |
1200 |
研发部 |
2 |
Alen |
29 |
13500 |
1200 |
研发部 |
5 |
Lily |
36 |
12500 |
1110 |
销售一部 |
6 |
David |
30 |
7900 |
1120 |
销售二部 |
7 |
Timmy |
26 |
8500 |
1000 |
总裁办 |
1 |
Paul |
45 |
36000 |
案例1:利用min(),max()分析函数分别取出不同部门不同员工工资的最高值和最低值,附带在原始数据上。
select b.dept_id,b.dept_name,t.emp_id,t.emp_name,t.age,t.salary
-- 获取组中工资最高值
max(t.salary) OVER(PARTITION BY t.dept_id) AS salary_max,
-- 获取组中工资最低值
min(t.salary) OVER(PARTITION BY t.dept_id) AS salary_min,
-- 分组窗口的第一个值(指定窗口为组中第一行到末尾行)
first_value(t.salary) OVER(PARTITION BY t.dept_id ORDER BY t.salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS salary_first,
-- 分组窗口的最后一个值(指定窗口为组中第一行到末尾行)
last_value(t.salary) OVER(PARTITION BY t.dept_id ORDER BY t.salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS salary_last,
-- 分组窗口的第一个值(不指定窗口)
first_value(t.salary) OVER(PARTITION BY t.dept_id ORDER BY t.salary DESC) AS salary_first_1,
-- 分组窗口的最后一个值(指定窗口才可以取到最低值,否则只能取到当前行)
last_value(t.salary) OVER(PARTITION BY t.dept_id ORDER BY t.salary DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS salary_last_1
from emp t,dept b
where t.dept_id = b.dept_id
order by t.dept_id,t.emp_id
根据查询结果可以看出如下信息:
min()和max()分析函数直接获取组中最小值和最大值;
first_value()和last_value()返回窗口的第一行和最后一行数据,因为我们通过工资字段对分组内的数据进行了降序排列,所以也可以达到在一定的窗口内获取最大值和最小值的功能;
排序不指定窗口时,就按照组内的第一行到当前行作为窗口,然后取出窗口的第一行和最后一行;
窗口子语句当中的第一行是UNBOUNDED PRECEDING,当前行是CURRENT ROW,最后一行是UNBOUNDED FOLLOWING,正是利用窗口范围是第一行到最后一行,得到同一部门内的最高工资和最低工资。
注意:ROWS的默认值是:BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。
案例2:利用rank()、dense_rank()、row_number()函数对员工的年龄进行排序,比较三个不同关键字的差异。
select t.emp_id,t.emp.name,t.age,
row_number() OVER(ORDER BY t.age) AS “row_number排名”,
rank() OVER(ORDER BY t.age) AS “rank排名”,
dense_rank() OVER(ORDER BY t.age) AS “dense_rank排名”
from emp t
order by t.age
根据查询结果可以看出如下信息:
emp_id |
emp_name |
age |
row_number |
rank |
dense_rank |
7 |
Timmy |
26 |
1 |
1 |
1 |
2 |
Alen |
29 |
2 |
2 |
2 |
6 |
David |
30 |
3 |
3 |
3 |
4 |
Michael |
36 |
4 |
4 |
4 |
5 |
Lily |
36 |
5 |
4 |
4 |
3 |
Jack |
40 |
6 |
6 |
5 |
1 |
Paul |
45 |
7 |
7 |
6 |
row_number()函数排名返回唯一值,当遇到相同的数据时,排名按照记录集中的记录顺序依次递增;
rank()函数返回唯一值,当遇到相同的数据时,所有相同的数据的排名是一样的,同时会在最后一条相同记录和下一条不同记录的排名之间空出排名;
dense_rank()函数返回唯一值,当遇到相同的数据时,所有相同数据的排名是一样的,同时在最后一条相同记录和下一条不同记录的排名之间不空出排名。
案例3:利用开窗函数对员工工资进行不同条件的汇总,以便对比ORDER BY和PARTITION BY的作用。
select t.emp_id,t.emp_name,t.age,t.salary,
sum(t.salary) OVER() AS “全局汇总”,
sum(t.salary) OVER(ORDER BY t.emp_id) AS “逐行累加”,
sum(t.salary) OVER(PARTITION BY t.dept_id) AS “分组汇总”,
sum(t.salary) OVER(PARTITION BY t.dept_id ORDER BY t.emp_id) AS “分组逐行汇总”
from emp t
order by t.emp_id
根据查询结果可以看出如下信息:
OVER()默认是全局汇总,即所有可以查到的行数指标合集,可用于计算占比;
OVER+ORDER BY用于根据条件逐行相加汇总,可用于计算类似于“工资占前80%的员工明细”之类的需求;
OVER+PARTITION BY用于分组汇总,可以计算分组的合计、分组的占比、分组的最大值和最小值等;
OVER+PARTITION BY+ORDER BY用于分组逐行汇总,可用于计算分组的排名,可以满足例如“取每一个组的前五名”之类的需求;
案例4:查询部门总工资大于所有部门平均总工资的部门员工信息及部门平均工资、公司平均工资。
select * from (
select emp_id,emp_name,dept_id,salary,
avg(salary) OVER(PARTITION BY dept_id) as dept_avg_salary,
avg(salary) OVER() as comp_avg_salary
from emp) t
where t.dept_avg_salary > t.comp_avg_salary;