postgresql/bigquery用date_trunc('month', created_at)最省事,返回可排序索引的日期类型;mysql需str_to_date(date_format())确保日期类型;sql server推荐datefromparts避免字符串拼接。

PostgreSQL / BigQuery 用 DATE_TRUNC 最省事
直接写 DATE_TRUNC('month', created_at) 就能拿到当月第一天(如 '2024-03-15' → '2024-03-01'),结果是 DATE 或 TIMESTAMP 类型,可排序、可索引、可直接用于 GROUP BY。
- 参数必须是字符串字面量:
'month',不能是变量或字段;大小写不敏感,但统一小写更稳妥 - 如果
created_at是字符串(比如'20240315'),得先转日期:TO_DATE(created_at, 'YYYYMMDD') - 别漏掉空值:加
WHERE created_at IS NOT NULL,否则DATE_TRUNC返回NULL,分组会出意外 - 时区注意:若字段是带时区的
TIMESTAMPTZ,DATE_TRUNC按数据库默认时区截断,不是 UTC —— 要对齐业务时区,先用AT TIME ZONE 'Asia/Shanghai'转换
MySQL 用 DATE_FORMAT 但得防类型陷阱
DATE_FORMAT(created_at, '%Y-%m-01') 看起来简洁,但它返回的是字符串,不是日期。这意味着后续做日期范围筛选(比如 WHERE month_start >= '2024-06-01')会隐式转换,可能走不了索引,大数据量时慢得明显。
- 安全写法是套一层:
STR_TO_DATE(DATE_FORMAT(created_at, '%Y-%m-01'), '%Y-%m-%d'),确保类型是DATE - 别用
YEAR(created_at)和MONTH(created_at)分开GROUP BY:虽然能分组,但无法表示“2024-06”这个完整时间点,也不方便和前端时间控件对接 - 如果字段是 Unix 时间戳(INT 类型),先转成日期:
FROM_UNIXTIME(created_at),再格式化 - 跨年周统计慎用
DATE_FORMAT(created_at, '%x-%v'):它依赖系统 locale,且返回字符串,排序错乱风险高;优先用YEARWEEK(created_at, 1)(整数,ISO 周,周一为起点)
SQL Server 2012+ 推荐 DATEFROMPARTS
DATEFROMPARTS(YEAR(created_at), MONTH(created_at), 1) 是最干净的方案,自动处理闰年、不同月份天数等边界,返回标准 DATE 类型,不依赖格式字符串,也不怕毫秒精度干扰。
- 别拼字符串:
CONVERT(VARCHAR, created_at, 120)截前 7 位再补'-01'—— 这是纯文本操作,索引完全失效,且CONVERT格式码受SET LANGUAGE影响,容易在不同会话里崩 - 老版本(DATEADD(MONTH, DATEDIFF(MONTH, 0, created_at), 0),虽可行但可读性差,还容易因负数日期出错
- 如果
created_at是DATETIME2(7),DATEFROMPARTS依然可靠;但CAST(created_at AS DATE)会四舍五入到毫秒级,不是严格“归零”,慎用
跨数据库兼容写法:用 EXTRACT 构造整数键
当同一份 SQL 要跑在 PostgreSQL、MySQL、SQL Server 上,DATE_TRUNC 和 DATE_FORMAT 都不通用,这时退回到标准 SQL 的 EXTRACT 函数组合。
- 按月:用
EXTRACT(YEAR FROM created_at) * 100 + EXTRACT(MONTH FROM created_at),得到202408这样的整数,可排序、可索引、无类型歧义 - 按 ISO 周:需要
EXTRACT(ISOYEAR FROM created_at) * 100 + EXTRACT(ISOWEEK FROM created_at),但注意 MySQL 不支持ISOWEEK,得降级用YEARWEEK(created_at, 1)替代 - 这种写法牺牲一点语义清晰度,但换来稳定性 —— 特别适合报表中间层或数据同步脚本
- 别忘了加
WHERE created_at IS NOT NULL,否则EXTRACT返回NULL,导致整个分组键为空,聚合结果不可靠
实际执行时,最容易被忽略的是字段类型和空值处理——哪怕语法全对,一个隐式类型转换或一条 NULL 记录就可能让月汇总少掉一整列数据。










