date_trunc在postgresql和bigquery中直接支持按周/月截断时间,返回可排序的时间类型;mysql需用yearweek或date_format模拟,但后者返回字符串有性能风险;跨库兼容推荐extract构造整数分组键;避免函数导致索引失效,宜用物化列或函数索引优化。

DATE_TRUNC 在 PostgreSQL 和 BigQuery 中按周/月分组
PostgreSQL 和 BigQuery 原生支持 DATE_TRUNC,它是做时间维度聚合最直接的函数。注意它返回的是一个时间戳(TIMESTAMP 或 DATE 类型),不是字符串,因此可直接用于 GROUP BY 且保留时序可排序性。
常见错误是误以为 DATE_TRUNC('week', order_time) 总从周一开始——实际取决于数据库的 lc_time(PostgreSQL)或默认周起始(BigQuery 默认周日为一周起点)。若需强制周一为起点,PostgreSQL 要配合 EXTRACT(ISODOW FROM ...) 调整;BigQuery 可用 DATE_TRUNC(order_time, WEEK(MONDAY)) 显式指定。
-
DATE_TRUNC('month', order_time)截到当月第一天,如'2024-05-15' → '2024-05-01' -
DATE_TRUNC('week', order_time)在 BigQuery 中默认截到最近的周日;PostgreSQL 默认行为依赖系统 locale - 聚合时务必把
DATE_TRUNC放在SELECT和GROUP BY两边保持一致,否则报错或逻辑错乱
DATE_FORMAT 在 MySQL 中替代 DATE_TRUNC 的写法
MySQL 没有 DATE_TRUNC,但 DATE_FORMAT 可以模拟类似效果,不过它返回字符串,会丢失原始类型语义——这意味着无法直接参与日期计算、索引失效风险更高,且跨年周(如 2024-W52 实际含 2025-01-01)需额外处理。
更稳妥的做法是用 YEARWEEK(order_time, 1)(第二个参数 1 表示周一为每周起点,ISO 标准),它返回整数如 202452,既可分组又可排序,还天然规避了字符串比较陷阱。
- 按月:用
DATE_FORMAT(order_time, '%Y-%m')或更安全的YEAR(order_time) * 100 + MONTH(order_time) - 按 ISO 周:优先选
YEARWEEK(order_time, 1),而非DATE_FORMAT(order_time, '%x-%v')(后者格式不稳定,且不能直接排序) - 如果必须用
DATE_FORMAT输出带“第X周”中文标签,建议在应用层拼接,不在 SQL 里做
跨数据库兼容写法:用 EXTRACT + 构造虚拟分组键
当需要同一份 SQL 同时跑在 PostgreSQL、MySQL、SQL Server 上时,DATE_TRUNC 和 DATE_FORMAT 都不可用。此时可退回到标准 SQL 的 EXTRACT 函数组合构造分组键。
例如按月汇总:用 EXTRACT(YEAR FROM order_time) * 100 + EXTRACT(MONTH FROM order_time) 得到 202405;按 ISO 周则需结合 EXTRACT(ISOYEAR FROM ...) 和 EXTRACT(ISOWEEK FROM ...)(PostgreSQL 支持,MySQL 不支持,需降级为 YEARWEEK 分支处理)。
- 纯标准 SQL 场景下,
EXTRACT(YEAR FROM d) || '-' || LPAD(EXTRACT(MONTH FROM d)::text, 2, '0')是 PostgreSQL 安全写法 - MySQL 中
EXTRACT不支持ISOWEEK,必须切到YEARWEEK(d, 1)分支 - 所有构造出的分组键都应定义为
INT或固定长度字符串,避免隐式类型转换引发排序异常
性能与索引注意事项:别让函数毁掉你的查询速度
在 WHERE 或 GROUP BY 中对时间字段套函数(如 DATE_TRUNC(created_at))会导致索引失效——即使你建了 created_at 上的 B-tree 索引,优化器也无法跳过函数计算直接定位数据。
真正高效的方案是:提前在表中增加物化列(如 report_month DATE GENERATED ALWAYS AS (DATE_TRUNC('month', created_at)) STORED),并为其单独建索引。MySQL 8.0+ 支持函数索引:CREATE INDEX idx_month ON orders ((DATE_FORMAT(created_at, '%Y-%m'))),但注意该索引只对该特定格式生效。
- 不要在
WHERE created_at >= DATE_TRUNC('month', NOW())这种条件里用函数,改写为范围查询:created_at >= '2024-05-01' AND created_at - MySQL 的
DATE_FORMAT索引仅在WHERE子句中使用完全相同格式时才命中,GROUP BY仍不走索引 - BigQuery 对
DATE_TRUNC列自动优化较好,但分区字段必须是原始DATE类型,不能是函数结果
按周汇总最容易被忽略的是跨年周的归属问题——比如 2024-12-30 属于 ISO 2025 年第 1 周,但很多业务口径要求“归入订单发生年份”。这种差异不会报错,只会悄悄让报表少算或多算几天数据。










