在MySQL中做按周汇总时,经常需要把数据归到本周的周一开始计算。单纯用日期加减在跨年或跨月时容易算错,而组合使用STR_TO_DATE与DATE_FORMAT可以稳定拿到本周一,且逻辑清晰、可移植性好。

一、为什么需要STR_TO_DATE与DATE_FORMAT组合
MySQL提供了很多日期函数,但并没有一个直接叫get_monday的函数。很多人会用DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)来取周一,这确实在大多数情况可行。不过当业务要求按自然周(周一开始)且要和年份、周数强绑定时,用DATE_FORMAT把日期格式化为“年-周”再让STR_TO_DATE解析回来,会更加直观,也更容易和ISO周标准对齐。
STR_TO_DATE的作用是把字符串按照指定格式转成日期,DATE_FORMAT则是把日期格式化为字符串。两者一正一反,配合%X(年份)、%V(周数)等格式符,就可以构造出“某年某周的第一天”这样的语义,再交给STR_TO_DATE变成真正的DATE类型。这种方式在写报表SQL或存储过程时,可读性比一堆INTERVAL嵌套更好。
二、核心函数用法说明
DATE_FORMAT的常用格式符中,%X表示ISO年份,%V表示ISO周数(01到53,周一为一周开始)。例如2024年1月1日是周一,它属于2024年的第01周。我们可以用下面语句观察:
SELECT DATE_FORMAT('2024-01-01', '%X-%V') AS iso_week;
-- 结果:2024-01
STR_TO_DATE则接收字符串和格式,返回日期。需要注意的是,如果只给“年-周”还无法直接变成周一,因为STR_TO_DATE需要更完整的日期成分。通常我们拼接一个“周一”的占位,比如把格式写成'%X-%V-1',其中1表示周一,再配合格式符%w(周几,0=周日,1=周一)来解析。
一个更稳妥的写法是借用CONCAT把年、周和“周一”拼成字符串,然后让STR_TO_DATE按'%X-%V-%w'解析。这样MySQL会自动将该周数下的周一映射为具体日期,避免手动计算偏移。
三、获取本周一的完整SQL示例
下面给出一段可直接运行的SQL,用于获取本周一的具体日期:
SELECT
STR_TO_DATE(
CONCAT(DATE_FORMAT(CURDATE(), '%X-%V'), '-1'),
'%X-%V-%w'
) AS this_monday;
这段代码中,DATE_FORMAT(CURDATE(), '%X-%V')先得到当前日期所在的ISO年和周,例如'2024-03';CONCAT加上'-1'变成'2024-03-1';STR_TO_DATE按'%X-%V-%w'解析,%w遇到1就取该周周一,于是返回2024年对应周的周一日期。
如果你希望周定义以周日为一周开始,可以把%V换成%U,并把后面的周几占位改成0(%U是周日开始计周的周数)。但要注意,业务上国内报表普遍以周一为起点,所以用%X和%V更合适。
四、与WEEKDAY写法的对比
另一种常见写法是:
SELECT DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) AS monday_by_weekday;
WEEKDAY函数返回0代表周一,6代表周日,所以用当前日期减去这个天数就能回到周一。它在绝大多数自然周场景下正确,而且不需要字符串转换,性能略好。但当数据和跨年周数强相关、或者要和ISO周字段(如按周分表名)保持一致时,STR_TO_DATE组合法在语义上更不容易出错。
从执行计划看,两种都是对常量或索引列做简单计算,开销极小。实际选型建议:若只为了筛选本周数据,WEEKDAY法简单直接;若为了生成“年周-周一”标签或做周维度维度表,STR_TO_DATE组合更自解释。
五、常见坑与注意事项
第一个坑是格式符混用。%Y是普通年份,%X是ISO年份,在跨年那一周两者可能不同。例如2023年12月31日是周日,按ISO算它属于2024年第01周,%X会返回2024,而%Y返回2023。如果错用%Y配合%V,会得到错误年份的周一。
第二个坑是会话语言或模式影响。STR_TO_DATE对格式不匹配会返回NULL,所以要确保CONCAT出来的字符串严格符合'%X-%V-%w'。建议在存储过程里先SELECT测试一周边界日期(如每年1月1日、12月31日)再上线。
| 方法 | 优点 | 缺点 |
|---|---|---|
| STR_TO_DATE + DATE_FORMAT | 语义清晰,易对齐ISO周 | 涉及字符串转换,稍复杂 |
| WEEKDAY + DATE_SUB | 写法简单,性能好 | 跨年周语义需自行验证 |
六、在查询中实际使用
假设有一张订单表orders,字段order_time为datetime,要统计本周一到现在的订单额:
SELECT SUM(amount) AS week_amount FROM orders WHERE order_time >= STR_TO_DATE( CONCAT(DATE_FORMAT(CURDATE(), '%X-%V'), '-1'), '%X-%V-%w' );
这里用到的比较是直接DATE对DATETIME,MySQL会做隐式转换且能走order_time上的索引前缀(若查询条件为大于等于某日期)。比起在应用层算好周一再传参,纯SQL写法更方便复用,也方便后续改成存储函数。
如果需要频繁使用,可以封装成函数:
CREATE FUNCTION get_monday(d DATE)
RETURNS DATE
DETERMINISTIC
BEGIN
RETURN STR_TO_DATE(
CONCAT(DATE_FORMAT(d, '%X-%V'), '-1'),
'%X-%V-%w'
);
END;
之后直接SELECT get_monday(CURDATE())即可。注意函数里用了DETERMINISTIC,因为相同输入日期永远返回相同周一,利于优化器缓存。
七、小结
利用STR_TO_DATE与DATE_FORMAT组合获取本周一,本质是把“年周”语义显式表达出来,再解析为日期。它比单纯天数相减更贴合ISO周规则,尤其适合跨年报表与周维度建模。掌握%X、%V、%w的差异,就能灵活写出稳定的周起始日SQL,减少数据归类的偏差。
MySQLSTR_TO_DATEDATE_FORMAT修改时间:2026-08-06 20:57:15