当前位置:首页 > 问答 > 正文

后浪带你慢慢聊聊MySQL里那些子查询的事儿,怎么用才不迷糊

(引用来源:后浪带你慢慢聊聊MySQL里那些子查询的事儿,怎么用才不迷糊)

大家好,我是后浪,今天咱们不整那些虚头巴脑的理论,就坐下来像朋友聊天一样,聊聊MySQL里那个让人又爱又恨的家伙——子查询,很多新手朋友一听到“子查询”仨字就头大,感觉一层套一层的,脑子立马就成浆糊了,别怕,今天我就带你把它捋清楚,保证让你用起来不再迷糊。

子查询到底是个啥?

说人话就是,在一个查询语句里面,又塞进去了另一个完整的查询语句,外面那个叫主查询,里面被塞进去的那个就是子查询,你可以把它想象成剥洋葱,或者套娃,主查询是最大的那个娃,它想干一件事,但自己一下子搞不定,需要先问里面的小娃要一个结果,然后拿着这个结果再去干自己的事。

举个例子你就明白了,假设我们有两张表,一张是学生表,里面有学生的学号、姓名;另一张是成绩表,记录着每个学号对应的课程成绩。

现在老板提了个需求:找出所有考试成绩超过了平均分的学生名单。

你一想,这得分成两步走: 第一步:先算出所有成绩的平均分是多少,这个活儿,就是一个单独的查询。 第二步:拿着这个平均分,去成绩表里找哪些学生的成绩比这个数大,然后再关联到学生表把名字找出来。

如果我们用子查询,就可以把这两步合成一步写完:

SELECT 姓名 FROM 学生表
WHERE 学号 IN (
    SELECT 学号 FROM 成绩表
    WHERE 成绩 > (SELECT AVG(成绩) FROM 成绩表)
);

你看,最里面那个(SELECT AVG(成绩) FROM 成绩表)就是一个子查询,它的任务很单纯,就是算出平均分,然后外面一层子查询拿着这个平均分去过滤出成绩达标的学号,最外层的主查询再根据学号把姓名找出来,这就是一个典型的子查询套子查询。

子查询都能放在哪儿?

这是容易迷糊的点之一,子查询很灵活,但常见的主要是三个位置:

  1. 放在 WHERE 或 HAVING 子句后面:就像上面的例子,用来做过滤条件,这时候子查询返回的结果,通常是给主查询的、>INNOT INEXISTS这些操作符来用的,这是最常用的场景。
  2. 放在 SELECT 子句后面:也就是我们常说的“标量子查询”,它必须保证只返回一个值(一行一列),相当于给查询结果里增加一列计算出来的信息。 我想在查询每个学生成绩的同时,在旁边显示全班的平均分做对比:
    SELECT 学号, 成绩, (SELECT AVG(成绩) FROM 成绩表) AS 全班平均分
    FROM 成绩表;

    这样每一行成绩旁边,都会显示同一个平均分数值。

  3. 放在 FROM 子句后面:这时候子查询相当于临时生成了一张虚拟表,主查询就把这个临时表当做一张真正的表来用。这种情况下,一定要给这个临时表起一个别名! 我们想从平均分大于80分的班级里找学生:
    SELECT * FROM
    (SELECT 班级, AVG(成绩) as 班平均分 FROM 成绩表 GROUP BY 班级) AS temp_table
    WHERE temp_table.班平均分 > 80;

    这里的(SELECT ...)生成的临时表,就必须用AS temp_table给它起个名,不然MySQL不知道咋称呼它。

怎么用才不迷糊?核心心法就两条

  1. 由内向外,层层拆解:这是最重要的心法!遇到复杂的嵌套子查询,别试图一眼看穿整个语句。先从最里面、最核心的那个子查询开始看和理解,把它单独拿出来在数据库里执行一下,看看它返回的是什么结果(是一个数字?是一列值?还是一张表?),理解了最里面的,再把它当做已知条件,去看外面一层是怎么利用这个结果的,一层层向外扩,就像剥洋葱,思路就清晰了。

  2. 看清子查询返回的是什么:子查询返回的结果类型,直接决定了你在主查询里能用什么操作符。

    • 如果子查询返回的是一个值(一行一列),比如平均分、总人数,那你前面可以用、><这些比较符号。
    • 如果子查询返回的是一列值(多行一列),比如一组成绩、一批学号,那你前面通常要用INNOT INANYSOMEALL这些操作符,用可就错了。
    • 如果子查询返回的是一张表(多行多列),那它通常只能放在FROM后面当临时表。

提个醒:小心性能陷阱

子查询虽然写起来思路清晰,但有时候效率可能不高,特别是当数据量非常大的时候,因为MySQL可能会很实在地执行很多次子查询,比如一个WHERE 学号 IN (SELECT ...),如果外层有10万条记录,MySQL会不会傻乎乎地执行10万次子查询呢?早期版本可能会,但现在优化器聪明多了,很多时候会把它转换成连接查询(JOIN)来提高效率。

当你发现子查询跑得慢的时候,可以试着把它改写成JOIN连接查询,往往会有奇效,但这又是另一个话题了。

对付子查询,别怕它复杂,由内向外拆解”和“看清返回结果”这两大法宝,多写多练,你就能把它治得服服帖帖,让它成为你SQL工具箱里的一把利器,好了,今天关于子查询的闲聊就先到这,希望能帮到你!

后浪带你慢慢聊聊MySQL里那些子查询的事儿,怎么用才不迷糊