利用ROW_NUMBER()对登录日期按用户分组排序编号,日期减去编号得到连续段锚点,再按用户和锚点分组统计天数,即可筛选出连续登录超过3天的用户。需注意日期格式统一、去重同一天多次登录,并为user_id和login_date建立联合索引以优化性能。
SELECT user_id
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 login_log
) t
GROUP BY user_id, grp
HA VING COUNT(*) > 3;
注意一下`HA VING`条件:`COUNT(*) > 3`表示“超过3天”,也就是至少连续登录4天;如果需要“大于等于3天”,那么把`> 3`改成`>= 3`就行。
### 重复登录与跨年边界的处理方式
用户同一天多次登录是一个常见陷阱——如果不处理,`COUNT(*)`的结果会偏大,导致统计失真。解决办法是:在子查询里先用`DISTINCT user_id, login_date`去重,或者用`GROUP BY user_id, login_date`预聚合一下。
至于跨年,其实不是什么大问题。`DATE_SUB`函数在MySQL里能正确处理,比如`2023-12-31 - INTERVAL 1 DAY = 2023-12-30`,不会有边界问题。但如果你用的是Oracle或PostgreSQL,日期运算的语法会有所不同:PostgreSQL需要写成`login_date - ROW_NUMBER() OVER (...)::INT`,Oracle则用`login_date - ROW_NUMBER() OVER (...)`。
### 性能优化的关键:一个恰当的联合索引
如果没有合适的索引,`PARTITION BY user_id ORDER BY login_date`这类操作在百万级数据上几乎必然导致全表扫描,速度会非常慢。
解决方案很明确:建立一个覆盖`user_id`和`login_date`的联合索引。
CREATE INDEX idx_user_login ON login_log (user_id, login_date);这个索引能同时支撑分组、排序和去重,比两个单列索引的组合有效得多。如果你的表已经有主键包含了这两列,请确认一下列的顺序是否一致——`user_id`必须在前。 连续登录分析本身不复杂,真正的难点往往在数据质量上。空值、时区混杂、日志漏采,任何一个问题都可能让锚点计算失准。上线之前,一定记得用真实的样本数据把中间结果跑一遍验证一下。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述