MySQL周/月分组应使用DATE_SUB对齐周期起点而非WEEK()/MONTH(),PostgreSQL用GENERATE_SERIES补全空周期,定时聚合宜用数据库内置调度,跨时区需先转换再截断。

MySQL里用DATE_SUB和GROUP BY做周/月分组统计
直接按自然周或自然月聚合,别硬套WEEK()或MONTH()函数——它们依赖系统时区和mode参数,一换环境就错。真正稳定的做法是把时间对齐到周期起点再分组。
比如统计“每周一零点开始的7天”数据:SELECT DATE_SUB(date_col, INTERVAL WEEKDAY(date_col) DAY) AS week_start, COUNT(*) FROM table GROUP BY week_start;
月度同理,用DATE_SUB(date_col, INTERVAL DAYOFMONTH(date_col)-1 DAY)拿到当月1号。
-
WEEKDAY()返回0(周一)~6(周日),比DAYOFWEEK()更可控 - 如果业务要求“周日为每周第一天”,改用
DATE_SUB(date_col, INTERVAL (WEEKDAY(date_col)+1) % 7 DAY) - 注意
date_col必须是DATE或DATETIME类型;如果是字符串,先用STR_TO_DATE()转
PostgreSQL中用GENERATE_SERIES补全缺失周期
原始数据往往不连续,比如某周没订单,GROUP BY后就直接跳过那周——但报表需要显示“0”。这时候不能靠应用层拼空行,得在SQL里补。
核心思路:用GENERATE_SERIES()生成完整周期序列,再LEFT JOIN原表聚合结果。
示例(补最近12周):SELECT s.week_start, COALESCE(t.cnt, 0) FROM GENERATE_SERIES(CURRENT_DATE - INTERVAL '11 weeks', CURRENT_DATE, '1 week') AS s(week_start) LEFT JOIN (SELECT DATE_TRUNC('week', created_at) AS week_start, COUNT(*) AS cnt FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '11 weeks' GROUP BY DATE_TRUNC('week', created_at)) AS t ON s.week_start = t.week_start;
-
DATE_TRUNC('week', ...)默认按周一截断,和MySQL对齐;如需周日,加AT TIME ZONE 'UTC'调整 -
GENERATE_SERIES不支持字符串步长,必须用INTERVAL单位 - 子查询里WHERE条件要和外层范围一致,否则LEFT JOIN会漏掉空周期
定时任务触发SQL聚合:避免在crontab里直连数据库跑复杂查询
用crontab调mysql -e或psql -c执行聚合SQL,看似简单,实际容易卡死或超时——尤其当表大、索引没建好、或凌晨备份正在跑的时候。
更稳的做法是把聚合逻辑封装进数据库内部:用存储过程+事件调度器(MySQL)或pg_cron扩展(PostgreSQL)。
- MySQL开启
event_scheduler=ON后,用CREATE EVENT定义每小时跑一次INSERT INTO summary_table SELECT ... GROUP BY ... - PostgreSQL推荐装
pg_cron,它走后台进程,失败可重试,还能设最大运行时长 - 千万别让定时任务直接UPDATE大表——先写入临时表,再用
TRUNCATE + INSERT原子切换 - 记得给聚合字段加组合索引,比如
(status, created_at),不然每次扫全表
跨时区业务下时间分组的坑
用户在北京、纽约、东京下单,created_at存的是UTC时间,但运营要看“当地时间的每日销量”。这时不能简单GROUP BY DATE(created_at)——那全是UTC日期。
正确做法是在分组前把时间转成目标时区再截断:
MySQL:GROUP BY DATE(CONVERT_TZ(created_at, '+00:00', '+08:00'))
PostgreSQL:GROUP BY (created_at AT TIME ZONE 'Asia/Shanghai')::DATE
-
CONVERT_TZ()依赖MySQL时区表,得先mysql_tzinfo_to_sql导入,否则只认+00:00这类偏移量 - PostgreSQL的
AT TIME ZONE支持IANA时区名(如'Asia/Tokyo'),但要注意夏令时切换点 - 如果一个报表要同时看多个时区,别在SQL里反复转换——把UTC时间拆成多个时区字段存在汇总表里
最麻烦的不是语法,而是确认业务方说的“每天”到底指哪个时区的午夜。这个一旦定错,后续所有数据都偏一天,而且很难回溯修正。









