写在前面:本篇为作者自学总结,学习内容为课堂所学和网络学习笔记汇总,对于内容引用部分在文中和文末注明。
文章仅供参考,如需深入了解,请查阅MySQL参考手册。附上下载链接:
链接:https://pan.baidu.com/s/1FpONA6oyI6O73m_ebN7J_Q
提取码:7pr8
复制这段内容后打开百度网盘手机App,操作更方便哦
目录
查(SELECT)的操作 (重点)
1. 补充知识:MySQL表的复制
2. SELECT语句的一般格式和讲解
2.1 SQL语句基本格式
2.2 SQL最基本的查询语句
3. 带条件的查询(WHERE)
3.1 比较搜索条件
3.2 复合比较搜索条件
3.3 范围搜索条件
3.4 设置搜索成员
3.5 模式匹配
3.6 NULL查询条件
4 查询结果的排序(ORDER BY)和返回指定数量的记录(LIMIT)
4.1 查询结果的排序(ORDER BY)
4.2 返回指定数出数目的记录(LIMIT)
5 连接字符串(CONCAT)和随机数(RAND)
5.1 连接字符串(CONCAT)
5.2 随机数(LIMIT)
6 聚合(Aggregates) --(COUNT, SUM, AVG, MIN, MAX)
6.1 COUNT
6.2 SUM
6.3 AVG
6.4 MIN
6.5 MAX
7 分组(Grouping) -- (GROUP BY )
7.1 GROUP BY与聚合函数配合使用
7.2 GROUP BY与HAVING配合使用
7.3 当GROUP BY与聚合函数,同时非聚合字段同时使用
7.4 分组聚合思想总结
8 MySQL多表查询
8.1 普通多表查询
8.2 嵌套查询或子查询
8.3 链接查询
9 自己的一些心得总结 (随着经验增加更新)
参考资料:
在之前的MySQL学习笔记(一)中已经总结了MySQL的基础概念, 在MySQL学习笔记(二)已经记录了增删改的命令,在本篇MySQL学习笔记(三)中将更新MySQL查询语句。
查询语句是MySQL的重难点,建议动手操作,跟着例子走一遍,加深理解。我在学习MySQL的时候使用的工具: MySQL Workbench, navicat。
增删改都会改变表,但是查询不会。接下来我们将讲解查询(SELECT)操作。
关于查询操作的讲解全部将基于user和class两个表。
/*student表*/ -- 创建student表 CREATE TABLE student( student_id SMALLINT(5) UNSIGNED NOT NULL AUTO_INCREMENT, student_name VARCHAR(20) NOT NULL, gender TINYINT(1) NOT NULL COMMENT '0为女生,1为男生', age TINYINT NOT NULL, class_id TINYINT(1) NOT NULL, PRIMARY KEY (student_id)); -- 向student表插入数据 INSERT INTO student(student_name, gender, age, class_id) VALUES('红红', 0, 16, 1); INSERT INTO student(student_name, gender, age, class_id) VALUES('李华', 1, 18, 5); INSERT INTO student(student_name, gender, age, class_id) VALUES('花花', 0, 14, 3); INSERT INTO student(student_name, gender, age, class_id) VALUES('明明', 1, 16, 5); INSERT INTO student(student_name, gender, age, class_id) VALUES('大明', 1, 20, 2); INSERT INTO student(student_name, gender, age, class_id) VALUES('贝贝', 1, 15, 1); INSERT INTO student(student_name, gender, age, class_id) VALUES('晶晶', 0, 16, 4); INSERT INTO student(student_name, gender, age, class_id) VALUES('欢欢', 0, 17, 3); INSERT INTO student(student_name, gender, age, class_id) VALUES('莹莹', 0, 18, 4); INSERT INTO student(student_name, gender, age, class_id) VALUES('妮妮', 0, 19, 4); SELECT * FROM student;
student表视图如下所示:
/*class表*/ -- 创建class表 CREATE TABLE class ( class_id TINYINT(1) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, class_name VARCHAR(10) NOT NULL ); -- 向class表插入数据 INSERT INTO class(class_name) VALUES('金融数学'); INSERT INTO class(class_name) VALUES('离散数学'); INSERT INTO class(class_name) VALUES('围棋'); INSERT INTO class(class_name) VALUES('古筝'); INSERT INTO class(class_name) VALUES('舞蹈'); SELECT * FROM class;
class表视图如下所示:
建议提前熟悉两个表中的数据,方便之后理解基于它们的操作。
MySQL也可以复制表,语句如下,我们创建了一个名为student1的表,它的字段属性和student一致,关键字是LIKE:
CREATE TABLE student1 LIkE student;
我们可以看到数据困中新建了一个student1的表:
查看两个表的字段信息,可以对比发现两个表的字段属性一致:
但是student1的内容为空,我们可以查看student1的试图来确定:
现在我们只是创建了一个和student属性一致的表,但是没有内容,接下开应该把student的内容赋值给student1:
INSERT INTO student1 SELECT * FROM student;
上面的语句将student表中的全部数据插入student1表中,我们用SELECT * FROM student1; 来查看一下student1的全部数据:
可以看到通过先赋值表,在插入数据,我们可以实现表的复制。
条款的顺序不能改变。只有SELECT和FROM是必需的。一般格式如下:
SELECT [DISTINCT | ALL]
{* | [columnExpression [AS newName]] [,...] }
FROM TableName [alias] [, ...]
[WHERE condition]
[GROUP BY columnList] [HAVING condition]
[ORDER BY columnList]
其中:
FROM 指定要使用的表
WHERE 筛选行
GROUP BY 形成具有相同列值的行组
HAVING 根据某些条件筛选组
SELECT 指定要在输出中显示的列
ORDER BY 指定输出的顺序
最基本的查询:
SELECT * FROM student;
*指的是全部列。该SQL语句选择student表中的全部数据,视图如下:
MySQL同样支持选择特定的字段。如果只想查询学生姓名和年龄,则将*换成学生姓名和年龄对应的字段名,用,连接,查找显示的字段顺序以我们写查找语句的时候的字段顺序为准:
SELECT student_name,age FROM student;
查询结果:
列数据中可能会有重复的数据,可以使用DISTINCT关键子来消除重复:
SELECT DISTINCT gender FROM student;
查询结果:
可以对查询的数据进行运算,如下:
SELECT age+2 AS agePlus2 FROM student;
在查询过程中对所有年龄进行了+2的数学运算,并通过AS将加2后的年龄重命名为了agePlus2,如果不选择重命名,则加2后的年龄的字段名还是age。修改名字主要是为了方便理解和避免误会。AS可以省略,上面的语句等同于SELECT age+2 agePlus2 FROM student; 。给给字段名起别名的时候,别名仅在查询结果显现,原表中的字段名和数据并未发生改变。查询结果如下:
WHERE后面一般带有查询条件,用来筛选满足条件的行。
比较搜索条件的时候,和我们数学常用的>,>=,=,<=,<,<>用法相同,注意不等于写为<>:
SELECT student_id,student_name, age FROM student WHERE age >= 18;
查询结果:
可以使用AND和OR来连接这些查询条件:
SELECT student_id,student_name, age FROM student WHERE age = 18 AND gender = 0;
查询结果:
可以使用BETWEEN a AND b,包括区间顶点a和b。下面的语句与SELECT student_id,student_name, age FROM student WHERE age>=16 AND age<=19;等效:
SELECT student_id,student_name, age FROM student WHERE age BETWEEN 16 AND 19;
查询结果:
可以使用IN加()来设置所需要选的成员。括号内是满足的条件,逻辑关系相当于OR。下面的查询等同于SELECT student_id,student_name, age FROM student WHERE age = 16 OR age = 19;:
SELECT student_id,student_name, age FROM student WHERE age IN(16, 19);
查询结果:
IN还有否定形式NOT IN,下面的查询语句等同于SELECT student_id,student_name, age FROM student WHERE age <>16 AND age <>19;,因为OR被否定,所以连接用AND:
SELECT student_id,student_name, age FROM student WHERE age NOT IN(16, 19);
查询结果:
虽然IN和OR可以互相替换,但当集合包含多个值时,IN更有效。
在class表中找到课程名中包含‘数学’的课程的所以信息:
SELECT * FROM class WHERE class_name LIKE '%数学%';
查询结果如下:
SQL有两个特殊的模式匹配符号:
%:零个或多个字符的序列;
_(下划线):任何单个字符。
SELECT * FROM class WHERE class_name LIKE '__数学'; 与
SELECT * FROM class WHERE class_name LIKE '%数学'; 的查询结果与上面的代码查询结果相同,但表达的含义不一样:'%数学%'表示包含“数学”的任意长度的字符序列,'__数学'表示长度为四的后两位为'数学'的字符序列,'%数学'表示末尾两个字为'数学'的任意长度的字符序列。
可以用IS NULL和 IS NOT NULL来进行查询。详情看MySQL学习笔记(一)的第五节的NULL的内容。注意区分NULL和'''。
使用ORDER BY对查询结果排序。排序分为升序ASC(从小到大),降序DESC(从大到小)和随机排列(RAND),本节只讲解升序和降序排列,随机排列将在下一节详述。
1)单列排序
选择学生的所有信息并以class_id从大到小的顺序排列:
SELECT * FROM student ORDER BY class_id DESC;
查询结果:
2)多列排序
从上面的结果可以知道,如果没有指定其他的排列顺序,在满足单列排序后,系统会自己选择行的排列顺序。如果我们可以自己设置行的排列顺序,需要我们指定次要顺序。
选择学生的所以信息并以学生class_id降序,gender升序的顺序排列:
SELECT * FROM student ORDER BY class_id DESC, gender;
如果是升序,可以加ASC关键字,如果没有加,则默认为升序;降序必须写DESC,不可省略。上面语句的查询结果:
注意ORDER BY的顺序对查询结果有影响,上面的查询是先以class_id降序排列,再在相同的class_id的行中按gender升序排列。如果将两者位置互换,例如下面的查询:
SELECT * FROM student ORDER BY gender, class_id DESC;
按照排序顺序,先以gender升序排列,再在gender相同的情况下以class_id降序的方式排列。查询结果:
使用LIMIT关键字。接受一个或两个数字参数,该参数必须是整数常量。语法为SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset;。
1)选择前n行(从第一行开始)
以下两种写法相同,都表示选取前3行。初始偏移量为0:
SELECT student_id, student_name, age FROM student LIMIT 3;
SELECT student_id, student_name, age FROM student LIMIT 0,3;
查询结果相同,如下所示:
2)选择从第n行开始选择m条记录(从第一行开始)
表示方法: LIMIT n-1, m。以下记录从第3行开始选取2行,即选取第3、4行:
SELECT student_id, student_name, age FROM student LIMIT 2,2;
查询结果如下:
将CONCAT函数括号里的数据连接起来,返回结果为连接参数产生的字符串。如下查询:
SELECT student_name,age, CONCAT(student_name,age) FROM student;
查询结果:
可以给我们连接的数据加别名以防止误会,同时便于理解,AS可以省略:
SELECT student_name,age, CONCAT(student_name,age) name_age FROM student;
查询结果:
可以自己添加任意字符:
SELECT student_name,age, CONCAT('合并:',student_name,'-',age,'岁') name_age FROM student;
查询结果:
如有任何一个参数为NULL ,则返回值为 NULL:
SELECT student_name,age, CONCAT('合并:',student_name,NULL,age,'岁') name_age FROM student;
查询结果:
RAND函数每次生成一个0到1的随机数,可以搭配ORDER BY来随机打乱数据表,每次输出结果各不相同。配合LIMIT函数可以用来随机生成一行记录,可以用来抽奖随机抽取一位幸运者等日常活动,很实用。
SELECT * FROM student ORDER BY RAND() LIMIT 1;
查询结果如下(注意每次生成结果不同):
MySQL有五种聚合函数:
COUNT 返回指定列中的值的个数。
SUM 返回指定列中的值之和。
AVG 返回指定列中值的平均值。
MIN 返回指定列中的最小值。
MAX 返回指定列中的最大值。
每个操作都对表的一列进行操作并返回一个值。COUNT、MIN和MAX适用于数字和非数字字段,但SUM和AVG只能用于数字字段。除了COUNT(*)之外,每个函数都首先消除空值,并且只对剩余的非空值进行操作。
COUNT(*)统计表中的所有行,而不管是否出现空值或重复值。可以在列名前使用DISTINCT来消除重复项。DISTINCT对MIN/MAX没有影响,但对SUM/AVG可能有影响。
统计student表的学生总数:
SELECT COUNT(*) 学生总数 FROM student;
统计结果:
统计学生中总共有几种年龄:
SELECT COUNT(DISTINCT age) FROM student;
查询结果:
计算所选列数据的总和。统计学生中年龄共有几种,并且算出全部学生的年龄之和:
SELECT COUNT(DISTINCT age), SUM(age) FROM student;
查询结果:
统计学生中年龄共有几种,并且算出这几种不同的年龄的总和是多少:
SELECT COUNT(DISTINCT age), SUM(DISTINCT age) FROM student;
计算所选列数据的平均数。统计学生中年龄共有几种,并且算出全部学生的平均年龄:
SELECT COUNT(DISTINCT age), AVG(age) FROM student;
查询结果:
统计学生中年龄共有几种,并且算出这几种不同的年龄的平均数是多少:
SELECT COUNT(DISTINCT age), AVG(DISTINCT age) FROM student;
查询结果:
计算所选列数据的最小数。统计学生中年龄共有几种,并且算出全部学生的最小年龄:
SELECT COUNT(DISTINCT age), MIN(age) FROM student;
查询结果:
统计学生中年龄共有几种,并且算出这几种不同的年龄的最小年龄是多少:
SELECT COUNT(DISTINCT age), MIN(DISTINCT age) FROM student;
查询结果:
从查询结果可以看出重复数据对MIN没有影响。
计算所选列数据的最大数。统计学生中年龄共有几种,并且算出全部学生的最大年龄:
SELECT COUNT(DISTINCT age), MAX(age) FROM student;
查询结果:
统计学生中年龄共有几种,并且算出这几种不同的年龄的最大年龄是多少:
SELECT COUNT(DISTINCT age), MAX(DISTINCT age) FROM student;
查询结果:
从查询结果可以看出重复数据对MAX没有影响。
当GROUP BY与聚合函数配合使用时,功能为分组后计算。
当GROUP BY与HAVING配合使用时,功能为分组后过滤。
当GROUP BY与聚合函数,同时非聚合字段同时使用时,非聚合字段的取值是第一个匹配到的字段内容,即id小的条目对应的字段内容。
GROUP BY的常规用法是配合聚合函数,利用分组信息进行统计。
计算student中男女生各多少人:
SELECT COUNT(student_id),gender FROM student GROUP BY gender;
查询结果:
配合HAVING进行筛选后过滤。在上述查询条件下继续筛选gender>0的数据:
SELECT COUNT(student_id),gender FROM student GROUP BY gender HAVING gender > 0;
查询结果:
SELECT COUNT(student_id), gender FROM student WHERE gender > 0;的查询结果与上面的查询结果相同。对同一需求的查询关系理解不同,查询语句也会不同,只要能正确得到所需数据且关系理解正确,选哪一种语句都可以。对于其他的查询语句同理。
非聚合条件字段的取值与数据写入的时间无关,非聚合字段的取值是第一个匹配到的字段内容,即student_id小的条目对应的字段内容:
SELECT student_id,COUNT(student_id),gender FROM student GROUP BY gender;
查询结果:
分组与聚合原理:
组是使用特定的条件将元数据进行划分为多个组。聚合是对每个分组中的数据执行某些操作,最后将计算结果进行整合。
分组与聚合的过程大概分三步:
拆分:将数据集按照一些标准拆分为若干组。
应用:将某个函数或者方法应用到每个分组。
合并:将产生的新值整合到结果对象中
1.普通多表查询
2.嵌套查询或子查询
3.链接查询
1)左链接
2)右链接
3)内链接
1)两表无条件查询:
SELECT * FROM student, class;
查询结果如下:
查询记录显示总共查询了(10乘以5)行。
由此可知两表无条件查询是对两表所有数据进行组合,数据量是两表条数乘积。
2)两表有条件查询
当student表中的student_id等于class表里的class_id的时候,选取student表里的student_id, student_name和age。其实为student表重命名为s,class表重命名为c,以便书写方便,简单易读:
SELECT s.student_id, s.student_name, s.age FROM student s, class c WHERE s.student_id = c.class_id;
查询结果:
SQL语句中可以嵌入SELECT查询,合起来被称为嵌套查询。嵌套查询是外层主查询和内层子查询的结合,是一种更复杂的查询。子查询还可以包括子查询,可以是多层子查询,主查询也可以包括多个子查询。子选择也可能出现在INSERT、UPDATE和DELETE语句中。
子查询就是括号中的查询。嵌套查询和子查询虽不是一个概念,但是相关的概念。
嵌套查询的格式:
其中括号外是主查询,括号内是子查询,合起来叫做嵌套查询。
1)子查询规则
ORDERBY子句不能用在子查询中(尽管它可以用在最外层的SELECT中)。子查询选择列表必须由单个列名(a single column name)或表达式(expression)组成,使用EXISTS的子查询除外。
默认情况下,列名引用子查询的FROM子句中的表名。可以通过使用别名引用中的表。(By default, column names refer to table name in FROM clause of subquery. Can refer to a table in FROM using an alias.)
当子查询是比较中的操作数时,子查询必须出现在右侧。子查询不能用作表达式中的操作数。
2)等号子查询
主查询和子查询是等号关系。举例,找到学金融数学的学生姓名。
我们需要从student表得到学生姓名,从class表得到学金融数学的信息,两个表靠class_id连接,嵌套查询的代码如下:
SELECT student_name FROM student WHERE class_id = (SELECT class_id FROM class WHERE class_name = '金融数学');
查询结果如下:
通过子查询选择和主查询相等的条件。
3) 带聚合的子查询
举例,找到学生比所有学生的平均年龄大的学生并求得他们比平均年龄大多少,代码如下:
SELECT student_name, age - (SELECT AVG(age) FROM student) AS AgeDiff FROM student WHERE age > (SELECT AVG(age) FROM student);
查询结果如下:
注意,不能使用“WHERE age > AVG(age)”。查询语句SELECT student_name, age - (SELECT AVG(age) FROM student AS AgeDiff FROM student WHERE age > AVG(age);会报错,错误如下:
相反,我们应该先使用子查询查找平均年龄(16.9000),然后使用外部选择(outer SELECT)查找工资高于此值的员工,逻辑等同于:
SELECT student_name, age - 16.9000 AS AgeDiff FROM student WHERE age > 16.9000;
4) 带in的子查询
IN关键字主要用于判断表达式是否在多值列表中。主选择进行多汁判断然后返回在多值列表中的记录。
举例,2)中举例找到学金融数学的学生姓名,我们用带IN的子查询可以改写为如下:
SELECT student_name FROM student WHERE class_id IN (SELECT class_id FROM class WHERE class_name = '金融数学');
查询结果如下:
与2)中结果一样。
5) 带any(some)或all的子查询
ANY和ALL可用于生成一列数字的子查询。对于ALL,条件只有在子查询生成的所有值都满足时才为true。对于ANY,如果子查询生成的任何值满足条件,则该条件将为true。如果子查询为空,则ALL返回true,ANY返回false。
SOME可以用来代替任ANY。
ANY(SOME):
举例,在student表中查找年龄大于至少一名其他学生的学生姓名,可以写成如下SQL语句:
SELECT student_name FROM student WHERE age > SOME (SELECT age FROM student);
查询结果如下:
可以看出来除了年龄最小的十四岁的花花外都被选择到了。将SOME换成ANY效果一样。
ALL:
举例,在student表中查找年龄大于class_id为1的学生的学生姓名,可以写成如下SQL语句:
SELECT student_name FROM student WHERE age > ALL (SELECT age FROM student WHERE class_id = 1);
查询结果如下:
其中class_id为1的学生的年龄分别为15和16岁,所以选择出来的学生是年龄大于16且class_id部位1的。
5) 带EXISTS的子查询
EXISTS和NOT EXISTS仅用于子查询。它们生成简单的真/假(true/false )结果。如果子查询返回的结果表中至少存在一行,则为True。如果子查询返回空结果表,则为False。NOT EXISTS是EXISTS的相反情况。
由于(NOT)EXISTS只检查子查询结果表中是否存在行,所以子查询可以包含任意数量的列。
举例:找到所有学金融数学或围棋的同学的姓名,SQL的包含EXISITS子查询的语句如下:
SELECT s.student_name FROM student s WHERE EXISTS (SELECT * FROM class c WHERE s.class_id = c.class_id AND c.class_name IN ("金融数学", "围棋"));
如果EXISTS后面的查询有满足的,则EXISTS后面的话可以理解为TRUE。
查询结果如下:
如果结果列来自同一个表,则可以使用子查询。如果结果列来自多个表,则必须使用联接(join)。要执行联接,请在FROM子句中包含多个表。使用逗号作为分隔符,通常包括WHERE子句来指定联接列。
可以对在FROM子句中命名的表使用别名。别名和表名之间用空格隔开。别名可用于在出现歧义时限定列名。
链接查询多用于复杂的连表查询,可以通过ON关键字后面的信息将几个表连在一起,方便我们进一步加条件进行筛选。
MySQL有多种连接方式,链接方式可以参考下图,我目前在工作实践中用的最多的是左链接(和右链接,二者只是链接方向不同,效果一样)。其他的链接方式这里不做总结,建议重点学会左链接,右链接则同理。
图片引用:sql左外连接和右外连接的区别
用了链接(join)后必用ON关键字,ON后面跟两个表的链接依据,在实际工作中多用索引进行链接。左链接和右链接用的较多,从逻辑上看只是把表放在左边或者右边的区别,在实际工作中我总结出了一个规律:对于左连接来说,如果我们需要的查询条件(WHERE后面的条件)主要是A表的,则把A表放在左边,A LEFT JOIN B ON A的条件。对于右链接同理,如果如果我们需要的查询条件(WHERE后面的条)主要是A表的,则把A表放在右边,B RIGHT JOIN A ON A的条件。
SQL的LEFT JOIN 、RIGHT JOIN 、INNER JOIN之间的区别:
我用的student表和class表不太适合举例表链接,建议自己往每个表中加入无法和其他表链接的独有的值和空值等,自己多练习几遍,便于理解。
1)左链接(LEFT JOIN)
我们先左连接一下,感受一下左连接是什么。让student表与class表左连接:
SELECT * FROM student s LEFT JOIN class c ON s.class_id = c.class_id;
链接后的试图如下:
我们的链接条件是两边的class_id相同。可以观察到上图两个表链接到了一起,且class_id相同。
在左连接后我们可以对链接后的表加筛选条件,举例我们想选择学金融数据学的同学的姓名,只需要加限制条件使得class_name为金融数学即可,SQL语句如下:
SELECT s.student_name FROM student s LEFT JOIN class c ON s.class_id = c.class_id WHERE c.class_name = "金融数学";
这里我们在WHERE后面的条件用别名c来专门指定了class_name属性是class中的,因为class_name只有class一个表有,所以哪怕不指明是哪个表的,直接用class_name = "金融数学"也是对的。这里我只是养成了标明属性来源的习惯。建议养成。
但是ON后面用了别名s和c来指明class_id分别是哪个表的很有必要,不然容易产生困惑。所以总的建议还是顺手指定表名比较好。
上面的SQL语句运行如下:
2)右链接(RIGHT JOIN)
左连接和右链接如果要效果一致,应该表的顺序相反,如果表的顺序没有换,则结果会变。如果把LEFT JOIN换成RIGHT JOIN,两个表的顺序也变了,语句如下:
SELECT * FROM class c RIGHT JOIN student s ON s.class_id = c.class_id;
表的逻辑空间顺序(左右)虽然变了,但是内容并不会变,视图如下:
这也就是为什么我说学会左连接即可,右链接只是变了顺序而已。
3)内链接(INNER JOIN)
INNER JOIN 与 JOIN 是相同的。INNER JOIN 关键字在表中存在至少一个匹配时返回行。我这里的例子不足以理解INNER JOIN:
SELECT * FROM student s INNER JOIN class c ON s.class_id = c.class_id;
视图如下:
课堂资料:
X. Zhu(2019). Introduction to Database [PowerPoint slides]. Available: https://ice.xjtlu.edu.cn/course/view.php?id=293
Mysql数据库设计与操作 (access time: 2021/6/23)
网络资料:
mySql limit 函数后的参数是什么意思 (access time: 2021/6/30)
MySQL之CONCAT()的用法 (access time: 2021/6/30)
mysql中group by 的用法解析 (access time: 2021/6/30)
https://blog.csdn.net/weixin_43336305/article/details/94445810 (access time: 2021/6/30)
嵌套查询和子查询是一样的概念吗? (access time: 2021/7/20)
【SQL】嵌套查询与子查询 (access time: 2021/7/20)
SQL INNER JOIN 关键字 (access time: 2021/7/20)