统计日活必须用DATE()提取日期再GROUP BY,否则毫秒级差异导致分组错误;需用COUNT(DISTINCT user_id)去重,不可用COUNT(*);月活应按自然月切分,避免用MONTH()等粗粒度函数。

GROUP BY 统计日活必须用 DATE() 提取日期,别直接 GROUP BY 时间戳
时间戳字段(如 created_at)如果直接 GROUP BY created_at,每条记录毫秒级差异都会被拆成独立分组,完全统计不出“某天有多少用户”。必须先归一化到日粒度。
MySQL 中用 DATE(created_at);PostgreSQL 用 created_at::date;SQLite 用 DATE(created_at)。注意时区:若数据存的是 UTC,而业务要求按本地日活统计,得先转换时区再截日期,比如 MySQL:DATE(CONVERT_TZ(created_at, '+00:00', '+08:00'))。
常见错误现象:COUNT(*) 数值远大于预期,或每天只有一两条记录——大概率是没做日期归一化,或用了 YEAR(created_at) 这类粗粒度函数。
去重统计用户数必须用 COUNT(DISTINCT user_id),不能 COUNT(*)
日活(DAU)定义是“当天至少启动/登录/产生行为的独立用户数”,本质是去重计数。用 COUNT(*) 统计的是行为次数,不是用户数。
实操建议:
- 确保
user_id字段非空且有索引,否则COUNT(DISTINCT ...)在大数据量下性能急剧下降 - MySQL 5.7+ 默认使用临时表 + 文件排序处理 DISTINCT,若内存不足会写磁盘,可临时调大
sort_buffer_size或改用近似算法(如 HyperLogLog,但需业务接受误差) - PostgreSQL 可用
COUNT(DISTINCT user_id) FILTER (WHERE event_type = 'login')精确限定行为类型,避免把埋点上报、心跳等无效行为混入
月活(MAU)不能简单套用 MONTH() 函数,要按自然月切分
MONTH(created_at) 只返回 1–12,无法区分 2023-01 和 2024-01;YEAR(created_at)*100 + MONTH(created_at) 虽能生成唯一标识,但不支持范围查询,也不符合“自然月”语义(比如 2024-02-01 至 2024-02-29)。
更稳妥的做法是构造月初和月末边界:
MySQL 示例:
SELECT DATE_FORMAT(created_at, '%Y-%m') AS ym, COUNT(DISTINCT user_id) AS mau FROM events WHERE created_at >= '2024-02-01' AND created_at < '2024-03-01' GROUP BY ym;
关键点:
- WHERE 条件必须显式限定自然月范围,否则 GROUP BY 会跨月聚合(比如把 1 月 31 日和 2 月 1 日都归到 ‘2024-01’)
- 用
而非 <code>,避免闰年或月末天数不一致问题 - 若需滚动 MAU(如最近 30 天),WHERE 改为
created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY),但注意这和自然月 MAU 是两个指标,别混用
一次查出日活+月活需要窗口函数或子查询,别硬塞进一个 GROUP BY
想在同一结果里看到某天的 DAU 和它所属月份的 MAU,不能靠单层 GROUP BY DATE(created_at) 实现——因为 MAU 需要跨多天聚合,维度不一致。
两种可行路径:
- 用子查询:主查询按天算 DAU,外层 JOIN 一个按月预计算的 MAU 表(
SELECT ym, COUNT(DISTINCT user_id) AS mau FROM ... GROUP BY ym),ON 条件为DATE_FORMAT(dau_day, '%Y-%m') = mau.ym - 用窗口函数(MySQL 8.0+/PostgreSQL):
COUNT(DISTINCT user_id) OVER (PARTITION BY DATE_FORMAT(created_at, '%Y-%m')),但注意窗口函数不能直接套在聚合函数里,得先用 CTE 或派生表展开用户-日期对
最容易被忽略的点:MAU 值在当月内每天重复出现,但它的计算基准是整个月的数据。如果 WHERE 条件只写了 created_at = '2024-02-15',那子查询里的 MAU 就只能基于这一天的数据——结果永远是 DAU 本身。必须让 MAU 子查询的 WHERE 覆盖整个月份,且与外层日期解耦。

















