列表

详情


SQL167. 连续签到领金币

描述

用户行为日志表tb_user_log

id uid artical_id in_time out_time sign_in
1 101 0 2021-07-07 10:00:00 2021-07-07 10:00:09 1
2 101 0
2021-07-08 10:00:00 2021-07-08 10:00:09 1
3 101 0 2021-07-09 10:00:00 2021-07-09 10:00:42 1
4 101 0
2021-07-10 10:00:00
2021-07-10 10:00:09 1
5 101 0
2021-07-11 23:59:55 2021-07-11 23:59:59
1
6
101 0 2021-07-12 10:00:28
2021-07-12 10:00:50
1
7 101 0 2021-07-13 10:00:28
2021-07-13 10:00:50
1
8
102 0 2021-10-01 10:00:28 2021-10-01 10:00:50
1
9
102 0 2021-10-02 10:00:01 2021-10-02 10:01:50
1
10 102 0
2021-10-03 10:00:55 2021-10-03 11:00:59 1
11
102 0
2021-10-04 10:00:45
2021-10-04 11:00:55
0
12 102 0
2021-10-05 10:00:53
2021-10-05 11:00:59
1
13 102 0
2021-10-06 10:00:45
2021-10-06 11:00:55
1
(uid-用户ID, artical_id-文章ID, in_time-进入时间, out_time-离开时间, sign_in-是否签到)

场景逻辑说明
  • artical_id-文章ID代表用户浏览的文章的ID,特殊情况artical_id-文章ID0表示用户在非文章内容页(比如App内的列表页、活动页等)。注意:只有artical_id为0时sign_in值才有效。
  • 从2021年7月7日0点开始,用户每天签到可以领1金币,并可以开始累积签到天数,连续签到的第3、7天分别可额外领2、6金币。
  • 每连续签到7天后重新累积签到天数(即重置签到天数:连续第8天签到时记为新的一轮签到的第一天,领1金币)
问题计算每个用户2021年7月以来每月获得的金币数(该活动到10月底结束,11月1日开始的签到不再获得金币)。结果按月份、ID升序排序。

:如果签到记录的in_time-进入时间out_time-离开时间跨天了,也只记作in_time对应的日期签到了。

输出示例:
示例数据的输出结果如下:

uid month coin
101 202107 15
102 202110 7

解释:
101在活动期内连续签到了7天,因此获得1*7+2+6=15金币;
102在10.01~10.03连续签到3天获得5金币
10.04断签了,10.05~10.06连续签到2天获得2金币,共得到7金币。

示例1

输入:

DROP TABLE IF EXISTS tb_user_log;
CREATE TABLE tb_user_log (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '自增ID',
    uid INT NOT NULL COMMENT '用户ID',
    artical_id INT NOT NULL COMMENT '视频ID',
    in_time datetime COMMENT '进入时间',
    out_time datetime COMMENT '离开时间',
    sign_in TINYINT DEFAULT 0 COMMENT '是否签到'
) CHARACTER SET utf8 COLLATE utf8_bin;

INSERT INTO tb_user_log(uid, artical_id, in_time, out_time, sign_in) VALUES
  (101, 0, '2021-07-07 10:00:00', '2021-07-07 10:00:09', 1),
  (101, 0, '2021-07-08 10:00:00', '2021-07-08 10:00:09', 1),
  (101, 0, '2021-07-09 10:00:00', '2021-07-09 10:00:42', 1),
  (101, 0, '2021-07-10 10:00:00', '2021-07-10 10:00:09', 1),
  (101, 0, '2021-07-11 23:59:55', '2021-07-11 23:59:59', 1),
  (101, 0, '2021-07-12 10:00:28', '2021-07-12 10:00:50', 1),
  (101, 0, '2021-07-13 10:00:28', '2021-07-13 10:00:50', 1),
  (102, 0, '2021-10-01 10:00:28', '2021-10-01 10:00:50', 1),
  (102, 0, '2021-10-02 10:00:01', '2021-10-02 10:01:50', 1),
  (102, 0, '2021-10-03 11:00:55', '2021-10-03 11:00:59', 1),
  (102, 0, '2021-10-04 11:00:45', '2021-10-04 11:00:55', 0),
  (102, 0, '2021-10-05 11:00:53', '2021-10-05 11:00:59', 1),
  (102, 0, '2021-10-06 11:00:45', '2021-10-06 11:00:55', 1);

输出:

101|202107|15
102|202110|7

原站题解

上次编辑到这里,代码来自缓存 点击恢复默认模板

Mysql 解法, 执行用时: 36ms, 内存消耗: 6444KB, 提交时间: 2022-01-22

#给确定每个人每个签到阶段的起始日期
select uid,date_format(dt,'%Y%m') as month
,sum(case when stage_index=2 then 3
   when stage_index=6 then 7
   else 1 end) as coin
from(
    select uid,dt
    ,(row_number()over(partition by uid,init_date order by dt)-1)%7 as stage_index
    from
    (
        select uid,dt,rn,subdate(dt,rn) as init_date
        from (
            #给符合条件的每个人的日期编号
            select uid,date(in_time) as dt
            ,row_number()over(partition by uid order by date(in_time)) as rn
            from tb_user_log
            where date(in_time) >='2021-07-07'
            and date(in_time)<'2021-11-01'
            and artical_id=0 and sign_in=1
        ) raw_t
    ) init_date_t
) a
group by uid,month
order by month,uid;

Mysql 解法, 执行用时: 36ms, 内存消耗: 6596KB, 提交时间: 2022-01-01

select uid,df_date,sum(jinbi) from
(
select uid,date_format(ds_date,'%Y%m') df_date,sum(jinbi) jinbi
FROM
(
select uid,date_sub(new_in_time,interval rn day) ds_date,count(*) ct,
(count(*) div 7)*8+(mod(count(*),7)  div 3)*2 jinbi
FROM
(
select uid,date_format(in_time,'%Y-%m-%d') new_in_time,row_number() over(partition by uid order by in_time)-1 rn
from tb_user_log
where sign_in = 1 and in_time between '2021-07-07' and '2021-11-01' and artical_id=0
)a group by uid,ds_date
)b group by uid,df_date

union ALL

select uid,date_format(in_time,'%Y%m') df_date ,sum(sign_in) jinbi
from tb_user_log
where sign_in = 1 and in_time between '2021-07-07' and '2021-11-01' and artical_id=0
group by uid,df_date
)c group by uid,df_date

Mysql 解法, 执行用时: 37ms, 内存消耗: 6396KB, 提交时间: 2022-02-21

select uid,date_format(sign_dt,"%Y%m") as mon,sum(coin)
from
(
    select uid,
       case (RANK() over(partition by uid ,TIMESTAMPADD(day,-diff+1,sign_dt)
                        order by sign_dt))%7  when 3 then 3
                                                 when 0 then 7
                                                 else 1 end as coin,
      sign_dt
from
(
select uid,DATE_FORMAT(in_time,"%Y%m%d") as sign_dt,
       RANK() over(partition by uid order by DATE_FORMAT(in_time,"%Y%m%d"))
       as diff
from tb_user_log
where DATE_FORMAT(in_time,"%Y%m%d") between "20210707" and "20211031"
      and artical_id=0 and sign_in=1
) tmp
    
) base
group by uid,date_format(sign_dt,"%Y%m")
order by mon,uid

Mysql 解法, 执行用时: 37ms, 内存消耗: 6404KB, 提交时间: 2022-02-25

SELECT uid,DATE_FORMAT(sign_dt,'%Y%m')as month,sum(coin)
FROM
(SELECT uid,sign_dt,TIMESTAMPADD(day,-diff+1,sign_dt)as start_day ,
case (DENSE_RANK() over (PARTITION by uid,TIMESTAMPADD(day,-diff+1,sign_dt) ORDER BY sign_dt))%7
WHEN 3 then 3 
WHEN 0 THEN 7
ELSE 1 end as coin
FROM
(SELECT uid ,DATE_FORMAT(in_time,'%Y%m%d')as sign_dt,
DENSE_RANK() over(PARTITION by uid ORDER BY in_time) as diff 
FROM tb_user_log
WHERE DATE_FORMAT(in_time,'%Y%m%d') BETWEEN 20210707 and 20211031 
AND artical_id =0 AND sign_in =1 )t1 )t2 
GROUP BY uid,DATE_FORMAT(sign_dt,'%Y%m')
ORDER BY DATE_FORMAT(sign_dt,'%Y%m') ,uid

Mysql 解法, 执行用时: 37ms, 内存消耗: 6412KB, 提交时间: 2022-01-24

# 1.我们只要先排序,再将日期减去排序值就可以得到第一天连续打卡的时间,从而解决打断的问题。
#   tmp表是得到排序数,base表是减去排序值。
# 2.之后我们根据 用户 和 每个阶段连续打卡的第一天 进行分组就可以得到今天是连续打卡的第几天 
# 3.之后根据数学规则mod7就可以根据规则得到改天得到的金币 
# 4.再根据月进行聚合求sum即可
# TIMESTAMPADD(unit,interval,datetime_expr)
# dense_rank() -- '1 2 2 4(排名不连续)

select uid,date_format(sign_dt,"%Y%m") as mon,sum(coin)
from
(select uid,
     TIMESTAMPADD(day,-diff+1,sign_dt) as start_dt,
     case (DENSE_RANK() over(partition by uid ,TIMESTAMPADD(day,-diff+1,sign_dt) order by sign_dt))%7
     when 3 then 3  -- 连续第3天,3分
     when 0 then 7  -- 连续第3天,7分
     else 1  -- 普通时间,1分
     end as coin,
     sign_dt
from
(select uid,
     DATE_FORMAT(in_time,"%Y%m%d") as sign_dt,
     DENSE_RANK() over(partition by uid order by DATE_FORMAT(in_time,"%Y%m%d")) as diff
     from tb_user_log
     where DATE_FORMAT(in_time,"%Y%m%d") between "20210707" and "20211031"
     and artical_id=0 and sign_in=1) tmp) base
group by uid,date_format(sign_dt,"%Y%m")
order by mon,uid