子查询如递归函数一样,有时侯能达到事半功倍的效果,但是其执行效率较低。与表连接相比,子查询比较灵活,方便,形式多样,适合作为查询的筛选条件,而表连接更适合查看多表的数据。
一般情况下,子查询会产生笛卡儿积,表连接的效率要高于子查询。因此在编写 SQL 语句时应尽量使用连接查询。
我们在《MySQL子查询》一节介绍表连接(内连接和外连接等)都可以用子查询替换,但反过来却不一定,有的子查询不能用表连接来替换。下面我们介绍哪些子查询的查询命令可以改写为表连接。
在检查那些倾向于编写成子查询的查询语句时,可以考虑将子查询替换为表连接,看看连接的效率是不是比子查询更好些。同样,如果某条使用子查询的 SELECT 语句需要花费很长时间才能执行完毕,那么可以尝试把它改写为表连接,看看执行效果是否有所改善。
下面讨论具体该如何做。
下面这条示例语句包含一个子查询,它会把 score 表里的考试成绩查询出来:
在编写以上语句时,可以不使用子查询,而是把它转换为一个简单的连接:
再来看另一个示例。下面这条查询语句可以把所有女生的考试成绩查询出来:
这条语句可以转换为以下连接:
我们可以发现这些子查询语句都遵从这样一种形式:
其中,column1 代表 table1 中的字段,column2a 和 column2b 代表 table2 表中的字段。这类查询都可以被转换为下面这种形式的连接查询:
在某些场合,子查询和关联查询可能会返回不同的结果。比如,当 table2 包含 column2a 的多个实例时,就会发生这种情况。这种形式的子查询只会为每个 column2a 值生成一个实例,而连接操作会为所有值生成实例,并且其输出会包含重复行。如果想要防止这种重复记录出现,就要在编写连接查询语句时使用 SELECT DISTINCT,而不能使用 SELECT。
另一种常见的子查询语句类型是:把存在于某个表里,但在另一个表里并不存在的那些值查找出来。“哪些值不存在”有关的问题通常都可以用 LEFT JOIN 来解决。
如下语句用来测试哪些学生没有出现在 absence 表里(用于查找全勤学生):
以上查询语句可以使用 LEFT JOIN 来改写:
通常情况下,如果子查询语句符合如下所示的形式:
那么可以把它改写为下面这样的连接查询:
这里需要假设 table2.column2 被定义成了 NOT NULL 的。
与 LEFT JOIN 相比,子查询更加直观。大部分人都可以毫无困难地理解“没被包含在...里面”的含义,因为它不是数据库编程技术带来的新概念。而“左连接”有所不同,很难用自然语言直观地描述出它的含义。