周同比需用lag(sales,7) over(order by dt)并确保数据按日连续且已补全日期;月环比应先date_trunc('month',dt)归一化再聚合开窗;须建dt索引、提前过滤时间范围、妥善处理null与除零。

窗口函数里怎么写周同比:用 LAG() 锁定 7 天前的值
周同比本质是「当前周值 ÷ 上周同日值 − 1」,关键在于精准取到 7 天前同一星期几的数据。直接用 LAG(value, 7) 是错的——它按行序取第 7 行,不保证是 7 天前;必须先按日期排序,再确保数据粒度是「日」且连续。
实操建议:
- 先用
GROUP BY date_trunc('day', order_time)汇总每日指标(如销售额),避免原始明细行干扰窗口顺序 - 排序必须显式写
ORDER BY dt(dt是日期列),不能依赖表默认顺序 - 用
LAG(sales, 1) OVER (ORDER BY dt)取上一日值,再结合dt - INTERVAL '6 days'找上周同日——但更稳的方式是先生成完整日期序列补全空缺日,再用LAG(sales, 7) - 如果某天无数据,
LAG()返回NULL,需用COALESCE(LAG(sales, 7), 0)或保留NULL并在除法前加WHERE LAG(sales, 7) IS NOT NULL
月环比别硬套 LAG():用 date_trunc('month', ...) 对齐周期
月环比要对比「本月累计值 vs 上月累计值」或「本月最后一天值 vs 上月最后一天值」,但原始数据若跨月不齐(比如 2 月只有 28 天),直接 LAG(value, 1) 会把 3 月 1 日连到 2 月 28 日,逻辑错乱。
正确做法是先归一化到月份维度:
- 用
date_trunc('month', dt) AS month_start把所有日期映射到当月 1 日 - 按
month_start分组聚合(如SUM(sales)),得到月度汇总表 - 再对这个结果集开窗:
LAG(monthly_sales, 1) OVER (ORDER BY month_start) - 注意:
date_trunc('month', ...)返回的是TIMESTAMP类型,比较安全;别用EXTRACT(YEAR FROM dt)*100 + EXTRACT(MONTH FROM dt)这种整数方式,易在跨年时出错
性能陷阱:没加索引 or 窗口范围过大导致慢查询
窗口函数本身不走索引,但 ORDER BY 列如果有索引能加速排序阶段。如果表有千万级数据,没索引的 ORDER BY dt 可能让查询从 200ms 拉长到 15s。
优化要点:
- 给排序列建索引:
CREATE INDEX idx_orders_dt ON orders (dt);(升序即可,ORDER BY dt DESC也能用) - 避免在窗口中用复杂表达式排序,例如
ORDER BY date_trunc('week', dt)无法利用dt索引,应先算好week_start字段并单独索引 - 如果只要最近 12 个月数据,务必在
WHERE子句里提前过滤:WHERE dt >= CURRENT_DATE - INTERVAL '12 months',别让窗口函数扫描全表
NULL 和除零问题:周同比结果突然变空不是代码错,是数据断层
常见现象:某天周同比字段全为 NULL,检查发现是上周同日无数据,LAG() 返回 NULL,后续除法直接得 NULL。这不是语法错误,是业务数据本身不连续。
处理建议:
- 用
NULLIF(denominator, 0)防止除零,再配合COALESCE(..., 0)统一补零(谨慎!零可能掩盖真实缺失) - 更合理的是保留
NULL,并在应用层区分「无数据」和「增长率为 0」 - 加个校验字段:
COUNT(*) OVER (PARTITION BY date_trunc('week', dt)) AS week_count,筛出周内数据不足 7 天的记录,这类周同比本就不该计算
真正麻烦的是时间维度对齐——比如你按自然周(周一到周日)算同比,但业务统计习惯是「滚动 7 天」,两者结果差异可能达 20%。确认清楚口径比调通 SQL 更重要。










