CodeMaster 2025-11-16 16:15 采纳率: 98.9%
浏览 0
已采纳

如何用MySQL查询用户最近连续签到天数?

如何用MySQL查询用户最近连续签到天数?一个常见问题是:当用户签到记录存在断签情况时,如何准确识别“最近连续签到”的起始点并排除历史不连续片段?例如,用户过去有连续7天签到,中断1天后又连续签到5天,应返回5而非7。难点在于需按时间倒序判断连续性,结合变量或窗口函数标记断点,仅统计最后一次连续段。实际实现中易出现逻辑错误或性能瓶颈,尤其在大数据量下缺乏索引优化时查询变慢。
  • 写回答

1条回答 默认 最新

  • 曲绿意 2025-11-16 16:30
    关注

    一、问题背景与业务场景分析

    在用户成长体系或积分系统中,签到功能是常见设计。为了激励用户每日活跃,系统常需统计“最近连续签到天数”。例如:某用户历史签到记录为连续7天,中断1天后再次连续签到5天,则当前应返回5而非7。

    该需求的核心难点在于:

    • 需按时间倒序判断连续性;
    • 识别最后一次连续段的起始点;
    • 排除历史不连续片段的影响;
    • 避免因数据量大导致性能下降。

    二、基础数据结构设计

    假设我们有一个签到表 user_sign_in,其结构如下:

    字段名类型说明
    user_idINT用户ID
    sign_dateDATE签到日期
    created_atDATETIME创建时间

    示例数据(按 user_id = 1):

    user_idsign_date
    12024-03-01
    12024-03-02
    12024-03-03
    12024-03-04
    12024-03-05
    12024-03-06
    12024-03-07
    12024-03-09
    12024-03-10
    12024-03-11
    12024-03-12
    12024-03-13

    注意:3月8日断签,之后从9号开始新一轮连续签到,共5天。

    三、解题思路演进:从变量法到窗口函数

    解决此问题的关键是“分组连续区间”,然后取最新一组的长度。

    步骤分解如下:

    1. 将签到记录按日期降序排列;
    2. 使用变量或窗口函数标记“是否断签”;
    3. 通过差值生成“连续块标识”;
    4. 对每个块计数,取第一个块(即最近)的天数。

    四、MySQL实现方案对比

    以下提供两种主流实现方式:

    方案一:用户变量法(适用于 MySQL < 8.0)

    
    SET @prev_date := NULL;
    SET @group_id := 0;
    
    SELECT 
        user_id,
        COUNT(*) AS consecutive_days
    FROM (
        SELECT 
            user_id,
            sign_date,
            @group_id := IF(
                @prev_date IS NULL OR DATEDIFF(@prev_date, sign_date) = 1,
                @group_id,
                @group_id + 1
            ) AS grp,
            @prev_date := sign_date
        FROM user_sign_in
        WHERE user_id = 1
        ORDER BY sign_date DESC
    ) t
    GROUP BY user_id, grp
    ORDER BY sign_date DESC
    LIMIT 1;
        

    方案二:窗口函数法(MySQL 8.0+ 推荐)

    
    WITH RankedSigns AS (
        SELECT 
            user_id,
            sign_date,
            LAG(sign_date) OVER (PARTITION BY user_id ORDER BY sign_date DESC) AS prev_date
        FROM user_sign_in
    ),
    GroupedBlocks AS (
        SELECT 
            user_id,
            sign_date,
            SUM(CASE WHEN prev_date IS NULL OR DATEDIFF(prev_date, sign_date) != 1 THEN 1 ELSE 0 END)
            OVER (PARTITION BY user_id ORDER BY sign_date DESC ROWS UNBOUNDED PRECEDING) AS block_id
        FROM RankedSigns
    )
    SELECT 
        user_id,
        COUNT(*) AS consecutive_days
    FROM GroupedBlocks
    GROUP BY user_id, block_id
    ORDER BY MAX(sign_date) DESC
    LIMIT 1;
        

    五、性能优化建议

    当用户量大、签到记录多时,查询性能至关重要。以下是关键优化点:

    • 索引策略:在 (user_id, sign_date) 上建立复合索引,确保排序和过滤高效;
    • 分区表:按月或年对签到表进行分区,减少扫描范围;
    • 缓存层:将最近连续签到结果缓存在 Redis 中,定时更新;
    • 物化视图:定期计算并存储每个用户的当前连续天数。

    六、流程图:逻辑处理过程

    graph TD A[获取用户签到记录] --> B[按日期降序排序] B --> C{是否为第一条记录?} C -->|是| D[初始化 group_id=0] C -->|否| E[比较与上一条日期差] E --> F{相差是否为1天?} F -->|是| G[保持当前 group_id] F -->|否| H[递增 group_id] G --> I[标记所属连续块] H --> I I --> J[按 group_id 分组统计] J --> K[取最大日期组的计数] K --> L[返回最近连续签到天数]

    七、边界情况与测试用例

    为验证算法鲁棒性,考虑以下测试场景:

    场景输入数据预期输出
    完全连续连续10天10
    最近无签到最后签到在3天前0
    仅一天签到今天签到1
    昨日断签昨天未签,前天有签0
    跨月连续2月28日~3月3日4
    多用户并发多个 user_id 混合数据按 user_id 独立计算
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 11月17日
  • 创建了问题 11月16日