按月分组时,WHERE中禁用DATE_FORMAT(created_at, '%Y-%m'),否则索引失效;应改用范围查询,如created_at >= '2026-05-01' AND created_at < '2026-06-01',并确保对齐语义、保留类型、适配数据库。

直接用 DATE_FORMAT 或 TRUNC 按日期区间分组,本质是“把时间字段对齐到某个粒度起点”,但错用会丢数据、跨年错乱、索引失效——关键不在函数本身,而在是否对齐语义、是否保留类型、是否适配数据库。
MySQL 里按月分组,别用 DATE_FORMAT(created_at, '%Y-%m') 做 WHERE 条件
这个写法在 GROUP BY 里能跑,但一旦写进 WHERE,比如 WHERE DATE_FORMAT(created_at, '%Y-%m') = '2026-05',索引就完全失效。因为函数作用于字段,数据库无法下推索引查找。
- 正确做法是改用范围查询:
WHERE created_at >= '2026-05-01' AND created_at - 如果必须动态算当月,用:
WHERE created_at >= DATE_FORMAT(NOW(), '%Y-%m-01') AND created_at -
DATE(created_at)比DATE_FORMAT(..., '%Y-%m-%d')更安全——前者返回DATE类型,能走索引;后者是字符串,排序和比较都多一层隐式转换
PostgreSQL 用 date_trunc('month', created_at),不是 TO_CHAR(created_at, 'YYYY-MM')
TO_CHAR 返回文本,分组后你没法对“上个月”做计算,也不能直接和 DATE 字段比较;而 date_trunc('month', created_at) 返回的是 TIMESTAMP(如 '2026-05-01 00:00:00'),天然支持加减、范围查询、索引优化(如果有函数索引)。
- 查某月全部记录:
WHERE created_at >= date_trunc('month', NOW()) AND created_at - 想补全空月份?用
generate_series()构造日期序列,再LEFT JOIN,别依赖GROUP BY自动产出 - 时区敏感:如果
created_at是TIMESTAMP WITH TIME ZONE,date_trunc默认按当前会话时区截断;要按北京时间统计,先转时区:date_trunc('month', created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Shanghai')
Oracle 里 TRUNC(created_at, 'MM') 和 EXTRACT 别混用
TRUNC(created_at, 'MM') 返回一个真实日期值(2026-05-01),可用于后续计算、索引(需建函数索引)、范围筛选;EXTRACT(YEAR FROM created_at) * 100 + EXTRACT(MONTH FROM created_at) 只返回整数(202605),语义断裂——你没法从这个数字反推出“这个月第一天是哪天”,也无法做“+1 month”操作。
- WHERE 中想查本月:
WHERE TRUNC(created_at, 'MM') = TRUNC(SYSDATE, 'MM')—— 能走函数索引,但没建索引时性能差,仍建议改范围写法 - 错误写法:
TRUNC(created_at, 'YYYY')不被标准支持,部分 Oracle 版本报错;要用TRUNC(created_at, 'YEAR') - 按 ISO 周统计,必须用
TRUNC(created_at, 'IW')(周一为起点),别用'WW'(周日为起点,跨年逻辑混乱)
跨数据库兼容的按天分组,优先用 CAST(created_at AS DATE)
MySQL、PostgreSQL、SQL Server、Oracle 都支持 CAST(... AS DATE)(Oracle 12c+,PostgreSQL 8.4+,SQL Server 2008+,MySQL 5.7+),它返回标准 DATE 类型,不拼字符串,不丢失语义,且多数情况下能利用索引(尤其当字段本身是 DATETIME 或 TIMESTAMP 时)。
- MySQL:
CAST(created_at AS DATE)等价于DATE(created_at) - PostgreSQL:
CAST(created_at AS DATE)等价于created_at::date - SQL Server:
CAST(created_at AS DATE)是最简写法,比CONVERT(VARCHAR, ..., 120)快得多 - Oracle:
CAST(created_at AS DATE)有效,但要注意原始字段如果是TIMESTAMP,CAST 会丢毫秒;若需保留精度,用TRUNC(created_at)
真正容易被忽略的点是:所有这些函数都默认按数据库所在服务器时区解释时间值。如果你的数据是 UTC 写入,但报表要按本地时区展示,必须显式转换时区后再截断——否则零点附近的数据会滑到错误日期里。

















