反常识的SQL条件筛选问题场景

引言

2023年10月16日,下午16点32分,天气晴。像往常一样,微信群里突然有人发了张图片,问这个SQL查询为什么这么慢?

不过以上面却有两个地方引起了我的注意:

  1. 单写JOIN。
  2. JOIN后面的ON条件使用了AND。

其中第一个很容易理解,只是这种写法不太推荐,单写JOIN等同于INNER JOIN

第二点则又勾起了我遥远的回忆,那是一个寒冷的下午。伴随着QQ的闪烁,我知道有人找我了,打开QQ,映入眼帘的是一个提问。

我们首先来看以下这两张SQL图片。

发现两个SQL的差别只是在于进行条件筛选的地方,一个是where一个是on。两个查询的数据出现了偏差不一致。这和前面中提到的第二点是一样的。这两个SQL我们第一时间理解是感觉它们的作用是一样的,虽然它们在一定场合下的确是一样的效果的。

当时不知道为啥,可能是疲于和上家公司的赔偿扯皮或者是被裁前的焦虑。我第一时间感觉以上两种写法并无什么差异,也没有时间和精力深究,于是只淡淡地回了三个字:

不知道!

你以为忘记了,其实只是藏起来了。被这今天的提问又从记忆的汪洋中找出来了,与它再一次的邂逅,我需要回答出这个问题了。

连接

首先我们来回顾一下SQL连接的知识点。下面这种图就可以清楚的表示出各种连接所取的数据。

其中我们主要讲下左右外连接:

  1. left join 是不管ON条件是否成立,左表的数据一定会有的。
  2. right join 是不管ON条件是否成立,右表的数据一定会有的。

如何区分左右表呢?很简单。

  1. 在join左边的表就是左表。
  2. 在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为什么这么慢,欢迎大家在评论区分析一波~

相关推荐
TDengine (老段)1 小时前
TDengine 新功能 VARBINARY 数据类型
大数据·c语言·数据库·时序数据库·tdengine·涛思数据
山山而川粤2 小时前
母婴用品系统|Java|SSM|JSP|
java·开发语言·后端·学习·mysql
yuenblue3 小时前
什么是ondelete cascade以及使用sqlite演示ondelete cascade使用案例
数据库·sqlite
howard_shooter3 小时前
Oracle Managed Files(OMF)
数据库·oracle
yangfeipancc4 小时前
数据库-用户管理
android·数据库
玉红7775 小时前
R语言的数据类型
开发语言·后端·golang
两点王爷6 小时前
Java读取csv文件内容,保存到sqlite数据库中
java·数据库·sqlite·csv
lvbu_2024war016 小时前
MATLAB语言的网络编程
开发语言·后端·golang
问道飞鱼6 小时前
【Springboot知识】Springboot进阶-实现CAS完整流程
java·spring boot·后端·cas
Q_19284999066 小时前
基于Spring Boot的电影网站系统
java·spring boot·后端