自连接查上月销量变慢是因为每行需全表扫描匹配日期,触发Nested Loop;窗口函数通过一次扫描+PARTITION BY product_id, region与ORDER BY order_date::DATE实现高效累积计算,但分区缺失或精度不足会导致数据错乱。

为什么自连接查上个月销量总和会变慢?
因为数据库要为每一行都扫描全表匹配 date = current_date - INTERVAL '1 month',数据量一过百万,执行计划里就全是 Nested Loop。窗口函数能用一次扫描完成累积计算,关键在于把「按时间排序 + 向前看一行」这个逻辑直接压进聚合过程。
常见错误是写成 LAG(sales, 1) OVER (ORDER BY order_date) 却没加 PARTITION BY product_id,结果不同商品的销量被串在一起比——比如手机销量被当成耳机的“上月值”。
- 必须明确分区:同一商品、同一地区才可比,
PARTITION BY product_id, region - 排序字段要带精度:
ORDER BY order_date::DATE,避免 timestamp 微秒差异打乱顺序 -
LAG()默认返回NULL,业务上常需补零:COALESCE(LAG(sales) OVER (...), 0)
替代多层JOIN统计用户首次/末次行为
用 JOIN 找每个用户的首单时间,通常得先 GROUP BY user_id 聚出 min(created_at),再连回原表取订单号——两遍全表扫描。窗口函数用 FIRST_VALUE(order_id) OVER (PARTITION BY user_id ORDER BY created_at) 一行搞定,且支持直接取关联字段(比如首单的 amount)。
注意 FIRST_VALUE 和 ROW_NUMBER() = 1 的区别:前者在窗口内重复值时可能返回多行相同结果,后者严格只标一行;若需唯一首单记录,应配合 QUALIFY ROW_NUMBER() OVER (...) = 1(BigQuery / Snowflake)或子查询过滤。
- 慎用
ORDER BY created_at DESC配FIRST_VALUE模拟“末次”,不如直接用LAST_VALUE+ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - PostgreSQL 中
LAST_VALUE默认窗口是ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不显式声明会拿不到真正的末尾值
用窗口函数实现滚动30天求和,避开日期维度表JOIN
传统做法建一张日期维表,用 LEFT JOIN 拼出每天+前29天的记录,再 SUM() —— 数据膨胀严重。改用 SUM(sales) OVER (PARTITION BY product_id ORDER BY sale_date RANGE BETWEEN INTERVAL '29 days' PRECEDING AND CURRENT ROW),引擎自动按日期范围切片,无中间膨胀。
但要注意:MySQL 8.0 不支持 RANGE 配 INTERVAL,得退化成 ROWS BETWEEN 29 PRECEDING AND CURRENT ROW(要求数据每日连续,缺日需先用 CTE 补齐);而 Redshift 对 RANGE 的日期支持也不完整,优先选 ROWS + 排序后序号。
-
RANGE按值计算,ROWS按行数计算——前者抗数据缺失,后者快但依赖连续性 - 如果
sale_date有重复(同天多笔),ROWS可能把同一天部分记录漏掉,此时必须用RANGE
哪些场景窗口函数也救不了,还得靠JOIN?
窗口函数无法跨分区取值,比如“找出所有订单中,金额大于其所属客户平均订单额的那些单”——AVG(amount) OVER (PARTITION BY user_id) 只能算出均值,但 WHERE 条件里不能直接引用该别名,得套一层子查询或 CTE;而更复杂的“每个用户最近三笔订单中,是否有任意一笔含优惠券”就得用 BOOL_OR(has_coupon) OVER (... ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),但 Presto 旧版不支持 BOOL_OR,只能回退到 MAX(CASE WHEN has_coupon THEN 1 END) 再判断。
真正卡住的地方往往不是语法,而是执行器对窗口帧的物化方式:当 PARTITION BY 组太多(如百万级用户ID),内存不够就会 spill to disk,这时反不如分批 JOIN 加索引快。

















