正确统计分组内分布须用窗口函数,PARTITION BY 须与业务分组维度严格一致;ROW_NUMBER() 仅编号不处理并列,分位分析应选 PERCENT_RANK() 或 NTILE();NTILE(N) 是行数均分而非数值切分,分位点计算需用 PERCENTILE_CONT 或 APPROX_PERCENTILE;避免窗口函数嵌套,优先用 CTE 分步计算。

窗口函数怎么写才能正确统计分组内分布
直接用 GROUP BY 会把行数压平,没法保留原始数据做分布分析;必须用窗口函数,且 PARTITION BY 要和业务分组维度严格一致。
常见错误是把 ORDER BY 写进窗口定义里却没想清楚排序目的——比如只为了编号就用 ROW_NUMBER(),但若要算百分位就得用 PERCENT_RANK() 或 NTILE(),顺序错结果全偏。
-
ROW_NUMBER()按指定顺序编号,相同值也强分先后,不适合等值合并场景 -
RANK()和DENSE_RANK()处理并列时行为不同:前者跳号(1,1,3),后者不跳(1,1,2),选哪个取决于你是否接受“第三名空缺” - 计算分位数优先用
PERCENT_RANK()(返回 0~1 区间),别手写(rank-1)/(total-1),它自动处理边界和重复值
NTILE(4) 分四分位为什么结果不均衡
NTILE() 是“尽量均分”,不是“按数值切分”。它把当前窗口的行数硬拆成 N 组,每组行数相差最多为 1。所以即使数据本身集中在某一段,分组后各桶的数值范围也可能重叠甚至完全一样。
典型误用:拿 NTILE(4) 当「按销售额切 25%/50%/75% 分位点」来用——它做不到,那是 PERCENTILE_CONT 或 APPROX_PERCENTILE 的事(取决于数据库支持)。
- PostgreSQL 支持
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY sales)算中位数 - MySQL 8.0+ 用
PERCENT_RANK() OVER (ORDER BY sales)配合过滤近似找分位点 - BigQuery 用
APPROX_QUANTILES(sales, 4)返回数组,更省资源但非精确
窗口函数嵌套导致性能崩了怎么办
在子查询里套多层窗口函数(比如外层再对 ROW_NUMBER() 结果做 AVG() 窗口)会让执行计划无法下推,尤其数据量一过百万,响应时间可能从毫秒跳到分钟。
真正需要的是“先分组、再算分布指标、最后聚合”,而不是靠窗口函数硬叠。多数情况可拆成两步:CTE 先算出每个分组的分布值(如每个用户的订单金额排名),再在外层按需聚合。
- 避免
SELECT AVG(ROW_NUMBER() OVER (...))这类写法,ROW_NUMBER()是整数序列,平均值无业务意义 - 如果只是要每个分组的 top 3,用
WHERE rn 过滤比在窗口里再套聚合快得多 - 分区字段加索引能显著加速
PARTITION BY user_id ORDER BY created_at类窗口
不同数据库对窗口函数的支持差异在哪
语法看着像,但关键行为不统一。比如 FRAME 子句默认行为就差很多:ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 在 PostgreSQL 和 SQL Server 是默认帧,但在 MySQL 8.0 默认是 RANGE 帧,遇到相同排序值会把它们全拉进来,结果突然变大。
还有 LEAD()/LAG() 的默认填充值:SQLite 返回 NULL,而某些旧版 Hive 会报错,必须显式写 LAG(val, 1, 0)。
- PostgreSQL:支持完整标准,
EXCLUDE CURRENT ROW可控性最强 - MySQL 8.0+:不支持
EXCLUDE,帧类型切换需显式声明ROWS或RANGE - Spark SQL:
PERCENT_RANK()和CUME_DIST()行为与标准一致,但NTILE()在空值处理上略保守
跨库迁移时,最易被忽略的是排序字段含 NULL 的情况:ORDER BY score DESC 中 NULL 默认排最前还是最后,各库策略不同,直接影响 ROW_NUMBER() 编号顺序。

















