应使用窗口函数ROW_NUMBER()为每行按score升序/降序排名,再通过rn_asc>1且rn_desc>1过滤,确保仅剔除一行最小值和一行最大值,避免重复值误删或多删,精准满足“去掉一个最高分、一个最低分”需求。

WHERE不能直接用MAX/MIN过滤原始表的极值
直接写 WHERE score NOT IN (SELECT MAX(score), MIN(score) FROM scores) 看似合理,但会出错:子查询返回多列时,NOT IN 无法匹配元组;更关键的是,若存在多个相同最大值或最小值(比如三个100分),这样会把所有100分全剔除,而非仅剔除“一个最大”和“一个最小”——这不符合“排除一个最高分、一个最低分”的常见需求(如评委打分场景)。
用窗口函数给每行打上排名再过滤
真正可控的做法是借助 ROW_NUMBER() 或 RANK() 标记出“第一个最大”和“第一个最小”。注意:必须按 score 排序,并用唯一键(如 id)破歧义,否则同分时排序不稳定:
SELECT AVG(score)
FROM (
SELECT score,
ROW_NUMBER() OVER (ORDER BY score ASC, id ASC) AS rn_asc,
ROW_NUMBER() OVER (ORDER BY score DESC, id DESC) AS rn_desc
FROM scores
) t
WHERE rn_asc > 1 AND rn_desc > 1;
这个逻辑确保只去掉**一行最小值**和**一行最大值**(哪怕有重复分,也只各去一行)。
-
rn_asc = 1是最小分中 id 最小的那一行 -
rn_desc = 1是最大分中 id 最大的那一行(避免与上一行冲突) - 若表只有1行,结果为空;2行则过滤后无数据,AVG返回NULL——符合预期
MySQL 8.0+ 可用CTE简化写法,但注意LIMIT不支持在子查询中直接用于聚合前过滤
有人尝试 (SELECT score FROM scores ORDER BY score LIMIT 1 OFFSET 1) 想跳过最小值,但这只能取单值,无法同时剔除两端。正确思路仍是先标记再排除:
WITH ranked AS (
SELECT score,
ROW_NUMBER() OVER (ORDER BY score) AS rnk_low,
ROW_NUMBER() OVER (ORDER BY score DESC) AS rnk_high
FROM scores
)
SELECT AVG(score)
FROM ranked
WHERE rnk_low > 1 AND rnk_high > 1;
MySQL 8.0+、PostgreSQL、SQL Server 都支持;SQLite 3.25+ 也支持窗口函数,但旧版需用自连接模拟排名。
如果只要剔除所有最大/最小值(而非各一行),改用NOT IN + 子查询即可
这是另一种语义:比如“去掉所有95分以上和50分以下的异常数据”,那就不是“极值各一个”,而是按阈值过滤。此时可用:
SELECT AVG(score) FROM scores WHERE score > (SELECT MIN(score) FROM scores) AND score < (SELECT MAX(score) FROM scores);
但注意:若全表分数相同(如全是80),该条件导致无结果;且它剔除的是**所有等于MIN或MAX的值**,不是“一个最值”。业务含义不同,选错就偏了。
真正难的不是写SQL,而是厘清“排除极端值”具体指什么:是统计学上的IQR截断?还是人工评审里去掉一个最高一个最低?前者要算四分位数,后者才用窗口函数标序号。没想清楚这点,代码写得再顺也答非所问。

















