Hive SQL— 连续发单天数

数据运营人员常需查找活跃用户名单,这涉及求用户某段时间内最大连续在线天数。SQL求连续在线天数是经典问题,难点在于判断日期连续性。文章介绍三种解法,包括通过与特定日期的日期差判定、left join进行笛卡尔积、使用lead或lag函数,还提及各解法特点。

背景
数据运营人员常常会需要查找活跃用户名单,而活跃用户很多情况下被定义为连续在线或发单n天及以上的用户。一方面我们可以根据n的值直接进行筛选;更具一般性地,就要求我们去求取每个用户某段时间内的最大连续在线或者发单天数了。

SQL求连续在线天数是一个非常经典的问题,该问题在不考虑计算成本下有非常多的解法。该问题也是我在面试实习生时最喜欢深入问的一个问题,在引导一个候选人去完成这个问题的过程中可以看出其对sql的理解深度以及其思维是否灵敏。

该问题的最大难点在于如何判断日期与日期间是否连续,那这就要涉及到处理行与行之间的关系了。说到这对SQL比较熟悉的同学应该就会反应出使用join或者窗口函数来处理了。

数据:
假设我们有19年一月份每日用户发单数据存储于订单表order_base:

user_id		 order_id		create_time
234520012	1231512416323	2019-01-02 12:21:11
123149908	2412298719221	2019-01-04 01:11:34
    …			…					…

解法1(通过与特定日期的日期差判定连续):
本方法比较tricky。连续的时间以为着这些时间点与某一个特定时间点的时间差也是连续的,从下表可以直观理解这一点:

日期				特定日期	  	日期差d
2019-01-01		2019-01-01		0
2019-01-02		2019-01-01		1
2019-01-04		2019-01-01		3
2019-01-05		2019-01-01		4
2019-01-06		2019-01-01		5

那么我们对该日期差d进行个排序,如果连续的话,d与序号的差值应该是相同的,如下表:

	日期			 特定日期	日期差d	  序号r	日期差d与序号r的差值
2019-01-01		2019-01-01		0		0		0
2019-01-02		2019-01-01		1		1		0
2019-01-04		2019-01-01		3		2		1
2019-01-05		2019-01-01		4		3		1
2019-01-06		2019-01-01		5		4		1

这样答案就显而易见了,只需要对上面这个子查询的最后一列进行分组统计行数,变得到了每次连续的天数,再取连续天数的最大值,便是我们想要的答案。

select
    user_id,
    max(date_cnt) as max_continuation_date_cnt  
from
(
    select
        user_id,
        d-d_ranking as d_group, -- 连续日期的组标记
        count(1) as date_cnt
    from 
    (
        select
            user_id,
            d, --与标记日期的日期差
            row_number() over(partition by user_id order by d) as d_ranking --与标记日期的日期差的排序
        from
        (
            select
                user_id,
                datediff(create_date,'2019-01-01') as d --与标记日期的日期差
            from
            (
                select
                    user_id,
                    to_date(create_time) as create_date
                from
                    order_base
                group by
                    user_id,
                    date(create_time)
            )a -- 在这一层获取用户的发单日期并去重
        )b --这一层获取与标记日期的日期差
    )c --获取连续日期的排序
    group by
        user_id,
        d-d_ranking
)d -- 获取每一个连续日期组的连续天数
group by
    user_id

解法2(left join进行笛卡尔积):
假设我们不需要知道用户最大的连续天数,只需要知道某个用户是否出现连续n天(假设n为3)登录的行为。那这里首先给出一种完全不考虑计算复杂度的解法,使用纯join关联去实现该问题。

整体思路是去获得同一个用户的发单日期对,看每一个发单日期的n天内是否有n个发单日期。

select
    user_id
from
(
    select
        user_id,
        to_date(create_time) as create_date
    from
        order_base
    group by
        user_id,
        date(create_time)
)a -- 在这一层获取用户的发单日期并去重
left join
(
    select
        user_id,
        to_date(create_time) as create_date
    from
        order_base
    group by
        user_id,
        to_date(create_time)
)b -- 与a完全相同的逻辑,为了得到日期与日期间的关联
on
    a.user_id = b.user_id --仅使用user_id进行关联,获取同一个用户发单日期间的笛卡尔积
where
    a.create_date <= b.create_date
    and date_add(a.create_date,3) > b.create_date --以a的日期为基准,保留从a.create_date开始的3天内发单日期
group by
    user_id,
    a.create_date
having
    count(1) = 3 --如果从a.create_date开始的3天内都有发单,则应该有3条记录

该方法容易理解,但其最大的弊端在于关联时造成的笛卡尔积大大增加了计算的复杂度。在较小的数据集上可以考虑该方法,但实际生产环境下意义并不大。

解法3 (lead或lag):
最后介绍一个最为直观,也是计算成本最小的方法。假设我们需要求连续登陆n天(假设n为7)及以上的用户,那么对于一个存在该行为的用户,他去重和排序后的发单日期信息中,必存在某一天,往前回溯(往后推)6条记录的日期,等于该日期减6(加6)。这么说可能不太好理解,但相信你看了以下代码便能很快明白我在说什么:

select
    user_id
from
(
    select
        user_id,
        create_date,
        lag(create_date,6,null) over(partition by user_id order by create_date) as last_6_row -- 按时间排序后6行之前的那一条记录
    (
        select
            user_id,
            to_date(create_time) as create_date
        from
            order_base
        group by
            user_id,
            date(create_time)
    )a -- 在这一层获取用户的发单日期并去重
)b --获取6行之前的那一条记录
where
    datediff(create_date,last_6_row) = 6
group by
    user_id

转载https://blog.youkuaiyun.com/Adrian_Wang/article/details/89791948

Hive SQL中计算最大连续活跃天数,可按以下思路和步骤进行: ### 思路分析 要计算最大连续活跃天数,关键在于将连续活跃的日期分组,然后统计每组的天数,最后找出最大的连续天数。可使用开窗函数`row_number`结合日期运算来实现分组。 ### 示例代码 以引用[3]的数据为例,先创建测试表并插入数据: ```sql -- 创建测试表 create table tmpdb.test_01 as select '1001' as user_id, '2017-01-01' as login_date union all select '1001' as user_id, '2017-01-02' as login_date union all select '1001' as user_id, '2017-01-04' as login_date union all select '1001' as user_id, '2017-01-06' as login_date union all select '1001' as user_id, '2017-01-07' as login_date; ``` 接着编写SQL计算最大连续活跃天数: ```sql -- 计算最大连续活跃天数 SELECT user_id, MAX(consecutive_days) as max_consecutive_days FROM ( SELECT user_id, grp, COUNT(*) as consecutive_days FROM ( SELECT user_id, login_date, -- 将日期减去对应的排名,连续日期相减后结果相同,用于分组 date_sub(login_date, row_number() over (partition by user_id order by login_date)) as grp FROM tmpdb.test_01 ) t1 GROUP BY user_id, grp ) t2 GROUP BY user_id; ``` ### 代码解释 1. **内层子查询**:使用`row_number`函数对每个用户的登录日期进行排名,再用`date_sub`函数将登录日期减去排名,得到一个新的字段`grp`。连续活跃的日期相减后`grp`值相同,这样就把连续活跃的日期分到了同一组。 2. **中间子查询**:按用户和`grp`分组,统计每组的记录数,即连续活跃的天数。 3. **外层查询**:按用户分组,找出每个用户的最大连续活跃天数。 ### 另一种示例 以引用[5]的数据为例,也可按上述思路计算最大连续活跃天数: ```sql -- 先创建表并插入数据 CREATE TABLE user_activity ( user_id INT, activity_date STRING ); INSERT INTO user_activity VALUES (1, '2022-07-11'), (1, '2022-07-12'), (1, '2022-07-13'), (1, '2022-07-14'), (1, '2022-07-15'), (1, '2022-07-16'), (1, '2022-07-17'), (1, '2022-07-18'), (2, '2022-07-12'), (2, '2022-07-13'), (2, '2022-07-14'), (2, '2022-07-15'), (2, '2022-07-16'), (2, '2022-07-21'), (2, '2022-07-22'), (3, '2022-08-01'), (3, '2022-08-02'), (3, '2022-08-03'), (3, '2022-08-04'), (3, '2022-08-05'), (3, '2022-08-06'), (3, '2022-08-07'), (3, '2022-08-08'), (3, '2022-08-09'), (3, '2022-08-10'), (3, '2022-08-11'); -- 计算最大连续活跃天数 SELECT user_id, MAX(consecutive_days) as max_consecutive_days FROM ( SELECT user_id, grp, COUNT(*) as consecutive_days FROM ( SELECT user_id, activity_date, date_sub(activity_date, row_number() over (partition by user_id order by activity_date)) as grp FROM user_activity ) t1 GROUP BY user_id, grp ) t2 GROUP BY user_id; ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值