按月分组时,where中禁用date_format(created_at, '%y-%m'),否则索引失效;应改用范围查询,如created_at >= '2026-05-01' and created_at
直接用
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_atDATE(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 写入,但报表要按本地时区展示,必须显式转换时区后再截断——否则零点附近的数据会滑到错误日期里。











