MySQL中WEEK()默认以周日为起点且第1周含1月1日,易致跨年周错乱;应改用WEEK(date,1)或YEARWEEK(date,1),后者返回202401格式避免年份混淆。
MySQL里用
分组为什么总少一周或多一周
因为
默认按周日为一周起点,且第1周定义为“包含1月1日的那一周”,和日常说的“周一到周日”或“自然周”不一致。比如2024-01-01是周一,但
返回1,而
返回53——这天其实属于2024年第1周,但年份还是2023。
实操建议:
用
:第二个参数
表示周一为每周第一天,且第1周必须包含4个及以上周一(ISO标准),更符合业务习惯
别只靠
分组,得配合
——它返回形如
的整数,能避免跨年周错乱
如果要“显示为2024-W01”这种格式,用
,但注意:跨年日期仍可能归属前一年,所以优先用
PostgreSQL中
返回的是周日还是周一
默认是周一。PostgreSQL的
会截断到**该周的周一零点**(时区按当前session设置)。比如
(周六)返回
,不是
。
常见错误现象:
误以为截断后是周日,结果分组时把周一数据丢进上一周
没考虑时区,服务器用UTC、应用用东八区,导致
结果偏移一天
对
字段直接
,结果按UTC算,而非本地时间
正确做法:显式转时区再截断,例如
SQL Server里
和
的区别
用的是SQL Server默认的周计算规则(由
控制,默认7=周日),而
强制走ISO 8601标准:周一为每周第一天,第1周是包含当年第一个周四的那周。
使用场景:
做BI报表、跨系统对账时,必须用
,否则和MySQL/PostgreSQL的
或PostgreSQL的
结果不一致
在老系统遗留逻辑里可能出现,但别在新需求里用——它依赖会话级设置,不可靠
组合年份要用
+
,但注意:12月最后一周可能属于下一年,所以推荐用
配合窗口函数推导ISO周边界
按季度分组时,
函数在不同数据库里的陷阱
MySQL和PostgreSQL的
都返回1~4,看起来安全,但问题出在“季度起止时间”的语义模糊上:业务常说的Q1是1月1日到3月31日,但有些分析场景需要按“财务季度”(比如财年从4月开始)或“滚动季度”(最近90天)。
容易踩的坑:
直接
+
,结果2024-Q1包含2024-01-01 ~ 2024-03-31,但若某条记录是
,毫秒精度下可能因时区或类型隐式转换被归入下季度
SQL Server没有
函数,得写
,但整除在T-SQL里是向下取整,
时
,没问题;可一旦
是
且含纳秒,表达式依然有效,但可读性差
真正稳健的做法是生成季度边界:比如MySQL用
算起始日,再
算结束日
复杂点在于:所有“动态分组”本质都是把连续时间轴切成离散桶,桶的边界定义比函数名更重要。很多人调对了函数,却没校验桶是否真的覆盖全量、无重叠、无空隙。
WEEK()WEEK()WEEK('2024-01-01')WEEK('2023-12-31')WEEK(date, 1)1WEEK()YEARWEEK(date, 1)202401CONCAT(YEAR(date), '-W', LPAD(WEEK(date, 1), 2, '0'))YEARWEEK()date_trunc('week')date_trunc('week', date)date_trunc('week', '2024-01-06'::date)2024-01-012024-01-07date_trunctimestamptzdate_trunc('week')date_trunc('week', (created_at AT TIME ZONE 'Asia/Shanghai')) AT TIME ZONE 'Asia/Shanghai'DATEPART(week, ...)DATEPART(iso_week, ...)DATEPART(week, ...)SET DATEFIRSTDATEPART(iso_week, ...)iso_weekYEARWEEK(..., 1)date_trunc('week')DATEPART(week, ...)YEAR(date)DATEPART(iso_week, date)FORMAT(date, 'yyyy-MM-dd')QUARTER()QUARTER(date)GROUP BY QUARTER(date)YEAR(date)2024-03-31 23:59:59.999QUARTER()(MONTH(date) + 2) / 3MONTH=1(1+2)/3 = 1datedatetime2MAKEDATE(YEAR(date), 1) + INTERVAL (QUARTER(date)-1) QUARTER+ INTERVAL 1 QUARTER - INTERVAL 1 DAY