求每个用户最大的连续登陆天数,断一天还算连续。(如果是绝对连续,使用一次等差思想即可)
id dt
1001 2021-11-05
1001 2021-11-06
1001 2021-11-08
1001 2021-11-10
1001 2021-11-15
1001 2021-11-16
1001 2021-11-18
1001 2021-11-19
1002 2021-11-05
1002 2021-11-07
1002 2021-11-09
1002 2021-11-11
1002 2021-11-13
1002 2021-11-16
方案一:等差数列思想
整体思路:利用等差数列思想,相差一天等差为1,对数据进行rank()排序、开窗,得到一列排序的值,使用日期减去排序的值,日期相差为一天的在运算后和第一行的天数相等,这种是绝对连续,先进行一次count(),然后再进行一次rank()排序、开窗,相减,可得到差两天的的绝对连续,最后 对天数求和+所有相同日期的-1 就是断一天也连续的绝对连续的天数。--1.1 按照用户分组,按照时间排序,给定Rank值,相当于了一列
select
id,
dt,
rank() over(partition by id order by dt) rk
from test1;t1
--结果
/*
id dt rk
1001 2021-11-05 1
1001 2021-11-06 2
1001 2021-11-08 3
1001 2021-11-10 4
1001 2021-11-15 5
1001 2021-11-16 6
1001 2021-11-18 7
1001 2021-11-19 8
*/
--1.2 进行平行相减操作,即dt-rk这样得到的结果中相同的dt即是像个一天的日期,date_sub
select
id,
date_sub(dt,rk) dt
from
t1;t2
/*
id dt
1001 2021-11-04
1001 2021-11-04
1001 2021-11-05
1001 2021-11-06
1001 2021-11-10
1001 2021-11-10
1001 2021-11-11
1001 2021-11-11
*/
--1.3 计算原始数据中绝对连续的天数,按照id、dt分组进行聚合操作,算相同id同一天的次数,即是绝对连续天数
select
id,
dt,
count(*) days
from
t2
group by id,dt;t3
/*
1001 2021-11-04 2
1001 2021-11-05 1
1001 2021-11-06 1
1001 2021-11-10 2
1001 2021-11-11 2
*/
--1.4 再次使用等差数列思想
select
id,
dt,
days,
rank() over(partition by id order by dt) rk
from
t3;t4
/*
1001 2021-11-04 2 1
1001 2021-11-05 1 2
1001 2021-11-06 1 3
1001 2021-11-10 2 4
1001 2021-11-11 2 5
*/
--1.5 再次平行相减
select
id,
date_sub(dt,rk) dt,
days
from
t4;t5
/*
1001 2021-11-03 2
1001 2021-11-03 1
1001 2021-11-03 1
1001 2021-11-06 2
1001 2021-11-06 2
*/
--1.6 计算连续天数(考虑上断一天的情况)
select
id,
dt,
(sum(days)+count(*)-1) days
from
t5
group by id,dt;
/*
1001 | 2021-11-06 | 5 |
1002 | 2021-11-04 | 1 |
1002 | 2021-11-03 | 9 |
1001 | 2021-11-03 | 6 |
*/
方案二:直接分组,可以应对所有情况,断一天、两天依旧算连续的情况。
整体思路:通过lat函数将当前行下移一行,放到新的字段中,第一行没有的补计算机元年,然后用原日期字段减去新的字段可以得到邻近两行相距的天数,使用sumif判断天数是否大于2,如果大于2则分组加1,否则分组不变,使用同一分组中最大天数减去(最小的天数+1),即是最后的天数。 ```sql —2.1 将上一行数据移动至当前行,根据id和dt进行开窗,使用lag函数将上一行数据移动到当前行 select id, dt, lag(dt,1,’1970-01-01’) over(partition by id order by dt) lag_dt from test1; / | 1001 | 2021-11-05 | 1970-01-01 | | 1001 | 2021-11-06 | 2021-11-05 | | 1001 | 2021-11-08 | 2021-11-06 | | 1001 | 2021-11-10 | 2021-11-08 | | 1001 | 2021-11-15 | 2021-11-10 | | 1001 | 2021-11-16 | 2021-11-15 | | 1001 | 2021-11-18 | 2021-11-16 | | 1001 | 2021-11-19 | 2021-11-18 | / —2.2 将当前行数据的日期减去上一行数据的日期 select id, dt, datediff(dt,lag_dt) dt_diff from t1;t2 /* | 1001 | 2021-11-05 | 18936 | | 1001 | 2021-11-06 | 1 | | 1001 | 2021-11-08 | 2 | | 1001 | 2021-11-10 | 2 | | 1001 | 2021-11-15 | 5 | | 1001 | 2021-11-16 | 1 | | 1001 | 2021-11-18 | 2 | | 1001 | 2021-11-19 | 1 |
/
—2.3 分组,间隔小于等于2的数据划分到一个组,每遇到一个大于2的数据,分组+1
select
id,
dt,
sum(if(dt_diff>2,1,0)) over(partition by id order by dt) g_id
from
t2;t3
/
| 1001 | 2021-11-05 | 1 |
| 1001 | 2021-11-06 | 1 |
| 1001 | 2021-11-08 | 1 |
| 1001 | 2021-11-10 | 1 |
| 1001 | 2021-11-15 | 2 |
| 1001 | 2021-11-16 | 2 |
| 1001 | 2021-11-18 | 2 |
| 1001 | 2021-11-19 | 2 |
/
—2.4 计算连续的登陆天数
select
id,
g_id,
(datediff(max(dt),min(dt))+1) days
from
t3
group by id,g_id;
/
| 1001 | 1 | 6 |
| 1001 | 2 | 5 |
| 1002 | 1 | 9 |
| 1002 | 2 | 1 |
*/
```
注:在使用方案二时不能使用spark引擎,汇报错误:Error: Error while processing statement: FAILED: Execution Error, return code 3 from org.apache.hadoop.hive.ql.exec.spark.SparkTask. Spark job failed during runtime. Please check stacktrace for the root cause. (state=42000,code=3);原因:Spark引擎+datediff函数(在子查询且开窗使用)+时间字段使用的是String类型 空指针
解决:使用MR引擎或修改时间字段为Date类型。