前几天刷手机时看到一道有趣的 sql 题:查询连续 3 天登录的用户。这让我联想到之前讨论过的开窗函数,深入思考后发现其实还有多种实现方式。今天就来和大家分享几种解决方案,欢迎一起讨论!
一、建表:还原场景问题
1.创建用户登录记录表t_login_records
,包含用户 id 和登录日期两个核心字段
drop table if exists t_login_records;#若表存在删除 -- 创建用户登录记录表 create table t_login_records ( id int primary key auto_increment, user_id int not null, login_date date not null )engine=innodb default charset=utf8 collate=utf8_unicode_ci comment='用户登录记录表';
2.插入测试数据
- 查看表结构
-- 查看表结构 desc t_login_records;
- 数据预览
insert into t_login_records (user_id, login_date) values (1, '2023-01-01'), (1, '2023-01-02'), (1, '2023-01-03'), -- 用户1连续3天登录 (1, '2023-01-05'), (2, '2023-01-01'), (2, '2023-01-02'), (2, '2023-01-04'), -- 用户2不连续 (3, '2023-01-01'), (3, '2023-01-02'), (3, '2023-01-03'), -- 用户3连续3天登录 (3, '2023-01-04'), -- 用户3连续4天登录 (4, '2023-01-01'), (4, '2023-01-03'), (4, '2023-01-05'), -- 用户4不连续 (5, '2023-01-01'), (5, '2023-01-02'), (5, '2023-01-03'), -- 用户5连续3天登录 (5, '2023-01-04'), -- 用户5连续4天登录 (5, '2023-01-05'), -- 用户5连续5天登录 (5, '2023-02-01'), -- 断开 (5, '2023-02-02'), -- 用户5再连续2天登录 (5, '2023-02-03'); -- 用户5再连续3天登录 select * from t_login_records;
二、查询:多种方法实现
1.自连接查询
- 原理: 通过三次连接同一张表,强制匹配同一用户的三条登录记录,且日期依此相差 1 天
-- 方法1:自连接查询 select distinct t1.user_id from t_login_records t1 join t_login_records t2 on t1.user_id = t2.user_id and datediff(t2.login_date, t1.login_date) = 1 join t_login_records t3 on t1.user_id = t3.user_id and datediff(t3.login_date, t1.login_date) = 2;
- 查询结果:
user_id
为1、3、5的用户
- 执行步骤: 自连接将表t1(基准记录)与t2(次日记录)、t3(第三日记录)连接,确保
- 用户相同
t2.login_date=t1.login_date+1
t3.login_date=t1.login_date+2
distinct
过滤重复的user_id
- 示例数据验证:
user_id=1
的用户在 2023-01-01、2023-01-02、2023-01-03 连续登录:t1(2023-01-01)
→t2(2023-01-02)
→t3(2023-01-03)
,匹配成功。
user_id=2
的用户仅在 2023-01-01 和 2023-01-02连续登录:- 无法找到连续三天的记录,匹配失败。
2.窗口函数
- 原理: 使用窗口函数
lead()
获取每个用户后续的登录日期,直接判断是否连续。
-- 方法2:窗口函数(适用于支持lead函数的数据库,如mysql 8.0+、postgresql) select distinct user_id from ( select user_id, login_date, lead(login_date, 1) over (partition by user_id order by login_date) as next_day, lead(login_date, 2) over (partition by user_id order by login_date) as next_2_days from t_login_records ) t where datediff(next_day, login_date) = 1 and datediff(next_2_days, login_date) = 2;
- 查询结果:
user_id
依然为1、3、5的用户
- 执行步骤: 窗口函数和条件过滤
lead(login_date, 1)
:获取当前记录的下一条日期。lead(login_date, 2)
:获取当前记录的下两条日期。- 确保
next_day = login_date + 1
且next_2_days = login_date + 2
。
- 示例数据验证:
- 用户1的第一条记录(2023-01-01):
- next_day=2023-01-02(差值1天)
- next_2_days=2023-01-03(差值2天)
- 满足条件,用户1被选中。
- 用户4的第一条记录(2023-01-01):
- next_day=2023-01-03
- next_2_days=2023-01-05
不满足条件,用户4未被选中。
3.日期差值分组
- 原理: 将每个登录日期减去其在用户组内的排序序号,连续日期会得到相同的差值,通过分组统计差值出现次数即可。
-- 方法3:日期差值分组(适用于支持row_number的数据库) select user_id from ( select user_id, login_date, date_sub(login_date, interval row_number() over (partition by user_id order by login_date) day) as grp from t_login_records ) t group by user_id, grp having count(distinct login_date) >= 3;
- 查询结果:
user_id
依然为1、3、5的用户
- 执行步骤:
计算分组标识grp
和分组统计
row_number()
为每个用户的登录记录分配连续序号(1,2,3…)。date_sub(login_date,row_number())
:将日期减去序号,连续日期会得到相同的结果。- 按
user_id
和grp
分组,统计每组的日期数量,若≥3则为连续登录。
- date_sub()函数:
date_sub(date, interval expr unit)
是 sql 中的一个日期函数,用于从指定日期中减去一个时间间隔。interval expr unit
:指定要减去的时间间隔interval
:固定关键字,表示时间间隔expr
是一个数值unit
是时间单位(如 day、month、year 等)
三、总结:三种方法对比与拓展
方法 | 优点 | 缺点 |
---|---|---|
自连接 | 简单直接,兼容性强 | 性能差(多次扫描表) |
窗口函数 | 逻辑清晰,一步到位 | 需数据库支持窗口函数 |
日期差值 | 性能最优,逻辑巧妙 | 理解难度较高 |
这道题虽然仅要求查询连续 3 天登录的用户,但通过这三种方法我们可以举一反三。如果要查询连续 4 天、5 天甚至 n 天登录的用户,第三种日期差值分组法更具优势,只需修改 having count(distinct login_date) >= n
即可实现 “一力破万法” 的效果!
到此这篇关于sql语句查询连续n天登录用户的文章就介绍到这了,更多相关sql连续n天登录用户内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论