昨天群上有人發個阿里的面試題,題目描述大概如下:
數據源:用戶登錄表,只有倆個字段,uid和dt
試用HQL抽取出連續登錄了K天的用戶uid
第一個想法就是直接用一個UDF解決,按uid分組,把dt收集起來然后在UDF里面判斷是否滿足條件
SELECT uid, isExist(collect_set(dt), k) flag FROM table_name GROUP BY uid HAVING flag = 1;
其中isExist的邏輯是判斷collect_set中是否存在k個連續的值
這種方法簡單明了,但是需要額外的寫一個UDF,對於不懂JAVA的來說確實比較麻煩
今天群里有個神人給出了一種新的解決思路,十分完美的解決了,下面是具體代碼
SELECT uid, MAX(dt) - MIN(dt) diff, COLLECT_set (dt) FROM (SELECT a.uid, a.dt, dt - rn num FROM (SELECT uid, dt, row_number () over (PARTITION BY uid ORDER BY dt) rn FROM table_name GROUP BY uid, dt) a) a GROUP BY uid, num
該思路首先利用窗口函數以uid分組然后按照dt排序給出每個dt在排序中的位置,然后用求出dt與位置的差(記為num)
最后按照uid和num做一個聚合,容易發現同一個num組內的dt是連續的值
然后直接計數(count(*))就可以得出結果了
上面的代碼只是為了更加方便看到輸出的結果正確性,輸出結果如下:
UID DIFF DT_ARRAY 1043736 3.0 {20140815 20140814 20140813 20140812} 1043736 0.0 {20140818} 1043736 1.0 {20140821 20140820} 1043844 0.0 {20140814} 1044090 1.0 {20140812 20140811} 1044090 2.0 {20140816 20140815 20140817} 1044090 0.0 {20140821} 1044264 0.0 {20140810} 1044264 3.0 {20140815 20140814 20140813 20140812} 1044264 5.0 {20140821 20140820 20140822 20140819 20140817 20140818}
結果中uid = 1043736 的一共登錄了7天,其中可以拆分成三個連續的登錄模塊,分別是連續登錄1天、2天和4天