hive 1.1.0版本 获取周数, 解决跨年的bug

背景

项目中有一个报表是留存率,包括日留存,周留存,月留存。其中在计算周留存率时,可能会直接想到【weekofyear】这个函数,然后简单拼接年就可以得到周数,大部分时候是对的,但在跨年的时候可能会出现bug。

hive> select concat(year('2019-08-01'),'-',weekofyear('2019-08-01'));
OK
2019-31
Time taken: 3.217 seconds, Fetched: 1 row(s)

上面的结果一看也没啥问题,但如果选极端一点的日期,比如【2018-12-31】:

hive> select concat(year('2018-12-31'),'-',weekofyear('2018-12-31'));
OK
2018-1
Time taken: 0.09 seconds, Fetched: 1 row(s)

咦? 得到的结果明显是错误的,【2018-12-31】这个日期算出来的周数竟然是【2018-1】(也就是2018年第一周)。

上面例子可以引出一个问题,可能会出现一周跨年的情况,比如例子中的【2018-12-31】所在的周横跨了2018和2019年,那这周是属于2018还是2019年的呢?
当然,从上面结论来看,hive的【weekofyear】函数是把【2018-12-31】所在的周认为是2019年的第一周了,所以才会出现【2018-12-31】是2018年第一周的bug。

Q:hive的【weekofyear】是怎么判断跨年的周到底属于哪一年的呢?
A:先看hive对【weekofyear】函数的官方定义:

hive> describe function weekofyear;
OK
weekofyear(date) - Returns the week of the year of the given date. A week is considered to start on a Monday and week 1 is the first week with >3 days.
Time taken: 0.008 seconds, Fetched: 1 row(s)

1、每周是从周一开始。
2、每年的第一周必须是属于这一年的天数大于3(不包括3)的第一周。
回到上面跨年周的问题,其实hive就是认为,跨年的这这一周,必须有4天及以上在新的一年,这周才算是新一年的第一周,否则就是旧一年的最后一周。

比较典型的两个日期【2009-12-31】和【2014-12-31】(注:以下两个计算结果年份都是错误的)

hive> select concat(year('2009-12-31'),'-',weekofyear('2009-12-31'));
OK
2009-53
Time taken: 0.092 seconds, Fetched: 1 row(s)
hive> select concat(year('2014-12-31'),'-',weekofyear('2014-12-31'));
OK
2014-1
Time taken: 0.066 seconds, Fetched: 1 row(s)

【2009-12-31】是星期四,说明这一周有4天在2008年,有3天在2009年,所以这周应该是2008年的最后一周,即第53周。
【2014-12-31】是星期三,说明这一周有3天在2014年,有4天灾2015年,所以这周应该是2015年的第一周,即第1周。

解决思路

使用case when 来判断【新一年的第一天】到【指定日期所在周的周一】的天数差值,如果差值大于3,将这周判定为【所在周的周一的日期所属的年份】即旧一年,否则将这周判定为【所在周的周日的日期所属的年份】即新一年。

-- 涉及到的 hive 日期函数 next_day, date_sub, add_months, trunc, datediff 
select next_day('2018-12-31', 'MO')   -- 获取下一周的周一日期
select date_sub(next_day('2018-12-31', 'MO'), 7)  --  在下一周的周一日期基础上,获取前7天的日期,实际就是获取本周周一的日期
select add_months(date_sub(next_day('2018-12-31', 'MO'), 7), 1) # 获取下一个月的日期
select trunc(add_months(date_sub(next_day('2018-12-31', 'MO'), 7), 1), 'MM');  -- 获取指定日期的当月第一天的日期。
select datediff(trunc(add_months(date_sub(next_day('2018-12-31', 'MO'), 7), 1), 'MM'), date_sub(next_day('2018-12-31', 'MO'), 7)) ; -- datediff 获取两个日期间隔, 这里其实就是获取新一年的第一天到指定日期天数。

最终的sql

select concat(
    case when datediff(trunc(add_months(date_sub(next_day('2018-12-31', 'MO'), 7), 1), 'MM'), date_sub(next_day('2018-12-31', 'MO'), 7)) > 3
    then year(date_sub(next_day('2018-12-31', 'MO'), 7 ))
    else year(next_day('2018-12-31', 'SU'))
    end,
    '-',
    case when weekofyear('2018-12-31') > 9
    then weekofyear('2018-12-31')
    else concat('0', weekofyear('2018-12-31'))
    end
) as week

执行结果,【2018-12-31】不再是2018年的第一周,而是2019年的第一周!

hive> select concat(
    >         case when datediff(trunc(add_months(date_sub(next_day('2018-12-31', 'MO'), 7), 1), 'MM'), date_sub(next_day('2018-12-31', 'MO'), 7)) > 3
    >         then year(date_sub(next_day('2018-12-31', 'MO'), 7 ))
    >         else year(next_day('2018-12-31', 'SU'))
    >         end,
    >         '-',
    >         case when weekofyear('2018-12-31') > 9
    >         then weekofyear('2018-12-31')
    >         else concat('0', weekofyear('2018-12-31'))
    >         end
    >     ) as week
    > ;
OK
2019-01
Time taken: 0.083 seconds, Fetched: 1 row(s)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章