必须用日期截取函数将created_at转为“年-月-日”格式再GROUP BY,否则按秒分组无法实现按天统计;不同数据库语法不同,需按环境选用;同时注意WHERE位置、NULL处理、时区转换及COUNT(*)与COUNT(字段)语义差异。

GROUP BY 配合日期截取函数才能按天分组
直接 GROUP BY created_at 会按精确到秒的时间戳分组,根本得不到“每天”的统计结果。必须先把时间字段转成“年-月-日”格式再分组。
不同数据库的日期截取函数不一样,别硬背,按实际环境选:
- MySQL:用
DATE(created_at)或DATE_FORMAT(created_at, '%Y-%m-%d') - PostgreSQL:用
created_at::DATE或TO_CHAR(created_at, 'YYYY-MM-DD') - SQL Server:用
CAST(created_at AS DATE)或CONVERT(DATE, created_at) - SQLite:用
DATE(created_at)
写法示例:统计 orders 表每天新增订单数
假设表名是 orders,时间字段是 created_at,最常用也最安全的写法是:
SELECT DATE(created_at) AS day, COUNT(*) AS count FROM orders WHERE created_at >= '2024-01-01' GROUP BY DATE(created_at) ORDER BY day;
注意几点:
-
WHERE条件要放在GROUP BY前,不然会先全表分组再过滤,性能差 -
ORDER BY day很有必要,否则结果顺序不确定 - 如果
created_at允许为NULL,DATE(NULL)结果也是NULL,会被单独分到一组——要排除就加AND created_at IS NOT NULL
时区问题会让“当天”统计出错
数据库服务器时区和业务时区不一致时,DATE(created_at) 返回的是服务器本地日期,可能把北京时间 2024-04-05 00:05 的记录算成 2024-04-04(如果服务器在 UTC+0)。
解决方法不是改服务器时区,而是显式转换:
- MySQL:
DATE(CONVERT_TZ(created_at, '+00:00', '+08:00')) - PostgreSQL:
(created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Shanghai')::DATE - 更稳妥的做法:应用层传入带时区的日期范围(如
'2024-04-05T00:00:00+08:00'),SQL 中用BETWEEN而非依赖DATE()
COUNT(*) 和 COUNT(字段) 的区别影响 NULL 数据
如果想统计“每天有用户ID的订单数”,别用 COUNT(*),得用 COUNT(user_id) —— 后者会自动跳过 user_id IS NULL 的行。
常见误用场景:
- 想看每天“成功支付”的订单量,但支付状态存在
NULL或'pending',这时COUNT(*)会把所有行都算进去 - 正确做法是加条件:
COUNT(CASE WHEN status = 'paid' THEN 1 END),或者先WHERE status = 'paid' -
COUNT(*)永远不忽略任何行,COUNT(字段)忽略该字段为NULL的行,两者语义完全不同
日期截取函数、时区、聚合函数语义这三块,漏掉任意一个都可能让“每天统计”变成“看起来像每天统计”。

















