最稳的周/月分组应使用DATE_TRUNC(PostgreSQL)或YEARWEEK(MySQL),避免字符串拼接;环比需先按周期聚合再用LAG,且必须补全缺失周期并用NULLIF防除零、COALESCE填零。

SQL中用DATE_TRUNC或YEARWEEK实现周/月分组
直接用DATE_TRUNC('month', order_date)(PostgreSQL)或YEARWEEK(order_date, 1)(MySQL)是最稳的起点,别手写CONCAT(YEAR(), '-', LPAD(MONTH(), 2, '0'))——这种拼字符串方式在跨年、时区、首日偏移上极易出错。MySQL 5.7+ 支持YEARWEEK()返回整数(如202412),天然可排序、可减1做环比;PostgreSQL 12+ 的DATE_TRUNC('week', ...)默认按周一截断,若业务以周日为周起始,得显式加INTERVAL '1 day'调整。
计算环比时必须对齐时间维度而非原始日期
常见错误是写成LAG(SUM(amount)) OVER (ORDER BY order_date),这会按单日排序,完全打乱周/月聚合逻辑。正确做法是先按周期分组聚合,再套窗口函数:
SELECT
period,
SUM(amount) AS weekly_sales,
ROUND(
(SUM(amount) - LAG(SUM(amount)) OVER (ORDER BY period)) * 100.0 / NULLIF(LAG(SUM(amount)) OVER (ORDER BY period), 0),
2
) AS week_on_week_pct
FROM (
SELECT
DATE_TRUNC('week', order_date) AS period,
amount
FROM sales
) t
GROUP BY period
ORDER BY period;注意三点:NULLIF防除零;ROUND(..., 2)避免浮点误差;LAG必须基于period而非原始日期字段排序。
处理缺失周期导致的环比断层
如果某周没销售数据,LAG会跳过该空档,把第3周直接跟第1周比,结果失真。必须补全周期:PostgreSQL可用GENERATE_SERIES生成连续周序列,MySQL需借助递归CTE或日历表。补全后用LEFT JOIN关联销售数据,再COALESCE(SUM(amount), 0)填零——否则LAG遇到NULL会中断链式计算,后续所有环比值都变NULL。
不同数据库对“周”的定义差异极大
MySQL的YEARWEEK(date, mode)中mode=1表示周一为每周第一天、且第一周含4天以上才属当年;mode=0则按周日为第一天、第一周只要含1月1日就算。PostgreSQL的DATE_TRUNC('week', ...)固定按周一截断,但EXTRACT(ISODOW FROM ...)返回0=Sunday,和ISO标准冲突。实际跑之前务必用SELECT DATE_TRUNC('week', '2024-01-01'::date)和SELECT YEARWEEK('2024-01-01', 1)实测验证首周归属——这个细节不校验,整个环比曲线就偏移了。

















