题解 | #连续签到领金币# 解题思路总结
连续签到领金币
http://www.nowcoder.com/practice/aef5adcef574468c82659e8911bb297f
题目分析
比较好理解的思考方式是根据需要的结果,一步一步反推自己需要什么的格式的数据
- 要求活动期间的签到获得的金币总数,那我最希望的是能够获得每一天用户签到时获得的金币数,然后只需要按照ID和month分组,sum一下就可以,如图
- 再反推,想要获得每一天用户签到时获得的金币数,那么我必须知道,用户当天签到是连续签到的第几天,得到天数以后很简单了,用case when 将天数 % 7 ,看余数。 余数是3 ,当天获得 3枚。余数是 0 ,当天获得7枚 。其他为 1 枚 。如图
-
推到这里那其实思路已经清晰了,求连续签到的天数,那无非就是连续问题了
-
连续问题核心就是利用排序编号与签到日期的差值是相等的。因为如果是连续的话,编号也是自增1,日期也是自增1。
-
如图,dt是签到日期,dt_tmp是编号和签到日期的差值。可以发现 编号 8 是断了连续签到的,所以dt_tmp与前面的不相同
-
- 那么再以dt_tmp和 uid 来分组,再dense_rank 一次,就可以获得连续签到的天数了。那么问题就解决了。
完整SQL
WITH t1 AS( -- t1表筛选出活动期间内的数据,并且为了防止一天有多次签到活动,distinct 去重
SELECT
DISTINCT uid,
DATE(in_time) dt,
DENSE_RANK() over(PARTITION BY uid ORDER BY DATE(in_time)) rn -- 编号
FROM
tb_user_log
WHERE
DATE(in_time) BETWEEN '2021-07-07' AND '2021-10-31' AND artical_id = 0 AND sign_in = 1
),
t2 AS (
SELECT
*,
DATE_SUB(dt,INTERVAL rn day) dt_tmp,
case DENSE_RANK() over(PARTITION BY DATE_SUB(dt,INTERVAL rn day),uid ORDER BY dt )%7 -- 再次编号
WHEN 3 THEN 3
WHEN 0 THEN 7
ELSE 1
END as day_coin -- 用户当天签到时应该获得的金币数
FROM
t1
)
SELECT
uid,DATE_FORMAT(dt,'%Y%m') `month`, sum(day_coin) coin -- 总金币数
FROM
t2
GROUP BY
uid,DATE_FORMAT(dt,'%Y%m')
ORDER BY
DATE_FORMAT(dt,'%Y%m'),uid;