首页 > 数据库 >SQL聚合函数计算连续签到天数的方法

SQL聚合函数计算连续签到天数的方法

来源:互联网 2026-06-19 08:44:12

利用日期减去行号(ROW_NUMBER)的差值作为分组标识,识别连续签到段。需确保用户ID和纯日期字段,并先对重复日期去重。窗口函数适用于MySQL8.0+、PostgreSQL等。标准写法借助差值分组计算最长连续天数,注意不同数据库日期运算语法差异。

连续签到天数这事儿,说穿了就是个分组问题。不是简单数一数总共有多少条签到记录,而是得把日期按“是不是连着的”切成一截一截,再数每一截有多长。关键在哪?在于识别断点:如果今天签到了,但昨天没签,那就是一个新连续段的开始。 实现这个逻辑,最经典的手法是用日期减去行号(`ROW_NUMBER()`)。原理说起来也简单:同一段连续签到里,日期是按天递增的,行号也是按顺序递增的,一减,出来的差值恒定不变;一旦中间断了一天,差值就会跳变。这个差值,就是给每段连续记录打上的“组标”。 当然,动手之前有几件事得先确认:签到表里一定要有用户ID和日期字段,日期那列必须是纯日期类型,不是带时间戳的`DATETIME`——如果是后者,得先`CAST`成日期才行。另外,给行号排序前,必须先按用户和日期排好序,不然计算出的差值会乱得离谱。至于窗口函数,MySQL 8.0+、PostgreSQL、SQL Server 2012+ 都支持,SQLite 也得3.25以上的版本才行。 **标准写法:用 `ROW_NUMBER()` + 差值分组** 以MySQL 8.0为例,要算每个用户历史上的最长连续签到天数,可以这么写: ```sql SELECT user_id, MAX(consecutive_days) AS max_streak FROM ( SELECT user_id, DATE_SUB(sign_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY sign_date) DAY) AS grp, COUNT(*) AS consecutive_days FROM sign_log GROUP BY user_id, grp ) t GROUP BY user_id; ``` 注意:代码里的`DATE_SUB`是MySQL的写法;换成PostgreSQL,就得写成`sign_date - ROW_NUMBER() OVER (...)::INT`;SQL Server则用`DATEADD(DAY, -ROW_NUMBER() OVER (...), sign_date)`。不同数据库的差值类型和日期运算语法得对上号,否则报错是轻的,算错才是大问题。 另外有两个容易踩的坑:一个是签到表里可能有重复日期——同一天同一个用户签了好几次,这时候必须先`DISTINCT`或者`GROUP BY`去重,不然行号会把同一天拆成多行,连续性就崩了。另一个是这种写法只统计已有记录里的连续段,不会帮你去预测未来或补缺失日期。如果想算“截至今天的连续签到天数”,逻辑就得换一下。 **查当前正在发生的连续签到(含今日)** 很多业务场景关心的不是历史最长,而是“到今天为止,我连续签到了多少天”。这时候光靠已有记录是不够的,得确认最后一条签到日是不是紧挨着今天。 判断逻辑其实就一条:最大签到日期必须等于今天,而且那一段连续记录的起始日不能早于“今天 - 天数 + 1”。更稳妥的做法是先筛出每个用户最近一段连续记录: ```sql WITH ranked AS ( SELECT user_id, sign_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY sign_date) AS rn FROM sign_log WHERE sign_date <= CURDATE() ), grouped AS ( SELECT user_id, DATE_SUB(sign_date, INTERVAL rn DAY) AS grp, MIN(sign_date) AS start_date, MAX(sign_date) AS end_date, COUNT(*) AS days FROM ranked GROUP BY user_id, grp ) SELECT user_id, days AS current_streak FROM grouped WHERE end_date = CURDATE(); ``` 这里也有几个地方要注意:`CURDATE()`在PostgreSQL里得换成`CURRENT_DATE`,SQL Server换成`GETDATE()`再`CAST`成日期。如果用户今天压根没签到,那`end_date = CURDATE()`这一条就不会命中,结果自然是空的——这也正是期望的行为。还有,这张表如果数据量很大,`ROW_NUMBER()`的开窗成本非常可观,务必在`(user_id, sign_date)`上建联合索引。 **容易被忽略的边界情况** 真实业务里,连续签到逻辑最容易栽在下面这几个细节上: 时区问题是个大杀器。服务器存的是UTC时间,但用户签到按本地时区算,一旦`DATE(sign_time)`转换后跨了日,用户觉得“我连续签到了”,系统却判定“断开了”。这里有个坑要提前说。 单日多次签到也很常见。有些产品允许当天多次打卡,但只算一次。如果没在聚合前去重,`ROW_NUMBER()`会多出一堆行,差值算出来全错位。 跨年、跨月听起来复杂,但实际上主流数据库的`DATE_SUB`和`INTERVAL`都能正确处理,不用额外操心。千万别自己手写`YEAR(sign_date)*365 + ...`这种近似算法——闰年、大小月分分钟让你翻车。 最后就是空数据。用户从没签过到,子查询返回空,外层的`MAX()`自然就是`NULL`。业务代码里如果不处理这个`NULL`场景,页面崩了才开始排查,大家都很累。 连续签到看着简单,真正落地时,日期语义、数据质量、时区、索引这四点——漏掉任一个,结果就不可信。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。