etl中间层必须用窗口函数生成row_number()去重,因distinct仅认整行全等,无法按业务规则(如同order_id取updated_at最新)精准保留代表行;而row_number()配合partition by和order by可表达该意图,且需where rn = 1筛选、order by末尾加唯一字段防序漂移,否则etl不可靠。

窗口函数本身不建模,但它是数据仓库ETL中间层清洗、打标、派生指标的底层支撑——没有它,星型模型里的事实表就很难稳定产出带业务语义的明细级指标。
为什么ETL中间层必须用窗口函数生成row_number()去重
源系统订单表常有order_id重复,但业务规则是“同order_id取updated_at最新的一条”。DISTINCT做不到这点,它只认整行全等。
-
ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY updated_at DESC, id DESC)能表达这个业务意图,id DESC防时间相同时序漂移 - 必须紧跟
WHERE rn = 1筛选,窗口函数只打标,不删行 - 如果源表没
updated_at或主键,ORDER BY缺失会导致结果不可复现,ETL任务就不可靠
累计类指标为什么不能靠GROUP BY在下游算
比如“每个用户截至当前订单的累计消费额”,用GROUP BY user_id会压缩成一行,丢失每笔订单的product_id、channel等字段,报表和下钻就断了。
- 正确写法:
SUM(amount) OVER (PARTITION BY user_id ORDER BY order_time) - 必须显式加
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,否则MySQL默认RANGE可能重复计数,PostgreSQL默认ROWS却行为不同 - 这种写法保留原始行粒度,下游可直接JOIN维度表,也方便按时间切片做增量重跑
LAG/LEAD做状态变化检测时最易踩的坑
用LAG(status) OVER (PARTITION BY user_id ORDER BY event_time)判断“从active变inactive”,看似合理,实际极易漏判。
- 上游事件时间精度丢失(如只到秒)、乱序写入、或两条记录
event_time完全相同,都会让LAG()拿到错误前值 - 解决办法:补强排序条件,比如
ORDER BY event_time, event_id,确保严格有序 - 更稳妥的做法是先用
ROW_NUMBER()给事件编号,再用JOIN自关联比对相邻行,可控性远高于LAG()
窗口函数不是万能胶,它的威力只在ETL中间层——那里有足够干净的排序字段、明确的分区逻辑、以及可审计的执行上下文。一旦挪到应用层或报表层硬套,反而放大不确定性。真正难的从来不是写对语法,而是确认那条ORDER BY背后的数据是否真的可靠。











