应使用 DATE(log_time) 提取日期分组,配合 STR_TO_DATE 处理字符串日期;统计活跃用户须用 COUNT(DISTINCT user_id);日期过滤必须放在 WHERE 而非 HAVING;结果列需显式 AS 别名。

用 DATE() 提取日期做分组,别用 strftime() 或字符串截取
MySQL 里最直接的方式是 DATE(log_time),它把 DATETIME 或 TIMESTAMP 字段转成 YYYY-MM-DD 格式再分组。很多人误用 strftime('%Y-%m-%d', log_time)(SQLite 语法)或 SUBSTRING(log_time, 1, 10),前者在 MySQL 不支持,后者可能因时区/格式不一致导致分组错乱。
-
DATE()自动处理时区和精度,比如'2024-05-20 23:59:59'和'2024-05-21 00:01:00'会被正确归到不同天 - 如果字段是
VARCHAR存的日期(如'2024/05/20 14:30'),先用STR_TO_DATE()转成时间类型,再套DATE(),否则分组会失效 - 索引能用上:给
log_time建普通索引即可,DATE(log_time)在 MySQL 8.0+ 支持函数索引,但日常够用
去重统计用户数必须用 COUNT(DISTINCT user_id)
活跃用户是“每天有多少不同的人”,不是“每天多少条记录”。漏掉 DISTINCT 是最常见错误,尤其当一张表里一个用户一天有多次操作时,结果会严重高估。
- 错误写法:
COUNT(user_id)→ 把同个用户多次登录算作多人 - 正确写法:
COUNT(DISTINCT user_id)→ 同一用户当天只计 1 次 - 注意 NULL:如果
user_id允许为空,COUNT(DISTINCT)会自动忽略 NULL 值,不用额外WHERE user_id IS NOT NULL(但加了更明确) - 性能提醒:大表上
COUNT(DISTINCT)比普通COUNT开销大,若数据量超千万,考虑加覆盖索引,比如(log_time, user_id)
WHERE 条件要写在 GROUP BY 前,别放 HAVING 里筛日期
想查最近 7 天?必须用 WHERE log_time >= '2024-05-14' 过滤原始行,而不是靠 HAVING DATE(log_time) >= '2024-05-14'。后者会让数据库先按所有日期分组,再丢弃不需要的组,白白浪费计算资源。
- 正确顺序:
SELECT ... FROM table WHERE log_time >= ? GROUP BY DATE(log_time) - 错误习惯:把日期过滤逻辑塞进
HAVING,尤其在没意识到HAVING是对聚合后结果筛选时 - 时区陷阱:如果数据库时区和业务时区不一致(比如 DB 是 UTC,业务是东八区),
WHERE log_time >= '2024-05-14'实际查的是 UTC 时间,需统一转换,例如用CONVERT_TZ(log_time, '+00:00', '+08:00')
结果日期列别用函数别名糊弄,显式 AS date
很多人写 SELECT DATE(log_time), COUNT(DISTINCT user_id) ...,结果列名是 DATE(log_time) 这种表达式,在程序里取值容易出错。尤其 ORM 或导出 CSV 时,列名不可控。
- 必须写成:
SELECT DATE(log_time) AS date, COUNT(DISTINCT user_id) AS active_users - 别依赖数据库默认别名,不同版本 MySQL 对函数列的默认别名规则可能变化
- 如果后续要 JOIN 或子查询引用该列,没别名会导致语法错误或歧义
SELECT DATE(log_time) AS date, COUNT(DISTINCT user_id) AS active_users FROM user_action WHERE log_time >= '2024-05-14' AND log_time < '2024-05-21' GROUP BY DATE(log_time) ORDER BY date;日期范围用左闭右开更安全,避免漏掉最后一秒;
ORDER BY date 让结果按时间正序,方便看趋势。这些细节不显眼,但线上跑着跑着就出问题。









