应使用percent_rank()窗口函数剔除分组内最高最低5%的值再求平均:需搭配partition by和order by,排序字段不能为null,否则整行被过滤;若需固定剔除数量则改用row_number()。

用 PERCENT_RANK() 剔除分组内最高最低 5% 的值再算平均
直接用 AVG() 算分组均值会受异常订单、测试数据或录入错误拖偏结果。更稳妥的做法是按分组对数值排序,去掉两端尾部数据——PERCENT_RANK() 是最直观的工具,它为每行返回 0 到 1 之间的归一化排名位置。
实操时注意三点:
-
PERCENT_RANK()是窗口函数,必须搭配OVER (PARTITION BY ... ORDER BY ...)使用,漏掉PARTITION BY就变成全表排名 - 排序字段不能为
NULL,否则该行PERCENT_RANK()返回NULL,后续WHERE过滤会丢弃整行(包括本应有效的数据) - 想剔除固定数量(如每组头尾各 2 条),改用
ROW_NUMBER() OVER (...) AS rn,再配合COUNT(*) OVER (PARTITION BY ...)算总数来判断边界
示例:剔除每部门薪资最高的 5% 和最低的 5%,算剩余人的平均薪资
SELECT dept, AVG(salary) AS avg_salary
FROM (
SELECT dept, salary,
PERCENT_RANK() OVER (PARTITION BY dept ORDER BY salary) AS pr
FROM employees
) t
WHERE t.pr > 0.05 AND t.pr <h3>用 <code>NTILE(20)</code> 实现近似等频分桶后舍弃首尾桶</h3><p><code>NTILE(n)</code> 把每组数据强行切成 <code>n</code> 个大小尽可能相等的桶,编号从 1 到 <code>n</code>。想剔除约 10% 的极端值,设 <code>n = 20</code>,舍弃第 1 桶(最小端)和第 20 桶(最大端)即可,比 <code>PERCENT_RANK()</code> 更少受重复值影响。</p><p>但要注意:</p>
- 当某组行数不能被 20 整除时,前几桶会多 1 行,所以实际剔除比例略高于 10%(例如 19 行分 20 桶,有 1 个桶含 2 行,其余为 1 行)
-
NTILE()要求ORDER BY子句非空,且不接受NULL排序键;若存在大量相同 salary,分桶结果可能把本该同属中间段的数据错劈到不同桶 - 别用
NTILE(10)再舍弃 1 和 10 —— 这实际只剔除了约 20%,容易误判
示例:按城市统计房价均值,排除最低一档和最高一档的楼盘
SELECT city, AVG(price) AS avg_price
FROM (
SELECT city, price,
NTILE(20) OVER (PARTITION BY city ORDER BY price) AS tile
FROM listings
) t
WHERE t.tile NOT IN (1, 20)
GROUP BY city;
遇到 GROUP BY 与窗口函数嵌套报错怎么办
常见错误是写成 SELECT dept, AVG(salary), PERCENT_RANK() OVER (...) FROM employees GROUP BY dept —— 这会报错,因为 PERCENT_RANK() 是窗口函数,不能和普通聚合混在同一层级。
正确解法只有一条路:先用子查询或 CTE 把窗口计算做完,再在外层做 GROUP BY 和聚合:
- 子查询必须包含所有后续
GROUP BY和SELECT中用到的列(比如dept,salary,pr) - CTE 更清晰,但某些老版本 MySQL(
- 别试图在
HAVING里过滤窗口结果——HAVING只能作用于聚合结果,而PERCENT_RANK()是行级值
性能敏感场景下如何避免全量排序
当单组数据量极大(如百万级日志 per user),PERCENT_RANK() 或 NTILE() 会强制触发完整排序,IO 和 CPU 开销陡增。
可降级处理:
- 先用
APPROX_PERCENTILE()(Trino/Presto/Spark SQL 支持)估算上下界,再用WHERE salary BETWEEN low_bound AND high_bound快速过滤,最后对子集精确求均值 - 对时间序列类数据,用
WHERE event_time > NOW() - INTERVAL '7 days'先缩小范围,再分组去极值——业务上往往近期数据才关键 - 如果数据库支持物化视图(如 PostgreSQL +
CREATE MATERIALIZED VIEW),把带PERCENT_RANK()的中间结果固化,查均值时直接扫物化视图
真正难的不是写出语法正确的 SQL,而是确认「极端值」的定义是否匹配业务语义——是按全局分布切?还是每组独立切?有没有要保留的特殊零值?这些没理清,函数再准也白搭。











