首选DATE_TRUNC('month', order_date)(PostgreSQL/BigQuery);MySQL用DATE_FORMAT(order_date, '%Y-%m-01');SQL Server 2012+用DATEFROMPARTS(YEAR(order_date), MONTH(order_date), 1);均需注意空值、时区及索引失效问题。

直接用 DATE_TRUNC 最省事,但得看数据库类型——PostgreSQL、BigQuery 支持;MySQL 和 SQL Server 得绕路。
PostgreSQL / BigQuery 用 DATE_TRUNC 按月聚合
这两个系统原生支持 DATE_TRUNC,语法统一、语义清晰,是首选方案。
-
DATE_TRUNC('month', order_date)会把任意日期(如'2024-03-15')截断为当月第一天('2024-03-01'),可直接用于GROUP BY - 注意参数必须是字符串字面量:
'month',不能是变量或列;大小写不敏感,但写小写更稳妥 - 如果字段是字符串类型(如
'20240315'),先用TO_DATE(order_date, 'YYYYMMDD')转成日期再截断
SELECT DATE_TRUNC('month', order_date) AS month_start,
COUNT(*) AS order_count
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month_start;
MySQL 用 DATE_FORMAT 或 YEAR/MONTH 组合
MySQL 没有 DATE_TRUNC,但 DATE_FORMAT 能达到等效效果,且结果仍是日期类型(利于排序和后续计算)。
-
DATE_FORMAT(order_date, '%Y-%m-01')返回字符串,但隐式转日期时行为稳定;若需确保类型安全,套一层STR_TO_DATE(DATE_FORMAT(...), '%Y-%m-%d') - 更轻量的写法是
MAKEDATE(YEAR(order_date), 1) + INTERVAL (MONTH(order_date)-1) MONTH,但可读性差,还可能在跨年边界出错(比如 12 月加 11 个月) - 避免只用
YEAR(order_date)和MONTH(order_date)分开GROUP BY:虽然能分组,但无法直接表示“2024-03”,也不方便做时间范围筛选
SELECT DATE_FORMAT(order_date, '%Y-%m-01') AS month_start,
SUM(amount) AS total_sales
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m-01')
ORDER BY month_start;
SQL Server 用 DATEFROMPARTS 或 DATEADD+DAY
SQL Server 2012+ 推荐用 DATEFROMPARTS,干净利落;老版本只能靠 DATEADD 手动归零。
-
DATEFROMPARTS(YEAR(order_date), MONTH(order_date), 1)安全可靠,自动处理闰年、月末天数等边界 - 别用
CONVERT(VARCHAR, order_date, 120)截取前 7 位再拼接,那是字符串操作,没法走索引,也容易因格式设置失败 - 如果表里
order_date是DATETIME2且精度高,DATEFROMPARTS仍能正确提取年月,无需担心毫秒干扰
SELECT DATEFROMPARTS(YEAR(order_date), MONTH(order_date), 1) AS month_start,
COUNT(*) AS order_count
FROM orders
GROUP BY DATEFROMPARTS(YEAR(order_date), MONTH(order_date), 1)
ORDER BY month_start;
通用陷阱:时区、空值和索引失效
不管用哪种函数,只要对日期字段做了计算,就大概率让原有索引失效——这是最容易被忽略的性能坑。
- 如果查询频繁且数据量大,建议额外增加一个生成列(如 PostgreSQL 的
GENERATED ALWAYS AS (DATE_TRUNC('month', order_date)) STORED)并建索引 -
order_date为空时,DATE_TRUNC等函数返回NULL,GROUP BY NULL会单独成一组,需确认是否符合业务预期(通常要加WHERE order_date IS NOT NULL过滤) - 跨时区场景下,
order_date存的是 UTC 时间,但业务要求按本地月份汇总?那就得先用AT TIME ZONE(SQL Server/PostgreSQL)或CONVERT_TZ(MySQL)转换,再截断

















