SQL查询连续登录用户方法详解

1. 基本数据表结构

假设有一个登录记录表:

sql 复制代码
CREATE TABLE login_log (
    user_id INT,
    login_date DATE
);

示例数据:

sql 复制代码
INSERT INTO login_log VALUES
(1, '2024-01-01'),
(1, '2024-01-02'),
(1, '2024-01-03'),
(1, '2024-01-05'),  -- 这里断了一天
(1, '2024-01-06'),
(1, '2024-01-07'),
(2, '2024-01-01'),
(2, '2024-01-02'),
(2, '2024-01-04');  -- 这里断了一天

2. 方法一:使用窗口函数(推荐)

2.1 查询连续登录3天及以上的用户

sql 复制代码
WITH ranked_logs AS (
    SELECT 
        user_id,
        login_date,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) as rn
    FROM login_log
    GROUP BY user_id, login_date  -- 去重,防止一天多次登录
),
date_diff AS (
    SELECT 
        user_id,
        login_date,
        DATE_SUB(login_date, INTERVAL rn DAY) as group_date
    FROM ranked_logs
)
SELECT 
    user_id,
    MIN(login_date) as start_date,
    MAX(login_date) as end_date,
    COUNT(*) as consecutive_days
FROM date_diff
GROUP BY user_id, group_date
HAVING COUNT(*) >= 3
ORDER BY user_id, start_date;

2.2 使用LEAD/LAG函数的简化版

sql 复制代码
WITH consecutive_groups AS (
    SELECT 
        user_id,
        login_date,
        LAG(login_date) OVER (PARTITION BY user_id ORDER BY login_date) as prev_date,
        LEAD(login_date) OVER (PARTITION BY user_id ORDER BY login_date) as next_date
    FROM (
        SELECT DISTINCT user_id, login_date
        FROM login_log
    ) t
)
SELECT 
    user_id,
    login_date as consecutive_date
FROM consecutive_groups
WHERE 
    -- 连续3天的情况:当前日期、前1天、前2天都存在
    (login_date = prev_date + INTERVAL 1 DAY 
     AND login_date = prev_date + INTERVAL 2 DAY)
    OR
    -- 或者检查连续区间
    (login_date = prev_date + INTERVAL 1 DAY 
     AND login_date = next_date - INTERVAL 1 DAY)
    OR
    (login_date = next_date - INTERVAL 1 DAY 
     AND login_date = next_date - INTERVAL 2 DAY);

3. 方法二:使用自连接

sql 复制代码
SELECT DISTINCT 
    l1.user_id
FROM login_log l1
JOIN login_log l2 ON l1.user_id = l2.user_id 
    AND l2.login_date = l1.login_date + INTERVAL 1 DAY
JOIN login_log l3 ON l1.user_id = l3.user_id 
    AND l3.login_date = l1.login_date + INTERVAL 2 DAY
WHERE EXISTS (
    SELECT 1 FROM login_log 
    WHERE user_id = l1.user_id 
    AND login_date = l1.login_date + INTERVAL 1 DAY
)
AND EXISTS (
    SELECT 1 FROM login_log 
    WHERE user_id = l1.user_id 
    AND login_date = l1.login_date + INTERVAL 2 DAY
);

4. 方法三:使用递归CTE(复杂但功能强大)

sql 复制代码
WITH RECURSIVE consecutive_login AS (
    -- 基础查询:每个用户的首次登录
    SELECT 
        user_id,
        login_date,
        login_date as start_date,
        1 as consecutive_days
    FROM (
        SELECT 
            user_id,
            login_date,
            ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) as rn
        FROM login_log
    ) t
    WHERE rn = 1
    
    UNION ALL
    
    -- 递归部分:查找连续的下一天
    SELECT 
        cl.user_id,
        ll.login_date,
        CASE 
            WHEN ll.login_date = cl.login_date + INTERVAL 1 DAY 
            THEN cl.start_date
            ELSE ll.login_date
        END as start_date,
        CASE 
            WHEN ll.login_date = cl.login_date + INTERVAL 1 DAY 
            THEN cl.consecutive_days + 1
            ELSE 1
        END as consecutive_days
    FROM consecutive_login cl
    JOIN login_log ll ON cl.user_id = ll.user_id 
        AND ll.login_date > cl.login_date
    WHERE ll.login_date = (
        SELECT MIN(login_date)
        FROM login_log
        WHERE user_id = cl.user_id 
        AND login_date > cl.login_date
    )
)
SELECT 
    user_id,
    MAX(consecutive_days) as max_consecutive_days
FROM consecutive_login
GROUP BY user_id
HAVING MAX(consecutive_days) >= 3;

5. 实用查询示例

5.1 查询每个用户的最大连续登录天数

sql 复制代码
WITH ranked_logs AS (
    SELECT 
        user_id,
        login_date,
        DATE_SUB(login_date, INTERVAL 
            ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY
        ) as group_date
    FROM (
        SELECT DISTINCT user_id, login_date
        FROM login_log
    ) t
)
SELECT 
    user_id,
    MAX(consecutive_days) as max_consecutive_days
FROM (
    SELECT 
        user_id,
        group_date,
        COUNT(*) as consecutive_days
    FROM ranked_logs
    GROUP BY user_id, group_date
) groups
GROUP BY user_id
ORDER BY max_consecutive_days DESC;

5.2 查询指定时间段内的连续登录

sql 复制代码
WITH ranked_logs AS (
    SELECT 
        user_id,
        login_date,
        DATE_SUB(login_date, INTERVAL 
            ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY
        ) as group_date
    FROM (
        SELECT DISTINCT user_id, login_date
        FROM login_log
        WHERE login_date BETWEEN '2024-01-01' AND '2024-01-31'
    ) t
)
SELECT 
    user_id,
    COUNT(*) as consecutive_days,
    MIN(login_date) as start_date,
    MAX(login_date) as end_date
FROM ranked_logs
GROUP BY user_id, group_date
HAVING COUNT(*) >= 7  -- 连续7天登录
ORDER BY consecutive_days DESC;

5.3 查询连续登录中断的情况

sql 复制代码
WITH login_gaps AS (
    SELECT 
        user_id,
        login_date,
        LEAD(login_date) OVER (PARTITION BY user_id ORDER BY login_date) as next_date,
        DATEDIFF(
            LEAD(login_date) OVER (PARTITION BY user_id ORDER BY login_date),
            login_date
        ) as gap_days
    FROM (
        SELECT DISTINCT user_id, login_date
        FROM login_log
    ) t
)
SELECT 
    user_id,
    login_date as last_login_before_gap,
    next_date as next_login_after_gap,
    gap_days - 1 as missed_days
FROM login_gaps
WHERE gap_days > 1
ORDER BY user_id, login_date;

6. 性能优化建议

  1. 创建索引:
sql 复制代码
CREATE INDEX idx_user_login ON login_log(user_id, login_date);
  1. 分区表:如果数据量很大,按月份或用户ID范围分区

  2. 物化视图:对于频繁查询的结果可以创建物化视图

  3. 定期清理:删除历史数据,只保留最近N天的数据

7. 不同数据库的语法差异

函数/特性 MySQL PostgreSQL SQL Server Oracle
日期加减 DATE_ADD() + INTERVAL DATEADD() + INTERVAL
日期差 DATEDIFF() - DATEDIFF() -
行号 ROW_NUMBER() ROW_NUMBER() ROW_NUMBER() ROW_NUMBER()
递归CTE 支持(8.0+) 支持 支持 支持

选择哪种方法取决于:

  • 数据量:大数据量建议使用窗口函数
  • 查询频率:频繁查询建议建立物化视图
  • 数据库版本:确保支持相关函数
  • 业务需求:是否需要实时结果还是可接受延迟
相关推荐
katasea6 分钟前
第05章:信创技术栈选型:服务器、操作系统、数据库、中间件适配对比
服务器·数据库·中间件
梓沂15 分钟前
记一次 Oracle 测试库自动刷新脚本的踩坑实录
数据库·oracle
润乾软件20 分钟前
LLM 规划,编译器生成:以工作流为契约的确定性 SQL 生成器
人工智能·sql·sqlazy
泡海椒30 分钟前
JQuick-Excel 字段映射实战:用 MAPPING 固化 Excel 表头与业务字段契约
xml·java·开发语言·excel
2501_9318037531 分钟前
MySQL 索引核心原理
数据库·mysql
浪潮IT馆34 分钟前
Windows 10 安装 PostgreSQL 9.6.24 完整教程
数据库·windows·postgresql
梦幻通灵36 分钟前
IDEA 实用快捷键【持续更新】
java·ide·intellij-idea
南归北隐36 分钟前
Spring AI Alibaba Graph框架实现Tools工具调用
java·后端·spring·spring ai·spring ai tools
开开心心就好44 分钟前
视频模糊怎么修复?免费工具支持批量处理
java·前端·人工智能·智能手机·pdf·excel
w_zero_one1 小时前
链表(3)
java·数据结构·算法