统计连续登陆的三天及以上的用户(三种方法编写)
这个问题可以扩展到很多相似的问题:连续几个月充值会员、连续天数有商品卖出、连续打滴滴、连续逾期。
测试数据:用户ID、登入日期
guid01,2018-02-28
guid01,2018-03-01
guid01,2018-03-02
guid01,2018-03-04
guid01,2018-03-05
guid01,2018-03-06
guid01,2018-03-07
guid02,2018-03-01
guid02,2018-03-02
guid02,2018-03-03
guid02,2018-03-06
计算好的结果:
±--------±-------±------------±------------±-+
| uid | times | start_date | end_date |
±--------±-------±------------±------------±-+
| guid01 | 4 | 2018-03-04 | 2018-03-07 |
| guid02 | 3 | 2018-03-01 | 2018-03-03 |
±--------±-------±------------±------------±-+
方式一:通过RDD算子编写
Spark代码:
package cn._51doit.day05
import java.text.SimpleDateFormat
import java.util.{Calendar, Date}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.math.Ordering.Implicits.seqDerivedOrdering
/**
* @Auther Zhang
* @Date 2020/8/10
*/
object ThreeDayDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("thereDayDemo").setMaster("local")
val sc = new SparkContext(conf)
//读取数据
val rdd1: RDD[String] = sc.textFile("data/data1.txt")
//对数据进行处理
val uidAndDate: RDD[(String, String)] = rdd1.map(x => {
val fis = x.split(",")
val uid = fis(0)
val date = fis(1)
(uid, date)
})
//根据uid进行分分组,将同一个用户的登录数据搞到同一个分组中
val grouped: RDD[(String, Iterable[String])] = uidAndDate.groupByKey()
//在组内进行排序
val uidAndDateDif = grouped.flatMapValues(it => {
//将迭代器中的数据toList/toSet,有可能会发生内存溢出
val sorted = it.toSet.toList.sorted
//定义一个日期的工具类
val calendar = Calendar.getInstance()
val sdf = new SimpleDateFormat("yyyy-MM-dd")
var index = 0;
sorted.map(desStr => {
val date: Date = sdf.parse(desStr)
calendar.setTime(date)
calendar.add(Calendar.DATE, -index)
index += 1
(desStr, sdf.format(calendar.getTime))
})
})
val result = uidAndDateDif.map(x => {
((x._1, x._2._2), x._2._1)
}).groupByKey().mapValues(it=>{
val list = it.toList
val times = list.size
val startTime = list.head
val endTime = list.last
(times,startTime,endTime)
}).map(t=>{
(t._1._1,t._2._1,t._2._2,t._2._3)
}).filter(x=>{
x._2>=3
})
val buffer = result.collect().toBuffer
println(buffer)
}
}
总结:
groupByKey得到的value是一个CompactBuffer,针对CompactBuffer可以用mapValues来处理,mapValues只会处理value的数据,不会影响key,就是对value来进行map操作,返回任何类型的值都可以
若要统计连续n天的啥啥啥,就需要先按用户分组,登录时间按正序排序,然后在其后面一列打上一个连续的序号(比如0开始,0,1,2,3,4…)
用登录时间减去序号,会得到一个新的时间,若时间相等,说明是连续登录
(这里涉及到一个日期格式转换的问题,需要用到SimpleDateFormat类,
用时间减去一天,求一个新的时间,需要用到日期的工具类Calendar类
方式二:使用SparkSql编写
package cn._51doit.day11
import org.apache.spark.sql.SparkSession
/** 使用sparksql解决连续三天登录的问题
*
* @Auther Zhang
* @Date 2020/8/19
*/
object SQLUserContinueLogin {
def main(args: Array[String]): Unit = {
//创建SparkSession
val spark = SparkSession.builder()
.appName(this.getClass.getCanonicalName)
.master("local[*]")
.getOrCreate()
//必须是结构化数据,才能够读取成功
val df = spark.read
.csv("data/data1.txt")
.toDF("uid", "dt") //设置表头
df.createTempView("v_user_login")
//写sql
spark.sql(
"""
SELECT
| uid,
| MIN(dt) start_time, --连续登陆最开始一天
| MAX(dt) end_time, --连续登陆的最后一天
| COUNT(dt) times --登陆的天数
|FROM
| (SELECT
| uid,
| dt,
| DATE_SUB(dt,rn) dt_dif --日期与行号的差值(新的日期)
| FROM
| (SELECT
| uid,
| dt,
| ROW_NUMBER() OVER(PARTITION BY uid ORDER BY dt) rn
| FROM
| v_user_login) v1) v2
|GROUP BY uid,dt_dif
|HAVING times >= 3 --筛选出连续登陆天数大于等于3天的记录
|""".stripMargin).show()
spark.stop()
}
}