DAU需按日期分组并用COUNT(DISTINCT user_id)统计独立用户数,MAU需按年月分组对整月user_id去重;均须正确处理时区与时间范围,避免行为次数误统计。

GROUP BY 统计日活 DAU:按日期分组去重用户
DAU 是单日独立用户数,核心是“当天有多少不同用户”。GROUP BY 本身不负责去重,必须配合 COUNT(DISTINCT user_id)。常见错误是写成 COUNT(user_id),结果统计的是行为次数而非用户数。
- 确保时间字段(如
event_time)能准确提取日期,推荐用DATE(event_time)或数据库对应函数(PostgreSQL 用event_time::DATE,MySQL 用DATE(event_time)) - 如果原始数据含时区,先用
CONVERT_TZ或AT TIME ZONE转为业务所在时区再截取日期 - 示例(MySQL):
SELECT DATE(event_time) AS day, COUNT(DISTINCT user_id) AS dau FROM events WHERE event_time >= '2024-01-01' GROUP BY DATE(event_time) ORDER BY day;
GROUP BY 统计月活 MAU:按年月分组去重用户
MAU 是自然月内任意一天活跃过的独立用户总数,不是每天 DAU 的简单相加。关键在于把整个月的 user_id 拉平后去重,再按月聚合。
- 错误做法:对
MONTH(event_time)和YEAR(event_time)分组后直接COUNT(DISTINCT user_id)—— 这在逻辑上正确,但性能差,且易受跨年、闰月等干扰 - 推荐做法:先用
DATE_FORMAT(event_time, '%Y-%m')(MySQL)或TO_CHAR(event_time, 'YYYY-MM')(PostgreSQL)生成标准年月字符串,再GROUP BY - 注意过滤条件要覆盖整月范围(比如
WHERE event_time >= '2024-01-01' AND event_time < '2024-02-01'),避免漏掉月末最后一秒的数据 - 示例(PostgreSQL):
SELECT TO_CHAR(event_time, 'YYYY-MM') AS month, COUNT(DISTINCT user_id) AS mau FROM events WHERE event_time >= '2024-01-01' AND event_time < '2024-02-01' GROUP BY TO_CHAR(event_time, 'YYYY-MM');
DAU/MAU 同时查:别在一个 GROUP BY 里硬凑
有人想用一个查询同时输出每日 DAU 和当月 MAU,于是尝试 GROUP BY DATE(event_time), EXTRACT(YEAR FROM event_time), EXTRACT(MONTH FROM event_time) —— 这会导致 MAU 被拆到每一天,失去意义。
- 正确思路是分开计算再关联:用子查询或 CTE 先算出每月 MAU 表,再与 DAU 表
LEFT JOIN,关联条件是 DAU 的日期落在该月范围内 - 更稳妥的做法是应用层拼接,SQL 层只做单一维度聚合,避免复杂嵌套带来的可读性与维护成本上升
- 如果非要在 SQL 中合并,用窗口函数更可靠,例如:
SELECT DATE(event_time) AS day, COUNT(DISTINCT user_id) AS dau, COUNT(DISTINCT user_id) OVER ( PARTITION BY DATE_TRUNC('month', event_time) ) AS mau_of_month FROM events WHERE event_time >= '2024-01-01' AND event_time < '2024-02-01' GROUP BY DATE(event_time);
性能和精度陷阱:DISTINCT 在大数据量下很吃力
COUNT(DISTINCT) 在千万级以上用户表中容易慢,尤其当 user_id 是字符串或未建索引时。
- 确保
user_id字段有索引(至少是联合索引的前导列),日期字段也建议参与索引,如(event_time, user_id) - 对超大表,考虑预聚合:每天凌晨跑一个任务,把当日 DAU 写入汇总表,MAU 则基于每日 DAU 表重新去重计算,而不是每次都扫原始日志
- 如果业务允许近似值,可用 HyperLogLog 类算法(如 PostgreSQL 的
HLL扩展、MySQL 的APPROX_COUNT_DISTINCT),误差率通常 <1%,但速度提升明显
真实场景中,DAU/MAU 的口径定义比写法更重要:是否包含测试账号?是否过滤机器人流量?是否以登录态为准还是设备 ID?这些逻辑一旦定错,GROUP BY 再准也没用。

















