MySQL中YEARWEEK(date, 1)按ISO周分组可避免跨年错位,推荐写法为SELECT YEARWEEK(created_at, 1) AS week_id, COUNT(*) FROM orders GROUP BY week_id;展示格式可用CONCAT(YEAR(created_at), '-W', LPAD(WEEK(created_at, 1), 2, '0'))。

MySQL里用YEARWEEK()按周分组,但要注意周起始日
MySQL默认把周一起始日设为周一,但YEARWEEK(date, mode)的mode参数决定怎么算——比如mode=1表示周一为每周第一天、周日为最后一天,且第一周必须包含4天以上才叫第1周;mode=0则按周日开始、且第一周从1月1日算起。实际写SQL时漏掉mode容易导致跨年周错位,比如2024-12-30(周一)可能被算进2025年第1周而不是2024年第53周。
推荐写法:
SELECT YEARWEEK(created_at, 1) AS week_id, COUNT(*) FROM orders GROUP BY week_id;如果要展示成“2024-W01”格式,可以拼接:
CONCAT(YEAR(created_at), '-W', LPAD(WEEK(created_at, 1), 2, '0'))
PostgreSQL用DATE_TRUNC('week')更直观,但得配timezone
PostgreSQL的DATE_TRUNC('week', created_at)默认以周日为一周起点,返回当周周日零点时间戳;想改成周一开头,得加AT TIME ZONE偏移或用EXTRACT(ISODOW FROM ...)手动调整。常见坑是:数据库时区和应用时区不一致时,DATE_TRUNC结果会偏移一天——比如服务器在UTC+8,但会话timezone设成UTC,那同一条记录分组结果就不同。
安全做法是显式声明时区:
SELECT DATE_TRUNC('week', created_at AT TIME ZONE 'Asia/Shanghai')::date AS week_start, COUNT(*) FROM orders GROUP BY week_start;注意::date转成日期去掉时间部分,否则分组键带时间精度可能重复。
SQL Server里DATEPART(week, ...)不能单独用,必须搭配YEAR
DATEPART(week, created_at)只返回1~53的数字,跨年时第1周会和上一年的第53周冲突,直接GROUP BY DATEPART(week, ...)会导致数据混在一起。必须和YEAR(created_at)组合使用,或者用DATEFROMPARTS(YEAR(...), DATEPART(week, ...), 1)构造周一日期再截断。
稳妥写法:
SELECT YEAR(created_at) AS y, DATEPART(week, created_at) AS w, COUNT(*) FROM orders GROUP BY YEAR(created_at), DATEPART(week, created_at);如果想按ISO标准(周一为始、第1周含4个以上周一),得用
DATEPART(isowk, ...)并同样配合YEAR——因为ISO年份可能和日历年份不同,比如2024-12-30属于ISO 2025年第1周。
按月分组看似简单,DATE_FORMAT()和TO_CHAR()的陷阱在边界日期
MySQL用DATE_FORMAT(created_at, '%Y-%m')没问题,但PostgreSQL的TO_CHAR(created_at, 'YYYY-MM')在时区处理上容易出错:如果created_at是timestamptz类型,TO_CHAR按当前会话时区转换,可能把UTC时间2024-03-01T15:00:00Z转成'2024-03'(北京时间),而同一时刻在纽约就是'2024-02'。SQL Server的FORMAT(created_at, 'yyyy-MM')也有类似问题,且性能较差。
更可靠的方式是用日期截断:
-- MySQL<br>DATE_SUB(created_at, INTERVAL DAYOFMONTH(created_at)-1 DAY)<br>-- PostgreSQL<br>DATE_TRUNC('month', created_at)<br>-- SQL Server<br>DATEFROMPARTS(YEAR(created_at), MONTH(created_at), 1)这样不管时区怎么变,逻辑上都是取当月1日作为分组键,避免因时区漂移导致同一天被分到不同月份。
跨数据库兼容写法难,优先选明确语义的日期截断逻辑
没有一行SQL能通吃所有数据库的周/月分组,尤其涉及ISO周、时区、起始日定义时。最易忽略的是:业务要求的“本周”往往指自然周(周一到周日),但数据库默认可能按日历年或本地时区算;导出报表时若没统一时区,前端看到的“2024-03”可能对应数据库里两个不同物理月份的数据。
建议在应用层做一次标准化:入库时统一存UTC时间,查询时用目标时区做DATE_TRUNC或YEARWEEK;或者干脆在ETL阶段预计算好year_month、iso_year_week字段,避免每次查都实时计算。


















