在总结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 order1
where 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 order1
where 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 time2
from 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 time2
from 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);
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 SC
where score > 60;