MySQL中YEARWEEK()支持跨年周分组(默认周日起点,参数1可设周一),DATE_FORMAT(dt,'%Y-%m')安全按月聚合;PostgreSQL用date_trunc('month'/ 'week',dt)更可靠,ISO周需同时提取ISOYEAR和ISOWEEK。

MySQL 中用 YEARWEEK() 和 DATE_FORMAT() 按周/月分组
MySQL 没有统一的“按自然周”函数,YEARWEEK() 默认以周日为每周起点,且返回的是带年份的整数(如 202405),适合跨年分组;而 DATE_FORMAT(dt, '%Y-%m') 最直接实现按月聚合,但注意它返回字符串,不能直接用于排序或范围查询(需配合 MIN(dt) 或 STR_TO_DATE() 补充)。
常见错误是写 GROUP BY YEAR(dt), MONTH(dt):看似合理,但会把 2023-12 和 2024-01 当作同一组(因 MONTH() 都是 12/1),实际必须带上年份才安全。更稳妥的做法是用 DATE_FORMAT(dt, '%Y-%m') 或 MAKEDATE(YEAR(dt), 1) + INTERVAL (MONTH(dt)-1) MONTH 生成首日锚点。
-
YEARWEEK(dt, 1):参数1表示周一为每周第一天,避免默认周日导致的偏移 - 按月求和时,若需输出“2024-01”格式且保证顺序,建议
ORDER BY DATE_FORMAT(dt, '%Y-%m')而非ORDER BY 1 - 对
TIMESTAMP字段用YEARWEEK()会隐式转为本地时区,若数据跨时区,先用CONVERT_TZ()标准化
PostgreSQL 中用 date_trunc() 处理周和月
PostgreSQL 的 date_trunc('month', dt) 返回当月第一天的 TIMESTAMP,天然支持范围查询和索引下推;date_trunc('week', dt) 默认按周一截断,且结果是标准时间类型,比字符串拼接更可靠。
容易忽略的是:PostgreSQL 的“week”从周一开始,但 ISO 周(EXTRACT(ISOWEEK FROM dt))和年份需搭配 EXTRACT(ISOYEAR FROM dt) 使用,否则 2024-01-01(周一)可能被归入 2023 年第 53 周。
- 按月聚合推荐:
GROUP BY date_trunc('month', dt),再用to_char(date_trunc('month', dt), 'YYYY-MM')格式化输出 - 按 ISO 周聚合必须同时用
EXTRACT(ISOYEAR FROM dt)和EXTRACT(ISOWEEK FROM dt),缺一不可 -
date_trunc('week', dt)返回值可直接参与BETWEEN查询,比如查某周内数据:dt >= date_trunc('week', '2024-02-05'::date) AND dt
SQL Server 中用 DATEPART() 和 DATEFROMPARTS() 构建分组键
SQL Server 的 DATEPART(week, dt) 返回的是年内第几周,不带年份,单独使用会导致 2023-W52 和 2024-W01 混在一起;必须组合 DATEPART(year, dt) 才能唯一标识。
更干净的做法是用 DATEFROMPARTS(YEAR(dt), MONTH(dt), 1) 生成每月首日,或用 DATEADD(day, 1 - DATEPART(weekday, dt), dt) 算出本周周一(注意 DATEFIRST 设置影响 weekday 含义)。
- 设置
SET DATEFIRST 1可确保DATEPART(weekday, dt)中周一=1,避免环境差异导致逻辑错乱 - 用
DATEFROMPARTS()构建月度键比字符串拼接快,且能走索引(如果字段上有计算列索引) - 若表数据量大,避免在
WHERE中对时间字段用函数(如YEAR(dt)=2024),改用范围条件:dt >= '2024-01-01' AND dt
通用陷阱:时区、空值与边界日期
所有数据库中,时间字段为 NULL 时,GROUP BY 会将其单独归为一组,常被误认为“汇总缺失”,实际应加 WHERE dt IS NOT NULL 显式过滤。
另一个高频问题是“最后一周跨年”或“某月最后一天凌晨入库的数据被算进下月”——本质是没区分业务日期(business_date)和入库时间(created_at)。务必确认分组依据的是哪个字段。
- 跨时区场景下,不要依赖数据库服务器本地时区做截断,统一转成 UTC 或业务所在时区再计算
- PostgreSQL 的
date_trunc('week', ...)和 MySQL 的YEARWEEK(..., 1)对同一天可能给出不同周编号,别混用做跨库比对 - 如果下游要对接 BI 工具(如 Tableau、Superset),优先输出标准日期类型而非字符串,减少前端解析负担










