ROW_NUMBER()差值法能识别连续登录,因同一用户连续日期的date-ROW_NUMBER()值恒定,形成分组标识;需用DATE(login_time)归一化并前置去重,MySQL 8.0+支持,5.7需变量或应用层处理。
为什么用
差值法能识别连续登录?
核心在于:对同一用户的登录日期排序后,连续日期的
值恒定。比如 2024-01-01、02、03 排序号为 1/2/3,则差值都是 2024-01-00(MySQL 会转成天数差),形成天然分组标识。
注意:必须用
归一化时间,否则带时分秒的
会导致同一天多次登录被拆成多行。
常见错误现象:
后再排序——这会丢失原始顺序,
就乱了。
实际 SQL 写法与关键参数说明
假设表名
,字段为
、
(
类型):
要点说明:
必须加,否则全表一起编号,用户间互相干扰
要明确用日期排序,不是原始
是 MySQL 特有写法,其他数据库(如 PostgreSQL)需改用
如果只要「当前连续多少天」,可在外层加
遇到重复登录或数据不干净怎么办?
真实场景中,用户一天可能登录多次,直接
去重最稳妥——但必须在窗口函数之前做,否则
仍会为同一天多条记录分配不同序号。
修正写法(子查询先去重):
MySQL(Linux)
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
下载
性能提示:如果表很大,
+ 窗口函数组合可能慢,建议在
上建联合索引,或预计算每日登录标记表。
这个方法在 MySQL 5.7 和 8.0 的兼容性差异
是 MySQL 8.0+ 才支持的窗口函数,MySQL 5.7 及更早版本会报错
。
5.7 替代方案只能用变量模拟,但风险高:
变量执行顺序不保证,尤其在
和
混用时极易出错
无法在子查询中安全复用变量值
官方已明确不推荐用于生产环境
如果必须支持 5.7,建议改用应用层聚合(如 Python/Java 拉取排序后日期列表再扫描),比硬套变量更可靠。
真正容易被忽略的是:差值法依赖日期严格连续,中间缺一天就断开;如果业务需要“7天内最多断1天也算连续”,就得换用自连接或递归 CTE,不再是简单差值能解决的了。
ROW_NUMBER()date - ROW_NUMBER()DATE(login_time)DATETIMEGROUP BY user_id, DATE(login_time)ROW_NUMBER()user_loginuser_idlogin_timeDATETIMESELECT
user_id,
MIN(login_date) AS start_date,
MAX(login_date) AS end_date,
COUNT(*) AS days
FROM (
SELECT
user_id,
DATE(login_time) AS login_date,
DATE(login_time) - INTERVAL ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY DATE(login_time)
) DAY AS grp
FROM user_login
) t
GROUP BY user_id, grp
ORDER BY user_id, start_date;PARTITION BY user_idORDER BY DATE(login_time)login_timeINTERVAL ... DAYlogin_date - ROW_NUMBER()::intHAVING MAX(login_date) = CURDATE()GROUP BY user_id, DATE(login_time)ROW_NUMBER()SELECT
user_id,
MIN(login_date) AS start_date,
MAX(login_date) AS end_date,
COUNT(*) AS days
FROM (
SELECT
user_id,
login_date,
login_date - INTERVAL ROW_NUMBER() OVER (
PARTITION BY user_id ORDER BY login_date
) DAY AS grp
FROM (
SELECT DISTINCT user_id, DATE(login_time) AS login_date
FROM user_login
) t0
) t
GROUP BY user_id, grp;DISTINCT(user_id, DATE(login_time))ROW_NUMBER()FUNCTION xxx.ROW_NUMBER does not existORDER BYJOIN