“自然周”指周一至周日的完整七天周期;不同数据库对周起始日默认定义不同:mysql默认周日为第1天,postgresql/bigquery默认周一为第1天,sql server可通过set datefirst调整,需统一处理避免错位。

什么是“自然周”?先确认你的数据库怎么定义周一
不同数据库对 WEEK() 或 EXTRACT() 的周起始日默认不同:MySQL 默认周日为第 1 天,PostgreSQL 和 BigQuery 默认周一为第 1 天,SQL Server 可通过 SET DATEFIRST 调整。不统一这个,算出来的“自然周”会错位。
实操建议:
- 用
DATE_TRUNC('week', date_col)(BigQuery / PostgreSQL)或DATE_SUB(date_col, INTERVAL WEEKDAY(date_col) DAY)(MySQL)显式截到周一凌晨,比依赖WEEK()更可靠 - 避免直接用
WEEK(date_col)+YEAR(date_col)拼周标识——跨年周(如 2024-12-30 是 2025 年第 1 周)会出错 - 验证方法:查几个已知周一的日期(如
'2024-06-03'),看窗口分组后是否都落在同一周起始值下
窗口函数里不能直接写 GROUP BY,得用 PARTITION BY + 显式周字段
常见错误是写成 AVG(value) OVER (PARTITION BY WEEK(date_col)) —— 这样跨年周会混在一起,且没考虑周起始日一致性。
正确做法是先派生出标准周起始日字段,再用于窗口分区:
SELECT
date_col,
value,
AVG(value) OVER (
PARTITION BY DATE_TRUNC('week', date_col)
ORDER BY date_col
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_avg_by_week
FROM sales;
注意点:
-
DATE_TRUNC('week', ...)在 BigQuery / Redshift / PostgreSQL 中返回周一 00:00;MySQL 需手动计算,例如:date_sub(date_col, interval weekday(date_col) day) - 如果只要当周均值(非累积),去掉
ORDER BY ... ROWS,只留PARTITION BY即可 - NULL 值会影响
AVG()计算结果,必要时加WHERE value IS NOT NULL预过滤
按自然周统计均值,但想保留原始行粒度?必须用窗口而非 GROUP BY
有人试过先 GROUP BY DATE_TRUNC('week', date_col) 再 JOIN 回原表,结果要么爆炸性膨胀,要么丢失明细。窗口函数才是原生解法。
典型场景:报表要展示每天销售额 + 所在自然周的平均销售额(含当天)。
- 用
AVG(value) OVER (PARTITION BY week_start)直接生成每行对应的周均值列 - 若需排除当天(即“本周均值不含今日”),改用
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING,但要注意首日会是 NULL - 性能提示:在
date_col上建索引能加速DATE_TRUNC计算和窗口排序,尤其数据量 >100 万行时
时区不对会导致周切分偏移,别忽略 timestamp 类型的隐式转换
如果你的 date_col 是 TIMESTAMP WITH TIME ZONE(如 '2024-06-03 01:00:00+08'),直接 DATE_TRUNC('week', ...) 会按服务器时区或会话时区截取——可能把周一凌晨 00:30 的记录算进上周日。
解决方式:
- 显式转成本地时区再截断:BigQuery 用
DATE_TRUNC(DATE(timestamp_col, 'Asia/Shanghai'), week(MONDAY)) - PostgreSQL 用
DATE_TRUNC('week', timestamp_col AT TIME ZONE 'Asia/Shanghai') - MySQL 没原生时区截断函数,建议先用
CONVERT_TZ()转换,再用date_sub(...)算周一
最易被忽略的是:开发环境和生产环境时区配置不同,测试用本地时间跑通,上线后周统计全偏一天。










