昨天群上有人发个阿里的面试题,题目描述大概如下:
数据源:用户登录表,只有俩个字段,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天