SQL计算连续登陆天数大于等于3天的用户

其中给出了用户(id)和用户登陆时间(dt)两列
其中要处理把登陆时间截取到日,然后根据id和登陆时间dt进行去重,然后得到如下的数据集
在这里插入图片描述
再进行下面的代码

select id, sdate, edate, days from (
select id, sdate, edate, days, rank() over(partition by id order by days desc) as ranking from
	(select id,dis,min(dt) as sdate, max(dt) as edate, count(id) as days from
		(select id, dt, rn, date_sub(dt, interval rn day) as dis from
			(select id, dt, row_number() over(partition by id order by dt) as rn from liucunlv) 
			as t1) 
		as t2 group by id, dis)
	as t3
) as t4 WHERE ranking=1 and days >2

使用lead来实现,连续登陆三天的用户:

select A.id from (
	select *, 
	lead(dt, 1) over(partition by user_id order by dt) as lead_1day,      # 提前一行的数据
	lead(dt, 2) over(partition by user_id order by dt) as lead_2day from  # 提前两行的数据
	liucunlv
	) as A
where (lead_2day - lead_1day) = 1 
	  and (lead_1day - login_date) = 1
<think>好的,我现在需要解决如何用SQL查询连续登录用户这个问题。首先,我得理解用户的需求是什么。用户想要找出那些至少连续都有登录记录的用户。这可能涉及到用户行为分析,比如活跃用户统计或者促销活动的参与条件。接下来,我得考虑数据库表的结构是什么样的。通常,这类问题会有一个登录日志表,包含用户ID和登录日期这两个关键字段。例如,表名可能是login_log,字段是user_id和login_date。 接下来,我需要思考如何判断用户是否连续登录连续登录意味着用户登录日期在三内是连续的,没有间隔。例如,如果一个用户在2023-10-01、2023-10-02和2023-10-03都有登录记录,那么他就符合条件。但是,用户可能在更长时间连续登录,比如四或五,这时候可能需要更通用的解决方案。 这时候,我想到可能需要使用窗口函数来处理这类问题。窗口函数可以在每行数据上执行计算,同时考虑到与当前行相关的其他行的数据。特别是LAG或LEAD函数,可以获取前一行或后一行的数据,从而帮助计算日期之间的差异。 假设有一个用户登录表,结构如下: | user_id | login_date | |---------|------------| | 1 | 2023-10-01 | | 1 | 2023-10-02 | | 1 | 2023-10-03 | | 2 | 2023-10-01 | | 2 | 2023-10-03 | 对于这个例子,用户1在三连续登录,而用户2在01和03登录,中间间隔了一,所以不符合条件。 接下来,我需要找出每个用户每次登录的前两次登录日期,然后检查是否存在连续的情况。例如,对于每个登录记录,检查前两登录是否存在。如果存在,则说明有连续。 或者,另一种方法是使用自连接,将同一用户的三条记录连接起来,确保它们的日期是连续的。但自连接可能在数据量大时效率不高,尤其是当用户有很多登录记录的时候。 因此,窗口函数可能是更好的选择。具体来说,可以使用LAG函数来获取前两次的登录日期,然后检查当前日期是否比前两各晚一和两。例如,对于某一行,如果当前日期是2023-10-03,那么前一次登录应该是2023-10-02,再前一次是2023-10-01。这时候,三连续登录的条件就满足了。 不过,需要注意的是,用户可能在一内有多次登录,所以需要先对日期进行去重处理,确保每个用户只保留一条记录。这时候可能需要先对user_id和login_date进行分组,或者使用DISTINCT来去重。 接下来,具体的步骤可能是这样的: 1. 对每个用户登录日期进行排序,按日期升序排列。 2. 使用窗口函数LAG两次,分别获取前一次和前两次的登录日期。 3. 计算当前日期与前一次和前两次的日期差,如果分别为1和2,那么说明存在连续登录的情况。 4. 最后,筛选出满足条件的用户去重以避免重复。 不过,这可能有问题,因为如果用户连续登录,比如1号、2号、3号、4号,那么每个中间的日期都会被计算到,导致重复记录。因此,可能需要更精确的条件,或者通过其他方式来判断是否存在至少三个连续的日期。 另一种方法是使用日期减去行号的连续值,如果日期是连续的,那么它们的差值会相同。例如,对于一个按日期排序的序列,如果给每个日期分配一个行号,然后用日期减去行号,连续的日期得到的差值会是相同的。例如: user_id | login_date | row_num | date - row_num 1 | 2023-10-01 | 1 | 2023-09-30 1 | 2023-10-02 | 2 | 2023-09-30 1 | 2023-10-03 | 3 | 2023-09-30 1 | 2023-10-05 | 4 | 2023-10-01 这里,连续的日期减去行号后的结果相同,所以可以通过分组这个差值来统计连续天数。如果某个分组的计数大于等于3,则说明存在至少三连续登录。 这种方法可能更高效,因为它只需要一次分组和计数。具体步骤可能是: 1. 对每个用户登录日期排序,生成行号。 2. 计算login_date减去行号的天数,得到一个分组键。 3. 按用户和这个分组键进行分组,统计每组的记录数。 4. 筛选出记录数大于等于3的组,对应用户即为所求。 这种方法的关键在于,连续日期减去行号后的结果相同,所以连续的日期会被分到同一组。例如,连续的日期,行号是n, n+1, n+2,那么日期减去行号后的结果都是相同的。这样,每组中的记录数就是连续天数。 不过,这需要确保login_date是唯一的,每个用户只有一条记录,所以在处理数据之前需要先去重。 现在,我需要将这两种方法转化为具体的SQL查询语句。 首先,处理去重的问题。假设原始表可能有重复的登录记录,因此需要先按user_id和login_date分组,或者使用DISTINCT。 假设表名为login_log,包含user_id和login_date字段,其中login_date是DATE类型。 第一步,获取每个用户唯一的登录日期: SELECT user_id, login_date FROM login_log GROUP BY user_id, login_date; 或者使用DISTINCT: SELECT DISTINCT user_id, login_date FROM login_log; 接下来,使用窗口函数为每个用户登录日期生成行号: WITH user_login AS ( SELECT user_id, login_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn FROM (SELECT DISTINCT user_id, login_date FROM login_log) AS distinct_logs ) 然后,计算分组键,即login_date减去行号的天数。在SQL中,可以使用DATE_SUB或直接相减,具体取决于数据库的支持。例如,在MySQL中: SELECT user_id, login_date, rn, DATE_SUB(login_date, INTERVAL rn DAY) AS grp FROM user_login; 这里,grp字段就是每个连续日期组的标识。如果日期是连续的,那么grp的值会是相同的。 然后,按user_id和grp分组,统计每组的记录数: SELECT user_id, grp, COUNT(*) AS consecutive_days FROM ( SELECT user_id, login_date, DATE_SUB(login_date, INTERVAL rn DAY) AS grp FROM user_login ) AS sub GROUP BY user_id, grp HAVING COUNT(*) >= 3; 最后,选择那些有至少三连续登录的user_id: SELECT DISTINCT user_id FROM ( -- 上面的分组查询 ) AS result; 但是,这可能在不同的数据库中有不同的实现方式,特别是日期的计算。例如,在PostgreSQL中,可以写成: login_date - rn * INTERVAL &#39;1 day&#39; AS grp 或者更简单的方式是将日期转换为天数,然后减去rn。例如: EXTRACT(DAY FROM login_date) - rn 或者类似的函数,但这样可能有问题,因为跨月份或年份的情况。更好的方法是使用日期直接减去行号的天数间隔。 另外,需要注意的是,不同的数据库对日期运算的支持可能不同,比如MySQL的DATE_SUB,PostgreSQL的login_date - rn * INTERVAL &#39;1 day&#39;,而SQL Server则可能用DATEADD(DAY, -rn, login_date)等。 所以,假设使用的是MySQL,完整的查询可能如下: WITH user_login AS ( SELECT DISTINCT user_id, login_date FROM login_log ), with_rn AS ( SELECT user_id, login_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn FROM user_login ), grouped AS ( SELECT user_id, DATE_SUB(login_date, INTERVAL rn DAY) AS grp, COUNT(*) AS consecutive_days FROM with_rn GROUP BY user_id, DATE_SUB(login_date, INTERVAL rn DAY) HAVING COUNT(*) >= 3 ) SELECT DISTINCT user_id FROM grouped; 这个查询首先去重,得到每个用户登录记录,然后按用户分区生成行号,接着计算grp,按用户和grp分组统计连续天数,最后筛选出连续以上的用户。 另一种方法是使用LAG函数来获取前两次的登录日期,然后检查是否存在连续的情况。例如: WITH user_login AS ( SELECT DISTINCT user_id, login_date FROM login_log ), lagged AS ( SELECT user_id, login_date, LAG(login_date, 1) OVER (PARTITION BY user_id ORDER BY login_date) AS prev1, LAG(login_date, 2) OVER (PARTITION BY user_id ORDER BY login_date) AS prev2 FROM user_login ) SELECT DISTINCT user_id FROM lagged WHERE DATEDIFF(login_date, prev1) = 1 AND DATEDIFF(prev1, prev2) = 1; 这里,LAG函数获取前一次和前两次的登录日期,然后检查当前日期与前一次相差1,前一次与前两次也相差1,这样就能确保三个连续的日期。例如,如果当前日期是第3,prev1是第2,prev2是第1,那么满足条件。 这种方法可能更直观,但需要注意的是,用户可能有多个连续的情况,比如连续登录,这样会有两行满足条件(第3和第4),但DISTINCT user_id可以避免重复。 然而,这种方法的缺点是如果用户连续登录超过三,比如四,那么第三和第四都会满足条件,但最终结果只需要唯一的user_id。所以DISTINCT足够。 但是,这种方法只能找到至少连续的情况,而无法统计更长的连续天数,但用户的问题只需要至少三,所以这已经足够。 比较这两种方法,LAG函数的版本可能在数据量大的情况下更高效,因为它不需要分组统计,只需要检查相邻两行的日期差。而使用分组键的方法可能更灵活,尤其是在需要统计更长的连续天数时。 但需要注意,LAG方法需要确保每个用户至少有三次登录记录,否则prev2可能为NULL,导致条件不满足。所以,在用户只有三次登录连续的情况下,这种方法可以正确识别,但如果用户有四次登录,其中中间有三连续,也会被正确识别。 例如,用户登录日期为1、2、3、5号,那么第三3号)的prev1是2号,prev2是1号,满足条件,用户会被选中。而第五的prev1是3号,差两,不满足条件。所以这种方法有效。 总结来说,两种方法都可以解决问题,但可能根据具体数据库和索引情况有不同的性能表现。如果登录表很大,使用窗口函数可能需要较多的计算资源,而分组方法可能更高效。 现在,我需要验证这两种方法的正确性。 对于第一种方法(分组键): 假设用户1的登录日期为2023-10-01, 02, 03, 04: 行号rn分别为1,2,3,4。grp为login_date - rn: 01 -1 = 2023-09-30 02 -2 = 2023-09-30 03 -3 = 2023-09-30 04 -4 = 2023-09-30 所以grp相同,统计为4,满足条件。 对于用户2的登录日期为01,03: rn为1,2。grp为01-1=2023-09-30,03-2=2023-10-01。分组后每个grp的计数为1,不满足条件。 正确。 对于第二种方法(LAG): 用户1的日期为01,02,03,04: 在第三行(03号),prev1=02,prev2=01,满足DATEDIFF(03-02)=1和02-01=1,所以满足条件。 在第四行(04号),prev1=03,prev2=02,同样满足,所以用户会被选中,但DISTINCT user_id只需一次。 用户2的日期01和03: 第三行不存在,所以不会被选中。 用户3的日期01,02,04: 在第三行(04号),prev1=02(相差2),不满足,所以不被选中。 因此,这两种方法都是正确的。 现在,我需要考虑不同数据库的语法差异。例如,DATEDIFF在MySQL中是DATEDIFF(end, start),返回天数差,而其他数据库如SQL Server也是类似,但PostgreSQL可能需要使用减法得到间隔,然后提取天数。 例如,在PostgreSQL中,可以写: (login_date - prev1) = 1 -- 假设结果为天数差 但实际在PostgreSQL中,日期相减得到的是整数天数,所以可以直接比较。 另外,使用LAG函数需要注意窗口函数的支持情况,较旧的数据库版本可能不支持,但现在主流数据库如MySQL 8.0+、PostgreSQLSQL Server等都支持窗口函数。 最后,根据用户的问题,给出一个通用的SQL查询示例,可能使用LAG函数的方法更为直观。 综上所述,最终的SQL查询可能如下(以MySQL为例): WITH user_login AS ( SELECT DISTINCT user_id, login_date FROM login_log ), lagged_logins AS ( SELECT user_id, login_date, LAG(login_date, 1) OVER (PARTITION BY user_id ORDER BY login_date) AS prev_login1, LAG(login_date, 2) OVER (PARTITION BY user_id ORDER BY login_date) AS prev_login2 FROM user_login ) SELECT DISTINCT user_id FROM lagged_logins WHERE DATEDIFF(login_date, prev_login1) = 1 AND DATEDIFF(prev_login1, prev_login2) = 1; 或者使用分组键的方法: WITH user_login AS ( SELECT DISTINCT user_id, login_date FROM login_log ), with_rn AS ( SELECT user_id, login_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn FROM user_login ), grouped AS ( SELECT user_id, DATE_SUB(login_date, INTERVAL rn DAY) AS grp FROM with_rn ) SELECT user_id FROM grouped GROUP BY user_id, grp HAVING COUNT(*) >= 3; 两种方法都可以,具体取决于实际情况。 可能的优化包括在login_date和user_id上创建索引,以加快去重和排序的速度。 此外,还需要考虑用户可能在多个不同的连续时间段内满足条件,但问题只需要知道至少有一次连续的情况,因此DISTINCT user_id即可。 最后,用户可能还需要考虑跨月或跨年的情况,例如12月31日、1月1日、1月2日,但日期差计算应该能正确处理这些情况。</think>要查询连续登录用户,可以通过分析登录日期的连续性来实现。以下是分步解决方案: ### 方法一:使用窗口函数`LAG()` **思路**:检查每个用户每次登录的前两次记录,判断是否连续。 ```sql WITH user_login AS ( -- 去重,确保每个用户只算一次 SELECT DISTINCT user_id, login_date FROM login_log ), lagged_logins AS ( SELECT user_id, login_date, LAG(login_date, 1) OVER (PARTITION BY user_id ORDER BY login_date) AS prev1, LAG(login_date, 2) OVER (PARTITION BY user_id ORDER BY login_date) AS prev2 FROM user_login ) -- 筛选出连续登录用户 SELECT DISTINCT user_id FROM lagged_logins WHERE DATEDIFF(login_date, prev1) = 1 -- 当前日期与前一日相差1 AND DATEDIFF(prev1, prev2) = 1; -- 前一日与前两日相差1 ``` ### 方法二:日期分组法 **思路**:通过日期与行号的差值生成分组标识,连续日期的差值相同。 ```sql WITH user_login AS ( SELECT DISTINCT user_id, login_date FROM login_log ), with_rn AS ( SELECT user_id, login_date, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) AS rn FROM user_login ) -- 按差值分组,统计连续天数 SELECT user_id FROM with_rn GROUP BY user_id, DATE_SUB(login_date, INTERVAL rn DAY) HAVING COUNT(*) >= 3; -- 连续天数3 ``` ### 关键点说明 1. **去重处理**:使用`DISTINCT`或`GROUP BY`确保每只计一次登录。 2. **窗口函数**: - `LAG(login_date, N)`获取前N次的登录日期。 - `ROW_NUMBER()`生成按日期排序的行号。 3. **日期计算**: - 方法一中,通过两次`LAG`直接比较相邻日期差。 - 方法二中,利用`日期 - 行号`生成分组标识,连续日期标识相同。 ### 示例数据验证 假设数据如下: | user_id | login_date | |---------|------------| | 1 | 2023-10-01 | | 1 | 2023-10-02 | | 1 | 2023-10-03 | | 2 | 2023-10-01 | | 2 | 2023-10-03 | - **用户1**:连续(01、02、03),符合条件。 - **用户2**:日期不连续(01、03),不符合条件。 两种方法均能正确筛选出用户1。 ### 性能建议 - 在`user_id`和`login_date`字段上创建索引,加速去重和排序。 - 方法一适合直接判断连续性,方法二适合统计更长的连续天数(如改为`HAVING COUNT(*) >= N`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值