postgresql按周分组需组合年份与iso周(如to_char(created_at, 'iyyy-iw')),避免date_part('week', ...)跨年混淆;oracle用trunc(date, 'iw'),mysql须yearweek(date, 1);时区和数据库特性差异要求不可盲目跨库抽象。

PostgreSQL里用DATE_PART按周分组,注意周起始日和年份边界
PostgreSQL默认把周看作周一到周日,但DATE_PART('week', ...)返回的是纯数字(1–53),不带年份信息。直接GROUP BY DATE_PART('week', created_at)会导致2023年第52周和2024年第52周被混在一起统计。
正确做法是组合年份和周数:
SELECT
DATE_PART('year', created_at)::INT AS year,
DATE_PART('week', created_at)::INT AS week_num,
COUNT(*)
FROM orders
GROUP BY 1, 2
ORDER BY 1, 2;
更稳妥的写法是用TO_CHAR(created_at, 'IYYY-IW')(ISO周格式),它自动处理跨年问题,比如2023-12-31属于2024年第1周。
- 别用
'YYYY-WW':它按日历年算周,2023-12-31会被标为2023-52,实际属于ISO第202401周 -
DATE_PART('isoyear', ...)和DATE_PART('isoweek', ...)可替代TO_CHAR(..., 'IYYY-IW'),语义更清晰 - 如果业务约定周日为每周第一天,需先用
created_at - INTERVAL '1 day'偏移再取ISO周
Oracle中用TRUNC按周分组,关键是指定'IW'或'WW'
Oracle的TRUNC(date_col, 'IW')会截断到最近的周一(ISO标准周),而TRUNC(date_col, 'WW')截断到当年1月1日所在周的周日。绝大多数场景应该用'IW',否则跨年时会出现断裂。
例如统计每周订单量:
SELECT TRUNC(order_date, 'IW') AS week_start, COUNT(*) FROM sales GROUP BY TRUNC(order_date, 'IW') ORDER BY 1;
-
TRUNC(order_date, 'IW')返回的是周一日期,天然可排序、可做范围查询 - 避免用
'W'(当月第几周):它只返回1–5,且每月重置,无法跨月聚合 - 如果前端要求显示“2024-W05”这类格式,可用
TO_CHAR(order_date, 'YYYY-"W"IW')
MySQL没有原生ISO周函数,YEARWEEK()参数必须设为1
MySQL的YEARWEEK(date)默认用mode=0(周日为起点,按日历年),这会导致2023-12-31(周一)被算进2023年第52周,但ISO标准下它属于2024年第1周。必须显式传1:即YEARWEEK(date, 1),表示周一为起点、ISO周规则。
安全写法:
SELECT YEARWEEK(order_time, 1) AS yw, COUNT(*) FROM t_order GROUP BY yw ORDER BY yw;
- 不要省略第二个参数:
YEARWEEK(order_time)等价于YEARWEEK(order_time, 0),结果不可靠 - 若需拆出年份和周数单独用,可用
FLOOR(YEARWEEK(order_time, 1) / 100)取年,YEARWEEK(order_time, 1) % 100取周 - 注意
WEEK()函数行为不同——它只返回周数,无年份,不能单独用于跨年分组
跨数据库兼容写法?别硬套,优先按目标库规范写
试图用EXTRACT(WEEK FROM ...)在PostgreSQL、SQLite、SQL Server间通用是行不通的。PostgreSQL支持,SQLite不支持,SQL Server的DATEPART(week, ...)又按周日起点且无ISO模式。强行抽象只会引入隐性bug。
- 应用层聚合:把原始日期拉到内存,用语言内置的周计算逻辑(如Python的
dt.isocalendar())再分组 - 建物化视图/中间表:在ETL阶段统一计算
week_key CHAR(7)(如'2024-W05'),后续SQL只按该字段分组 - 最易忽略的一点:时区。所有时间字段务必确认是否已转为业务所在时区,否则UTC时间下的“周一”可能对应本地周日










