浅尝一下NOT EXISTS
最近老婆在看视频学习MySQL,然后碰到了这样一道习题:有三个表,分别记录学生、课程,以及学生选修了什么课程的信息,问如何用NOT EXISTS
找出选修了所有课程的学生。
为了避免想破脑袋编造一些尴尬的学生姓名和课程名,我简化了一下习题中的表的结构,只留下它们的ID列。建表语句如下
1 | -- 学生表 |
还需要给它们塞入一些示例数据
1 | INSERT INTO `student` (`id`) VALUES (1), (2), (3), (4), (5); |
显然,只有id
列的值为1和5的学生是选修了全部课程的。用NOT EXISTS
写出来的SQL语句如下
1 | SELECT * |
在DBEaver中运行后的结果为
正确地找出了两个选修了所有课程的学生的id
。
如何理解双重NOT EXISTS
当第一次被请教这道习题的时候,我其实并不能理解NOT EXISTS
的含义。直到后来去看EXISTS
的文档,才顿悟了上面的SQL。
我的理解方法是将双重NOT EXISTS
转换为三层循环。以上面的SQL为例,转述为人话就是:找出student
表中所有的、没有任何一门course
表中的课程是没有选修的、的学生——双重的 没有。
转换为三层循环大概长这样
1 | for (const student of students) { |
NOT EXISTS
的本质
即使不强行理解,也可以让MySQL明确告知双重NOT EXISTS
是怎么运作的。用EXPLAIN
解释上面的SQL的结果如下图所示
MySQL的EXPLAIN命令的文档中说明了如何解读执行计划
EXPLAIN returns a row of information for each table used in the SELECT statement. It lists the tables in the output in the order that MySQL would read them while processing the statement. This means that MySQL reads a row from the first table, then finds a matching row in the second table, and then in the third table, and so on. When all tables are processed, MySQL outputs the selected columns and backtracks through the table list until a table is found for which there are more matching rows. The next row is read from this table and the process continues with the next table.
以上面的EXPLAIN
为例,MySQL从student
表中读出一行,再从course
表中读取一行,最后从elective
表中读取一行,然后看看WHERE
子句是否能够被满足。如果可以,就输出从student
表中读出来的这行数据。上图第2和第3行的select_type
都是DEPENDENT SUBQUERY
,表示它们依赖于“外层”的查询上下文——elective
的WHERE
子句依赖于student
和course
中读出来的行。
似乎和方才的三重循环有异曲同工之妙呢。
后记
像NOT EXISTS
这么“高阶”的功能我从未在业务代码中读过和使用过——别说NOT EXISTS
,就算是EXISTS
也是从未有之,甚至连子查询也极少。毕竟“正经的互联网公司”只是把MySQL当妹妹当一个具备复杂查询查询功能的key-value数据库来使用(笑
比起双重NOT EXISTS
,我更可能凭直觉写出基于子查询的解决方法
1 | SELECT * |
我甚至觉得会有人把数据库里的行读进内存然后用应用层代码来找出选修了全部课程的学生!
全文完。