SQL经典实例(附录)窗口函数

窗口函数针对指定的行集合(分组)执行聚合运算。不同之处在于,窗口函数能够为每个分组返回多个值,而聚合函数只能返回单一值。聚合运算的对象其实是一组行记录,我们称之为“窗口”(因此才有了术语“窗口函数”)。在Oracle中成为分析函数。

窗口操作

如果要计算整个公司的员工总数,传统做法是执行count()*:

select count(*) from emp;

clipboard.png
但是有时候我们可能需要从非聚合数据行或者从不同纬度的聚合数据行里访问这一类聚合运算结果。

select ename,
       deptno,
       count(*) over() as cnt
    from emp
order by 2;

clipboard.png

关键字 OVER 表明 COUNT 函数会作为窗口函数来调用,而不是一次普通的聚合函数调用。

执行时机

这里我们为前一节的查询语句加上一个 WHERE 子句,以过滤掉 DEPTNO 等于 20和 30 的员工。

select ename,
       deptno,
       count(*) over() as cnt
    from emp where deptno = 10
order by 2;

clipboard.png

该示例表明 WHERE 和 GROUP BY 这一类子句执行完之后,才轮到窗口函数执行。

分区

可以使用 PARTITION BY 子句针对行数据进行分区(partition)或者分组(group),并根据其结果执行聚合运算。我们在前面的示例中看到过,如果 OVER 关键字后面跟着一个空的圆括号,那么窗口函数执行聚合运算时,会把该查询结果集整体作为一个分区来看待。因此,我们不妨把 PARTITION BY 子句理解成“动态的 GROUP BY”,它不同于传统的 GROUP BY,因为在最终的结果集中允许出现多种由 PARTITION BY 生成的分区。
考虑如下查询语句:

select ename,
       deptno,
       count(*) over(partition by deptno) as cnt
    from emp 
order by 2;

clipboard.png

由于使用了 PARTITION BY DEPTNO 子句,现在聚合函数 COUNT 会分别计算出每一个部门的员工人数。

相较于传统的 GROUP BY,PARTITION BY 子句的另一个好处是,在同一个 SELECT 语句里我们可以按照不同的列进行分区,而且不同的窗口函数调用之间互不影响。

如下所示的查询,它会逐一列出全体员工,并返回每一个人所属的部门,所在部门的员工总数,每一个人的职位,以及公司范围内从事相同工作的员工总数。

select ename,
       deptno,
       count(*) over(partition by deptno) as dept_cnt,
       job,
       count(*) over(partition by job) as job_cnt
    from emp 
order by 2;

clipboard.png

Null的影响

类似于 GROUP BY 子句,PARTITION BY 子句会把所有的 Null 归入同一个分区或者分组。
考虑如下查询:

select coalesce(comm,-1) as comm,
    count(*)over(partition by comm) as cnt
from emp 

clipboard.png
如果不用count()而是用count(comm)*则会有如下结果:

select coalesce(comm,-1) as comm,
    count(comm)over(partition by comm) as cnt
from emp 

clipboard.png
聚合函数会忽略掉 NULL 值。

当使用 COUNT 函数时,我们应该思考一下是否要把 Null 包括在内。使用COUNT(column) 会忽略 Null。如果希望把 NULL 值一并计入,则应该使用COUNT(*)。(此时我们要计算的不是实际的列值,而是希望知道有多少行。)

排序

当在窗口函数的 OVER 子句中使用 ORDER BY 时,我们实际上是在决定两件事:
(1) 分区内的行数据如何排序;
(2) 计算涉及哪些行数据。
我们来看一下如下所示的查询,该查询计算出了 DEPTNO 等于 10 的员工的工资累计合计值。

select deptno,
       ename,
       hiredate, 
       sal,
       sum(sal) over(partition by deptno) as total1,
       sum(sal) over() as total2,
       sum(sal) over(order by hiredate) as running_total
    from emp
where deptno = 10;

这个查询与下列查询等价,使用range between...and显式指定了order by hiredate默认行为方式:

select deptno,
       ename,
       hiredate, 
       sal,
       sum(sal) over(partition by deptno) as total1,
       sum(sal) over() as total2,
       sum(sal) over(order by hiredate
                    range between unbounded preceding
                    and current row) as running_total
    from emp
where deptno = 10;

结果均为:
clipboard.png
上述查询中出现的 RANGE BETWEEN 子句在 ANSI 标准中被称作 Framing 子句。Framing 子句能定义动态变化的“数据子窗口”,并将其融入聚合运算。
例如如下查询语句:

select deptno,
       ename,
       hiredate, 
       sal,
       sum(sal) over(order by hiredate
                    range between unbounded preceding
                    and current row) as run_total1,
       sum(sal) over(order by hiredate
                    range between 1 preceding
                    and current row) as run_total2,
       sum(sal) over(order by hiredate
                    range between current row
                    and unbounded following) as run_total3,
       sum(sal) over(order by hiredate
                    range between current row
                    and 1 following) as run_total4
    from emp
where deptno = 10;

clipboard.png

select 
    ename,
    sal,
    min(sal)over(order by sal) min1,
    max(sal)over(order by sal) max1,
    min(sal)over(order by sal
                range between unbounded preceding
                and unbounded following) min2,
    max(sal)over(order by sal
                range between unbounded preceding
                and unbounded following) max2,
    min(sal)over(order by sal
                range between current row
                and current row) min3,
    max(sal)over(order by sal
                range between current row
                and current row) max3,
    max(sal)over(order by sal
                rows between 3 preceding
                and 3 following) max4
 from emp;

clipboard.png

《SQL经典实例》 附录A
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值