mysql多表联合查询

1. 什么是多表联合查询

前面所讲的查询语句都是针对一个表的,但是在关系型数据库中,表与表之间是有联系的,所以在实际应用中,经常使用多表查询。多表查询就是同时查询两个或两个以上的表。 在 MySQL 中,多表查询主要有交叉连接、内连接、外连接、分组查询与子查询等5种。

2. 多表联合查询优点

1, 缓存效率更高, 许多应用程序可以方便地缓存单表查询对应的结果对象. 如果关联中的某个表发生了变化, 那么就无法使用查询缓存了, 而拆分后, 如果某个表很少改变, 那么基于该表的查询就可以重复利用查询缓存结果了.

2, 多表信息联合的列表页面分页显示, 只需要显示一部分的数据, 如果是多表联合查询那要把所有数据联结查出来再执行 limit, 如果是多次单表查询, 先对单表进行筛选, 先执行 limit 再与其余表去关联, 数据量会大大减小

3, 如果数据库没有进行读写分离 (主从备份), 在并发量高的时候, 由于写表会加排他锁, 把多表联合查询改成单表查询后锁的粒度变小, 减少了锁的竞争

4, 在数据量变大之后, 普遍会采用分库分表的方法来缓解数据库的压力, 采用单表查询比多表联合查询更容易进行分库, 不需要对 sql 语句进行大量的修改, 更易扩展. 分库分表的中间件一般对跨库 join 都支持不好

5, 查询本身效率也可能会有所提升. 查询 id 集的时候, 使用 IN() 代替关联查询, 可以让 MySQL 按照 ID 顺序进行查询, 这可能比随机的关联要更高效.

6, 业务高速增长时, 数据库作为最底层, 最容易遇到瓶颈, 单机数据库计算资源很贵, 数据库同时要服务写和读, 都需要消耗 CPU, 为了能让数据库的吞吐变得更高,

而业务又不在乎那几百微妙到毫秒级的延时差距, 业务会把更多计算放到 service 层做, 毕竟计算资源很好水平扩展, 数据库很难啊, 这是一种重业务, 轻 DB 的架构

7, 可以减少冗余记录的查询, 在应用层做关联查询, 意味着对于某条记录应用只需要查询一次, 而在数据库中做关联查询, 则可能需要重复地访问一部分数据.

3. 交叉连接(CROSS JOIN)

3.1 笛卡尔积

交叉连接(CROSS JOIN):有两种,显式的和隐式的2种,一般用来返回连接表的笛卡尔积。 笛卡尔积(Cartesian product)是指两个集合 X 和 Y 的乘积。 例如,有 A 和 B 两个集合,它们的值如下:

 A = {1,2}
 B = {3,4,5}

集合 A×B 和 B×A 的结果集分别表示为:

 A×B={(1,3), (1,4), (1,5), (2,3), (2,4), (2,5) };
 B×A={(3,1), (3,2), (4,1), (4,2), (5,1), (5,2) };

以上 A×B 和 B×A 的结果就叫做两个集合的笛卡尔积。

并且,从以上结果我们可以看出:

  • 两个集合相乘,不满足交换率,即 A×B≠B×A。

  • A 集合和 B 集合的笛卡尔积是 A 集合的元素个数 × B 集合的元素个数。

多表查询遵循的算法就是以上提到的笛卡尔积,表与表之间的连接可以看成是在做乘法运算。在实际应用中,应避免使用笛卡尔积,因为笛卡尔积中容易存在大量的不合理数据,简单来说就是容易导致查询结果重复、混乱。

3.2 交叉连接

 #SELECT <字段名> FROM <表1> CROSS JOIN <表2> [WHERE子句];
 #SELECT <字段名> FROM <表1>, <表2> [WHERE子句];
 字段名:需要查询的字段名称。
 <表1><表2>:需要交叉连接的表名。
 WHERE 子句:用来设置交叉连接的查询条件
 #创建school库 创建tb_students_info表
 mysql> create database school;
 Query OK, 1 row affected (0.01 sec)
 
 mysql> use school;
 Database changed
 mysql> create table tb_students_info(id int not null primary key auto_increment,name varchar(50),age tinyint,sex varchar(10),height int,course_id int);
 Query OK, 0 rows affected (0.01 sec)
 mysql> desc tb_students_info;
 +-----------+-------------+------+-----+---------+----------------+
 | Field     | Type       | Null | Key | Default | Extra         |
 +-----------+-------------+------+-----+---------+----------------+
 | id       | int(11)     | NO   | PRI | NULL   | auto_increment |
 | name     | varchar(50) | YES |     | NULL   |               |
 | age       | tinyint(4) | YES |     | NULL   |               |
 | sex       | varchar(10) | YES |     | NULL   |               |
 | height   | int(11)     | YES |     | NULL   |               |
 | course_id | int(11)     | YES |     | NULL   |               |
 +-----------+-------------+------+-----+---------+----------------+
 6 rows in set (0.01 sec)
 
 mysql>
 #插入表tb_students_info数据
 mysql> insert tb_students_info(name,age,sex,height,course_id) values('Dany',25,'Male',160,1),('Green',23,'Male',158,2),('Henry',23,'Female',185,1),('Jane',22,'Male',162,3),('Jim',24,'Female',175,2),('John',21,'Male',172,4),('Lily',22,'Male',165,4),('Susan',23,'Male',170,5),('Thomas',22,'Female',178,5),('Tom',23,'Female',165,5);
 Query OK, 10 rows affected (0.01 sec)
 Records: 10 Duplicates: 0 Warnings: 0
 mysql> select * from tb_students_info;
 +----+--------+------+--------+--------+-----------+
 | id | name   | age | sex   | height | course_id |
 +----+--------+------+--------+--------+-----------+
 |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  2 | Green |   23 | Male   |    158 |         2 |
 |  3 | Henry |   23 | Female |    185 |         1 |
 |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  5 | Jim   |   24 | Female |    175 |         2 |
 |  6 | John   |   21 | Male   |    172 |         4 |
 |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  8 | Susan |   23 | Male   |    170 |         5 |
 |  9 | Thomas |   22 | Female |    178 |         5 |
 | 10 | Tom   |   23 | Female |    165 |         5 |
 +----+--------+------+--------+--------+-----------+
 10 rows in set (0.00 sec)
 
 mysql>
 #创建tb_course表
 mysql> create table tb_course(id int not null primary key auto_increment,course_name varchar(50));
 Query OK, 0 rows affected (0.00 sec)
 #插入tb_course表数据
 mysql> insert tb_course(course_name) values('Java'),('Mysql'),('Python'),('Go'),('C++');
 Query OK, 5 rows affected (0.00 sec)
 Records: 5 Duplicates: 0 Warnings: 0
 
 mysql> select * from tb_course;
 +----+-------------+
 | id | course_name |
 +----+-------------+
 |  1 | Java       |
 |  2 | Mysql       |
 |  3 | Python     |
 |  4 | Go         |
 |  5 | C++         |
 +----+-------------+
 5 rows in set (0.00 sec)
 #使用 CROSS JOIN 查询出两张表中的笛卡尔积 5*10 返回50条记录
 mysql> SELECT * FROM tb_course CROSS JOIN tb_students_info;
 +----+-------------+----+--------+------+--------+--------+-----------+
 | id | course_name | id | name   | age | sex   | height | course_id |
 +----+-------------+----+--------+------+--------+--------+-----------+
 |  1 | Java       |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  2 | Mysql       |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  3 | Python     |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  4 | Go         |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  5 | C++         |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  1 | Java       |  2 | Green |   23 | Male   |    158 |         2 |
 |  2 | Mysql       |  2 | Green |   23 | Male   |    158 |         2 |
 |  3 | Python     |  2 | Green |   23 | Male   |    158 |         2 |
 |  4 | Go         |  2 | Green |   23 | Male   |    158 |         2 |
 |  5 | C++         |  2 | Green |   23 | Male   |    158 |         2 |
 |  1 | Java       |  3 | Henry |   23 | Female |    185 |         1 |
 |  2 | Mysql       |  3 | Henry |   23 | Female |    185 |         1 |
 |  3 | Python     |  3 | Henry |   23 | Female |    185 |         1 |
 |  4 | Go         |  3 | Henry |   23 | Female |    185 |         1 |
 |  5 | C++         |  3 | Henry |   23 | Female |    185 |         1 |
 |  1 | Java       |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  2 | Mysql       |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  3 | Python     |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  4 | Go         |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  5 | C++         |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  1 | Java       |  5 | Jim   |   24 | Female |    175 |         2 |
 |  2 | Mysql       |  5 | Jim   |   24 | Female |    175 |         2 |
 |  3 | Python     |  5 | Jim   |   24 | Female |    175 |         2 |
 |  4 | Go         |  5 | Jim   |   24 | Female |    175 |         2 |
 |  5 | C++         |  5 | Jim   |   24 | Female |    175 |         2 |
 |  1 | Java       |  6 | John   |   21 | Male   |    172 |         4 |
 |  2 | Mysql       |  6 | John   |   21 | Male   |    172 |         4 |
 |  3 | Python     |  6 | John   |   21 | Male   |    172 |         4 |
 |  4 | Go         |  6 | John   |   21 | Male   |    172 |         4 |
 |  5 | C++         |  6 | John   |   21 | Male   |    172 |         4 |
 |  1 | Java       |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  2 | Mysql       |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  3 | Python     |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  4 | Go         |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  5 | C++         |  7 | Lily   |   22 | Male   |    165 |         4 | 
|  1 | Java       |  8 | Susan |   23 | Male   |    170 |         5 | 
|  2 | Mysql       |  8 | Susan |   23 | Male   |    170 |         5 | 
|  3 | Python     |  8 | Susan |   23 | Male   |    170 |         5 | 
|  4 | Go         |  8 | Susan |   23 | Male   |    170 |         5 | 
|  5 | C++         |  8 | Susan |   23 | Male   |    170 |         5 | 
|  1 | Java       |  9 | Thomas |   22 | Female |    178 |         5 | 
|  2 | Mysql       |  9 | Thomas |   22 | Female |    178 |         5 | 
|  3 | Python     |  9 | Thomas |   22 | Female |    178 |         5 | 
|  4 | Go         |  9 | Thomas |   22 | Female |    178 |         5 | 
|  5 | C++         |  9 | Thomas |   22 | Female |    178 |         5 | 
|  1 | Java       | 10 | Tom   |   23 | Female |    165 |         5 | 
|  2 | Mysql       | 10 | Tom   |   23 | Female |    165 |         5 | 
|  3 | Python     | 10 | Tom   |   23 | Female |    165 |         5 | 
|  4 | Go         | 10 | Tom   |   23 | Female |    165 |         5 | 
|  5 | C++         | 10 | Tom   |   23 | Female |    165 |         5 | 
+----+-------------+----+--------+------+--------+--------+-----------
50 rows in set (0.01 sec) 
由运行结果可以看出,tb_course 和 tb_students_info 表交叉连接查询后,返回了 50 条记录。可以想象,当表中的数据较多时,得到的运行结果会非常长,而且得到的运行结果也没太大的意义。所以,通过交叉连接的方式进行多表查询的这种方法并不常用,我们应该尽量避免这种查询。 
#查询 tb_course 表中的 id 字段和 tb_students_info 表中的 course_id 字段相等的内容 
mysql> select * from tb_course cross join tb_students_info where tb_students_info.course_id = tb_course.id; 
+----+-------------+----+--------+------+--------+--------+-----------
| id | course_name | id | name   | age | sex   | height | course_id | 
+----+-------------+----+--------+------+--------+--------+-----------
|  1 | Java       |  1 | Dany   |   25 | Male   |    160 |         1 | 
|  2 | Mysql       |  2 | Green |   23 | Male   |    158 |         2 | 
|  1 | Java       |  3 | Henry |   23 | Female |    185 |         1 | 
|  3 | Python     |  4 | Jane   |   22 | Male   |    162 |         3 | 
|  2 | Mysql       |  5 | Jim   |   24 | Female |    175 |         2 | 
|  4 | Go         |  6 | John   |   21 | Male   |    172 |         4 | 
|  4 | Go         |  7 | Lily   |   22 | Male   |    165 |         4 | 
|  5 | C++         |  8 | Susan |   23 | Male   |    170 |         5 | 
|  5 | C++         |  9 | Thomas |   22 | Female |    178 |         5 | 
|  5 | C++         | 10 | Tom   |   23 | Female |    165 |         5 | 
+----+-------------+----+--------+------+--------+--------+-----------
10 rows in set (0.00 sec) 
#更换位置查询结果相同  
mysql> select * from tb_course cross join tb_students_info where tb_course.id = tb_students_info.course_id; 
+----+-------------+----+--------+------+--------+--------+-----------
| id | course_name | id | name   | age | sex   | height | course_id | 
+----+-------------+----+--------+------+--------+--------+-----------
|  1 | Java       |  1 | Dany   |   25 | Male   |    160 |         1 | 
|  2 | Mysql       |  2 | Green |   23 | Male   |    158 |         2 | 
|  1 | Java       |  3 | Henry |   23 | Female |    185 |         1 | 
|  3 | Python     |  4 | Jane   |   22 | Male   |    162 |         3 | 
|  2 | Mysql       |  5 | Jim   |   24 | Female |    175 |         2 | 
|  4 | Go         |  6 | John   |   21 | Male   |    172 |         4 | 
|  4 | Go         |  7 | Lily   |   22 | Male   |    165 |         4 | 
|  5 | C++         |  8 | Susan |   23 | Male   |    170 |         5 | 
|  5 | C++         |  9 | Thomas |   22 | Female |    178 |         5 | 
|  5 | C++         | 10 | Tom   |   23 | Female |    165 |         5 | 
+----+-------------+----+--------+------+--------+--------+-----------
10 rows in set (0.00 sec) 
mysql> select * from tb_students_info cross join tb_course where tb_course.id = tb_students_info.course_id; 
+----+--------+------+--------+--------+-----------+----+-------------
| id | name   | age | sex   | height | course_id | id | course_name | 
+----+--------+------+--------+--------+-----------+----+-------------
|  1 | Dany   |   25 | Male   |    160 |         1 |  1 | Java       | 
|  2 | Green |   23 | Male   |    158 |         2 |  2 | Mysql       | 
|  3 | Henry |   23 | Female |    185 |         1 |  1 | Java       | 
|  4 | Jane   |   22 | Male   |    162 |         3 |  3 | Python     | 
|  5 | Jim   |   24 | Female |    175 |         2 |  2 | Mysql       | 
|  6 | John   |   21 | Male   |    172 |         4 |  4 | Go         | 
|  7 | Lily   |   22 | Male   |    165 |         4 |  4 | Go         | 
|  8 | Susan |   23 | Male   |    170 |         5 |  5 | C++         | 
|  9 | Thomas |   22 | Female |    178 |         5 |  5 | C++         | 
| 10 | Tom   |   23 | Female |    165 |         5 |  5 | C++         | 
+----+--------+------+--------+--------+-----------+----+-------------
10 rows in set (0.00 sec) 
#使用order by排序course_id 
mysql> select * from tb_students_info cross join tb_course where tb_course.id = tb_students_info.course_id order by tb_course.id; 
+----+--------+------+--------+--------+-----------+----+-------------
| id | name   | age | sex   | height | course_id | id | course_name | 
+----+--------+------+--------+--------+-----------+----+-------------
|  1 | Dany   |   25 | Male   |    160 |         1 |  1 | Java       | 
|  3 | Henry |   23 | Female |    185 |         1 |  1 | Java       | 
|  5 | Jim   |   24 | Female |    175 |         2 |  2 | Mysql       | 
|  2 | Green |   23 | Male   |    158 |         2 |  2 | Mysql       | 
|  4 | Jane   |   22 | Male   |    162 |         3 |  3 | Python     | 
|  7 | Lily   |   22 | Male   |    165 |         4 |  4 | Go         | 
|  6 | John   |   21 | Male   |    172 |         4 |  4 | Go         | 
|  9 | Thomas |   22 | Female |    178 |         5 |  5 | C++         | 
|  8 | Susan |   23 | Male   |    170 |         5 |  5 | C++         | 
| 10 | Tom   |   23 | Female |    165 |         5 |  5 | C++         | 
+----+--------+------+--------+--------+-----------+----+-------------
10 rows in set (0.00 sec) 
#查询学习某个语言的人名的单独列出 
mysql> select tb_students_info.name from tb_students_info cross join tb_course where tb_course.id = tb_students_info.course_id and tb_course.course_name = 'Go'; 
+------
| name | 
+------
| John | 
| Lily | 
+------
2 rows in set (0.00 sec) 

如果在交叉连接时使用 WHERE 子句,MySQL 会先生成两个表的笛卡尔积,然后再选择满足 WHERE 条件的记录。因此,表的数量较多时,交叉连接会非常非常慢。一般情况下不建议使用交叉连接。

在 MySQL 中,多表查询一般使用内连接和外连接,它们的效率要高于交叉连接。

4. 内连接

 #SELECT <字段名> FROM <表1> INNER JOIN <表2> [ON子句];
 字段名:需要查询的字段名称。
 <表1><表2>:需要内连接的表名。
 INNER JOIN :内连接中可以省略 INNER 关键字,只用关键字 JOIN。
 ON 子句:用来设置内连接的连接条件。
 #tb_students_info别名为s tb_course别名为c 取s的name和c的course_name值 条件是s的course_id = c的id;
 mysql> select s.name,c.course_name from tb_students_info as s inner join tb_course as c on s.course_id = c.id;
 +--------+-------------+
 | name   | course_name |
 +--------+-------------+
 | Dany   | Java       |
 | Green | Mysql       |
 | Henry | Java       |
 | Jane   | Python     |
 | Jim   | Mysql       |
 | John   | Go         |
 | Lily   | Go         |
 | Susan | C++         |
 | Thomas | C++         |
 | Tom   | C++         |
 +--------+-------------+
 10 rows in set (0.00 sec)
 #按照course_name来排序
 mysql> select s.name,c.course_name from tb_students_info as s inner join tb_course as c on s.course_id = c.id order by c.course_name;
 +--------+-------------+
 | name   | course_name |
 +--------+-------------+
 | Thomas | C++         |
 | Tom   | C++         |
 | Susan | C++         |
 | John   | Go         |
 | Lily   | Go         |
 | Dany   | Java       |
 | Henry | Java       |
 | Green | Mysql       |
 | Jim   | Mysql       |
 | Jane   | Python     |
 +--------+-------------+
 10 rows in set (0.00 sec)
 

在这里的查询语句中,两个表之间的关系通过 INNER JOIN指定,连接的条件使用ON子句给出。

注意:当对多个表进行查询时,要在 SELECT 语句后面指定字段是来源于哪一张表。因此,在多表查询时,SELECT 语句后面的写法是表名.列名。另外,如果表名非常长的话,也可以给表设置别名,这样就可以直接在 SELECT 语句后面写上表的别名.列名。

5. 外连接

内连接的查询结果都是符合连接条件的记录,而外连接会先将连接的表分为基表和参考表,再以基表为依据返回满足和不满足条件的记录。

外连接可以分为左外连接和右外连接2种,下面根据实例分别介绍左外连接和右外连接。

5.1 左连接

左外连接又称为左连接,使用 LEFT OUTER JOIN 关键字连接两个表,并使用 ON 子句来设置连接条件。

 #SELECT <字段名> FROM <表1> LEFT OUTER JOIN <表2> <ON子句>;
 字段名:需要查询的字段名称。
 <表1><表2>:需要左连接的表名。
 LEFT OUTER JOIN:左连接中可以省略 OUTER 关键字,只使用关键字 LEFT JOIN。
 ON 子句:用来设置左连接的连接条件,不能省略。
 

上述语法中,"表1"为基表,"表2"为参考表。左连接查询时,可以查询出"表1"中的所有记录和"表2"中匹配连接条件的记录。如果"表1"的某行在"表2"中没有匹配行,那么在返回结果中,"表2"的字段值均为空值(NULL)。

 #在tb_course表插入Html
 mysql> insert tb_course(course_name) value('Html');
 Query OK, 1 row affected (0.00 sec)
 #以tb_students_info表为基础查询tb_students_info表的course_id 匹配 tb_course表的id 没有显示6号Html 因为基础表中没有6
 mysql> select * from tb_students_info left join tb_course on tb_students_info.course_id = tb_course.id;
 +----+--------+------+--------+--------+-----------+------+-------------+
 | id | name   | age | sex   | height | course_id | id   | course_name |
 +----+--------+------+--------+--------+-----------+------+-------------+
 |  1 | Dany   |   25 | Male   |    160 |         1 |    1 | Java       |
 |  2 | Green |   23 | Male   |    158 |         2 |    2 | Mysql       |
 |  3 | Henry |   23 | Female |    185 |         1 |    1 | Java       |
 |  4 | Jane   |   22 | Male   |    162 |         3 |    3 | Python     |
 |  5 | Jim   |   24 | Female |    175 |         2 |    2 | Mysql       |
 |  6 | John   |   21 | Male   |    172 |         4 |    4 | Go         |
 |  7 | Lily   |   22 | Male   |    165 |         4 |    4 | Go         |
 |  8 | Susan |   23 | Male   |    170 |         5 |    5 | C++         |
 |  9 | Thomas |   22 | Female |    178 |         5 |    5 | C++         |
 | 10 | Tom   |   23 | Female |    165 |         5 |    5 | C++         |
 +----+--------+------+--------+--------+-----------+------+-------------+
 10 rows in set (0.00 sec)
 #添加Liming
 mysql> insert tb_students_info(name,age,sex,height,course_id) value('Liming',22,'Male',180,7);
 Query OK, 1 row affected (0.00 sec)
 mysql> select * from tb_students_info;
 +----+--------+------+--------+--------+-----------+
 | id | name   | age | sex   | height | course_id |
 +----+--------+------+--------+--------+-----------+
 |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  2 | Green |   23 | Male   |    158 |         2 |
 |  3 | Henry |   23 | Female |    185 |         1 |
 |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  5 | Jim   |   24 | Female |    175 |         2 |
 |  6 | John   |   21 | Male   |    172 |         4 |
 |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  8 | Susan |   23 | Male   |    170 |         5 |
 |  9 | Thomas |   22 | Female |    178 |         5 |
 | 10 | Tom   |   23 | Female |    165 |         5 |
 | 11 | Liming |   22 | Male   |    180 |         7 |
 +----+--------+------+--------+--------+-----------+
 11 rows in set (0.00 sec)
 #再次以tb_students_info表为基础查询tb_students_info表的course_id 匹配 tb_course表的id 因为没有 值会为空null
 mysql> select * from tb_students_info left join tb_course on tb_students_info.course_id = tb_course.id;
 +----+--------+------+--------+--------+-----------+------+-------------+
 | id | name   | age | sex   | height | course_id | id   | course_name |
 +----+--------+------+--------+--------+-----------+------+-------------+
 |  1 | Dany   |   25 | Male   |    160 |         1 |    1 | Java       |
 |  2 | Green |   23 | Male   |    158 |         2 |    2 | Mysql       |
 |  3 | Henry |   23 | Female |    185 |         1 |    1 | Java       |
 |  4 | Jane   |   22 | Male   |    162 |         3 |    3 | Python     |
 |  5 | Jim   |   24 | Female |    175 |         2 |    2 | Mysql       |
 |  6 | John   |   21 | Male   |    172 |         4 |    4 | Go         |
 |  7 | Lily   |   22 | Male   |    165 |         4 |    4 | Go         |
 |  8 | Susan |   23 | Male   |    170 |         5 |    5 | C++         |
 |  9 | Thomas |   22 | Female |    178 |         5 |    5 | C++         |
 | 10 | Tom   |   23 | Female |    165 |         5 |    5 | C++         |
 | 11 | Liming |   22 | Male   |    180 |         7 | NULL | NULL       |
 +----+--------+------+--------+--------+-----------+------+-------------+
 11 rows in set (0.00 sec)
 #s为tb_students_info表 c为tb_course表 以s表为基础表查询course_id匹配c表的id c表没有对应值 所有为空null值
 mysql> select s.name,c.course_name from tb_students_info s left join tb_course c on s.course_id=c.id;
 +--------+-------------+
 | name   | course_name |
 +--------+-------------+
 | Dany   | Java       |
 | Green | Mysql       |
 | Henry | Java       |
 | Jane   | Python     |
 | Jim   | Mysql       |
 | John   | Go         |
 | Lily   | Go         |
 | Susan | C++         |
 | Thomas | C++         |
 | Tom   | C++         |
 | Liming | NULL       |
 +--------+-------------+
 11 rows in set (0.00 sec)
 

可以看到,运行结果显示了 11 条记录,name 为 LiMing 的学生目前没有课程,因为对应的 tb_course 表中没有该学生的课程信息,所以该条记录只取出了 tb_students_info 表中相应的值,而从 tb_course 表中取出的值为 NULL。

5.2 右连接

右外连接又称为右连接,右连接是左连接的反向连接。使用 RIGHT OUTER JOIN 关键字连接两个表,并使用 ON 子句来设置连接条件。

 #SELECT <字段名> FROM <表1> RIGHT OUTER JOIN <表2> <ON子句>;
 字段名:需要查询的字段名称。
 <表1><表2>:需要右连接的表名。
 RIGHT OUTER JOIN:右连接中可以省略 OUTER 关键字,只使用关键字 RIGHT JOIN。
 ON 子句:用来设置右连接的连接条件,不能省略。

与左连接相反,右连接以"表2"为基表,"表1"为参考表。右连接查询时,可以查询出"表2"中的所有记录和"表1"中匹配连接条件的记录。如果"表2"的某行在"表1"中没有匹配行,那么在返回结果中,"表1"的字段值均为空值(NULL)。

 #s为tb_students_info表 c为tb_course表 以c表为基础表查询id匹配s表的course_id c表没有对应值 所有为空null值
 mysql> select s.name,c.course_name from tb_students_info s right join tb_course c on s.course_id=c.id;
 +--------+-------------+
 | name   | course_name |
 +--------+-------------+
 | Dany   | Java       |
 | Green | Mysql       |
 | Henry | Java       |
 | Jane   | Python     |
 | Jim   | Mysql       |
 | John   | Go         |
 | Lily   | Go         |
 | Susan | C++         |
 | Thomas | C++         |
 | Tom   | C++         |
 | NULL   | Html       |
 +--------+-------------+
 11 rows in set (0.00 sec)
 

6. 分组查询

在 MySQL 中,GROUP BY 关键字可以根据一个或多个字段对查询结果进行分组。

 #GROUP BY  <字段名>
 <字段名>表示需要分组的字段名称,多个字段时用逗号隔开

6.1 GROUP BY单独使用

单独使用 GROUP BY 关键字时,查询结果会只显示每个分组的第一条记录。

 mysql> SELECT name,sex FROM tb_students_info GROUP BY sex;
 +-------+--------+
 | name | sex   |
 +-------+--------+
 | Henry | Female |
 | Dany | Male   |
 +-------+--------+
 2 rows in set (0.00 sec)
 

结果中只显示了两条记录,这两条记录的 sex 字段的值分别为“女”和“男”。

6.2 GROUP BY 与 GROUP_CONCAT()

GROUP BY 关键字可以和 GROUP_CONCAT() 函数一起使用。GROUP_CONCAT() 函数会把每个分组的字段值都显示出来。

 GROUP BY 关键字可以和 GROUP_CONCAT() 函数一起使用。GROUP_CONCAT() 函数会把每个分组的字段值都显示出来。
 #把性别sex男,女的名字都列出来
 mysql> select sex, group_concat(name) from tb_students_info group by sex;
 +--------+----------------------------------------+
 | sex   | group_concat(name)                     |
 +--------+----------------------------------------+
 | Female | Henry,Jim,Thomas,Tom                   |
 | Male   | Dany,Green,Jane,John,Lily,Susan,Liming |
 +--------+----------------------------------------+
 2 rows in set (0.00 sec)
 #输入别名names就会显示names
 mysql> select sex, group_concat(name) names from tb_students_info group by sex;
 +--------+----------------------------------------+
 | sex   | names                                 |
 +--------+----------------------------------------+
 | Female | Henry,Jim,Thomas,Tom                   |
 | Male   | Dany,Green,Jane,John,Lily,Susan,Liming |
 +--------+----------------------------------------+
 2 rows in set (0.00 sec)
 #根据 tb_students_info 表中的 age 和 sex 字段进行分组查询
 mysql> select age,sex,group_concat(name) names from tb_students_info group by age,sex;
 +------+--------+------------------+
 | age | sex   | names           |
 +------+--------+------------------+
 |   21 | Male   | John             |
 |   22 | Female | Thomas           |
 |   22 | Male   | Jane,Lily,Liming |
 |   23 | Female | Henry,Tom       |
 |   23 | Male   | Green,Susan     |
 |   24 | Female | Jim             |
 |   25 | Male   | Dany             |
 +------+--------+------------------+
 7 rows in set (0.00 sec)

多个字段分组查询时,会先按照第一个字段进行分组。如果第一个字段中有相同的值,MySQL 才会按照第二个字段进行分组。如果第一个字段中的数据都是唯一的,那么 MySQL 将不再对第二个字段进行分组。

6.3 GROUP BY 与聚合函数

在数据统计时,GROUP BY 关键字经常和聚合函数一起使用。

聚合函数包括 COUNT(),SUM(),AVG(),MAX() 和 MIN()。其中,COUNT() 用来统计记录的条数;SUM() 用来计算字段值的总和;AVG() 用来计算字段值的平均值;MAX() 用来查询字段的最大值;MIN() 用来查询字段的最小值。

 #查询 sex 字段值为"女"的记录是一组;sex 字段值为"男"的记录是一组。
 mysql> select sex,COUNT(sex) from tb_students_info group by sex;
 +--------+------------+
 | sex   | COUNT(sex) |
 +--------+------------+
 | Female |          4 |
 | Male   |          7 |
 +--------+------------+
 2 rows in set (0.37 sec)
 
 mysql>

6.4 GROUP BY 与 WITH ROLLUP

WITH POLLUP 关键字用来在所有记录的最后加上一条记录,这条记录是上面所有记录的总和,即统计记录数量。

 #查询 GROUP_CONCAT(name) 显示了每个分组的 name 字段值。同时,最后一条记录的 GROUP_CONCAT(name) 字段的值刚好是上面分组 name 字段值的总和。
 mysql> select sex,group_concat(name) from tb_students_info group by sex with rollup;
 +--------+-------------------------------------------------------------+
 | sex   | group_concat(name)                                         |
 +--------+-------------------------------------------------------------+
 | Female | Henry,Jim,Thomas,Tom                                       |
 | Male   | Dany,Green,Jane,John,Lily,Susan,Liming                     |
 | NULL   | Henry,Jim,Thomas,Tom,Green,Dany,Jane,John,Lily,Susan,Liming |
 +--------+-------------------------------------------------------------+
 3 rows in set (0.35 sec)
 
 mysql>

7. 子查询

子查询是 MySQL 中比较常用的查询方法,通过子查询可以实现多表查询。子查询指将一个查询语句嵌套在另一个查询语句中。子查询可以在 SELECT、UPDATE 和 DELETE 语句中使用,而且可以进行多层嵌套。在实际开发时,子查询经常出现在 WHERE 子句中。

 WHERE <表达式> <操作符> (子查询)
 其中,操作符可以是比较运算符和 IN、NOT IN、EXISTS、NOT EXISTS 等关键字。

1)IN | NOT IN 当表达式与子查询返回的结果集中的某个值相等时,返回 TRUE,否则返回 FALSE;若使用关键字 NOT,则返回值正好相反。

2)EXISTS | NOT EXISTS 用于判断子查询的结果集是否为空,若子查询的结果集不为空,返回 TRUE,否则返回 FALSE;若使用关键字 NOT,则返回的值正好相反。

 #查询 学习 Java 课程的只有 Dany 和 Henry。上述查询过程也可以分为以下 2 步执行,实现效果是相同的。
 mysql> select name from tb_students_info where course_id in (select id from tb_course where course_name = 'Java');
 +-------+
 | name |
 +-------+
 | Dany |
 | Henry |
 +-------+
 2 rows in set (0.42 sec)
 
 mysql>

首先单独执行内查询,查询出 tb_course 表中课程为 Java 的 id

 #查询 符合条件的 id 字段的值为 1。
 mysql> select id from tb_course where course_name = 'Java';
 +----+
 | id |
 +----+
 |  1 |
 +----+
 1 row in set (0.00 sec)
 
 mysql>

然后执行外层查询,在 tb_students_info 表中查询 course_id 等于 1 的学生姓名

 #习惯上,外层的 SELECT 查询称为父查询,圆括号中嵌入的查询称为子查询(子查询必须放在圆括号内)。MySQL 在处理上例的 SELECT 语句时,执行流程为:先执行子查询,再执行父查询。
 mysql> select name from tb_students_info where course_id in (1);
 +-------+
 | name |
 +-------+
 | Dany |
 | Henry |
 +-------+
 2 rows in set (0.00 sec)
 
 mysql>

在 SELECT 语句中使用 NOT IN 关键字

 #运行结果与上面的例子刚好相反,没有学习 Java 课程的是除了 Dany 和 Henry 之外的学生。
 mysql> select name from tb_students_info where course_id not in (select id from tb_coursee where course_name = 'Java');
 +--------+
 | name   |
 +--------+
 | Green |
 | Jane   |
 | Jim   |
 | John   |
 | Lily   |
 | Susan |
 | Thomas |
 | Tom   |
 | Liming |
 +--------+
 9 rows in set (0.39 sec)
 
 mysql>

使用=运算符

 #查询 在 tb_course 表和 tb_students_info 表中查询出所有学习 Python 课程的学生姓名
 mysql> select name from tb_students_info where course_id = (select id from tb_course where course_name = 'Python');
 +------+
 | name |
 +------+
 | Jane |
 +------+
 1 row in set (0.00 sec)
 
 mysql>

使用<>运算符(也可以用!=表示)

 #显示在 tb_course 表和 tb_students_info 表中查询出没有学习 Python 课程的学生姓名
 mysql> select name from tb_students_info where course_id <> (select id from tb_course wherre course_name = 'Python');
 +--------+
 | name   |
 +--------+
 | Dany   |
 | Green |
 | Henry |
 | Jim   |
 | John   |
 | Lily   |
 | Susan |
 | Thomas |
 | Tom   |
 | Liming |
 +--------+
 10 rows in set (0.00 sec)
 
 mysql>
 #查询 tb_course 表中是否存在 id=1 的课程
 mysql> select *from tb_students_info where exists(select course_name from tb_course where  id=1);
 +----+--------+------+--------+--------+-----------+
 | id | name   | age | sex   | height | course_id |
 +----+--------+------+--------+--------+-----------+
 |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  2 | Green |   23 | Ma,le |    158 |         2 |
 |  3 | Henry |   23 | Female |    185 |         1 |
 |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  5 | Jim   |   24 | Female |    175 |         2 |
 |  6 | John   |   21 | Male   |    172 |         4 |
 |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  8 | Susan |   23 | Male   |    170 |         5 |
 |  9 | Thomas |   22 | Female |    178 |         5 |
 | 10 | Tom   |   23 | Female |    165 |         5 |
 | 11 | Liming |   22 | Male   |    180 |         7 |
 +----+--------+------+--------+--------+-----------+
 11 rows in set (0.00 sec)
 #EXISTS 关键字可以和其它查询条件一起使用,条件表达式与 EXISTS 关键字之间用 AND 和 OR 连接。
 mysql>
 #查询 tb_course 表中是否存在 id=1 的课程,如果存在,就查询出 tb_students_info 表中 age 字段大于 24 的记录
 mysql> select * from tb_students_info where age>24 and exists(select course_name from tb_ccourse where id=1);
 +----+------+------+------+--------+-----------+
 | id | name | age | sex | height | course_id |
 +----+------+------+------+--------+-----------+
 |  1 | Dany |   25 | Male |    160 |         1 |
 +----+------+------+------+--------+-----------+
 1 row in set (0.01 sec)
 #子查询的功能也可以通过表连接完成,但是子查询会使 SQL 语句更容易阅读和编写。
 #一般来说,表连接(内连接和外连接等)都可以用子查询替换,但反过来却不一定,有的子查询不能用表连接来替换。子查询比较灵活、方便、形式多样,适合作为查询的筛选条件,而表连接更适合于查看连接表的数据。
 #查询 tb_course 表中是否存在 id=2 的课程,如果存在,就查询出 tb_students_info 表中 age 字段大于 24 的记录
 mysql> select * from tb_students_info where age > 24 and exists (select course_name from tb_course where id = 2);
 +----+------+------+------+--------+-----------+
 | id | name | age | sex | height | course_id |
 +----+------+------+------+--------+-----------+
 |  1 | Dany |   25 | Male |    160 |         1 |
 +----+------+------+------+--------+-----------+
 1 row in set (0.00 sec)
 
 mysql>
 #查询 班级身高最高的学生
 mysql> select * from tb_students_info;
 +----+--------+------+--------+--------+-----------+
 | id | name   | age | sex   | height | course_id |
 +----+--------+------+--------+--------+-----------+
 |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  2 | Green |   23 | Ma,le |    158 |         2 |
 |  3 | Henry |   23 | Female |    185 |         1 |
 |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  5 | Jim   |   24 | Female |    175 |         2 |
 |  6 | John   |   21 | Male   |    172 |         4 |
 |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  8 | Susan |   23 | Male   |    170 |         5 |
 |  9 | Thomas |   22 | Female |    178 |         5 |
 | 10 | Tom   |   23 | Female |    165 |         5 |
 | 11 | Liming |   22 | Male   |    180 |         7 |
 +----+--------+------+--------+--------+-----------+
 11 rows in set (0.00 sec)
 
 mysql> select name,height from tb_students_info where height = (select max(height) from tbb_students_info);
 +-------+--------+
 | name | height |
 +-------+--------+
 | Henry |    185 |
 +-------+--------+
 1 row in set (0.00 sec)
 
 mysql>
 #查询 班级年龄最小的学生姓名和性别
 mysql> select * from tb_students_info;
 +----+--------+------+--------+--------+-----------+
 | id | name   | age | sex   | height | course_id |
 +----+--------+------+--------+--------+-----------+
 |  1 | Dany   |   25 | Male   |    160 |         1 |
 |  2 | Green |   23 | Ma,le |    158 |         2 |
 |  3 | Henry |   23 | Female |    185 |         1 |
 |  4 | Jane   |   22 | Male   |    162 |         3 |
 |  5 | Jim   |   24 | Female |    175 |         2 |
 |  6 | John   |   21 | Male   |    172 |         4 |
 |  7 | Lily   |   22 | Male   |    165 |         4 |
 |  8 | Susan |   23 | Male   |    170 |         5 |
 |  9 | Thomas |   22 | Female |    178 |         5 |
 | 10 | Tom   |   23 | Female |    165 |         5 |
 | 11 | Liming |   22 | Male   |    180 |         7 |
 +----+--------+------+--------+--------+-----------+
 11 rows in set (0.00 sec)
 
 mysql> select name,sex,age from tb_students_info where age = (select min(age) from tb_studdents_info);
 +------+------+------+
 | name | sex | age |
 +------+------+------+
 | John | Male |   21 |
 +------+------+------+
 1 row in set (0.00 sec)
 
 mysql>
 #查询 tb_students_info表的名字,人数,平均年龄,身高根据tb_students_info的姓名分组
 mysql> select s.name,count(*),avg(s.age),s.height from tb_students_info s,tb_course c where s.course_id = c.id group by s.name;
 +--------+----------+------------+--------+
 | name   | count(*) | avg(s.age) | height |
 +--------+----------+------------+--------+
 | Dany   |        1 |    25.0000 |    160 |
 | Green |        1 |    23.0000 |    158 |
 | Henry |        2 |    24.0000 |    185 |
 | Jane   |        1 |    22.0000 |    162 |
 | Jim   |        1 |    24.0000 |    175 |
 | John   |        1 |    21.0000 |    172 |
 | Lily   |        1 |    22.0000 |    165 |
 | Susan |        1 |    23.0000 |    170 |
 | Thomas |        1 |    22.0000 |    178 |
 | Tom   |        1 |    23.0000 |    165 |
 +--------+----------+------------+--------+
 10 rows in set (0.36 sec)
 
 mysql>
 #查询 tb_students_info表的名字,人数,平均年龄,身高根据身高进行降序
 mysql> select s.name,count(*),avg(s.age),s.height from tb_students_info s,tb_course c where s.course_id = c.id group by s.name order by s.height desc;
 +--------+----------+------------+--------+
 | name   | count(*) | avg(s.age) | height |
 +--------+----------+------------+--------+
 | Henry |        2 |    24.0000 |    185 |
 | Thomas |        1 |    22.0000 |    178 |
 | Jim   |        1 |    24.0000 |    175 |
 | John   |        1 |    21.0000 |    172 |
 | Susan |        1 |    23.0000 |    170 |
 | Lily   |        1 |    22.0000 |    165 |
 | Tom   |        1 |    23.0000 |    165 |
 | Jane   |        1 |    22.0000 |    162 |
 | Dany   |        1 |    25.0000 |    160 |
 | Green |        1 |    23.0000 |    158 |
 +--------+----------+------------+--------+
 10 rows in set (0.00 sec)
 
 mysql>
 #查询 班级身高最矮三个学生的名字,人数,平均年龄,身高
 mysql> select s.name,count(*),avg(s.age),s.height from tb_students_info s,tb_course c where s.course_id = c.id group by s.name order by s.height limit 3;
 +-------+----------+------------+--------+
 | name | count(*) | avg(s.age) | height |
 +-------+----------+------------+--------+
 | Green |        1 |    23.0000 |    158 |
 | Dany |        1 |    25.0000 |    160 |
 | Jane |        1 |    22.0000 |    162 |
 +-------+----------+------------+--------+
 3 rows in set (0.00 sec)
 
 mysql>
 
posted @ 2022-12-29 20:59  Thespace  阅读(179)  评论(0编辑  收藏  举报