引言
2023年10月16日,下午16点32分,天气晴。像往常一样,微信群里突然有人发了张图片,问这个SQL查询为什么这么慢?
不过以上面却有两个地方引起了我的注意:
- 单写JOIN。
- JOIN后面的ON条件使用了AND。
其中第一个很容易理解,只是这种写法不太推荐,单写JOIN
等同于INNER JOIN
。
第二点则又勾起了我遥远的回忆,那是一个寒冷的下午。伴随着QQ的闪烁,我知道有人找我了,打开QQ,映入眼帘的是一个提问。
我们首先来看以下这两张SQL图片。
发现两个SQL的差别只是在于进行条件筛选的地方,一个是where
一个是on
。两个查询的数据出现了偏差不一致。这和前面中提到的第二点是一样的。这两个SQL我们第一时间理解是感觉它们的作用是一样的,虽然它们在一定场合下的确是一样的效果的。
当时不知道为啥,可能是疲于和上家公司的赔偿扯皮或者是被裁前的焦虑。我第一时间感觉以上两种写法并无什么差异,也没有时间和精力深究,于是只淡淡地回了三个字:
不知道!
你以为忘记了,其实只是藏起来了。被这今天的提问又从记忆的汪洋中找出来了,与它再一次的邂逅,我需要回答出这个问题了。
连接
首先我们来回顾一下SQL连接的知识点。下面这种图就可以清楚的表示出各种连接所取的数据。
其中我们主要讲下左右外连接:
- left join 是不管ON条件是否成立,左表的数据一定会有的。
- right join 是不管ON条件是否成立,右表的数据一定会有的。
如何区分左右表呢?很简单。
- 在join左边的表就是左表。
- 在join右边的表就是右表。
On 和 Where 的区别
我们通过学习发现在进行join连接的时候,可以通过与之配套出现的on来进行条件筛选。同时我们在学习SQL的时候,学到了使用Where来进行条件筛选。那他们两个有何差别呢?
- 连接表时,SQL 会根据连接条件生成一张新的临时表。
ON
就是连接条件,它决定临时表的生成。 WHERE
是在临时表生成以后,再对临时表中的数据进行过滤,生成最终的结果集,这个时候已经没有 JOIN-ON 了。
所以总结来说就是:SQL 先根据 ON 生成一张临时表,然后再根据 WHERE 对临时表进行筛选。
答案
通过以上两点,我们可以解答出这个问题了。
上图的第一种写法使用的是将所有的条件筛选写在了on里面,并且使用了外连接left join
。同时我们回想起我们连接中所强调的,left join是不会管ON条件是否成立,无论为true
还是false
。所以这个条件是没有真正筛选出来符合条件的数据,存在了一些不符合条件的数据。
而where中的条件则会对On生成的临时表数据进行了再次筛选,那么此时查出来的数据是一定符合Where条件的。所以第一种的数据会比第二种的数据多。
我们可以通过创建两种简单的表来验证一下。
创建一张student
表
SQL
create table student
(
id int not null
primary key,
name varchar(255) null,
age int(2) null
);
INSERT INTO edu_1.student (id, name, age) VALUES (1, 'zs', 18);
INSERT INTO edu_1.student (id, name, age) VALUES (2, 'ls', 19);
INSERT INTO edu_1.student (id, name, age) VALUES (3, 'ww', 25);
一张course
课程表
SQL
create table course
(
id int not null
primary key,
subject varchar(255) null,
score int(10) null,
student_id int null
);
INSERT INTO edu_1.course (id, subject, score, student_id) VALUES (1, '数学', 90, 1);
INSERT INTO edu_1.course (id, subject, score, student_id) VALUES (2, '语文', 120, 1);
INSERT INTO edu_1.course (id, subject, score, student_id) VALUES (3, '英语', 130, 1);
INSERT INTO edu_1.course (id, subject, score, student_id) VALUES (4, '数学', 110, 2);
INSERT INTO edu_1.course (id, subject, score, student_id) VALUES (5, '语文', 125, 2);
INSERT INTO edu_1.course (id, subject, score, student_id) VALUES (6, '英语', 145, 2);
首先我们使用left join
通过student_id
对两个表进行关联。
SQL
SELECT *
FROM student s
LEFT JOIN course c ON (s.id = c.student_id)
得到以上结果集。其中学生ww没有课程记录,因为使用的是left join
所以左表ww这条记录是一定有的。
接着我们想在此基础上,筛选出学生年龄小于等于20的。我们首先在on里面增加条件。
SQL
SELECT *
FROM student s
LEFT JOIN course c ON (s.id = c.student_id and s.age <= 20)
查出的结果和上面的一样,ww这条没有被筛选掉。left join是不会管ON条件是否成立,无论为true
还是false
。
接着我们使用Where
进行条件筛选
SQL
SELECT *
FROM student s
LEFT JOIN course c ON (s.id = c.student_id)
WHERE s.age <= 20
得出了我们想要的结果了,ww被剔除了。通过外连接首先生成了上面那种带有7条记录的临时表,然后Where
关键字按条件进行了筛选~
问题解答完毕,逝去的时间如何找寻呢~。。。
至于群里那个SQL为什么这么慢,欢迎大家在评论区分析一波~