最稳妥方式是group by year(date_col), month(date_col),语义清晰、跨库兼容、避免字符串排序错误和索引失效;切忌仅用month()导致跨年合并,或用date_format/to_char引发时区与性能问题。

GROUP BY 日期转月份:用 YEAR() 和 MONTH() 拆解最稳妥
直接对 DATE 或 DATETIME 字段 GROUP BY 会按日粒度分组,无法实现“按月归档”。必须先提取年份和月份组合成逻辑上的“月标识”。YEAR(date_col) 和 MONTH(date_col) 是跨数据库兼容性最好的方式,MySQL、PostgreSQL(需配合 EXTRACT)、SQL Server 都支持,且语义清晰、无时区陷阱。
常见错误是只写 GROUP BY MONTH(date_col) —— 这会导致 2023-01 和 2024-01 被合并到同一组。必须同时带上年份:
SELECT YEAR(order_date) AS y, MONTH(order_date) AS m, COUNT(*) FROM orders GROUP BY YEAR(order_date), MONTH(order_date) ORDER BY y, m;
- 别用
DATE_FORMAT(order_date, '%Y-%m')(MySQL)或TO_CHAR(order_date, 'YYYY-MM')(PostgreSQL)做分组键——字符串比较在边界场景(如排序、索引利用)可能出人意料 - 如果字段是
TIMESTAMP WITH TIME ZONE,先确认时区是否已按业务要求标准化,否则YEAR()可能因时区偏移错判月份
GROUP BY 季度:优先用 QUARTER(),别手动除法
QUARTER(date_col) 是 MySQL、SQL Server 原生函数,PostgreSQL 用 EXTRACT(QUARTER FROM date_col)。它比 FLOOR((MONTH(date_col) - 1) / 3) + 1 更可靠——后者在某些方言里会因整数除法规则出错,且可读性差。
季度归档通常还需年份维度,否则 Q1 会混掉所有年份:
SELECT YEAR(sale_date) AS y, QUARTER(sale_date) AS q, SUM(amount) FROM sales GROUP BY YEAR(sale_date), QUARTER(sale_date) ORDER BY y, q;
- 注意:
QUARTER('2023-12-31')返回 4,QUARTER('2024-01-01')返回 1 —— 完全按日历季度切分,不考虑财年 - 若需财年(如财年从4月开始),必须用
CASE WHEN MONTH(date_col) IN (4,5,6,7,8,9,10,11,12) THEN YEAR(date_col) ELSE YEAR(date_col)-1 END手动计算财年,再配合月份映射季度
避免隐式类型转换导致的 GROUP BY 失效
当 date_col 实际是字符串类型(如 '2023-01-15'),直接套用 YEAR() 会报错或返回 NULL(取决于数据库严格模式)。此时必须先显式转为日期类型:
-- MySQL 示例
SELECT YEAR(STR_TO_DATE(date_str, '%Y-%m-%d')) AS y, COUNT(*)
FROM logs
WHERE date_str REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' -- 先过滤非法格式
GROUP BY YEAR(STR_TO_DATE(date_str, '%Y-%m-%d'));
- PostgreSQL 必须用
::DATE或TO_DATE(date_str, 'YYYY-MM-DD'),CAST(date_str AS DATE)在遇到空字符串时会炸 - 哪怕表结构定义为
DATE,ETL 导入时若字段含空格或隐藏字符(如'2023-01-15 '),YEAR()也可能静默失败——建议加TRIM()防御
性能关键:给日期字段建函数索引前先确认数据库支持
对 YEAR(create_time) 或 QUARTER(create_time) 分组时,若数据量大,全表扫描很慢。但并非所有数据库都支持函数索引:
- MySQL 8.0+ 支持:需先创建生成列
ALTER TABLE events ADD COLUMN ym INT AS (YEAR(create_time)*100 + MONTH(create_time)) STORED,再对ym建索引 - PostgreSQL 支持表达式索引:
CREATE INDEX idx_events_ym ON events ((EXTRACT(YEAR FROM create_time)*100 + EXTRACT(MONTH FROM create_time))); - SQL Server 不支持函数索引,只能靠
DATEPART(YEAR, dt)+DATEPART(MONTH, dt)的组合索引,且查询条件必须严格匹配该组合
没索引时,GROUP BY 本身不会触发索引下推,最终执行计划大概率走 Seq Scan —— 这点容易被忽略,尤其在测试小表时看不出瓶颈。











