窗口函数不改变行数,仅在每行附加计算结果;PARTITION BY仅划分计算范围而不折叠数据;实现降维需配合QUALIFY或子查询过滤,因窗口结果为虚拟列不可直接WHERE筛选。

窗口函数不改变行结构,只附加计算结果
窗口函数不会减少行数,是因为它根本没做“归并”这件事。它只是在原始每一行上,额外算一个值,然后贴回去——就像给每条记录加个标签,而不是把几条记录揉成一条。ROW_NUMBER() 给每行编号,AVG(salary) OVER (PARTITION BY dept) 给每行标上所在部门平均薪资,原始字段一个不少,行数也一个不丢。
PARTITION BY 不是 GROUP BY,它不分组、不折叠
PARTITION BY 看起来像分组,但语义完全不同:它只是划定计算范围,不触发聚合逻辑。你写 COUNT(*) OVER (PARTITION BY dept),数据库会在每个部门内部各自数一遍,结果广播到该部门所有员工行;而 GROUP BY dept 是强制把所有 tech 员工压成一行,其他字段必须进 GROUP BY 或套聚合函数,否则直接报错 column "name" must appear in the GROUP BY clause。
- 漏写
PARTITION BY→ 默认全表一个分区,COUNT(*) OVER ()每行都返回总行数 - 写错粒度(比如只按年份分区,但业务要按月+岗位)→ 结果看起来“重复”或“不准”,其实是分区切歪了
- 误把
PARTITION BY当GROUP BY用 → 以为能省掉外层查询,结果发现明细还在,没达到“取每组一条”的目标
想降维就得叠加过滤,窗口函数自己不筛选
窗口函数输出和输入行数严格一致,所以它不能单独实现“每个部门只留最高薪那条”。常见错误是写完 ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) 就停手,结果返回全部员工——编号是算出来了,但没过滤。真正落地得靠:
-
QUALIFY(Snowflake / BigQuery / Doris):QUALIFY ROW_NUMBER() OVER (...) = 1 - 子查询嵌套(PostgreSQL / MySQL 8.0+):
SELECT * FROM (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t) s WHERE rn = 1 -
DISTINCT ON(PostgreSQL):SELECT DISTINCT ON (dept) * FROM t ORDER BY dept, salary DESC
注意:ORDER BY 在 OVER 里不是可选的——ROW_NUMBER() 必须指定排序,否则行为未定义;而 AVG() 类函数不写 ORDER BY 虽不报错,但默认帧类型(ROWS 还是 RANGE)因数据库而异,可能引发时间字段重复时的累计偏差。
容易被忽略的虚拟列本质
窗口函数产生的列是运行时计算的虚拟列,不能直接用于 WHERE 过滤——你不能写 WHERE ROW_NUMBER() OVER (...) = 1,会报语法错。必须用 QUALIFY,或者套一层子查询/CTE。这点和 GROUP BY 输出的物理行完全不同:GROUP BY 后的列可直接 WHERE,但窗口结果只能靠外层再筛。这也是为什么“先算再滤”这个动作无法省略,哪怕看起来多了一层嵌套。

















