年度平均销售额需先通过子查询计算全年各月销售额均值,再在外层筛选高于该均值的月份;子查询必须按月聚合并返回单值,且YEAR条件需在内外两层同时限定,否则导致范围错误。

直接用子查询算年度平均再筛选月份
核心思路是:先在内层查出全年销售额均值,外层把每个月的销售额和这个均值对比。不能用 AVG() 直接套在 GROUP BY month 外面——那会算每月均值,不是全年均值。
常见错误是写成这样(错):
SELECT month, SUM(sales) FROM orders GROUP BY month HAVING SUM(sales) > AVG(SUM(sales))
AVG(SUM(sales)) 在 SQL 标准里非法,多数数据库(如 MySQL 8.0+、PostgreSQL)会报错 Invalid use of group function。
- 必须把年度均值计算单独放在子查询里,作为标量值参与比较
- 子查询必须返回单个数值,所以要用
SELECT AVG(total_monthly)而不是SELECT AVG(sales)(后者没按月聚合,逻辑错) - 如果表里有空月份(比如 2 月没数据),这个查询不会返回它——这符合“有销售才统计”的业务直觉
两层嵌套的标准写法(以 MySQL / PostgreSQL 为例)
第一层(内层)按月聚合,第二层(外层)拿结果和年度均值比。注意子查询必须加别名(如 t),否则部分数据库报错 Every derived table must have its own alias。
SELECT month, monthly_sales
FROM (
SELECT MONTH(order_date) AS month, SUM(amount) AS monthly_sales
FROM orders
WHERE YEAR(order_date) = 2023
GROUP BY MONTH(order_date)
) AS t
WHERE monthly_sales > (
SELECT AVG(monthly_sales)
FROM (
SELECT SUM(amount) AS monthly_sales
FROM orders
WHERE YEAR(order_date) = 2023
GROUP BY MONTH(order_date)
) AS avg_sub
);
-
YEAR(order_date) = 2023要写在两层里,不能只写在外层——内层子查询不带这个条件会算错年度范围 - MySQL 中
MONTH()返回 1–12;PostgreSQL 要用EXTRACT(MONTH FROM order_date),函数名不同但结构一致 - 如果订单表数据量大,这个写法会扫描表两次。生产环境建议先把月度汇总存成临时表或物化视图
为什么不用窗口函数替代?
虽然 AVG() OVER() 看起来更简洁,但它没法直接过滤——窗口函数结果不能出现在 WHERE 子句中。你得再套一层:
SELECT month, monthly_sales
FROM (
SELECT MONTH(order_date) AS month,
SUM(amount) AS monthly_sales,
AVG(SUM(amount)) OVER() AS yearly_avg
FROM orders
WHERE YEAR(order_date) = 2023
GROUP BY MONTH(order_date)
) AS t
WHERE monthly_sales > yearly_avg;
- 这其实是三层逻辑(聚合 → 窗口计算 → 过滤),可读性不如纯子查询直观
- 某些旧版 MySQL(
- 如果只需要“哪些月份超均值”,不需要展示均值本身,子查询更轻量
容易被忽略的边界情况
真实业务里这几个点常导致结果偏差,但很少人检查:
- 日期字段类型是
DATETIME还是DATE?用MONTH(order_date)没问题,但若字段含时分秒且跨日(比如 2023-01-31 23:59:59),不影响月份提取 - 存在跨年订单(如预付款记在 2022 年,发货在 2023 年)?确认
amount是按哪个时间点计入——是下单时间、支付时间还是确认收货时间 - 平均值是算术平均,但如果有某个月份销售额为 0(比如系统故障没记录),它会被计入分母。是否该排除零值月份?此时子查询里要加
HAVING SUM(amount) > 0

















