PostgreSQL 窗口函数复习笔记

来源:互联网 发布:黑莓z10能用淘宝吗 编辑:程序博客网 时间:2024/06/12 19:51

在总结hive之前特地回去复习了一下,以前PostgreSQL的窗口函数(MySQL目前还没有支持),以下基于版本9.3实验。

我们都知道在sql中有一类函数叫做聚合函数,例如sum()、avg()、max()等等,这类函数可以将多行数据按照规则聚集为一行,一般来讲聚集后的行数是要少于聚集前的行数的.但是有时我们想要既显示聚集前的数据,又要显示聚集后的数据,这时我们便引入了窗口函数.

在深入研究Over字句之前,一定要注意:在SQL处理中,窗口函数都是最后一步执行,而且仅位于Order by字句之前.

数据准备
我们准备一张order1表,字段分别为name,orderdate,cost.数据内容如下:

insert into order1 values('jack','2017-01-01',10);insert into order1 values('tony','2017-01-02',15);insert into order1 values('jack','2017-02-03',23);insert into order1 values('tony','2017-01-04',29);insert into order1 values('jack','2017-01-01',46);insert into order1 values('jack','2017-04-06',42);insert into order1 values('tony','2017-01-07',50);insert into order1 values('jack','2017-01-08',55);insert into order1 values('mart','2017-04-08',62);insert into order1 values('mart','2017-04-09',68);insert into order1 values('neil','2017-05-10',12);insert into order1 values('mart','2017-04-11',75);insert into order1 values('neil','2017-06-12',80);insert into order1 values('mart','2017-04-13',94);insert into order1 values('jack','2017-06-06',23);insert into order1 values('jack','2017-09-06',42);

这里写图片描述

聚合函数+over
假如说我们想要查询在2017年4月份购买过的顾客及总人数,我们便可以使用窗口函数去去实现

select  name,orderdate,count(*) over()from order1where  substring(to_char(orderdate, 'yyyy-MM-dd') from 1 for 7)='2017-04';

这里写图片描述

partition by子句

Partition By子句可以称为查询分区子句,非常类似于Group By,都是将数据按照边界值分组,而Over之前的函数在每一个分组之内进行,如果超出了分组,则函数会重新计算.

我们想要去看顾客的购买明细及月购买总额,可以执行如下的sql

select name ,orderdate,cost,sum(cost) over(partition by extract(MONTH from orderdate))from order1;

这里写图片描述

order by子句

上述的场景,假如我们想要将cost按照月进行累加.这时我们引入order by子句.

order by子句会让输入的数据强制排序(文章前面提到过,窗口函数是SQL语句最后执行的函数,因此可以把SQL结果集想象成输入数据)。Order By子句对于诸如Row_Number(),Lead(),LAG()等函数是必须的,因为如果数据无序,这些函数的结果就没有任何意义。因此如果有了Order By子句,则Count(),Min()等计算出来的结果就没有任何意义。

我们在上面的代码中加入order by

select name ,orderdate,cost,sum(cost) over(partition by extract(month from orderdate) order by orderdate)from order1;

这里写图片描述

window子句

我们在上面已经通过使用partition by子句将数据进行了分组的处理.如果我们想要更细粒度的划分,我们就要引入window子句了.

我们首先要理解两个概念:
- 如果只使用partition by子句,未指定order by的话,我们的聚合是分组内的聚合.

当同一个select查询中存在多个窗口函数时,他们相互之间是没有影响的.

  • 每个窗口函数应用自己的规则.

window子句:
- PRECEDING(preceding):往前
- FOLLOWING(following):往后
- CURRENT ROW(current row):当前行
- UNBOUNDED(unbounded):起点,UNBOUNDED PRECEDING 表示从前面的起点, UNBOUNDED FOLLOWING:表示到后面的终点

我们按照name进行分区,按照购物时间进行排序,做cost的累加.
如下我们结合使用window子句进行查询。

select name ,orderdate,cost,sum(cost) over() as sample1,--所有行相加sum(cost) over(partition by name) as sample2,--按name 分组,组内数据相加sum(cost) over(partition by name order by orderdate) as sample3,--按name分组并按orderdate排序,组内数据累加--和sample3一样,由起点到当前行的聚合sum(cost) over(partition by name order by orderdate rows between unbounded preceding  and current row) as sample4,sum(cost) over(partition by name order by orderdate rows between 1 preceding and current row) as sample5,--当前行和前面一行做聚合sum(cost) over(partition by name order by orderdate rows between 1 preceding and 1 following )as sample6,--当前行和前一行及后面一行sum(cost) over(partition by name order by orderdate rows between current row and unbounded following) as sample7 --当前行及后面所有行from order1;

这里写图片描述

窗口函数中的序列函数

主要序列函数是不支持window子句的.

常用的序列函数有下面几个:

NTILE(ntile)

NTILE(n),用于将分组数据按照顺序切分成n片,返回当前切片值

NTILE不支持ROWS BETWEEN,
比如 NTILE(2) OVER(PARTITION BY cost ORDER BY name ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)

如果切片不均匀,默认增加第一个切片的分布
应用场景:
假如我们想要每位顾客购买金额前1/3的交易记录,我们便可以使用这个函数.

select name,orderdate,cost,ntile(3) over() as sample1,--全局数据切片ntile(3) over(partition by name),--按name进行分组,在分组内将数据切分成3份ntile(3) over(order by cost),--全局按cost升序排列,数据切成3份ntile(3) over(partition by name order by cost) --按照name分组,在分组内按照cost升序排列,数据切成3份from order1;

这里写图片描述

row_number、rank、dense_rank

这三个窗口函数的使用场景非常多
- row_number()从1开始,按照顺序,生成分组内记录的序列,row_number()的值不会存在重复,当排序的值相同时,按照表中记录的顺序进行排列
- RANK() 生成数据项在分组中的排名,排名相等会在名次中留下空位
- DENSE_RANK() 生成数据项在分组中的排名,排名相等会在名次中不会留下空位

**注意:
rank和dense_rank的区别在于排名相等时会不会留下空位.**

举例如下:

select  name ,orderdate,cost,rank() over(partition by name order by cost desc) as rnl,--值同排名相同,同时不保留被占用的排名序号,即总排名号不连续dense_rank() over(partition by name order by cost desc) as rn2,--值同,排名相同,保留下一个的排名序列号,即总排名连续row_number() over(partition by name order by cost desc) as rn3--强制按罗列的结果排序,更像行号。from order1where name='jack';

这里写图片描述

LAG和LEAD函数

以我们的订单表为例,假如我们想要查看顾客上n次的购买时间可以这样去查询
time1取的为按name进行分组,分组内升序,取上一行数据值。
time2取的为按name进行分组,分组内升序排序,取上面2行的数据的值,注意当log函数没设置时,默认为1行。未设定取不到默认值时,取null值。

select name,orderdate,cost,lag(orderdate,1,'1900-01-01') over(partition by name order by orderdate ) as time1,lag(orderdate,2) over (partition by name order by orderdate) as time2from order1;

这里写图片描述

lead函数与lag函数方向相反,取向下的数据.

first_value和last_value

first_value取分组内排序后,截止到当前行,第一个值
last_value取分组内排序后,截止到当前行,最后一个值

select name,orderdate,cost ,first_value(orderdate) over(partition by name order by orderdate)as time1,last_value(orderdate) over(partition by name order by orderdate) as time2from order1;

这里写图片描述

以上转载参考:http://blog.csdn.net/qq_26937525/article/details/54925827

按分组取出TOP值,是非常常见的业务需求。如:取每门课的前3名:
数据准备如下:

create table SC(stdid int,clazzid int,course VARCHAR,score  int)
insert into SC VALUES(1,1,'Eng',89);insert into SC VALUES(2,2,'Eng',79);insert into SC VALUES(3,1,'Eng',69);insert into SC VALUES(4,2,'Eng',39);insert into SC VALUES(5,1,'Eng',99);insert into SC VALUES(1,1,'yuwen',86);insert into SC VALUES(2,2,'yuwen',76);insert into SC VALUES(3,1,'yuwen',64);insert into SC VALUES(4,2,'yuwen',32);insert into SC VALUES(5,1,'yuwen',91);insert into SC VALUES(1,1,'shuxue',11);insert into SC VALUES(2,2,'shuxue',52);insert into SC VALUES(3,1,'shuxue',55);insert into SC VALUES(4,2,'shuxue',88);insert into SC VALUES(5,1,'shuxue',59);

这里写图片描述
分组取topN:
取每门课的前3名

select * from (select *,row_number() over(partition by course order by score desc) as rn from SC ) t where t.rn<4;

这里写图片描述

以上为一般做法,此做法会全表扫描,效率不是很好,优化可以参考德哥的另外一篇文章:https://yq.aliyun.com/articles/57315

分组排序:

select * from sc where score >60 order by course,score desc ;

这里写图片描述
或者:

select * ,row_number() over(partition by course order by score desc) as tmpT from SCwhere score > 60;

这里写图片描述

原创粉丝点击