一、默认窗口与排名函数
最常用的分析函数是排名函数,用于返回当前记录在窗口中的位置。常用的排名函数包括rank()、dense_rank()、row_number()等。排名函数所对应的窗口函数必须指定排序规则,即必须使用order by 子句,而Oracle为每条记录提供的默认窗口为从表中第一条记录开始,直至与当前记录具有相同排名的所有记录。
1、rank()函数
将表中员工按入职时间升序排列,列出排名次序
select empno, ename, hiredate, rank() over(order by hiredate) hiredate_order from emp;
该函数的排名具有跳跃性,如7900和7902员工排名都是10,而下一位7934员工排名则为12。
2、dense_rank()函数
类似rank()函数,但排名不具有跳跃性
select empno, ename, hiredate, dense_rank() over(order by hiredate) hiredate_order from emp;
7900和7902员工排名都是10,而下一位7934员工排名则为11,排名不存在跳跃。
3、row_number()函数
row_number()函数单纯返回当前记录在窗口中所处位置的行号,该行号具有唯一性。以上查询用row_number()函数替代后如下
select empno, ename, hiredate, row_number() over(order by hiredate) hiredate_order from emp;
4、count()函数
通过count()函数可以查看窗口中实际的记录数目,以上查询用count()函数替代后如下
select empno, ename, hiredate, count(1) over(order by hiredate) hiredate_order from emp;
二、分区窗口
分区窗口是与当前记录拥有相同的分区标准的所有记录,语法为
partition by 列名
partition by语句首先根据列名获得当前记录的列值,接着获得表中具有相同列值的所有记录,并将该记录集合作为当前记录的窗口。
需要注意的是,当在分区窗口中使用order by排序后,窗口返回的不再是全部记录,而是只到当前记录,从而成为默认大小的窗口。
示例:
查询EMP表每个员工的职位和薪水,并给出该职位员工的平均薪水
select empno, ename, job, sal, round(avg(sal) over(partition by job)) avg_sal from emp;
示例:
在查询每个员工职位和入职时间的同时,获得其在该职位中按入职时间进行的排序位置
select empno, ename, job, hiredate, dense_rank() over(partition by job order by hiredate) hiredata_order from emp;
三、窗口子句
窗口子句可以进一步限制或扩展窗口的范围。
1、rows子句
rows子句的使用前提为窗口已经利用order by 进行了排序。此时可以利用rows子句按照位置向前或向后追溯,从而进一步调整窗口大小。语法如下
over(order by 列名 rows between 位移量 preceding and 位移量 following)
preceding向前追溯处于当前记录之前的记录,following向后追溯处于当前记录之后的记录。
示例:
在查询每个员工入职时间和薪水的同时,计算与其前后相邻入职时间的共三位员工的平均薪水
select empno, ename, job, hiredate, sal, round(avg(sal) over(order by hiredate rows between 1 preceding and 1 following)) avg_sal from emp;
对于第一条记录和最后一条记录来说,实际上对应的窗口只有两条记录。
2、range子句
rows子句以相对位置作为获取记录的标准,而range子句则以相对列值作为筛选记录的标准。语法如下
over(order by 列名 range between 差值 preceding and 差值 following)
示例:
在查询每个员工入职时间的同时,计算包括与其入职时间前后相差一年之内的员工数量
select empno, ename, job, hiredate, count(1) over(order by hiredate range between 365 preceding and 365 following) count_emp from emp;
3、current row与unbounded
在窗口子句中,除了可以用确定的数值来限定窗口外,还可以使用current row来指定当前记录,使用unbounded来代替数值,表示不受限制的窗口范围。
向前位移无限制
select empno, ename, job, hiredate, sal, round(avg(sal) over(order by hiredate rows between unbounded preceding and current row)) avg_sal from emp;
向后位移无限制
select empno, ename, job, hiredate, sal, round(avg(sal) over(order by hiredate rows between 1 preceding and unbounded following)) avg_sal from emp;
前后位移均无限制
select empno, ename, job, hiredate, sal, round(avg(sal) over(order by hiredate rows between unbounded preceding and unbounded following)) avg_sal from emp;
四、常用分析函数
很多聚合函数均可以和窗口函数结合使用,如max()、min()、sum()等。除此之外,Oracle还提供了另外几种常用的分析函数。
1、first_value()函数
first_value()函数返回已排序窗口中第一条记录的相关信息。
示例:
在查询员工入职时间和所在部门的同时,指出该部门入职时间最早的员工
select empno, ename, hiredate, deptno, first_value(empno) over(partition by deptno order by hiredate) first_emp from emp;
2、last_value()函数
与first_value()函数相反,last_value()函数返回分区中最后一条记录的相关信息。
示例:
在查询员工入职时间和所在部门的同时,指出该部门入职时间最迟的员工。首先说明,套用fist_value()的查询写法是不行的,如下
select empno, ename, hiredate, deptno, last_value(empno) over(partition by deptno order by hiredate) last_emp from emp;
没有达到希望的查询结果,原因正如前面所说,当在分区窗口中使用order by排序后,窗口返回的不再是全部记录,而是只到当前记录,从而成为默认大小的窗口。正确的做法是可以利用rows子句来指定无限制的窗口,如下
select empno, ename, hiredate, deptno, last_value(empno) over(partition by deptno order by hiredate rows between unbounded preceding and unbounded following) last_emp from emp;
由此可见,last_value()函数往往需要结合使用rows子句使用,因此,使用first_value()函数要优于使用last_value()函数。其实为了获得与last_value()相同的效果,使用first_value()函数时,只要为order by子句添加desc进行降序排列即可。
3、lag()函数
first_value()和last_value()可以返回窗口中第一条和最后一条记录,但不能处理其他记录。为此,Oracle提供了lag()函数和lead()函数来灵活处理其他记录。
lag()函数以当前记录为坐标,按照指定的位移量向上搜索,并尝试捕获记录,捕获不到时返回指定的默认值,语法如下
lag(列名或列表达式, 位移量, 默认值)
这里要求默认值的数据类型应当与列名或列表达式的类型兼容。
示例:
查询每个员工的工资信息,并给出工资排名在他前一位的员工
select empno, ename, sal, lag(empno, 1, null) over(order by sal) lag_empno, lag(ename, 1, 'N/A') over(order by sal) lag_ename from emp;
4、lead()函数
该函数与lag()函数类似,不同之处是lead()函数的位移量为向下偏移。
示例:
查询每个员工的工资信息,并给出工资排名在他后一位的员工,这里直接用lead()替换lag()即可
select empno, ename, sal, lead(empno, 1, null) over(order by sal) lag_empno, lead(ename, 1, 'N/A') over(order by sal) lag_ename from emp;
这里发现,当使用lead()函数做位移推进时,over(order by)这样原本只到当前记录的默认大小的窗口实际范围变大了,从而能够获取到往下的记录。
5、ratio_to_report()占比函数
查询每个员工的工资及其在所有员工和所在部门员工中的百分比
select empno, ename, sal, round((ratio_to_report(sal) over()) * 100, 2) pct1, round((ratio_to_report(sal) over(partition by deptno)) * 100, 2) pct2 from scott.emp;