按天统计每日活跃用户数需先用日期截取函数(如date、to_date等)将created_at归为日粒度,再group by该日期字段,并用count(distinct user_id)去重计数;where过滤行为类型(如event_type='login')须在group by前,时间范围推荐闭开区间。

GROUP BY 配合日期截取函数才能按天分组
直接 GROUP BY user_id 或 GROUP BY created_at 都得不到“每日活跃用户数”。关键是要先把时间字段归到“日”粒度,再按日分组计数。不同数据库的日期截取函数不同:DATE(created_at)(MySQL、PostgreSQL)、TO_DATE(created_at)(Oracle)、CAST(created_at AS DATE)(SQL Server、Snowflake)。别用 strftime('%Y-%m-%d', created_at)(SQLite)以外的方式硬拼字符串——容易漏掉时区或格式不一致问题。
COUNT(DISTINCT user_id) 是核心,不是 COUNT(*)
活跃用户强调“去重”,同一天多个行为只算 1 人。如果写成 COUNT(*),会统计行为次数而非人数;如果漏掉 DISTINCT,重复登录或多次点击会导致用户被重复计数。注意:部分旧版 MySQL(5.6 及之前)对 COUNT(DISTINCT) 在大表上性能较差,可考虑先用子查询去重再聚合。
- 正确写法:
COUNT(DISTINCT user_id) - 错误写法:
COUNT(user_id)(NULL 值会被忽略,且不去重) - 危险写法:
COUNT(*)(统计行数,不是人数)
WHERE 条件必须放在 GROUP BY 之前
想统计“登录行为”的活跃用户?得先过滤行为类型,而不是在 HAVING 里筛。比如筛选 event_type = 'login',必须写在 WHERE 子句;若误写进 HAVING,会先按日分组再过滤,逻辑错乱且无法利用索引。另外,时间范围建议用闭开区间:WHERE created_at >= '2024-01-01' AND created_at ,避免因秒级精度导致边界数据遗漏。
时区处理不当会让数据少算或跨天
服务器时间、数据库时区、业务时区三者不一致时,DATE(created_at) 可能把凌晨 00:05 的行为算进前一天。最稳妥做法是统一转换为业务时区再截取日期,例如 PostgreSQL:DATE(created_at AT TIME ZONE 'Asia/Shanghai');MySQL 8.0+:DATE(CONVERT_TZ(created_at, '+00:00', '+08:00'))。别依赖数据库默认时区,尤其当应用部署在海外节点时。
实际跑出来的结果里,某天数值突然归零或跳变,八成是时区或时间字段类型(如存的是字符串)惹的祸。











