相关子查询是子查询中引用外部查询列、随外部每行执行一次的查询,用于按区域计算平均值并与当前行比较;必须用它才能实现“所在区域”而非全局平均值的筛选。

什么是相关子查询,为什么这里必须用它
直接用普通子查询(比如 SELECT * FROM stores WHERE sales > (SELECT AVG(sales) FROM stores))算的是全局平均值,不是“所在区域”的平均值。要按区域分组比对,必须让子查询能感知当前行的区域信息——这就得靠相关子查询:子查询里引用外部查询的列,每次随外部行执行一次。
WHERE 中嵌套 SELECT AVG() 的写法要点
核心是让子查询带上 WHERE region = t1.region 这个关联条件。注意别漏掉表别名,否则会报错或逻辑错误:
SELECT t1.store_id, t1.region, t1.sales FROM stores t1 WHERE t1.sales > ( SELECT AVG(t2.sales) FROM stores t2 WHERE t2.region = t1.region );
- 外部查询用别名
t1,子查询用t2,避免列名歧义 -
WHERE t2.region = t1.region是关键,它把子查询绑定到当前行的区域 - 子查询返回单个数值(
AVG聚合后天然满足),才能用于>比较 - 如果某区域只有 1 家店,
AVG就是它自己,此时该店不会被选出(因为不满足“超过”)
用 JOIN + 窗口函数替代的更高效写法
相关子查询在大数据量时可能慢(每行都执行一次子查询)。如果数据库支持窗口函数(如 PostgreSQL、SQL Server、MySQL 8.0+),用 AVG() OVER (PARTITION BY region) 更优:
SELECT store_id, region, sales
FROM (
SELECT store_id, region, sales,
AVG(sales) OVER (PARTITION BY region) AS avg_region_sales
FROM stores
) t
WHERE sales > avg_region_sales;
- 只扫描表一次,性能明显更好
-
PARTITION BY region等价于按区域分组求平均,但不压缩行数 - 注意:SQLite 和旧版 MySQL 不支持窗口函数,此时只能退回相关子查询
- 如果区域字段含
NULL,PARTITION BY会把所有NULL归为一组,相关子查询里也要留意region = t1.region对NULL不成立(需额外处理)
常见错误和调试技巧
执行报错或结果不对,大概率卡在这几个点:
- 子查询返回多行:检查是否漏了
WHERE t2.region = t1.region或写成了=以外的运算符 - 结果为空:确认区域字段拼写一致(大小写、空格、隐藏字符),可用
SELECT DISTINCT region FROM stores先核对 - 某些区域没结果:可能是该区域所有店销售额都 ≤ 平均值,或者存在
NULL销售额(AVG自动忽略NULL,但比较时sales > NULL结果为UNKNOWN,被过滤掉) - 用
EXISTS或IN替代?不行——它们解决的是“是否存在”,不是“数值比较”问题
真正麻烦的是区域划分动态变化或需要多层嵌套(比如“超过所在城市、所在省份、所在大区各自平均值的店”),这时窗口函数的可读性和维护性优势会立刻凸显。











