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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章