子查询在使用方式上有些类似递归调用,虽然 SQL 编写起来相对简单,但执行效率通常偏低。表连接更适合读取多张表的数据,而子查询则更加灵活,常被用作查询条件筛选。

我们曾在《MySQL 子查询》中介绍过表连接。很多场景下,子查询可以改写为表连接,但并不是所有子查询都能 100% 被表连接替代。下面将说明哪些 MySQL 子查询可以优化为表连接:
在进行 MySQL 查询优化时,对于那些可以重写的子查询,需要重点评估它与表连接在性能上的差异。如果子查询已经出现明显的性能瓶颈,那么将其重构为 JOIN 语句,往往是非常有效的优化方法之一。同时,还应结合执行计划对比,验证 SQL 优化后的实际效果。
举个例子:
先准备一个子查询场景。有两张表:一张学生表,包含 id、name、gender 三个字段;另一张成绩表,包含 id、grade、summary 三个字段;
现在需要查询总评为'优秀'的学生,可以这样写:
SELECT * FROM studentsTab WHERE id IN (SELECT id FROM gradeTab WHERE summary = '优秀');
如果改写成表连接,则可以写成:
SELECT s.* FROM studentsTab INNER JOIN gradeTab g ON s.id = g.id WHERE g.summary = '优秀';
我们可以看到,如果子查询属于下面这种形式:
SELECT * FROM table1 WHERE column1a IN (SELECT column2a FROM table2 WHERE column2b = value);
那么对应的表连接通常可以写成:
SELECT table1.* FROM table1 INNER JOIN table2 ON table1.column1a = table2.column2a WHERE table2.column2b = value;
不过在一对多关系的场景中,等价的子查询与关联查询可能返回不同的行数,原因在于它们对 table2 中重复值的处理方式并不相同。此时,在关联查询中通常需要使用 SELECT DISTINCT 来代替 SELECT,以避免结果集重复。
如果我们需要查询非匹配值呢,例如查找总评不是'优秀'的学生,该如何编写 SQL?
可以这样写:
SELECT * FROM studentsTab WHERE id NOT IN (SELECT id FROM gradeTab WHERE summary = '优秀');
它对应的表连接写法如下:
SELECT s.* FROM studentsTab s LEFT JOIN gradeTab g ON s.id = g.id AND g.summary = '优秀' WHERE g.id IS NULL;
