PostgreSQLSQL高级技巧——Window Function

举报
xcc-2022 发表于 2022/07/04 21:23:56 2022/07/04
【摘要】 Postgresql window function 第一篇  window function,简单来说就是窗口函数。postgresql 在8.4版本开始有了窗口函数的特性。    看多很多中文解释,找不到合适的解释什么是窗口函数,有句英文很好的诠释了什么是窗口函数。 “ The whole idea behind window functions is to allow you to p...

Postgresql window function 第一篇 

 window function,简单来说就是窗口函数。postgresql 在8.4版本开始有了窗口函数的特性。

    看多很多中文解释,找不到合适的解释什么是窗口函数,有句英文很好的诠释了什么是窗口函数。  The whole idea behind window functions is to allow you to process several values of the result set at a time: you see through the window some peer rows and are able to compute a single output value from them, much like when using an aggregate function. ”
    
 先来介绍一下postgresql里面关于窗口函数的语法吧。
   function name over ( partition by column order by column [ RANGE | ROWS ] BETWEEN frame_start AND frame_end);
   这是简化的版本,官网上对于语句写的更详细,有兴趣的可以自己去看看。 对于第一次看见的人来说,这么长的语法可能一下子无法接受,那我们就慢慢一步一步来的说。
   
   首先创建下面这张表

点击(此处)折叠或打开

  1. create table empsalary
  2. (
  3.     depname varchar(20),
  4.     empno varchar(20),
  5.     salary integer
  6. )

    然后插入数据

点击(此处)折叠或打开

  1. postgres=# select * from empsalary;
  2.  depname | empno | salary
  3. ----------+-------+--------
  4.  develop | 11 | 5200
  5.  develop | 7 | 4200
  6.  develop | 9 | 4500
  7.  personel | 5 | 3500
  8.  personel | 6 | 6500
  9.  personel | 12 | 6500
  10.  personel | 15 | 8900
  11. (7 行记录)

EXAMPLE 1
    只使用 function name over(),这是窗口函数使用最简单的方式了吧。

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, sum(salary) over() from empsalary;
  2.  depname | empno | salary | sum
  3. ----------+-------+--------+-------
  4.  develop | 11 | 5200 | 39300
  5.  develop | 7 | 4200 | 39300
  6.  develop | 9 | 4500 | 39300
  7.  personel | 5 | 3500 | 39300
  8.  personel | 6 | 6500 | 39300
  9.  personel | 12 | 6500 | 39300
  10.  personel | 15 | 8900 | 39300
  11. (7 行记录)

光看这个结果,可以看出最后相等的一列就是表是所有行的salary的总值。仔细体会上面那句英文: you see through the window some peer rows and are able to compute a single output value from them。这里的single output value 就是sum,这里的rows就是说有的表中所有的行。这就可以很好的理解了窗口函数的意思了吧。由于这里没有对窗口进行什么设定,所以看到的就是整个表当中的数据。

   关于窗口函数就先写到这里吧。


 Postgresql window function 第二篇 


 上一篇很好的解释了什么是窗口函数,并且举了一个最简单的例子,帮助理解什么是窗口函数。接下来我们来更深入的理解postgresql的窗口函数。还是借用上一篇新建的表来讲解。
   

点击(此处)折叠或打开

  1. postgres=# \\d empsalary
  2.         资料表 \"public.empsalary\"
  3.   栏位 | 型别 | 修饰词
  4. ---------+-----------------------+--------
  5.  depname | character varying(20) |
  6.  empno | character varying(20) |
  7.  salary | integer |

   EXAMPLE 2:
   function name over ( partition by column)   这里的partition by 子句用于对行进行分组的。
  

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, sum(salary) over(partition by depname)  from empsalary;
  2.  depname | empno | salary | sum
  3. ----------+-------+--------+-------
  4.  develop | 11 | 5200 | 13900
  5.  develop | 7 | 4200 | 13900
  6.  develop | 9 | 4500 | 13900
  7.  personel | 5 | 3500 | 25400
  8.  personel | 6 | 6500 | 25400
  9.  personel | 12 | 6500 | 25400
  10.  personel | 15 | 8900 | 25400
  11. (7 行记录)

    由于这次对窗口进行了限制,每一行只能看见自己的分组,所以develop组的sum都是一样的,而personel组的sum是一样的。

   EXAMPLE3:
   function name over (order by column)
   

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, sum(salary) over(order by salary) from
  2.  empsalary;
  3.  depname | empno | salary | sum
  4. ----------+-------+--------+-------
  5.  personel | 5 | 3500 | 3500
  6.  develop | 7 | 4200 | 7700
  7.  develop | 9 | 4500 | 12200
  8.  develop | 11 | 5200 | 17400
  9.  personel | 6 | 6500 | 30400
  10.  personel | 12 | 6500 | 30400
  11.  personel | 15 | 8900 | 39300
  12. (7 行记录)

    上面的结果是按照salary的正序排列的,但是sum列显得很奇怪,好像是累积的加法,好像不是。这就取决于,到底这里的每一行从窗口看看到了什么,我们可以用以下的语句看一下。

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary,array_agg(salary) over(order by salary)
  2.  ,sum(salary) over(order by salary) from empsalary;
  3.  depname | empno | salary | array_agg                            | sum
  4. ----------+-------+--------+--------------------------------------+-------
  5.  personel | 5  | 3500     | {3500}                               | 3500
  6.  develop  | 7  | 4200     | {3500,4200}                          | 7700
  7.  develop  | 9  | 4500     | {3500,4200,4500}                     | 12200
  8.  develop  | 11 | 5200     | {3500,4200,4500,5200}                | 17400
  9.  personel | 6  | 6500     | {3500,4200,4500,5200,6500,6500}      | 30400
  10.  personel | 12 | 6500     | {3500,4200,4500,5200,6500,6500}      | 30400
  11.  personel | 15 | 8900     | {3500,4200,4500,5200,6500,6500,8900} | 39300
  12. (7 行记录)

   每一行能看见的结果都在array_agg列当中可以看出,这就很容易理解为什么sum的值会变成现在像上面一样奇怪的结果吧。其实这里的

   EXAMPLE 3:
    function name over (order by column [rows | range ] between framestart and frameend);
   rows between 子句无法独自和over一起使用,这个子句的作用也是决定了那些行可以被每一行看到。
   

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary,sum(salary) over(order by salary rows between unbounded preceding and current row) from empsalary;
  2.  depname | empno | salary | sum
  3. ----------+-------+--------+-------
  4.  personel | 5 | 3500 | 3500
  5.  develop | 7 | 4200 | 7700
  6.  develop | 9 | 4500 | 12200
  7.  develop | 11 | 5200 | 17400
  8.  personel | 6 | 6500 | 23900
  9.  personel | 12 | 6500 | 30400
  10.  personel | 15 | 8900 | 39300
  11. (7 行记录)

   现在的结果是安salary从小到大排列的,sum刚好就是累积的加法运算。这里的framestart和frameend只能从固定的三个值当中取: unbounded preceding , current row, unbounded following。第一个表示第一行,第二个表示当前行,最后一个表示最后一行,而且他们顺序也不能调换。这能是 unbounded preceding and current row 或者是 current row and unbounded following 再或者就是  unbounded preceding and  unbounded following。
 至此所有的部分都已经讲完了,在复杂的用法就是把他们结合起来使用。相信大家能很好的使用窗口函数。 



postgresql windows function 第三篇 


 前面两篇已经很好的介绍了窗口函数,这篇我们来关注一下function name这个部分。postgresql有一些内置的函数,专门用于窗口函数。如下
   

Function Return Type Description
row_number() bigint number of the current row within its partition, counting from 1
rank() bigint rank of the current row with gaps; same as row_number of its first peer
dense_rank() bigint rank of the current row without gaps; this function counts peer groups
percent_rank() double precision relative rank of the current row: (rank - 1) / (total rows - 1)
cume_dist() double precision relative rank of the current row: (number of rows preceding or peer with current row) / (total rows)
ntile(num_buckets integer) integer integer ranging from 1 to the argument value, dividing the partition as equally as possible
lag(value any [, offsetinteger [, default any ]]) same type as value returns value evaluated at the row that is offset rows before the current row within the partition; if there is no such row, instead return default. Bothoffset and default are evaluated with respect to the current row. If omitted, offset defaults to 1 and default to null
lead(value any [, offsetinteger [, default any ]]) same type as value returns value evaluated at the row that is offset rows after the current row within the partition; if there is no such row, instead return default. Bothoffset and default are evaluated with respect to the current row. If omitted, offset defaults to 1 and default to null
first_value(value any) same type as value returns value evaluated at the row that is the first row of the window frame
last_value(value any) same type as value returns value evaluated at the row that is the last row of the window frame
nth_value(value any, nthinteger) same type as value returns value evaluated at the row that is the nth row of the window frame (counting from 1); null if no such row

这里我们一个一个的来。
  row_number() 
  


点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, row_number() over() from empsalary;
  2.  depname | empno | salary | row_number
  3. ----------+-------+--------+------------
  4.  develop  | 11 | 5200 | 1
  5.  develop  | 7  | 4200 | 2
  6.  develop  | 9  | 4500 | 3
  7.  personel | 5  | 3500 | 4
  8.  personel | 6  | 6500 | 5
  9.  personel | 12 | 6500 | 6
  10.  personel | 15 | 8900 | 7
  11. (7 行记录)

其实这个函数就是给窗口内的每一行编号,从一号开始。不过如果没有排序的话,这样的编号感觉也没有意思啊。
 

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, row_number() over() from empsalary order by salary;
  2.  depname | empno | salary | row_number
  3. ----------+-------+--------+------------
  4.  personel | 5 | 3500 | 4
  5.  develop | 7 | 4200 | 2
  6.  develop | 9 | 4500 | 3
  7.  develop | 11 | 5200 | 1
  8.  personel | 6 | 6500 | 5
  9.  personel | 12 | 6500 | 6
  10.  personel | 15 | 8900 | 7
  11. (7 行记录)

怪了,使用order by 但是这里放的地方错误,可以从这里推断,row_number()是比order by 先执行的。
正确的写法是

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, row_number() over(order by salary) from empsalary ;
  2.  depname | empno | salary | row_number
  3. ----------+-------+--------+------------
  4.  personel | 5 | 3500 | 1
  5.  develop | 7 | 4200 | 2
  6.  develop | 9 | 4500 | 3
  7.  develop | 11 | 5200 | 4
  8.  personel | 6 | 6500 | 5
  9.  personel | 12 | 6500 | 6
  10.  personel | 15 | 8900 | 7
  11. (7 行记录)


rank()

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, rank() over(order by salary) from empsalary ;
  2.  depname | empno | salary | rank
  3. ----------+-------+--------+------
  4.  personel | 5 | 3500 | 1
  5.  develop | 7 | 4200 | 2
  6.  develop | 9 | 4500 | 3
  7.  develop | 11 | 5200 | 4
  8.  personel | 6 | 6500 | 5
  9.  personel | 12 | 6500 | 5
  10.  personel | 15 | 8900 | 7
  11. (7 行记录)

从结果可以看出,rank其实和row_number()差不多,唯一的差别有两个5,没有6.这就是所谓的gap吧。

dense_rank()

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, dense_rank() over(order by salary) from empsalary ;
  2.  depname | empno | salary | dense_rank
  3. ----------+-------+--------+------------
  4.  personel | 5 | 3500 | 1
  5.  develop | 7 | 4200 | 2
  6.  develop | 9 | 4500 | 3
  7.  develop | 11 | 5200 | 4
  8.  personel | 6 | 6500 | 5
  9.  personel | 12 | 6500 | 5
  10.  personel | 15 | 8900 | 6
  11. (7 行记录)

dense_rank()和rank是一样的,但是区别还是有的,他是没有gap的。


percent_rank() 和 cume_dist() 这两个函数不知道有什么用,不过计算的公式就在上面,看看就知道怎么算了。


ntile()

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, ntile(3) over(order by salary) from empsalary ;
  2.  depname | empno | salary | ntile
  3. ----------+-------+--------+-------
  4.  personel | 5 | 3500 | 1
  5.  develop | 7 | 4200 | 1
  6.  develop | 9 | 4500 | 1
  7.  develop | 11 | 5200 | 2
  8.  personel | 6 | 6500 | 2
  9.  personel | 12 | 6500 | 3
  10.  personel | 15 | 8900 | 3
  11. (7 行记录)

这个函数的作用是将结果分组,3 表示分为三组。每一组再编号。


lag( value   any  [,  offsetinteger  [,  default   any  ]])

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, lag(salary,1,0) over(order by salary)
  2.  from empsalary ;
  3.  depname | empno | salary | lag
  4. ----------+-------+--------+------
  5.  personel | 5 | 3500 | 0
  6.  develop | 7 | 4200 | 3500
  7.  develop | 9 | 4500 | 4200
  8.  develop | 11 | 5200 | 4500
  9.  personel | 6 | 6500 | 5200
  10.  personel | 12 | 6500 | 6500
  11.  personel | 15 | 8900 | 6500
  12. (7 行记录)

获取前一行的对应列,如果没有的话,就用0表示。而lead刚好和他相反。自己试一下就知道了。
first_value() 

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, first_value(salary) over () from empsa
  2. lary;
  3.  depname | empno | salary | first_value
  4. ----------+-------+--------+-------------
  5.  develop | 11 | 5200 | 5200
  6.  develop | 7 | 4200 | 5200
  7.  develop | 9 | 4500 | 5200
  8.  personel | 5 | 3500 | 5200
  9.  personel | 6 | 6500 | 5200
  10.  personel | 12 | 6500 | 5200
  11.  personel | 15 | 8900 | 5200
  12. (7 行记录)

获取windows frame的第一行。
last_value() 刚好和first_value()相反,取最后一列。

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, last_value(salary) over () from empsal
  2. ary;
  3.  depname | empno | salary | last_value
  4. ----------+-------+--------+------------
  5.  develop | 11 | 5200 | 8900
  6.  develop | 7 | 4200 | 8900
  7.  develop | 9 | 4500 | 8900
  8.  personel | 5 | 3500 | 8900
  9.  personel | 6 | 6500 | 8900
  10.  personel | 12 | 6500 | 8900
  11.  personel | 15 | 8900 | 8900
  12. (7 行记录)

再来一个

点击(此处)折叠或打开

  1. postgres=# select depname, empno, salary, last_value(salary) over (order by sala
  2. ry) from empsalary;
  3.  depname | empno | salary | last_value
  4. ----------+-------+--------+------------
  5.  personel | 5 | 3500 | 3500
  6.  develop | 7 | 4200 | 4200
  7.  develop | 9 | 4500 | 4500
  8.  develop | 11 | 5200 | 5200
  9.  personel | 6 | 6500 | 6500
  10.  personel | 12 | 6500 | 6500
  11.  personel | 15 | 8900 | 8900
  12. (7 行记录)

为什么这里会不一样的,仔细想想order by为什么会导致这么样的结果,如果你看了第一篇的话,就能明白了。

而nth_value()我想你自己从字面上就能理解了吧。这里就不讲解了。


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。