有这样一个需求,如下图:对于每个员工(uid)每日(dt)工作记录,按不同的事项类型(type)进行排序(no)存储了每个事项的持续时间(hours)。
现在需要对每个人(uid)、每天(dt)、每个事项(type)的最长连续时间做统计,如图中“做报表”事项,共可以分出4个连续时段,每个时段的工作总时长为最后一列,需要获得的结果就是4.2。并依次计算出休息和会议的连续最长时长【结果应分别为:1和2】。
思路1:
(1)对数据按uid,dt,no进行排序
(2)添加一列type2,规则为:初始赋值为0,对type进行逐行判断,如果前后不同则值加1,否则为原值
(3)然后可以通过对uid,dt,type,type2对hours进行聚合,即可计算出每个连续段的时间
(4)最后通过max(hours) over(partition by uid,dt,type) 即可计算出各类型的最大连续时间了
思路1看似可以使用自定义参数实现,但在使用hive进行操作时没有成功,于是我们另辟蹊径,看看思路2.
思路2:
(1)使用sum(hours) over(partition by uid,dt,type)计算 uid,dt,type维度的累计小时数cusum_h
(2)取同一类连续几个数据的最后一条:通过将上述计算结果,进行自关联,判断出相邻两行不一致,则表示这一行是同类型阶段的最后一行
(3)计算每个阶段的时长:可以再通过上述结果表的自关联,使用后一个累计小时数减前一个累计小时数,即为该阶段的小时数phase_h
(4)最后通过max(phase_h) over(partition by uid,dt,type)计算出uid,dt,type维度的最大小时数
下面来看详细操作:
(1)使用sum(hours) over(partition by uid,dt,type)计算 uid,dt,type维度的累计小时数
select
uid
, dt
, type
, noid
, hours
, sum(hours) over(partition by uid,dt,type order by noid) as cusum_h -- 计算累计值
, row_number() over(order by uid,dt,noid) as rk -- 增加一个总体排序,方便后面取值处理
from t_group0326
order by uid,noid
(2)然后我们可以看到,数据已经按照uid,dt,type归类,并按noid维度进行排序和计算出累计值(cusum_h)。这里我们通过自关联的方式取连续阶段最后一个累计值
-- 借助临时表,方便进行自关联操作
with tmp_a as (
select
uid
, dt
, type
, noid
, hours
, sum(hours) over(partition by uid,dt,type order by noid) as cusum_h -- 计算累计值
, row_number() over(order by uid,dt,noid) as rk -- 增加一个总体排序,方便后面取值处理
from t_group0326
order by uid,noid
)
select
a1.*
, case when a1.uid=a2.uid and a1.dt=a2.dt and a1.type=a2.type then 0 else 1 end as is_unequal -- 相同为0,不相同则为1
from tmp_a a1
LEFT JOIN tmp_a a2 on a1.rk=a2.rk-1
(3)从上表结果可以看到,我们取is_unequal=1既是同类型连续的最后一条数据。然后我们将最后一条数据取出,再对剩下的数据分组排序,再进行自关联取每个阶段的小时数
with tmp_a as (
select
uid
, dt
, type
, noid
, hours
, sum(hours) over(partition by uid,dt,type order by noid) as cusum_h -- 计算累计值
, row_number() over(order by uid,dt,noid) as rk -- 增加一个总体排序,方便后面取值处理
from t_group0326
order by uid,noid
),
tmp_b as (
select
a3.*
, ROW_NUMBER() over(PARTITION by uid,dt,type order by cusum_h desc) as rk2
from (
select
a1.*
, case when a1.uid=a2.uid and a1.dt=a2.dt and a1.type=a2.type then 0 else 1 end as is_unequal -- 相同为0,不相同则为1
from tmp_a a1
LEFT JOIN tmp_a a2 on a1.rk=a2.rk-1
) a3
where is_unequal=1
)
select
b3.*
, max(phase_h) over(PARTITION by uid,dt,type) as max_phase_h -- 取连续阶段的最大时间,为最终需要的结果
from (
select
b1.*
, case when b2.cusum_h is null then b1.cusum_h else b1.cusum_h-b2.cusum_h end as phase_h -- 取每个阶段的时间,如果关联表为空,则取自身时间
from tmp_b b1
left join tmp_b b2 on b1.uid=b2.uid and b1.dt=b2.dt and b1.type=b2.type and b1.rk2=b2.rk2-1
) b3
order by rk
此时,我们可以拿u0001的结果验证一下,做报表、休息和会议最长时长分别为4.2、1和2,与开头手动计算的结果一致。