连续活跃天数统计需用DENSE_RANK()计算“日志日期-排名”差值,该差值恒定即为同一连续段;必须去重并建立(user_id, log_date)复合索引以保障性能与准确性。

用 DENSE_RANK() 和日期差值做连续分组,核心是“日志日期 - 排名”相等即连续
连续活跃天数统计的本质,不是查最大间隔,而是把用户每天的登录记录按「自然连续日期」切分成若干段,再取每段长度。关键在于:同一连续段内,log_date - DENSE_RANK() OVER (PARTITION BY user_id ORDER BY log_date) 的值恒定。
这个差值是个“锚点”,它把 2024-01-01、02、03 映射成相同的数字(比如 2024-01-01 - 1 = 2023-12-31),而一旦断开(比如缺了 04),下一段的排名重置,差值就变了。
常见错误是直接用 ROW_NUMBER() —— 它在有重复日期时会出错;或者用 RANK(),跳名次会导致差值不唯一。必须用 DENSE_RANK(),保证相同日期排同一名次且不跳号。
实际 SQL 写法:两层子查询 + GROUP BY 差值
不能一步到位,得先算排名和差值,再按差值分组聚合。多数人卡在嵌套层级和别名引用上。
- 外层只负责按
user_id和差值分组,求COUNT(*)得到每段连续天数 - 中间层必须显式计算
log_date - INTERVAL DENSE_RANK() OVER (...) DAY(MySQL)或log_date - DENSE_RANK() OVER (...) * INTERVAL '1 day'(PostgreSQL) - 注意日期类型对齐:如果
log_date是TIMESTAMP,先CAST(log_date AS DATE)再参与运算,否则时分秒干扰差值 - SQL Server 要用
DATEADD(day, -DENSE_RANK() OVER (...), log_date),语法差异大,别硬套
遇到重复登录日或跨午夜日志,DENSE_RANK() 仍可靠
用户一天登多次,DENSE_RANK() 会给同一天所有记录相同排名,差值一致,不会被拆散——这正是它比 ROW_NUMBER() 强的地方。
但要注意去重逻辑是否该由业务决定:如果需求是“只要当天有行为就算活跃”,那应在最外层或子查询里先 DISTINCT ON (user_id, CAST(log_date AS DATE)) 或 GROUP BY user_id, DATE(log_date)。
没去重就直接跑,可能把单日多条日志当成多日,导致连续段误判。
性能坑:没加 (user_id, log_date) 复合索引,窗口函数全表扫描
DENSE_RANK() OVER (PARTITION BY user_id ORDER BY log_date) 看似简单,但没索引时,数据库要对每个 user_id 单独排序,数据量一过百万,查询直接卡住。
必须建索引:CREATE INDEX idx_user_date ON user_login_log (user_id, log_date);
别用 (log_date, user_id)——顺序反了,PARTITION BY 优先级高于 ORDER BY,索引失效。另外,如果表太大,考虑加时间分区(如按月),避免扫全量历史。
连续段识别依赖的是“差值稳定”,不是日期本身;差值计算对时区、日期精度、重复数据都敏感,写完务必用含断点、重复、跨月的数据集验证一遍。










