1141. 查询近30天活跃用户数

难度:简单 | 题号:1141 | 链接:1141. 查询近30天活跃用户数 - 力扣(LeetCode) | 数据结构:SQL

关联:订单最多的用户(同为「按维度分组计数」)· 每月交易 I(同为日期函数 + 分组聚合)· 可对照 SQL原理_02(日期函数 / 分组聚合)。

🎯 问题描述

描述:写一条 SQL 查询,求出截至 2019-07-27(含)的近 30 天内,每天的活跃用户数。活跃用户指当天至少进行一次活动的用户,返回结果表的列名须为 day 和 active_users,且每天都可能出现重复用户(同一用户一天多次活动只算一次)。

表:Activity

1
2
3
4
5
6
7
8
9
10
11
+---------------+---------+
| Column Name | Type |
+---------------+---------+
| user_id | int |
| session_id | int |
| activity_date | date |
| activity_type | enum |
+---------------+---------+
该表没有主键,它可能有重复行。
activity_type 列是 ENUM ['open_session', 'end_session', 'scroll_down', 'send_message'] 之一。
该表包含用户 ID、会话 ID、活动日期与活动类型。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
输入:
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 |
+---------+------------+---------------+---------------+
输出:
+------------+--------------+
| day | active_users |
+------------+--------------+
| 2019-07-20 | 2 |
| 2019-07-21 | 2 |
+------------+--------------+
解释:
- 2019-06-25 不在近 30 天窗口内(早于 2019-06-28),被排除。
- 2019-07-20 有用户 1、2 活动 → 2 个活跃用户。
- 2019-07-21 有用户 2、3 活动 → 2 个活跃用户。

提示:

  • 窗口是「左闭右开」的滑窗:activity_date 落在 [2019-06-28, 2019-07-27] 之间,即 DATEDIFF('2019-07-27', activity_date) < 30(等价于 <= 29)。
  • 同一天同一用户多次活动只能计一次 → 必须 COUNT(DISTINCT user_id)。
  • 结果按天分组,列名严格为 day / active_users(active_users 是保留歧义最小的别名,建议加反引号或双引号)。

💻 解题思路

思路1:WHERE 筛日期 + 按天分组去重计数(用户解法)

核心逻辑

三步走:① WHERE 用 DATEDIFF 把数据裁到近 30 天窗口;② GROUP BY activity_date 按天分组;③ COUNT(DISTINCT user_id) 数出当天不重复的活跃用户数,并把日期列取别名为 day。

代码实现

1
2
3
4
5
6
7
# Write your MySQL query statement below
SELECT activity_date AS day,
COUNT(DISTINCT user_id) AS active_users
FROM Activity
WHERE activity_date <= '2019-07-27'
AND DATEDIFF('2019-07-27', activity_date) < 30
GROUP BY activity_date;

💡 精简写法:BETWEEN '2019-06-28' AND '2019-07-27' 更直观(右边界含 27 号,左边界 28 号正好 30 天);用 DATEDIFF 的好处是无需手算起始日期。

📊 性能分析

  • 时间复杂度:O(n)O(n)(扫描全表 + 分组聚合;activity_date 上有索引时可先 range scan 裁剪数据量)。
  • 空间复杂度:O(d)O(d),dd 为不同日期数(分组哈希表规模)。

思考

  • 别用 >= 30:DATEDIFF('2019-07-27', '2019-06-27') = 30,已经出界;< 30 或 <= 29 才是「近 30 天」。
  • 本题是滑动窗口计数的模板:先用日期函数把窗口裁出来,再分组去重计数。同类场景:日活 / 周活 / 月活(DAU/WAU/MAU)。
  • 若题目改成「近 30 天总体活跃用户数」,就要去掉 GROUP BY,直接 COUNT(DISTINCT user_id)。

🔗 相关链接

  • 所属 MOC:学习-数据结构与算法MOC
  • 题集索引:LeetCode题集索引
  • 关联概念:SQL原理_02(日期函数 / 分组聚合)
  • 同类题目:每月交易 I · 订单最多的用户

📝 总结

「滑动窗口 + 按维度去重计数」模板:

  • 用 DATEDIFF(ref_date, date_col) < N 或 BETWEEN ... AND ... 裁出窗口 → GROUP BY 日期 → COUNT(DISTINCT 用户)。
  • 两个易错点:① 窗口边界左闭右开(< 30 不是 <= 30);② 同一用户同一天多次活动必须 DISTINCT。

🔗 题集索引:LeetCode题集索引 · 算法地图:学习-数据结构与算法MOC
📎 同类题:每月交易 I · 订单最多的用户 | 概念:SQL原理_02