必须显式指定周起始日和年份归属规则,否则跨年、跨月的“第1周”会错乱,统计结果偏差可能高达30%;sql server应使用datepart(isoyy, dt), datepart(isowk, dt)组合分组,mysql推荐week(dt, 3)或yearweek(dt, 3),postgresql用extract(isoweek from dt),clickhouse直接用toisoyear/toisoweek,且所有数据库均需先统一时区再计算。

直接说结论:用 DATEPART(SQL Server)或 WEEK(MySQL 8.0+)提取周数时,必须显式指定周起始日和年份归属规则,否则跨年、跨月的“第1周”会错乱,统计结果偏差可能高达30%。
SQL Server里用 DATEPART 按周分组,为什么第1周总对不上日历?
SQL Server 默认按美国习惯(周日为每周第一天,且第1周必须含1月1日),所以2024-01-01(周一)会被算进第1周,但2023-12-31(周日)反而属于2023年第52周——而业务上你很可能希望它属于2024年第1周。
- 用
DATEPART(week, order_time)不可靠,它不区分 ISO 周和系统周 - 正确做法是强制使用 ISO 标准:
DATEPART(isowk, order_time)(返回 ISO 周数,周一为起点,第1周含当年第一个周四) - 同时要带上年份:
DATEPART(isoyy, order_time),因为 ISO 周可能跨年(如2023-12-31属2024年第1周) - 组合分组写法:
GROUP BY DATEPART(isoyy, order_time), DATEPART(isowk, order_time)
MySQL 5.7 / 8.0 怎么用 WEEK 避免“周日开头导致周一订单进错周”?
MySQL 的 WEEK() 函数有 10 种模式(mode 参数 0–9),默认 mode=0(周日开始,第1周需含1月1日),这和大多数国内业务习惯冲突。
- 推荐用
WEEK(order_time, 1):周一为每周第一天,第1周只要含4天及以上就算(接近 ISO) - 更严格对标 ISO?用
WEEK(order_time, 3):周一为第一天,且第1周必须含当年第一个周四 - 注意:MySQL 5.7 不支持
WEEKOFYEAR()的 ISO 模式,别误用;YEARWEEK(order_time, 3)才能安全返回“年+周”整数(如 202401) - 分组时务必用
YEARWEEK(order_time, 3),而不是分开取YEAR()和WEEK(),否则跨年周会拆散
PostgreSQL 和 ClickHouse 怎么办?没有 WEEK 函数就硬凑?
PostgreSQL 没内置 WEEK,但 EXTRACT(isodow FROM order_time) + EXTRACT(week FROM order_time) 组合也不等于 ISO 周——它用的是“年度第几周”,非 ISO 定义。
- PostgreSQL 推荐:
EXTRACT(YEAR FROM order_time AT TIME ZONE 'UTC') || '-' || LPAD(EXTRACT(ISOWEEK FROM order_time)::text, 2, '0'),再按此字符串分组 - ClickHouse 更简单:
toISOWeek(order_time)和toISOYear(order_time)是原生支持的,直接GROUP BY toISOYear(order_time), toISOWeek(order_time) - 所有数据库中,如果原始时间带时区(如
TIMESTAMP WITH TIME ZONE),务必先AT TIME ZONE 'Asia/Shanghai'转成本地时区再提取周,否则凌晨订单可能被划入前一天的周
真正麻烦的不是函数怎么写,而是业务定义没对齐:销售说“本周”指周一到周日,财务说“本周”指自然周(周日到周六),BI 报表又按 ISO 周拉取。一旦底层 SQL 没锁定具体规则,上游所有汇总都不可信。










