窗口函数本身不产生重复,错误根源在于JOIN已导致行膨胀,窗口函数只是忠实地对每条膨胀后的行计算;例如1单3明细JOIN后为3行,COUNT(*) OVER(PARTITION BY order_id)返回每行都是3,而非按订单逻辑计1次。

窗口函数本身不产生重复,但用法不当会放大重复行影响
窗口函数(如 SUM() OVER()、COUNT() OVER())从不合并或复制行,它只是在现有行集上附加计算。所谓“重复统计”,其实是底层 JOIN 或关联已把主表行撑开,窗口函数忠实地对每一条膨胀后的行都算一遍——结果看起来翻倍,根源不在窗口函数,在它前面的数据形态。
JOIN 后直接套 COUNT() OVER(PARTITION BY ...) 为什么还翻倍
常见错误是以为加了 PARTITION BY 就能“自动去重”。比如订单表 orders 和明细表 order_items JOIN 后,1 单 3 明细 → 3 行;此时写 COUNT(*) OVER (PARTITION BY order_id),结果是每行都返回 3,不是“每个订单计 1 次”,而是“每个明细行都看到所属订单共 3 条明细”。这没出错,但不符合业务中“按订单计数”的意图。
- 真正要的是“每个订单只参与一次计数”,得先让数据回到订单粒度(1 行/单),再聚合
-
COUNT(DISTINCT order_id) OVER ()在部分引擎(Trino/Presto/Spark SQL)可行,但多数数据库(MySQL/PostgreSQL)不支持该语法,会报aggregate function calls cannot be nested - 若想在明细行上显示“该订单总金额”,应先用子查询聚合明细:
SELECT order_id, SUM(amount) AS total FROM order_items GROUP BY order_id,再 JOIN 回主表
ROW_NUMBER() 用错 ORDER BY 导致去重失效
用 ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY created_at) 取首行时,如果 created_at 有重复(比如批量导入无毫秒精度),多个行可能被分到同一序号,rn = 1 就不止一条——看似去重,实则漏掉或重复保留。
- 必须确保
ORDER BY子句含足够区分度的列,例如ORDER BY created_at, item_id或ORDER BY created_at, id - 别用
RANK()或DENSE_RANK()替代ROW_NUMBER():它们对相同排序值返回相同序号,无法保证每行唯一编号 - 在 Snowflake/BigQuery 中可用
QUALIFY简化过滤;PostgreSQL/MySQL 需 CTE 或子查询,且WHERE rn = 1必须在外层,不能写在窗口计算同级
LEFT JOIN + 窗口函数仍出现多行,问题出在右表没“瘦身”
LEFT JOIN 的语义就是左表每行匹配右表所有符合条件的记录。窗口函数跑在 JOIN 结果上,自然继承全部膨胀行。比如用户表 LEFT JOIN 日志表,一个用户 5 条日志 → 用户信息重复 5 次,FIRST_VALUE(log_time) OVER (PARTITION BY user_id) 虽然值一样,但仍是 5 行输出——这不是 bug,是设计如此。
- 若只需汇总值(如最新 IP、登录次数),用子查询
GROUP BY user_id预聚合右表 - 若需完整单条记录(如最新日志全字段),必须用
ROW_NUMBER()在右表子查询里编号,并在ON条件中加AND rn = 1,而不是靠外层WHERE -
DISTINCT放在外层 SELECT 对这种场景基本无效:只要任意一列不同(如日志时间、ID),整行就不重复

















