应全删最高分和最低分,但业务可能要求各删一个;需用相关子查询或窗口函数实现分组极值剔除,并注意NULL、空集及重复值处理。

用子查询排除最大最小值再算平均分
直接在 AVG() 外层套一层子查询,把原始数据中 MAX(score) 和 MIN(score) 对应的行干掉——但注意:如果最高分或最低分有多个,只删一个还是全删?默认是全删,这常导致结果偏高。
- 先查出最高分和最低分:
SELECT MAX(score), MIN(score) FROM scores - 再用
WHERE score NOT IN (SELECT MAX(score) FROM scores), (SELECT MIN(score) FROM scores)排除——但这样写会报错,因为NOT IN不能直接接两个标量子查询 - 正确写法是合并成单个子查询:
WHERE score NOT IN (SELECT MAX(score) FROM scores UNION SELECT MIN(score) FROM scores) - 更稳妥的做法是用
NOT EXISTS或两次AND score != (SELECT MAX(...)) AND score != (SELECT MIN(...)),避免NULL导致整条NOT IN判定为UNKNOWN
GROUP BY 场景下每个分组单独剔除极值
比如按班级算「去掉最高最低后的平均分」,必须保证每个班级各自找自己的极值,而不是全表统算。一不小心就会用错作用域。
- 错误示范:
AVG(CASE WHEN score NOT IN (SELECT MAX(score), MIN(score) FROM scores) THEN score END)—— 这里子查询没关联班级,所有班级共用同一组极值 - 正确做法:用相关子查询,让内层绑定外层的
class_id:WHERE score NOT IN (SELECT MAX(s2.score) FROM scores s2 WHERE s2.class_id = s1.class_id UNION SELECT MIN(s2.score) FROM scores s2 WHERE s2.class_id = s1.class_id) - 性能上,这种写法在大数据量时容易变慢;如果表有索引(如
(class_id, score)),优化器可能走索引范围扫描
处理重复极值时的逻辑歧义
当一个班有 5 个学生,分数是 [80, 85, 85, 90, 90],你打算去掉几个 80 和几个 90?SQL 默认是全去,结果只剩中间三个 85,平均还是 85。但业务上可能只要各删一个。
- 若需「各删一个极值」,得用窗口函数加排序:
ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score)和ROW_NUMBER() OVER (PARTITION BY class_id ORDER BY score DESC) - 然后筛选:既不是第一个(最小)也不是最后一个(最大)的记录,即
rnum_asc > 1 AND rnum_desc > 1 - 注意:
RANK()和DENSE_RANK()在并列时行为不同,这里必须用ROW_NUMBER()才能确保每行唯一序号 - MySQL 8.0+、PostgreSQL、SQL Server 都支持;老版本 MySQL 只能靠变量模拟,稳定性差
AVG 计算前务必确认 NULL 和空集行为
AVG() 会自动忽略 NULL,但如果你剔除极值后某组只剩空集(比如只有两个学生,全被剔掉了),AVG() 返回 NULL,而不是 0 或报错——这点常被前端代码误判为“没数据”而非“无有效分数”。
- 检查是否为空集:可以在外层加
COUNT(*),或用CASE WHEN COUNT(*) = 0 THEN NULL ELSE AVG(score) END - 别依赖
COALESCE(AVG(...), 0)直接补 0,除非业务明确允许用 0 占位 - 某些场景(如考试系统)要求至少保留 3 个有效分数才计算均值,那就得在子查询里加
HAVING COUNT(*) >= 3
真正麻烦的是极值判断与分组、空值、重复值三者叠加时的组合逻辑,光看语句看不出问题,得拿几组边界数据手工推一遍。










