前言
前面我们梳理过了分组、排序、聚合函数等查询操作。这些都只是针对一个表的查询操作,这一节,我们要来梳理下多表查询的内容,让内容筛选的范围从单个表变成多个表。
在设计关系型数据库的时候,我们强调表与表之间的关系,还强调数据库范式。
表之间的关系可简单分为一对一、一对多、多对多三种。
表单设计的时候后要遵循三范式。
这都导致了我们要查询的信息往往不会都存在一个表单中,而是分布在多个表单中的,每个表单都可能映射着不同的实体。
例如,在学生的表单中存放着学生的基本信息,当我们需要查询学生及其成绩信息时,单独查询学生一个表就不能满足需求。因为学生和成绩信息往往是一对多的关系,且成绩信息还要管关联科目等信息。
而将这些信息都存放在学生表单中就违背了第二范式和第三范式,即产生了与学生主键不直接相关的列,且这些列存在着传递依赖。
因此分开存放就是不可毋庸置疑的操作,而查询的时候,就需要使用多表查询将其关联起来取出。
合并结果集
合并结果集的意思就是将查询到的两个结果集以增加行的形式合并到一张表中。其实说白了就是将一个表的结果集直接追加到另一个结果集之后。
这样做需要满足前提条件:列数相等,类型相同
满足了这两个条件后就可以和合并结果集了。合并结果集使用的关键字是UNION
和UNION ALL
两者的区别在于前者将去重重复的记录而后者将保留所有的记录而不会去除重复的。
书写他们的格式为:select 语句1 UNION select 语句2
其实就是字面意义上的“合并”两张表
下面有这样的两张表,其结构和数据为:
我们查询两张表的内容并分别使用UNION
和 UNION ALL
合并两个结果集来看看结果如何:
可以看到,在使用UNION 的时候,重复的id和姓名同时相同的张三被删除了而在使用UNION ALL 的时候第二个张三却没有被删除。
连接查询
同时查询多个表,只需要在FROM
关键字后面使用逗号隔开要查的多个表即可。
像这样:SELECT * FROM 表1,表2...; 限制条件
这样的查询是将多个表的列合并到一起,使得每一条记录有更多的信息。
笛卡尔积现象
以查询两个表为例,这样的查询会造成笛卡尔积的现象。笛卡尔积现象简单说就是两个表中的记录两两搭配出所有可能的组合情况。
这其实就是两个集合的笛卡尔积,我们设第一个表代表结果集为X,第二个表代表结果集为Y,用百度百科的描述来说就是:第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员
这么说可能有些抽象,那就来直接看个例子,以车和人为例,其表间关系为一对多(一个人可以对应多辆车,一辆车仅对应一个人):
有下面的表和数据;
来直接使用多表查询:
SELECT * FROM person,car;
结果如下:
可以看到一共出现了16条数据,他们有序的排列,枚举出所有地组合。这种现象就是笛卡尔积现象,这其中存在着很大的问题,因为这样查询出来的信息有很大部分是我们不需要的。甚至说,这样的查询是无效的,因为它不能体现期望的表关系。.
值得讨论的是,笛卡尔积现象出现在这里是十分合理的,因为我们并没有告知应该如何处理两张表的连接关系,所以提供笛卡尔积这样两张表连接的全集是没有问题的。这也为后面在此基础上去笛卡尔积现象做出铺垫。
为表单命别名
为表单命别名的写法同给列命别名的方式差不多,需要使用AS
关键字来表示这个别名。例如:
给人的表单命别名‘p’,给车的表单命别名‘c’
SELECT * FROM person AS p,car AS c;
一般的,可以省略到中间的AS
关键字,直接使用空格隔开原名和别名:
SELECT * FROM person p,car c;
使用WHERE去笛卡尔积
在上面的例子中,我们将人的表单和车辆的表单进行了共同查询,结果就是两个表单的笛卡尔积。
使用WHERE条件对结果集进行筛选可以去笛卡尔积,注意到两个表单是通过车辆表单中人员id列进行关联的,因此只需要在笛卡尔结积的结果中筛选出车辆表人员id
和人员表中id
值相同的记录即可。
写成SQL
SELECT * FROM person,car WHERE id = pid;
但是直接这样写,就会被警告,因为两个表单中都存在id列,发生了冲突。
所以在写条件的时候列名前最好带上表名:表名.列名
。这里的表名也可以是表的别名。
将上面的语句改为:
SELECT * FROM person p,car c WHERE p.id = c.pid;
就能很好的查询到过滤后的记录,笛卡尔积的现象就消除了,转而得到了我们期望的结果。
使用联结
刚刚我们使用where完成了两张表的连接。联结(JOIN)也可以完成这个工作,而且提供了更多的机制。
联结使用到的关键字就是JOIN
下面来介绍几个常用的联结:内联结 INNER JOIN
,自然联结NATURAL JOIN
,外联结分为左外联结LEFT OUTER JOIN
和右外联结RIGHT OUTER JOIN
内联结
内联结(INNER JOIN)的作用和使用WHERE
进行连接很近似,但是它的语法与where不同,使用INNER JOIN
的结构可以更加清晰的指明联结两张表的意图,并且这也强制程序员写上联结的条件,这在某些程度上避免了出错的可能性。
INNER JOIN
的语法如下:SELECT * FROM 表1 INNER JOIN 表2 ON 条件
内联结(这里特指INNER JOIN
) 还可以省略掉INNER
而直接写成JOIN
:SELECT * FROM 表1 JOIN 表2; ON 条件
在使用内联结时为表起别名的方法是直接在表名后面书写:SELECT * FROM 表1 AS 表1别名 INNER JOIN 表2 表2别名 ON 条件;
我们将刚刚的查询改成内联结的形式:
SELECT * FROM person AS p INNER JOIN car AS c ON p.id = c.pid;
当然,我们还可以将刚刚提到可以简写的地方进行简写:
SELECT * FROM person p JOIN car c ON p.id = c.pid;
他们的作用是一样的。
自然联结
自然联结的关键字是:NATURAL JOIN
这个不能简写。
它会自动根据两个表中的 相同列(名称和类型) 进行等值联结,并且会去除重复的列。
上面的两个表中相同地列为id,但这不是我们希望的两个表中的关联列,因此我们将人员表的id
列重命名为pid
:
使用自然联结来连接人和车两个表:
SELECT * FROM person NATURAL JOIN car;
结果出人意料,空集。问题就出在两个表单都有name
列,在判等的时候也被拿了进去,因此结果是空集。将人员表中的姓名改成name_person
这次就没有问题了:
可以看到,重复的列pid
被删除了
外联结
在内联结中,两个相关表的数据仅显示了双方都存在的记录,对于没有关联的行,则没有显示。例如在在人员表中,id为4的人没有汽车,则在联结查询中就没有显示4号人员。
外联结与内联结的书写格式相同,主要差别就是相较于内联结,其特点就是显示出那些没有关联的记录。
外联结使用的关键字是OUTER JION
。需要注意的是,外联结必须同关键字LEFT
或者right
一起使用。也就是指明究竟是左外联结还是右外联结。即LEFT OUTER JOIN
或者 RIGHT OUTER JOIN
。
左外联结是显式左边表单的所有记录包括没有关联的记录,而右外联结则是显式右边表单全部的记录。
这个特性很关键也很常用。在查询某个对象同时要带上其关联或者聚合的其他对象时,首先一点是查询出所有对象,再接着才是其关联的对象,这时就需要使用到外联结的特性。
例如,查询所有符合条件的人员及其汽车,首要任务是查询出人员,再次是他们的汽车,这是就不宜使用内联结而必须使用外联结来完成这个需求:
SELECT * FROM person p LEFT OUTER JOIN car c ON p.pid = c.pid;
结果如下:
可以看到没有汽车的4号也被查询了出来,其汽车部分,都以null来填充。
另外,外联结可以省略掉关键字OUTER
而仅写LEFT JOIN
或者RIGHT JOIN
他们的效果是一样的:
SELECT * FROM person p LEFT JOIN car c ON p.pid = c.pid;
往期博客在这里:
- MySQL数据库基础 MySQL的下载安装及使用
- MySQL数据库基础 数据库,表,字段的增删改查
- MySQL数据库基础 数据的增、删、改
- MySQL数据库基础 数据查询语句DQL(一)字段控制,where限制查询
- MySQL数据库基础 数据查询语句DQL(二)结果排序,聚合函数
- MySQL数据库基础 数据查询语言DQL(三) 分组查询,limit限制,SELECT语句执行顺序
参考资料:
- 《MySQL必知必会》
- 站内视频教程:Mysql数据库基础入门视频教程