HiveSQL核心技能之窗口计算

目标:
1、掌握 sum()、avg()等用于累计计算的聚合函数,学会对行数的限制(移动计算);
2、掌握 row_number(),rank()、dense_rank()用于排序的函数;
3、掌握 ntile()用于分组查询的函数;
4、掌握 lag()、lead()偏移分析函数

窗口函数(window function):
与聚合函数类似,但是窗口函数是每一行数据都生成一个结果,聚合函数可以将多行数据按照规定聚合为一行,一般来说聚合后的行数要少于聚合前的行数,但是有时我们想要既显示聚合前的数据,又要显示聚合后的数据,这时便引入了窗口函数,窗口函数是在 select 时执行的,位于 order by 之前

一、累计计算窗口函数(相当于计算聚合的聚合?二次聚合?)

1、sum(...) over(...)

在日常工作中,经常遇到计算截止某月或某天的累计数值,在Excel可以通过函数来实现,
在HiveSQL里,可以利用窗口函数实现。

1)2018年每月的支付总额和当年累计支付总额

select a.month
        ,a.total_amount
        ,sum(a.total_amount) over(order by a.month)
from 
   ( select month(dt) as month,sum(pay_amount) as total_amount
    from user_trade
    where year(dt)=2018
    group by month(dt)
    )a

2)对2017年和2018年公司的支付总额按月度累计进行分析,按年度进行汇总

写法一:

select a.year
        ,a.month
        ,a.amount
        ,sum(a.amount) over(partition by a.year order by a.month)
from 
   (select substr(trade_time,1,4) as year
            ,substr(trade_time,6,2) as month
            ,sum(amount)as amount
    from trade_2017
    group by substr(trade_time,1,4),substr(trade_time,6,2)
union all
    select substr(trade_time,1,4) as year
            ,substr(trade_time,6,2) as month
            ,sum(amount) as amount 
    from trade_2018
    group by substr(trade_time,1,4),substr(trade_time,6,2)
    )a

写法二:

select a.year
        ,a.month
        ,a.pay_amount
        ,sum(a.pay_amount) over(partition by a.year order by a.month)
from
   (select year(dt)as year
            ,month(dt) as month
            ,sum(pay_amount) as pay_amount
    from user_trade
    where year(dt) in (2017,2018)
    group by year(dt),month(dt)
    )a

说明:1、over中的 partition by 起到分组的作用;
2、order by 按照什么顺序进行累加,升序ASC、降序DESC,默认升序
3、正确的分组是非常重要的,partition by 后面的字段是需要累计计算的区域,需要仔细理解

2、avg(...) over(...):移动平均

(计算三日留存、七日留存、三十日留存等方式可以使用这个函数。)

3)对2018年每个月的近三个月进行移动的求平均支付金额

    select a.month
            ,a.amount
            ,avg(a.amount) over(order by a.month rows between 2 preceding and current row)
    from 
       (select month(dt) as month
                ,sum(pay_amount) as amount
        from user_trade
        where year(dt)=2018
        group by month(dt)
        )a
    注意:如果使用聚合函数,则必须要在group by里使用,窗口函数使用时不用group by,
        但是窗口函数必须要在子查询里配合使用聚合函数才能得出正确的结果

3、语法总结

sum(...A...) over(partition by ...B... order by ...C... rows between ...D1... and ...D2...)
avg(...A...) over(partition by ...B... order by ...C... rows between ...D1... and ...D2...) 

A:需要被加工的字段名称
B:分组的字段名称
C:排序的字段名称
D:计算的行数范围

rows between unbounded preceding and current row
包括本行和之前所有的行

rows between current row and unbounded following
包括本行和之后所有的行

rows between 3 preceding and current row
包括本行以内和前三行

rows between 3 preceding and 1 following 
从前三行到下一行(5行)

拓展:
max(...A...) over(partition by ...B... order by ...C... rows between ...D1... and ...D2...)
min(...A...) over(partition by ...B... order by ...C... rows between ...D1... and ...D2...) 

二、分区排序窗口函数(row_number()、rank()、dense_rank())

用法:这三个函数的作用都是返回相应规则的排序序号,由于排序函数不是二次聚合计算,因此不一定要使用子查询

row_number() over(partition by ...A... order by ...B...)

rank() over(partition by ...A... order by ...B...)

dense_rank() over(partition by ...A... order by ...B...)

A:分组的字段名称
B:排序的字段名称

注意:这3个函数的括号内是不加任何字段名称的!

row_number:为查询出来的每一行生成一个序号,依次排序且不会重复;
rank和dense_rank:在各个分组内,rank()是跳跃排序,有两个第一名时接下来就是第三名,
dense_rank()是连续排序,有两个第一名时,仍然跟着第二名。

4)2019年1月,用户购买商品品类数量的排名

select user_name
        ,count(distinct goods_category) as goods_num
        ,row_number() over(order by count(distinct goods_category)) as row_number
        ,rank() over(order by count(distinct goods_category)) as rank
        ,dense_rank() over(order by count(distinct goods_category)) as dense_rank
from user_trade
where substr(dt,1,7)='2019-01'
group by user_name

注意:由于substr()函数截取的日期是字符串格式,所以要加引号,用日期函数截取的日期才可以不加引号。

5)选出2019年支付金额排名在第10、20、30名的用户

select a.user_name,a.amount,a.row_number
from
   (select user_name
            ,sum(pay_amount) as amount
            ,row_number() over(order by sum(pay_amount) desc)as row_number
    from user_trade
    where year(dt)=2019
    group by user_name
    )a
   where a.row_number in(10,20,30)

三、切片排序窗口函数

    ntile(n) over(...)
    
    ntile(n) over(partition by ...A... order by ...B...)

    n:切分的片数
    A:分组的字段名称
    B:排序的字段名称

    ntile(n):用于将分组数据按照顺序切分成n片,返回当前切片值。
    ntile不支持rows between,比如
    ntile(2) over(partition by ... order by ... rows between 3 preceding and current row)
    如果切片数据不均匀,则前面的组分得的数据较多。

6)将2019年1月的支付用户,按照支付金额分成5组

    select user_name
            ,sum(pay_amount) as amount
            ,ntile(5) over(order by sum(pay_amount) desc)as ntile
    from user_trade
    where substr(dt,1,7)='2019-01'
    group by user_name

7)选出2019年退款金额排名前10%的用户

   select a.user_name,a.amount,a.ntile
   from
    (select user_name
            ,sum(refund_amount) amount
            ,ntile(10) over(order by sum(refund_amount) desc)as ntile
    from user_refund
    where year(dt)=2019
    group by user_name
    )a
    where ntile=1

四、偏移分析窗口函数

说明:Lag和Lead分析函数可以在同一次查询中取出同一字段的前N行数据(Lag)和后N行的数据(Lead)作为独立的列。

在实际应用当中,若要用到取今天和昨天的某字段差值时,Lag和Lead函数的应用就显得尤为重要。
当然,这种操作可以用表的自连接实现,但是Lag和Lead与 left join、 right join等自连接相比,效率更高,SQL语句更简洁。

lag(exp_str,offset,defval) over(partition by ... order by ...)
lead(exp_str,offset,defval) over(partition by ... order by ...)

exp_str是字段名称;
offset表示偏移量,即是上一个或上N个的值,假设当前行在表中排在第5行,offset为3,
则表示我们所要找的数据行就是表中的第2行(即5-3=2),offset默认值为1。
defval默认值,当这两个函数取上N/下N个值时,在表中从当前行位置向前数N行已经超出了
表的范围时,lag()函数将defval这个参数作为函数的返回值,若每月指定默认值,则返回NULL。
lead()函数也是一样的道理。在数学运算中,一般都是要给一个默认值才不会出错。

8)支付时间间隔超过100天的用户数(这一次购买距离下一次购买的时间?,注意datediff函数是日期大的在前面)

写法一:

select count(distinct a.user_name)
from
   (select user_name
            ,dt
            ,lead(dt,1,dt) over(partition by user_name order by dt) lead_dt
    from user_trade
    where dt>'0'
    )a
where datediff(a.lead_dt,dt)>100



写法二:

select count(distinct a.user_name)
from
   (select user_name
            ,dt
            ,lag(dt,1,dt) over(partition by user_name order by dt) lag_dt
    from user_trade
    where dt>'0'
    )a
where datediff(dt,a.lag_dt)>100

9)每个城市,不同性别,2018年支付金额最高的TOP3用户

select *
from(
select
    b.city
    ,b.sex
    ,a.user_name
    ,a.amount
    ,row_number()over(partition by b.city,b.sex order by a.amount desc) rank 
from
    (select user_name
            ,sum(pay_amount) amount
    from user_trade
    where year(dt)=2018
    group by user_name)a
left join
    (select user_name,city,sex
    from user_info)b
on a.user_name=b.user_name)c
where c.rank<=3

步骤总结:
1、首先筛选出每个用户和每个用户总的消费金额;
2、对两个表进行连接提取需要的字段;
3、对连接后的表进行二次聚合计算,计算出不同城市、性别的金额排名;
4、对二次聚合计算的表进行条件筛选提取

10)每个手机品牌退款金额前25%的用户

                                                                        "phonebrand":"iphone X"

  select *
  from
  (
  select b.phonebrand
          ,a.user_name
          ,a.amount
          ,ntile(4) over(partition by b.phonebrand order by a.amount desc) ntile
  from 
      (select user_name,sum(refund_amount) amount
      from user_refund
      where dt>'0'
      group by user_name)a
  left join
      (select user_name,extra2["phonebrand"]as phonebrand
      from user_info
      )b
  on a.user_name=b.user_name
  )c
  where c.ntile=1

步骤总结:
1、首先筛选出每个用户和每个用户的总退款金额;
2、对两个表进行连接提取需要的字段;
3、对连接后的表进行按手机品牌内分组;
4、对分组后的表进行条件筛选提取

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容