MySQL中WEEK()默认返回周数与日历不符,因默认模式以周日为始且要求1月1日所在周为第1周,致2024-01-01(周一)被算作第52周;应使用WEEK(date, 1)符合ISO标准,或用DATE_SUB(order_date, INTERVAL WEEKDAY(order_date) DAY)获取周一基准日期。

MySQL中WEEK()函数返回的周数为什么和日历对不上
MySQL的WEEK()默认按“周日为一周开始、第1周需包含1月1日”规则计算,导致2024-01-01(周一)被算进第52周——这和业务常说的“2024年第1周”冲突。直接用WEEK(date)做分组,报表首尾周数据会错位。
真正可用的是WEEK(date, 1):第二个参数设为1表示“周一为每周起始,且第1周必须含4个及以上周一”,这才符合ISO 8601标准。但注意:WEEK(date, 1)在MySQL 5.7+才稳定支持,低版本会静默忽略参数。
- 验证当前行为:
SELECT WEEK('2024-01-01', 1), WEEK('2024-01-01', 0);—— 前者应返回1,后者返回52 - 生产环境务必显式指定模式参数,别依赖默认值
- 如果数据库是MariaDB或旧版MySQL,优先改用日期偏移法(见下节)
用DATE_SUB + WEEKDAY实现跨数据库兼容的周一基准周计算
当不能依赖WEEK()函数时,用“把任意日期回退到当周周一”再截取年份+周数是最稳妥的方案。核心是WEEKDAY():它返回0(周一)到6(周日),比DAYOFWEEK()更符合业务直觉。
关键表达式:DATE_SUB(order_date, INTERVAL WEEKDAY(order_date) DAY) 得到当周周一日期;再用YEARWEEK(order_date, 1)或组合YEAR()+WEEK()生成分组键。
- 安全写法(兼容MySQL/MariaDB/PostgreSQL部分场景):
DATE_SUB(order_date, INTERVAL WEEKDAY(order_date) DAY) AS week_start - 生成“2024-W01”格式:
CONCAT(YEAR(order_date), '-W', LPAD(WEEK(order_date, 1), 2, '0')) - 注意:
WEEKDAY('2024-01-01')返回0,WEEKDAY('2024-01-07')返回6,逻辑清晰无歧义
GROUP BY周分组时WHERE条件漏掉时间范围导致全表扫描
常见错误是先用WEEK(order_date)分组,再在WHERE里写order_date >= '2024-01-01'——如果order_date字段没索引,或索引未被优化器选中,MySQL可能放弃索引走全表扫描,百万级订单表秒变卡顿。
- 强制走索引:WHERE条件优先用日期范围,例如
WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31' - 避免在WHERE里对日期字段用函数:
WHERE WEEK(order_date) = 1会让索引失效 - 如果必须按周过滤,用日期偏移法构造范围:
WHERE order_date >= '2024-01-01' AND order_date
销售报表里“自然周”和“财务周”混用引发的数据割裂
业务方说的“第17周”可能指自然周(周一到周日),也可能指财务周(比如每月1号到当月最后一天)。一旦前端传参只给“2024-W17”,后端没明确约定解释规则,同一份SQL在不同系统里跑出两套结果。
解决方案不是加注释,而是把周定义下沉到SQL层:
- 自然周:用
YEARWEEK(order_date, 1)或DATE_SUB(... WEEKDAY ...) - 财务周(例:每月1-30日为一周期):
FLOOR(DAYOFMONTH(order_date) / 30.0) + 1,配合YEAR(order_date)和MONTH(order_date)拼接 - 所有报表接口必须在文档中标明所用周类型,SQL里禁止出现未定义的“week_num”别名
周计算看着简单,但年份切换点(如2023-12-31可能是2024年第1周)、数据库版本差异、业务定义漂移,三者叠加最容易在季度结账时暴雷。别省那几行代码,把周基准逻辑写死在SQL里。

















