count无法计算连续登录天数,因其只统计次数而不识别日期顺序与间隔;需用row_number()与日期差构造连续段标识,再分组计数。

为什么直接用 COUNT 无法算出“连续登录天数”
因为 COUNT 只统计总次数,不感知日期顺序和间隔。比如用户在 2024-01-01、2024-01-02、2024-01-04 登录三次,COUNT(*) 返回 3,但最大连续天数是 2(前两天),不是 3。关键在于识别“日期是否连贯”,必须借助日期差和分组逻辑。
用 ROW_NUMBER() + 日期差构造连续段标识
核心思路:对每个用户的登录日期排序,再用日期本身减去行号,同一连续段的结果会恒定——这是最稳定、兼容性最好的方法(PostgreSQL / MySQL 8.0+ / SQL Server / Oracle 都支持)。
实操建议:
- 先按
user_id分组,按login_date升序排,用ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date)得到序号 - 把
login_date转成天数(如用TO_DAYS(login_date)或login_date::DATE - '1970-01-01'::DATE),再减去序号,结果即为“连续段锚点” - 按
user_id和该锚点分组,COUNT(*)就是每段连续天数
示例片段(MySQL):
SELECT user_id, COUNT(*) AS consecutive_days
FROM (
SELECT user_id, login_date,
TO_DAYS(login_date) - ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
) AS grp
FROM user_login
) t
GROUP BY user_id, grp;
注意 DATE 类型精度和时区导致的隐式截断
如果原始字段是 datetime 或带时区的 timestamptz,直接参与减法可能因毫秒/时区偏移破坏连续性判断。
常见错误现象:
- 同一天不同时间的两条记录被拆成两段(如
'2024-01-01 09:00'和'2024-01-01 23:59'算作非连续) - 跨时区同步数据后,
login_date实际是同一天,但存储值因 UTC 转换错位一天
解决办法:
- 统一用
DATE(login_date)或login_date::DATE提前归一化 - 确认数据库时区设置与业务时区一致,必要时用
AT TIME ZONE 'Asia/Shanghai'显式转换
性能瓶颈常出现在未建索引的 user_id + login_date 组合上
窗口函数依赖排序,若没索引,每次执行都要全表扫描+临时排序,百万级日志表可能超 10 秒。
必须做的优化:
- 创建联合索引:
CREATE INDEX idx_user_login ON user_login (user_id, login_date); - 避免在
login_date上用函数(如DATE(login_date))做条件或分组,否则索引失效;应预先存login_date_only DATE字段并索引它 - 若只查“当前最大连续天数”,可加
WHERE login_date >= DATE_SUB(CURDATE(), INTERVAL 90 DAY)限定范围,避免扫历史冷数据
连续登录统计真正难的不是写法,而是当 login_date 不干净、索引没对齐、或需要实时响应时,结果会悄然出错——这些地方几乎不会报错,但数字就是不对。











