窗口函数不支持 count(distinct) 是因语义冲突:窗口按行计算,distinct 需先确定整个分区唯一值集合,引擎无法兼顾二者;spark/hive 推荐用 size(collect_set(col) over (...)),postgresql 等需子查询嵌套。

窗口函数不支持 COUNT(DISTINCT) 的根本原因
不是语法写错,而是绝大多数 SQL 引擎(包括 Spark SQL、Hive、PostgreSQL、MySQL 8.0+)**明确禁止**在 COUNT 窗口调用中使用 DISTINCT。报错信息通常是:Distinct window functions are not supported 或类似 AnalysisException。
核心限制在于语义冲突:窗口函数按行逐行计算,而 DISTINCT 是集合级操作,需要先确定整个分区的唯一值集合——这与“滑动”“累积”类窗口逻辑天然不兼容。引擎无法在保持窗口语义的同时高效完成去重计数。
Spark/Hive 中最实用的替代写法:COLLECT_SET + SIZE
在 Spark SQL 和 Hive 中,COLLECT_SET 会自动对分区内的值去重并返回一个集合(array),SIZE 则取其长度,效果等价于去重计数。
-
SIZE(COLLECT_SET(user_id) OVER (PARTITION BY city))—— 正确,且保留明细行 - 注意:
COLLECT_SET会忽略NULL,如需计入,先用COALESCE(user_id, 'NULL')处理 - 性能上,大数据量时比子查询更轻量;但集合过大可能触发内存溢出(如单个分区超百万唯一值)
通用兼容方案:子查询 + 窗口函数嵌套
如果目标环境不支持 COLLECT_SET(如标准 PostgreSQL),必须用两层结构:先去重聚合,再关联回明细。
- 第一层:
SELECT user_id, department FROM orders GROUP BY user_id, department - 第二层:对这个结果集再用
COUNT(*) OVER (PARTITION BY department) - 若需保留原始明细行(比如带时间戳),必须通过
JOIN或LEFT JOIN关联回原表,注意关联键是否唯一 - 该方案兼容性最好,但 IO 和 shuffle 开销明显高于
COLLECT_SET
别踩这些坑
APPROX_COUNT_DISTINCT 虽然支持窗口,但它是估算值,误差率约 2%;生产环境做精确统计时不能用它替代 COLLECT_SET 或子查询。
另外,DISTINCT 在 SELECT 顶层和窗口函数里是两回事:顶层 SELECT DISTINCT a, b 没问题,但 COUNT(DISTINCT a) OVER (...) 就一定报错——这点容易混淆。
真正要小心的是 NULL 和数据类型:两个 user_id 值看似相同,但一个是 INT、一个是 STRING,COLLECT_SET 会把它们当不同值处理,导致计数偏高。











