题目分析

比较好理解的思考方式是根据需要的结果,一步一步反推自己需要什么的格式的数据

  1. 要求活动期间的签到获得的金币总数,那我最希望的是能够获得每一天用户签到时获得的金币数,然后只需要按照ID和month分组,sum一下就可以,如图

alt

  1. 再反推,想要获得每一天用户签到时获得的金币数,那么我必须知道,用户当天签到是连续签到的第几天,得到天数以后很简单了,用case when 将天数 % 7 ,看余数。 余数是3 ,当天获得 3枚。余数是 0 ,当天获得7枚 。其他为 1 枚 。如图

alt

  1. 推到这里那其实思路已经清晰了,求连续签到的天数,那无非就是连续问题

    1. 连续问题核心就是利用排序编号与签到日期的差值是相等的。因为如果是连续的话,编号也是自增1,日期也是自增1。

    2. 如图,dt是签到日期,dt_tmp是编号和签到日期的差值。可以发现 编号 8 是断了连续签到的,所以dt_tmp与前面的不相同

alt

  1. 那么再以dt_tmp和 uid 来分组,再dense_rank 一次,就可以获得连续签到的天数了。那么问题就解决了。

alt

完整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;