最可靠做法是将日期归一化为当周周一再分组:mysql用date_sub(date_col, interval weekday(date_col) day),postgresql用date_trunc('week', date_col - interval '1 day') + interval '1 day',sql server需结合datefirst设为1及dateadd计算周一。

MySQL 中用 WEEK() 函数按周一为起始周分组会出错
直接用 WEEK(date_col) 默认以周日为起点,且不同模式下行为不一致(比如 WEEK() 模式 0 和 1 对周一/周日的定义相反),容易导致同一周的数据被拆到两个分组里。真正可靠的做法是先将日期归一化到当周周一,再按该日期分组。
-
WEEK(date_col, 1)表示以周一为一周开始,但返回的是“第几周”数值,跨年时可能重复(如 2024-12-30 和 2025-01-01 都可能返回 week 1) - 更稳妥的方式是计算每个日期对应的周一日期:
DATE_SUB(date_col, INTERVAL WEEKDAY(date_col) DAY),其中WEEKDAY()返回 0=周一、6=周日 - 示例:对
2024-03-18(周一),WEEKDAY('2024-03-18')是 0,DATE_SUB(... INTERVAL 0 DAY)得到自身;对2024-03-20(周三),WEEKDAY是 2,减去 2 天得2024-03-18
PostgreSQL 中用 date_trunc('week', ...) 默认按周日截断
PostgreSQL 的 date_trunc('week', date_col) 实际截断到**最近的周日**(不是周一),所以不能直接用。需要手动偏移:先减 1 天转成以周日为基准的日期,截断后再加 1 天,就能得到周一。
- 正确写法:
date_trunc('week', date_col - INTERVAL '1 day') + INTERVAL '1 day' - 原理:假设
date_col = '2024-03-18'::date(周一),减 1 天得周日'2024-03-17',date_trunc('week', ...)截断后仍是'2024-03-17',再加 1 天回到'2024-03-18' - 注意:必须用
INTERVAL而非字符串拼接,否则类型错误;别漏掉括号,运算优先级会影响结果
SQL Server 中 DATEPART(week, ...) 不足以唯一标识自然周
DATEPART(week, date_col) 只返回数字(如 1–53),无法区分跨年的同周号(例如 2024 年最后一周和 2025 年第一周都可能是 week 1)。必须同时带上年份,且需确保“周所属年份”逻辑符合周一为起点的要求。
- 推荐组合:
DATEPART(iso_week, date_col)+YEAR(DATEADD(day, 1 - DATEPART(weekday, date_col), date_col))—— 但太绕 - 更清晰做法:用
DATEADD(day, 2 - DATEPART(weekday, date_col), date_col)计算周一日期(SQL Server 中DATEPART(weekday, ...)默认 1=周日,所以周一对应 weekday=2,需减 0 天?不对——实际要让结果落在周一,公式应为DATEADD(day, (2 - DATEPART(weekday, date_col) + 7) % 7, date_col),但更稳的是用DATEADD(day, 1 - DATEPART(weekday, date_col), date_col)再调整 - 实操建议:直接用
DATEADD(day, 1 - DATEPART(weekday, date_col), date_col),前提是把DATEFIRST设为 1(SET DATEFIRST 1),否则weekday含义会变
通用技巧:用归一化周一日期代替“周编号”做分组键
所有数据库中,最不容易出错的方式是把原始日期转换成它所在周的周一日期(一个具体 DATE 值),然后直接按这个日期 GROUP BY。它天然具备唯一性、可读性、跨年安全,且能直接用于排序或展示。
- MySQL:
DATE_SUB(date_col, INTERVAL WEEKDAY(date_col) DAY) - PostgreSQL:
(date_col - CAST(EXTRACT(dow FROM date_col) AS INTEGER) + 1)::date(dow是 0=周日,所以周一 =dow=1→ 减 1 天?不对,应减(dow + 6) % 7;更简单用前面提到的date_trunc偏移法) - 兼容写法(多数引擎支持):先用表达式生成周一日期列,再在
GROUP BY中引用该列别名(注意部分旧版 MySQL 不支持在GROUP BY中用别名,得重复表达式)
真正麻烦的不是算周一,而是确认你用的函数在目标数据库版本里是否按预期处理边界日期(比如 1 月 1 日刚好是周日时,它的“周一”属于上一年)。测试时务必覆盖年初、年末、跨月日期。











