前面四篇,WHERE 后面跟的都是你自己写死的条件:amount > 100、city = '杭州'。条件里的数你事先知道。

现在换一个问题:哪些订单的金额高于所有订单的平均值?

这道题不一样了。条件里的那个数,你自己都不知道是多少,得先让数据库算出来。这一篇就讲这一件事:当筛选条件本身需要一个查询结果时,怎么把两条查询串成一条。

先把数据摆出来

orders 表还是前面那张,9 行:

order_iduser_idcityamount
10018823杭州129.00
10029107上海58.50
10038823杭州320.00
10049310NULL76.00
10059107杭州45.00
10069107上海210.00
10079310上海91.50
10088823NULL19.90
10099500北京199.00

先手算一遍,后面才好核对:九笔加起来是 1148.90,除以 9,平均值 1148.90÷9≈127.661148.90 \div 9 \approx 127.66。所以“高于平均值”的应该是 129.00、320.00、210.00、199.00 这四笔。

卡住的地方:条件里的数要先算

最直接的想法是分两步:先单独跑一条 SELECT AVG(amount) FROM orders; 拿到 127.66,再把它填进条件 WHERE amount > 127.66。结果确实对,但这个做法站不住脚——明天新增一笔订单,平均值就不是 127.66 了,条件立刻过期;每换一个时间范围,你都得再手工抄一遍。

你想的是:能不能让数据库自己先算平均值,再拿这个结果去筛?答案是可以,但写法有讲究。

为什么不能直接写成 WHERE amount > AVG(amount)

很多人第一次会这么写,然后收到一条报错:

1SELECT order_id, city, amount
2FROM orders
3WHERE amount > AVG(amount);
4
5-- ERROR 1111 (HY000): Invalid use of group function

“Invalid use of group function” 的意思是:聚合函数用错了地方 1。要理解为什么错,得回想一条查询内部的执行顺序:

FROM→WHERE→GROUP BY 与聚合→HAVING→SELECT→ORDER BY\text{FROM} \rightarrow \text{WHERE} \rightarrow \text{GROUP BY 与聚合} \rightarrow \text{HAVING} \rightarrow \text{SELECT} \rightarrow \text{ORDER BY}

关键在这两段的先后:WHERE 是逐行判断,它跑的时候聚合还根本没开始算。 数据库手里只有一行行的订单,没有“一组数”,AVG(amount) 无从下手。这跟第三篇里 HAVING 能放聚合函数是同一件事的两面——HAVING 站在聚合之后,WHERE 站在聚合之前。顺带说一句,这里的 HAVING 也帮不上忙:它筛的是“组”,而这道题要留下的是单笔订单。

办法:把算平均值的那条语句放进括号

真正要用的是这个写法:

1SELECT order_id, city, amount
2FROM orders
3WHERE amount > (SELECT AVG(amount) FROM orders);

括号里的 (SELECT AVG(amount) FROM orders) 就是一个 子查询:一条完整的 SELECT,被当成一个值嵌进另一条语句里。

它的执行方式和你想的一致:括号里那条先独立跑一次,得到一个数 127.66;然后外层拿这个 127.66 当成普通数字,逐行判断 amount > 127.66。所以结果和手算一致:

order_idcityamount
1001杭州129.00
1003杭州320.00
1006上海210.00
1009北京199.00

1001 的 129.00 只比 127.66 高一点点,照样进;1007 的 91.50 离平均线还差一截,进不来。这类“刚好跨过线”的行,是检验条件写对没写对的最好样本。

“标量”是什么意思,以及它带来的一个限制

括号里那种只返回 一行一列、也就是一个格子 的子查询,叫 标量子查询。“标量”就是“单个值”的意思,和你熟悉的 127.66、'杭州' 是同一类东西,区别只是这个值由查询算出来。

为什么值得专门给它一个名字?因为它能放的位置,取决于它返回的形状。amount > (...) 里,> 的右边只能站一个值。所以:

括号里那条查询只有在你确定它最多返回一行时,才能这样用。 如果它返回好几行,数据库不知道该拿哪一个去比较,直接报错:

1SELECT user_name FROM users
2WHERE user_id = (SELECT user_id FROM orders);
3
4-- ERROR 1242 (21000): Subquery returns more than 1 row

这是最容易踩的一步。orders.user_id 有 9 行、8823 还重复三次,所以括号里吐出 9 行,= 右边站不住 2。

而 AVG(amount) 那种写法为什么稳?因为不带 GROUP BY 的聚合函数,无论表里有多少行,永远塌成一行。“聚合 + 没有 GROUP BY”是标量子查询最可靠的来源。

当括号里给出的是一份名单,就该用 IN

换一个问题:哪些用户下过单? 这次从 users 出发,条件是“这个 user_id 出现在 orders 的那一列里”:

1SELECT user_id, user_name FROM users
2WHERE user_id IN (SELECT user_id FROM orders);

结果是三行:8823 阿May、9107 老周、9310 小林。Tony(9402)一单没下,不在名单里,所以不出现;9500 在名单里,但 users 里根本没有这个人,所以也不影响结果。

这里括号里返回的是 一列、九行——一份名单,不是单个值。IN 正好吃这个形状:它问的是“这个值在不在右边这一堆值里”,右边有几个都不妨碍。

一个细节值得留意:名单里 8823 重复了三次,结果并没有变成三行阿May。IN 只做“在不在”的判断,不会一对一把外层行复制出去。 这和上一篇的 JOIN 正好是鲜明对照:同样是“下过单的用户”,用 JOIN 拼回三笔订单就是三行,用 IN 仍然是一行。

判断该用哪种:只看括号里的形状

这两种写法不是“高级”和“低级”的区别,而是 形状匹配 的问题。写之前先在心里问一句:括号里那条查询会吐出什么?

<figure>
绘制中
</figure>

养成一个习惯:拿不准时,先把括号里那条语句单独跑一次,看它到底返回几行几列。 这一步花三秒,能省掉大半的报错。

一个必须知道的地方:括号里的查询看不见外面的条件

这是子查询里最容易悄悄算错的地方。题目改成:杭州的订单里,哪些金额高于杭州的平均值?

杭州只有三笔:129.00、320.00、45.00,平均是 494.00÷3≈164.67494.00 \div 3 \approx 164.67。所以正确答案只有 320.00 一笔。

如果你顺手写成这样:

1SELECT order_id, city, amount
2FROM orders
3WHERE city = '杭州'
4  AND amount > (SELECT AVG(amount) FROM orders);

结果会是两行:129.00 和 320.00。多出来的 129.00 是错的。

原因就是前面说的:括号里那条查询是 独立跑的一遍,它面对的是整张 orders 表,算出来是 127.66 这个全表平均,它根本不知道外层还写了 city = '杭州'。要让平均值也只算杭州,必须在子查询自己的 WHERE 里再写一次条件:

1SELECT order_id, city, amount
2FROM orders
3WHERE city = '杭州'
4  AND amount > (SELECT AVG(amount) FROM orders WHERE city = '杭州');

这样括号里算出 164.67,结果只剩 320.00 一笔。

把这件事记住成一句话:子查询是一个自足的查询,你在它里面没写的条件,它一律不认。 所以每次用子查询,都要追问一句:括号里那张表,应该被哪些条件限制?

现在你能回答什么

子查询要做的事只有一件:把一条查询的结果交给另一条查询用。写之前先确定括号里的查询会返回什么形状——一个值就放到比较运算符右边,一份名单就放 IN 的括号里;再检查括号里该有的筛选条件有没有漏。剩下的,就是多跑几次、拿手算的数对一对。

还有两件相邻的事这一篇没讲:括号里引用外层某一列的值(相关子查询),以及把子查询放进 FROM 里当一张临时表来用——后者是下一步把复杂问题拆成几步的关键,留到下一篇。