因为sql执行顺序中where在select(窗口函数计算阶段)之前,窗口函数尚未生成结果,故where中引用avg() over()会报错;正确做法是用cte或子查询先计算再过滤。

不能直接在 WHERE 中用 AVG() OVER() 做过滤——它还没算出来,数据库会报错。
WHERE 里写 AVG() OVER() 为什么报错
SQL 执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY,而窗口函数在 SELECT 阶段才计算。所以当你在 WHERE 里写 salary > AVG(salary) OVER(PARTITION BY dept),数据库根本还没生成这个值,自然无法比较。
常见错误现象:ERROR: window functions are not allowed in WHERE(PostgreSQL)、Invalid use of window function(MySQL 8.0+)或类似提示。
- 别试图绕过:加括号、子查询嵌套一层、用别名都无效——语法层就禁止
- 正确思路是“先算再筛”:把窗口结果放到子查询或 CTE 里,再在外层
WHERE过滤 - CTE 写法最清晰,也方便后续追加差值列、四舍五入等处理
用 CTE 实现“每组内超均值”的标准写法
这是目前兼容性最好、语义最直白的方案,所有主流引擎(MySQL 8.0+、PostgreSQL、SQL Server、Oracle)都支持:
WITH dept_stats AS (
SELECT id, name, dept, salary,
AVG(salary) OVER (PARTITION BY dept) AS avg_dept_salary
FROM employees
)
SELECT id, name, dept, salary, ROUND(avg_dept_salary, 2) AS avg_dept_salary
FROM dept_stats
WHERE salary > avg_dept_salary;
关键点:
-
PARTITION BY dept必须显式写出,漏掉就变成全表平均 - 别在
SELECT里对窗口函数套ROUND()后又在WHERE引用——ROUND(AVG() OVER(), 2)是表达式,不是列名,外层仍不可见 - 如果部门只有 1 人,
AVG()就等于他本人薪资,salary > avg_dept_salary永远为假,这类边界要心里有数 - 想看“高出多少”,直接加
salary - avg_dept_salary列即可,不用额外窗口
相关子查询能用,但性能差很多
如果你用的是不支持窗口函数的老版本(如 MySQL 5.7),或者临时凑合,可以用相关子查询:
SELECT e1.id, e1.name, e1.dept, e1.salary FROM employees e1 WHERE e1.salary > ( SELECT AVG(e2.salary) FROM employees e2 WHERE e2.dept = e1.dept );
但它的问题很实际:
- 对
e1的每一行,都要重新执行一次子查询——1000 行员工,就要算 1000 次部门平均,数据量一上去就明显变慢 - 无法利用索引加速聚合(
dept上有索引也没用,因为每次都是动态条件) - 如果
dept字段含NULL,e2.dept = e1.dept会失效(NULL = NULL为 false),这部分记录永远被过滤掉 - 某些 SQLite 版本不支持标量子查询出现在
WHERE右侧,必须改用JOIN模拟
GROUP BY + JOIN 看似直观,其实更绕
这种写法也能跑通,但逻辑冗余、维护成本高:
SELECT e.id, e.name, e.dept, e.salary, ROUND(d.avg_sal, 2) AS avg_dept_salary FROM employees e JOIN ( SELECT dept, AVG(salary) AS avg_sal FROM employees GROUP BY dept ) d ON e.dept = d.dept WHERE e.salary > d.avg_sal;
问题在于:
- 多了一次
GROUP BY聚合 +JOIN,执行计划通常比 CTE 多出一个哈希表构建和连接步骤 - 如果
employees表很大,JOIN可能触发磁盘临时表,而 CTE 中的窗口函数往往能流式计算 - 当需要扩展功能(比如同时统计标准差、计数、中位数),CTE 只需多加几列;而 JOIN 方案得反复写子查询或嵌套更多层
-
dept为NULL时,JOIN会自动丢弃这些行——这可能是你想要的,也可能不是,得额外加OR e.dept IS NULL处理
真正容易被忽略的是:窗口函数里的 NULL 处理逻辑。AVG() 默认跳过被聚合列中的 NULL,但分组键(如 dept)为 NULL 时,它们会被归为同一组——这不是 bug,是 SQL 标准行为,但上线后常被当成数据异常去查半天。










