问题描述:
数据表字段:user_id,login_date,一天多次登录只算当天登录一次。
问题需求1:
统计每个用户的总登陆天数
sql
SELECT
user_id,
COUNT(DISTINCT login_date) AS total_login_days
FROM
your_table_name -- 替换成你实际的表名
GROUP BY
user_id;
问题需求2:
统计每个用户的连续登陆天数
思路:假设有个用户连续三天登录:
- 1号(序号是1):用 1号 减去 1天,结果是 0号
- 2号(序号是2):用 2号 减去 2天,结果还是 0号
- 3号(序号是3):用 3号 减去 3天,结果依然是 0号
你看,只要是连续的,算出来的结果(也就是那个"差值")永远都是同一个数(比如都是0号)。
那如果中间断了一天呢?比如跳到了5号:
- 5号(序号变成了4):用 5号 减去 4天,结果就变成了 1号。
结果从"0号"变成了"1号",不一样了!这就说明中间断开了,属于新的一段连续登录。
简单总结一下就是:
我们在 SQL 里算出这个"差值",然后按这个差值分组。只要差值一样,就说明这几天是连着的;差值变了,就说明中间断过。
这样解释是不是清楚多啦?😄
💡 代码逻辑拆解
- 去重 (
DISTINCT):先把同一天内的多次登录合并成一条,避免干扰后续的序号排列。 - 打标 (
ROW_NUMBER+DATE_SUB) :ROW_NUMBER()会为每个用户的登录记录按时间先后生成 1, 2, 3... 的序号。- 假设某用户连续在
01-01,01-02,01-03登录,序号分别是 1, 2, 3。 - 用日期减去序号(
01-01减 1天 =12-31;01-02减 2天 =12-31...),你会发现算出来的grp都是同一天(12-31)。这个grp就是该段连续登录的"唯一锚点"。
- 分组统计 (
GROUP BY) :只要grp相同,就说明它们在同一段连续登录区间里。直接按user_id和grp分组,COUNT(*)就能算出这段连续了多少天。 - 筛选 (
HAVING) :最后可以根据业务需求,比如筛选出COUNT(*) >= 3的"忠实用户"。
sql
SELECT
user_id,
MIN(login_date) AS start_date, -- 这段连续登录的开始日期
MAX(login_date) AS end_date, -- 这段连续登录的结束日期
COUNT(*) AS consecutive_days -- 连续登录的天数
FROM (
-- 第二步:计算"差值日期"(打上连续区间的标签)
SELECT
user_id,
login_date,
-- 核心逻辑:用登录日期 减去 它在组内的排名序号
DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp
FROM (
-- 第一步:数据去重(同一天多次登录只保留一条)
SELECT DISTINCT user_id, login_date
FROM user_login
) t1
) t2
GROUP BY user_id, grp -- 第三步:按用户和差值日期分组
HAVING COUNT(*) >= 3; -- 第四步:筛选(比如找出连续登录3天及以上的记录)
问题需求3:
统计每个用户的最长连续登陆天数
如果你不需要列出每一段连续记录,只想知道每个用户历史上最长连续登了多少天 ,只需要在上面的基础上再套一层 MAX 聚合:
sql
SELECT
user_id,
MAX(consecutive_days) AS max_consecutive_days -- 取出该用户所有连续段中的最大值
FROM (
SELECT
user_id,
COUNT(*) AS consecutive_days
FROM (
SELECT
user_id,
DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp
FROM (
SELECT DISTINCT user_id, login_date
FROM user_login
) t1
) t2
GROUP BY user_id, grp
) t3
GROUP BY user_id;
问题需求4:
想看"连续登录天数最长"的用户
sql
SELECT
user_id,
MAX(consecutive_days) AS max_consecutive_days -- 算出每个用户的最长连续天数
FROM (
SELECT
user_id,
COUNT(*) AS consecutive_days
FROM (
SELECT
user_id,
-- 核心:日期减去序号,得出连续分组的基准日期
DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp
FROM (
SELECT DISTINCT user_id, login_date
FROM user_login
) t1
) t2
GROUP BY user_id, grp
) t3
GROUP BY user_id
ORDER BY max_consecutive_days DESC -- 按最长天数降序排列
LIMIT 1; -- 只取第1名(如果想看前10名,就改成 LIMIT 10)