查询近30天活跃用户数

题目描述:

活动纪录表:Activity

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| user_id       | int     |
| session_id    | int     |
| activity_date | date    |
| activity_type | enum    |
+---------------+---------+
该表是用户在社交网站的活动记录。
该表没有主键,可能包含重复数据。
activity_type 字段为以下四种值 ('open_session', 'end_session', 'scroll_down', 'send_message')。
每个 session_id 只属于一个用户。
 

请写SQL查询出截至 2019-07-27(包含2019-07-27),近 30天的每日活跃用户(当天只要有一条活跃记录,即为活跃用户),

查询结果示例如下:

Activity table:
+---------+------------+---------------+---------------+
| user_id | session_id | activity_date | activity_type |
+---------+------------+---------------+---------------+
| 1       | 1          | 2019-07-20    | open_session  |
| 1       | 1          | 2019-07-20    | scroll_down   |
| 1       | 1          | 2019-07-20    | end_session   |
| 2       | 4          | 2019-07-20    | open_session  |
| 2       | 4          | 2019-07-21    | send_message  |
| 2       | 4          | 2019-07-21    | end_session   |
| 3       | 2          | 2019-07-21    | open_session  |
| 3       | 2          | 2019-07-21    | send_message  |
| 3       | 2          | 2019-07-21    | end_session   |
| 4       | 3          | 2019-06-25    | open_session  |
| 4       | 3          | 2019-06-25    | end_session   |
+---------+------------+---------------+---------------+

Result table:
+------------+--------------+ 
| day        | active_users |
+------------+--------------+ 
| 2019-07-20 | 2            |
| 2019-07-21 | 2            |
+------------+--------------+ 
非活跃用户的记录不需要展示。

解题思路:

条件,一个月内,按照日期分组

SELECT activity_date 'day' , COUNT(DISTINCT user_id) activity_user
FROM Activity
WHERE activity_date BETWEEN '2019-06-28' AND '2019-07-27'
GROUP BY activity_date

 

<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 '1 day' AS grp 或者更简单的方式是将日期转换为数,然后减去rn。例如: EXTRACT(DAY FROM login_date) - rn 或者类似的函数,但这样可能有问题,因为跨月份或年份的情况。更好的方法是使用日期直接减去行号的数间隔。 另外,需要注意的是,不同的数据库对日期运算的支持可能不同,比如MySQL的DATE_SUB,PostgreSQL的login_date - rn * INTERVAL '1 day',而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+、PostgreSQL、SQL 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、付费专栏及课程。

余额充值