• PostgreSQL窗口函数(转)


     

    转自:http://time-track.cn/postgresql-window-function.html

    PostgreSQL提供了窗口函数的特性。窗口函数也是计算一些行集合(多个行组成的集合,我们称之为窗口window frame)的数据,有点类似与聚集函数(aggregate function)。但和常规的聚集函数不同的是,窗口函数不会将参与计算的行合并成一行输出,而是保留它们原来的样子。看下面一个例子:

    有一个表示员工薪资的表(部门、员工id,工资):

    postgres=# d empsal 
              Table "public.empsal"
     Column  |       Type        | Modifiers 
    ---------+-------------------+-----------
     depname | character varying | 
     empno   | integer           | 
     salary  | integer           |

    表内现在有如下数据:

    postgres=# select * from empsal ;
      depname  | empno | salary 
    -----------+-------+--------
     develop   |    11 |   5200
     develop   |     7 |   4200
     develop   |     9 |   4500
     develop   |     8 |   6000
     develop   |    10 |   5200
     personnel |     5 |   3500
     personnel |     2 |   3900
     sales     |     3 |   4800
     sales     |     1 |   5000
     sales     |     4 |   4800
    (10 rows)

    我们现在想将每个员工的工资与他所在部门的平均工资进行比较,SQL语句该如何写?利用窗口函数,该查询可以很容易的实现:

    postgres=# SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsal;
      depname  | empno | salary |          avg          
    -----------+-------+--------+-----------------------
     develop   |    11 |   5200 | 5020.0000000000000000
     develop   |     7 |   4200 | 5020.0000000000000000
     develop   |     9 |   4500 | 5020.0000000000000000
     develop   |     8 |   6000 | 5020.0000000000000000
     develop   |    10 |   5200 | 5020.0000000000000000
     personnel |     5 |   3500 | 3700.0000000000000000
     personnel |     2 |   3900 | 3700.0000000000000000
     sales     |     3 |   4800 | 4866.6666666666666667
     sales     |     1 |   5000 | 4866.6666666666666667
     sales     |     4 |   4800 | 4866.6666666666666667
    (10 rows)

    可以看到,聚集函数avg的含义没有变,仍然是求平均值。但和普通的聚集函数不同的是,它不再对表中所有的salary求平均值,而是对同一个部门(PARTITION BY指定的depname)内的salary求平均值,而且得到的结果由同一个部门内的所有行共享,并没有将这些行合并。为了更好的体现普通聚集函数与窗口函数中的聚集函数的区别,再看下面的两个查询:

    postgres=# SELECT avg(salary) FROM empsal;
              avg          
    -----------------------
     4710.0000000000000000
    (1 row)
    
    postgres=# SELECT avg(salary) OVER (PARTITION BY depname) FROM empsal;
              avg          
    -----------------------
     5020.0000000000000000
     5020.0000000000000000
     5020.0000000000000000
     5020.0000000000000000
     5020.0000000000000000
     3700.0000000000000000
     3700.0000000000000000
     4866.6666666666666667
     4866.6666666666666667
     4866.6666666666666667
    (10 rows)
    

    窗口函数总是包含OVER子句,它指定了窗口函数的名字和参数,也是由这个关键字来区分常规聚集函数和窗口函数。OVER子句里面的内容决定窗口函数即将处理的数据该如何划分。在OVER子句里面我们使用PARTITION BY将数据划分成一个个的组(或者称之为分区)。聚集函数处理的时候以分区为单位进行处理,处理结果也由同一个分区内的所有行共享。比如上面的例子,PARTITION BY后面跟着的字段是depname,所以avg函数将以部门为单位进行计算。其实,这个分区就是窗口(window frame),这也是窗口函数名字的由来。

    我们还可以在一个窗口中使用ORDER BY来对输出进行排序:

    postgres=# SELECT depname, empno, salary, rank() OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsal;
      depname  | empno | salary | rank 
    -----------+-------+--------+------
     develop   |     8 |   6000 |    1
     develop   |    10 |   5200 |    2
     develop   |    11 |   5200 |    2
     develop   |     9 |   4500 |    4
     develop   |     7 |   4200 |    5
     personnel |     2 |   3900 |    1
     personnel |     5 |   3500 |    2
     sales     |     1 |   5000 |    1
     sales     |     3 |   4800 |    2
     sales     |     4 |   4800 |    2
    (10 rows)

    窗口函数处理的行来自于FROM子句产生的“virtual table”,如果还有WHERE、GROUP BY、HAVING子句的话,还要经过这些条件的过滤,符合条件的子句才会作为窗口函数的输入。另外,一个查询可以包含多个窗口函数。

    刚才提到,我们使用PARTITION BY来划分窗口,如果省略了该关键字,那么整个表将作为一个窗口来处理:

    postgres=# SELECT salary, sum(salary) OVER () FROM empsal;
     salary |  sum  
    --------+-------
       5200 | 47100
       4200 | 47100
       4500 | 47100
       6000 | 47100
       5200 | 47100
       3500 | 47100
       3900 | 47100
       4800 | 47100
       5000 | 47100
       4800 | 47100
    (10 rows)

    但是,需要注意的是,如果在OVER子句中省略了PARTITION BY但却包含了ORDER BY子句,情况将和上面不太一样:

    postgres=# SELECT salary, sum(salary) OVER(ORDER BY salary ) FROM empsal;
     salary |  sum  
    --------+-------
       3500 |  3500
       3900 |  7400
       4200 | 11600
       4500 | 16100
       4800 | 25700
       4800 | 25700
       5000 | 30700
       5200 | 41100
       5200 | 41100
       6000 | 47100
    (10 rows)

    从结果可以看出,在省略了PARTITION BY但却包含了ORDER BY子句的情况下,并不是整个表是一个窗口,而是将从最低(此例中是salary,所以这里用最低这个词)的行当前行作为一个窗口。这是要特别注意的。

    最后,我们要注意窗口函数使用的场景:

    • 只能在SELECT和ORDER BY子句中使用,不能在任何其他地方使用,比如GROUP BY、HAVING和WHERE子句。这是因为窗口函数的输入是这些子句的输出。这个先后逻辑不可以变。
    • 可以在窗口函数的参数中使用聚集函数,但是不能将窗内函数作为聚集函数的参数。因为窗口函数要在聚集函数之后执行。这个先后逻辑也不能变。

    如果我们真的需要将窗口函数作为某个子句的输入的话,我们可以构造一个SELECT子句,比如:

    SELECT depname, empno, salary
    FROM
      (SELECT depname, empno, salary,
              rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
         FROM empsal
      ) AS ss
    WHERE pos < 3;
    
    postgres=# SELECT depname, empno, salary
    postgres-# FROM
    postgres-#   (SELECT depname, empno, salary,
    postgres(#           rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
    postgres(#      FROM empsal
    postgres(#   ) AS ss
    postgres-# WHERE pos < 3;
      depname  | empno | salary 
    -----------+-------+--------
     develop   |     8 |   6000
     develop   |    10 |   5200
     personnel |     2 |   3900
     personnel |     5 |   3500
     sales     |     1 |   5000
     sales     |     3 |   4800
    (6 rows)

    如果一个查询中包含多个窗口函数,那么我们可以写多个OVER子句,但如果这些窗口函数的作用是一样的,那分开写多个既是一种重复性工作,而且也容易出错。这种情况下,我们可以将窗口里面的内容写成一个WINDOW子句,然后在多个OVER子句中引用。看下例中的两种写法:

    第一种:
    SELECT sum(salary) OVER (PARTITION BY depname ORDER BY salary DESC), 	   avg(salary) OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsal;
    
    postgres=# SELECT sum(salary) OVER (PARTITION BY depname ORDER BY salary DESC), avg(salary) OVER (PARTITION BY depname ORDER BY salary DESC) FROM empsal;
      sum  |          avg          
    -------+-----------------------
      6000 | 6000.0000000000000000
     16400 | 5466.6666666666666667
     16400 | 5466.6666666666666667
     20900 | 5225.0000000000000000
     25100 | 5020.0000000000000000
      3900 | 3900.0000000000000000
      7400 | 3700.0000000000000000
      5000 | 5000.0000000000000000
     14600 | 4866.6666666666666667
     14600 | 4866.6666666666666667
    (10 rows)
    
    
    第二种:
    SELECT sum(salary) OVER w, avg(salary) OVER w
      FROM empsal
      WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
    
    postgres=# SELECT sum(salary) OVER w, avg(salary) OVER w
    postgres-#   FROM empsal
    postgres-#   WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
      sum  |          avg          
    -------+-----------------------
      6000 | 6000.0000000000000000
     16400 | 5466.6666666666666667
     16400 | 5466.6666666666666667
     20900 | 5225.0000000000000000
     25100 | 5020.0000000000000000
      3900 | 3900.0000000000000000
      7400 | 3700.0000000000000000
      5000 | 5000.0000000000000000
     14600 | 4866.6666666666666667
     14600 | 4866.6666666666666667
    (10 rows)
  • 相关阅读:
    Unity3d 汽车物理系第二篇
    Unity3d 汽车物理系统
    RabbitMq 之客户端(publish,subscrbe)
    MongoDB Python create by lee
    sql 分页语句 备忘
    结构化结点定位(数据结构)
    Mongodb 安装
    url 的相对路径转换成绝对路径方法
    mongodb cloud store db
    快速备份指定的表 create by lee
  • 原文地址:https://www.cnblogs.com/kuang17/p/7080946.html
Copyright © 2020-2023  润新知