Spark案例 统计出连续三天登录的用户(两种方法)

统计连续登陆的三天及以上的用户(三种方法编写)

这个问题可以扩展到很多相似的问题:连续几个月充值会员、连续天数有商品卖出、连续打滴滴、连续逾期。

测试数据:用户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()

  }

}

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值