hive日常数据需求处理(窗口函数的使用)

背景:

日常工作中有许多数据处理需求需要解决,在此之间,获得需求,用hive实现需求,最终完成任务。

数据源:

hive中的adventure_ods库的ods_sales_orders表
表结构:


需求一:

统计每个用户截止到每月为止的最大交易金额和累计到该月的总交易金额,结果数据格式如下:


思路:
“每个用户每月”--分组依据(create_date)
“最大”--max() over()
“累计”--sum() over()
实现:
(1)先按年月进行分组的基础查询(用户id,日期,数量,金额)

select 
customer_key,substr(create_date,1,7) as  `umonth(当月)`,
count(1) as ucount ,sum(unit_price) as usum
from ods_sales_orders
group by customer_key,substr(create_date,1,7)
limit 10;

(2)利用窗口函数,求最大金额和累计金额

select customer_key, 
`umonth(当月)`, 
ucount,
max(usum) over(partition by customer_key order by `umonth(当月)` rows between unbounded preceding and current row) as current_max,
sum(usum) over(partition by customer_key order by `umonth(当月)` rows between unbounded preceding and current row) as current_sum
from 
(select 
customer_key,substr(create_date,1,7) as  `umonth(当月)`,
count(1) as ucount ,
sum(unit_price) as usum
from ods_sales_orders
group by customer_key,substr(create_date,1,7)) a 
limit 10;

相关知识点:

需求二:

计算用户的回购率和复购率

复购率:当前月份购买2次及以上的客户占所有客户比例
回购率:当前月份购买且上个月份也购买的客户占当月所有月份客户比例

思路:

  • 复购率:
    (1)对客户分组,计数购买次数
select customer_key,
substr(create_date,1,7) as umonth,
count(1) as con
from ods_sales_orders
group by customer_key,substr(create_date,1,7)
limit 10;

(2)筛选购买次数为2及以上的,即为复购群体,并计算复购率

select 
umonth,
count(1) as `该月客户总数`,
count(if (con>1,1,null)) as `该月再次购买客户数`,
count(if(con>1,1,null))/count(1) as `该月复购率`
from 
(select customer_key,
substr(create_date,1,7) as umonth,
count(1) as con
from ods_sales_orders
group by customer_key,substr(create_date,1,7)
)a 
group by umonth
limit 10;

注意:
mysql和hive中都是count(null)=0。

  • 回购率:
    思路:
    这里回购的定义是:当月和下月购买时间相隔1个月即可(感觉定义不是很合理的样子...)

所以限制条件:表自关联,左右表在当月和间隔1个月的下月的相同客户id
把复购率和回购率一起查询以便于查看。

select 
a.`日期`,
count(if(a.con>1,1,null))/count(1) as `复购率`,
count(b.customer_key)/count(a.customer_key)  as `回购率`
from (select customer_key,
substr(create_date,1,7) as `日期`,
count(1) as con 
from ods_sales_orders
group by customer_key,substr(create_date,1,7)) a 
left join 
(select customer_key,
substr(create_date,1,7) as `日期`
from ods_sales_orders
group by customer_key,substr(create_date,1,7)) b 
on a.customer_key=b.customer_key
and concat(a.`日期`,'-01') =add_months(concat(b.`日期`,'-01'),1)
group by a.`日期`
limit 10;

注意:
hive中add_months(日期,N) 函数可将日期往前、往后推N个月。主要有两种格式:
add_months(‘yyyy-MM-dd hh:mm:ss’,N),add_months(‘yyyy-MM-dd’,N)。N为正往后推,N为负往前推。
(强调:这里的日期格式必须包含年月日三部分,否则(比如只包含年月)会报错,)

可以再优化一下格式,把比率以百分比的形式显示,利用concat(round(复购率*100,2),'%')
(round()函数的作用是四舍五入保留两位数)

select 
a.`日期`,
concat(round(count(if(a.con>1,1,null))/count(1)*100,2),'%') as `复购率`,
concat(round(count(b.customer_key)/count(a.customer_key)*100,2),'%')  as `回购率`
from (select customer_key,
substr(create_date,1,7) as `日期`,
count(1) as con 
from ods_sales_orders
group by customer_key,substr(create_date,1,7)) a 
left join 
(select customer_key,
substr(create_date,1,7) as `日期`
from ods_sales_orders
group by customer_key,substr(create_date,1,7)) b 
on a.customer_key=b.customer_key
and concat(a.`日期`,'-01') =add_months(concat(b.`日期`,'-01'),1)
group by a.`日期`
limit 10;

需求三:

求用户最先购买的两种子类别产品信息,要求拼接成 “用户号-产品1-产品2” 的形式

with demo as ()
,
demo2 as (
select ...
from demo)
select
concat(customer_key, '-', concat_ws('-', collect_set(cpzl_zw))) as zl
from demo2
where cpzl_zw_num < 3
GROUP BY customer_key;

完整代码:

with demo as (select
customer_key,
cpzl_zw,
order_num,
cpzl_zw1
from 
(select 
customer_key,
cpzl_zw,
row_number() over(partition by customer_key order by create_date asc)  as order_num,
lag(cpzl_zw,1,0) over(partition by customer_key order by create_date asc) as cpzl_zw1
from ods_sales_orders)a
where cpzl_zw!=cpzl_zw1
),demo2 as (
select
customer_key,
cpzl_zw,
order_num,
cpzl_zw1,
row_number()over(partition by customer_key order by order_num) as cpzl_zw_num
from demo)
select concat(customer_key,'-',concat_ws('-',collect_set(cpzl_zw))) as zl
from demo2
where cpzl_zw_num<3
group by customer_key
limit 10;

知识点回顾:

  • lag()over()
    lag(参数1,参数2,参数3)over(order by 列)
    参数1:表中列名 参数2:往下偏移多少位 参数3:超出行数时默认设置值
  • with as
    with as 也叫做子查询部分,首先定义一个sql片段,该sql片段会被整个sql语句所用到,为了让sql语句的可读性更高些,作为提供数据的部分,也常常用在union等集合操作中。
    with as就类似于一个视图或临时表,可以用来存储一部分的sql语句作为别名,不同的是with as 属于一次性的,而且必须要和其他sql一起使用才可以!
WITH t1 AS (
        SELECT *
        FROM carinfo
    ), 
    t2 AS (
        SELECT *
        FROM car_blacklist
    )
SELECT *
FROM t1, t2

注意:这里必须要整体作为一条sql查询,即with as语句后不能加分号,不然会报错。

  • with子句必须在引用的select语句之前定义,同级with关键字只能使用一次,多个只能用逗号分割;最后一个with 子句与下面的查询之间不能有逗号,只通过右括号分割,with 子句的查询必须用括号括起来.
  • 如果定义了with子句,但其后没有跟select查询,则会报错!
    没有使用 t1没关系,其后有select就行
  • 前面的with子句定义的查询在后面的with子句中可以使用。但是一个with子句内部不能嵌套with子句!
  • collect_set()
    collect_set(col)函数只接受基本数据类型,它的主要作用是将某字段的值进行去重汇总,产生array类型字段。
select customer_key,
collect_set(cpzl_zw)
from ods_sales_orders
group by customer_key
limit 10;
  • concat_ws
    如何指定参数之间的分隔符
    使用函数CONCAT_WS()。使用语法为:CONCAT_WS(separator,str1,str2,…)
    CONCAT_WS() 代表 CONCAT With Separator ,是CONCAT()的特殊形式。第一个参数是其它参数的分隔符。分隔符的位置放在要连接的两个字符串之间。分隔符可以是一个字符串,也可以是其它参数。如果分隔符为 NULL,则结果为 NULL。函数会忽略任何分隔符参数后的 NULL 值。但是CONCAT_WS()不会忽略任何空字符串。 (然而会忽略所有的 NULL)。
select customer_key,
concat_ws('-',collect_set(cpzl_zw))
from ods_sales_orders
group by customer_key
limit 10;

对比:
CONCAT(str1,str2,…)
返回结果为连接参数产生的字符串。如有任何一个参数为NULL ,则返回值为 NULL。可以有一个或多个参数。

需求4:

统计各个省份所属城市下最受欢迎的Top 3产品和其销量(不能出现有null)
思路:

查询目标:
(ods_sales_orders 表)
"产品" --product_key(产品编号)
"销量"--sales_order_key(订单主键,一个订单表示销售一个产品)

分组依据:
(ods_customer表)
"省份、城市" --chinese_province(所在省份)、chinese_city(所在城市)
结合查询目标以及group by 特性,分组依据调整为:chinese_city(所在城市)以及product_key(产品编号)

需要用到 ods_customer 表和 ods_sales_orders 表,二者用customer_key关联

“最受欢迎top3” --row_number() over()
(1)基础查询

select 
a.chinese_city,
b.product_key,
count(b.sales_order_key)  as sale_amount
from ods_customer a 
left join ods_sales_orders b 
on a.customer_key=b.customer_key
group by a.chinese_city,b.product_key
limit 10;

(2)利用row_number()over()进行排序

select chinese_city,
product_key,
sale_amount,
row_number() over(partition by chinese_city order by sale_amount desc) as rank
from 
(select 
a.chinese_city ,
b.product_key,
count(b.sales_order_key)  as sale_amount
from ods_customer a 
left join ods_sales_orders b 
on a.customer_key=b.customer_key
group by a.chinese_city,b.product_key) m
limit 10; 

(3)取Top3
框架:

select 
n.chinese_city,
n.product_key,
n.sale_amount,
n.rank
from ()n
where n.rank<4;

完整代码:

select 
n.chinese_city,
n.product_key,
n.sale_amount,
n.rank
from (select chinese_city,
product_key,
sale_amount,
row_number() over(partition by chinese_city order by sale_amount desc) as rank
from 
(select 
a.chinese_city ,
b.product_key,
count(b.sales_order_key)  as sale_amount
from ods_customer a 
left join ods_sales_orders b 
on a.customer_key=b.customer_key
group by a.chinese_city,b.product_key) m)n
where n.rank<4
limit 10;

但发现n.chinese_city中存在'null'值,修改完善完整代码如下:

select 
n.chinese_city,
n.product_key,
n.sale_amount,
n.rank
from (select chinese_city,
product_key,
sale_amount,
row_number() over(partition by chinese_city order by sale_amount desc) as rank
from 
(select 
a.chinese_city ,
b.product_key,
count(b.sales_order_key)  as sale_amount
from ods_customer a 
left join ods_sales_orders b 
on a.customer_key=b.customer_key
group by a.chinese_city,b.product_key) m)n
where n.rank<4
and n.chinese_city!='null'
limit 10;

注意:null需要加单引号,如果不加会报错。
但是存在疑问:
拿到一张表,如何判断表中的null是单纯的null还是‘null’呢?
都试一下。
总结:
对于取特定条件下的前几个排名的需求,一般分三步,
第一步:安装限定分组条件进行基础查询
第二步:利用row_number() over()进行排序标注
第三步:利用where取出目标记录

需求5:

商品的销售数量top10,排名需考虑并列排名的情况
思路:
“考虑并列排名”--dense_rank() over()
"top10"--基础查询-->排名-->目标筛选
(1)基础查询

select 
product_key,
count(sales_order_key) as sale_amount
from ods_sales_orders
group by product_key
limit 10;

(2)利用dense_rank() over()进行并列排序

select 
a.product_key,
a.sale_amount,
dense_rank() over(order by a.sale_amount desc ) as rank1
from (select 
product_key,
count(sales_order_key) as sale_amount
from ods_sales_orders
group by product_key) a limit 10;

(3)筛选目标查询值

select
m.product_key,
m.sale_amount,
m.rank1
from
(select 
a.product_key,
a.sale_amount,
dense_rank() over(order by a.sale_amount desc ) as rank1
from (select 
product_key,
count(sales_order_key) as sale_amount
from ods_sales_orders
group by product_key) a) m
where m.rank1<11
limit 15;

需求六:

计算累计和(统计2019年1-12月的累积销量,即1月为1月份的值,2月为1、2月份值的和,3月为1、2、3月份的和,12月为1-12月份值的和)

“到当前月份” --rows between...and...
"累计"--sum() over()
(1)按月分组进行基础查询

select 
substr(create_date,1,7) as `日期`,
count(sales_order_key) as sale_amount
from ods_sales_orders
group by substr(create_date,1,7)
limit 10;

(2)计算累计销量并筛选日期为2019年的记录

select 
substr(create_date,1,7) as `日期`,
count(sales_order_key) as sale_amount,
sum(count(sales_order_key))over(order by substr(create_date,1,7) asc rows between 
unbounded preceding and current row) as `累计销量`
from ods_sales_orders
where year(create_date)='2019'
group by substr(create_date,1,7)
limit 10;

相关知识:
hive时间函数

需求七:

计算客户平均购买一次商品的间隔时间
思路:
利用lead() over()
(1)基础查询

select customer_key,
create_date,
lead(create_date,1) over(partition by customer_key order by create_date) as `下次购买时间`
,datediff(lead(create_date,1) over(partition by customer_key order by create_date),create_date) as `时间间隔`
from ods_sales_orders
limit 10;

(2)完善细化成目标查询
框架:

select customer_key,avg(`时间间隔`) as `平均时间间隔`
from () a 
where `时间间隔`>0
group by customer_key;

完整代码:

select customer_key,avg(`时间间隔`) as `平均时间间隔`
from (select customer_key,
create_date,
lead(create_date,1) over(partition by customer_key order by create_date) as `下次购买时间`
,datediff(lead(create_date,1) over(partition by customer_key order by create_date),create_date) as `时间间隔`
from ods_sales_orders) a 
where `时间间隔`>0
group by customer_key
limit 10 offset 50;

相关知识点:

  • LAG(col,n,DEFAULT) 用于统计窗口内往上第n行值

第一个参数为列名,
第二个参数为往上第n行(可选,默认为1),
第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL)

  • 与LAG相反

LEAD(col,n,DEFAULT) 用于统计窗口内往下第n行值

第一个参数为列名,
第二个参数为往下第n行(可选,默认为1),
第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL)

  • 日期比较函数:datediff语法: datediff(string enddate,string startdate)
    返回值: int
    说明: 返回结束日期减去开始日期的天数。
  • 日期增加函数: date_add语法: date_add(string startdate, intdays)
    返回值: string
    说明: 返回开始日期startdate增加days天后的日期。
    -日期减少函数: date_sub语法: date_sub (string startdate,int days)
    返回值: string
    说明: 返回开始日期startdate减少days天后的日期。

需求八:

查询最近前20%时间的订单信息
思路:使用ntile(n)切片函数
(1)使用ntile()函数将订单时间按顺序分为5部分

select *,
ntile(5) over(order by create_date desc) 
from ods_sales_orders 
limit 10;

(2)去编号数为1的即为所求前20%

select *
from (select *,
ntile(5) over(order by create_date desc) as number
from ods_sales_orders)m
where m.number=1
limit 10;

相关知识点:

  • NTILE(n)
    用于将分组数据按照顺序切分成n片,返回当前记录所在的切片值
    NTILE不支持ROWS BETWEEN,比如 NTILE(2) OVER(PARTITION BY cookieid ORDER BY createtime ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)

如果切片不均匀,默认增加第一个切片的分布
经常用来取前30% 带有百分之多少比例的记录什么的

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