七MySQL 多表查询详解(附练习题及答案----超详细)
Posted Amo Xiang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了七MySQL 多表查询详解(附练习题及答案----超详细)相关的知识,希望对你有一定的参考价值。
文章目录
连接是关系数据库模型的主要特点。连接查询是关系数据库中最主要的查询,主要包括内连接、外连接等。通过连接运算符可以实现多个表查询 (故也称为多表查询)。 在关系数据库管理系统中,表建立时各数据之间的关系不必确定,常把一个实体的所有信息存放在一个表中。在查询数据时,通过连接操作查询出存放在多个表中的不同实体的信息。当两个或多个表中存在相同意义的字段时,便可以通过这些字段对不同的表进行连接查询。本文将介绍多表之间的内连接查询、外连接查询以及复合条件连接查询。
前提条件:这些一起查询的表之间是有关系的(一对一、一对多),它们之间一定是有关联字段,这个 关联字段可能建立了外键,也可能没有建立外键。比如:员工表和部门表,这两个表依靠
部门编号
进行关联。
前置知识:
一、数据库开发与实战专栏导学及数据库基础概念入门
二、MySQL 介绍及 MySQL 安装与配置
三、MySQL 数据库的基本操作
四、MySQL 存储引擎及数据类型
五、数据导入与基本的 SELECT 语句
六、MySQL 数据库练习题1(包含前5章练习题目及答案)
一、笛卡尔积(或交叉连接)的理解
笛卡尔积(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 集合的元素个数。
图示:
SQL92中,笛卡尔积也称为交叉连接 ,英文是 CROSS JOIN 。在 SQL99 中也是使用 CROSS JOIN 表示交叉连接。它的作用就是可以把任意表进行连接,即使这两张表不相关。在 mysql 中如下情况会出现笛卡尔积:
#查询员工姓名和所在部门名称 但是根据查询结果来看 是错误的
SELECT last_name,department_name FROM employees,departments;
SELECT last_name,department_name FROM employees CROSS JOIN departments;
SELECT last_name,department_name FROM employees INNER JOIN departments;
SELECT last_name,department_name FROM employees JOIN departments;
笛卡尔积的错误会在下面条件下产生:
省略多个表的连接条件(或关联条件)
连接条件(或关联条件)无效
所有表中的所有行互相连接
为了避免笛卡尔积的错误, 可以在 WHERE 加入有效的连接条件。 交叉连接的语法格式如下:
#语法格式1
SELECT <字段名> FROM <表1> CROSS JOIN <表2> [WHERE子句]
#语法格式2
SELECT <字段名> FROM <表1>, <表2> [WHERE子句]
#字段名:需要查询的字段名称。在表中有相同列时,在列名之前加上表名前缀
#<表1><表2>:需要交叉连接的表名
#WHERE 子句:用来设置交叉连接的查询条件
注意: 多个表交叉连接时,在 FROM 后连续使用 CROSS JOIN
或 ,
即可。以上两种语法的返回结果是相同的,但是第一种语法才是官方建议的标准写法。当连接的表之间没有关系时,我们会省略掉 WHERE 子句,这时返回结果就是两个表的笛卡尔积,返回结果数量就是两个表的数据行相乘。需要注意的是,如果每个表有 1000 行,那么返回结果的数量就有 1000×1000 = 1000000 行,数据量是非常巨大的,就是上述所说的笛卡尔积错误,得到的运行结果没太大的意义。
【示例1】查询员工姓名和所在部门名称(正确写法)。
SELECT emp.last_name,dep.department_name FROM employees emp,departments dep WHERE emp.department_id=dep.department_id;
SELECT emp.last_name,dep.department_name FROM employees emp CROSS JOIN departments dep ON emp.department_id=dep.department_id;
如果在交叉连接时使用 WHERE 子句,MySQL 会先生成两个表的笛卡尔积,然后再选择满足 WHERE 条件的记录。因此,表的数量较多时,交叉连接会非常非常慢。一般情况下不建议使用交叉连接。在 MySQL 中,多表查询一般使用内连接和外连接,它们的效率要高于交叉连接。
二、多表查询分类讲解
2.1 分类1:等值连接 vs 非等值连接
等值连接:
SQL 语句如下所示:
mysql> SELECT emp.last_name,dep.department_name,dep.location_id
-> FROM employees emp,departments dep
-> WHERE emp.department_id=dep.department_id;
【示例2】查询员工姓名和所在部门名称(正确写法)。多个连接条件与 AND 操作符:
#1.区分重复的列名 多个表中有相同列时,必须在列名之前加上表名前缀
#2.使用别名可以简化查询 列名前使用表名前缀可以提高查询效率
SELECT emp.last_name,emp.department_id,dep.department_name,dep.location_id
FROM employees emp,departments dep
WHERE emp.department_id=dep.department_id AND dep.department_id=50;
#3.需要注意的是,如果我们使用了表的别名,在查询字段中、过滤条件中就只能使用别名进行代替,使用原表名会报错!
【示例3】连接多个表:查询出公司员工的 last_name、department_name、city。
mysql> SELECT emp.last_name,emp.department_id,dep.department_name,dep.location_id, loc.city
-> FROM employees emp,departments dep,locations loc
-> WHERE emp.department_id=dep.department_id AND dep.location_id=loc.location_id;
非等值连接:
【示例4】非等值连接。使用sql语句实现上图所示查询结果。
mysql> SELECT emp.last_name,emp.salary,gra.grade_level,gra.lowest_sal,gra.highest_sal
-> FROM employees emp,job_grades gra
-> WHERE emp.salary BETWEEN gra.lowest_sal AND gra.highest_sal;
2.2 分类2:自连接 vs 非自连接
【示例5】查询出员工的 manager 的信息。
mysql> SELECT
-> emp1.employee_id,emp1.last_name,emp1.manager_id,
-> emp2.employee_id,emp2.last_name
-> FROM employees emp1, employees emp2
-> WHERE emp1.manager_id=emp2.employee_id;
查询结果如下图所示:
emp1 和 emp2 本质上是同一张表,只是用取别名的方式虚拟成两张表以代表不同的意义。然后两个表再进行内连接,外连接等查询。【示例6】查询出 last_name为 Chen
的员工的 manager 的信息。
mysql> SELECT
-> emp1.employee_id,emp1.last_name,emp1.manager_id,
-> emp2.employee_id,emp2.last_name
-> FROM employees emp1, employees emp2
-> WHERE emp1.manager_id=emp2.employee_id AND emp1.last_name='Chen';
+-------------+-----------+------------+-------------+-----------+
| employee_id | last_name | manager_id | employee_id | last_name |
+-------------+-----------+------------+-------------+-----------+
| 110 | Chen | 108 | 108 | Greenberg |
+-------------+-----------+------------+-------------+-----------+
1 row in set (0.00 sec)
2.3 分类3:内连接 vs 外连接
内连接(INNER JOIN): 使用比较运算符进行表间某(些)列数据的比较操作,并列出这些表中与连接条件相匹配的数据行,组合成新的记录,也就是说,在内连接查询中,只有满足条件的记录才能出现在结果关系中。
连接查询将查询多个表中相关联的行;内连接时,返回查询结果集合中仅是符合查询条件和连接条件的行。有时候需要包含没有关联的行中数据,如下图所示:
如上图所示, 返回查询结果集合中不仅包含符合连接条件的行,还包括 左表
(左外连接或左连接)[右表(右外连接或右连接)或两个边接表(全外连接)]
中的所有数据行。外连接分为左外连接(左连接)和右外连接(右连接):
- LEFT JOIN(左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。左连接的结果包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是连接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果行中,右表的所有选择列表列均为空值。连接条件中左边的表也称为
主表
,右边的表称为从表
。 - RIGHT JOIN(右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。右连接是左连接的反向连接,将返回右表的所有行。如果右表的某行在左表中没有匹配行,左表将返回空值。如果是右外连接,则连接条件中右边的表也称为
主表
,左边的表称为从表
。
2.4 SQL99语法实现多表查询
2.4.1 内连接
内连接的语法格式如下:
SELECT <字段名> FROM <表1> INNER JOIN <表2> [ON子句]
#字段名:需要查询的字段名称。
#<表1><表2>:需要内连接的表名。
#INNER JOIN :内连接中可以省略 INNER 关键字,只用关键字 JOIN。
#ON 子句:用来设置内连接的连接条件。
多个表内连接时,在 FROM 后连续使用 INNER JOIN 或 JOIN 即可。
INNER JOIN 也可以使用 WHERE 子句指定连接条件,但是 INNER JOIN … ON 语法是官方的标准写法,而且 WHERE 子句在某些时候会影响查询的性能。
【示例7】查询员工信息及其部门信息。
mysql> SELECT
-> emp1.employee_id,emp1.last_name,emp1.department_id,
-> dep.department_id,dep.location_id
-> FROM employees emp1 INNER JOIN departments dep
-> ON emp1.department_id=dep.department_id;
【示例8】查询出员工的 manager 的信息。
mysql> SELECT
-> emp1.employee_id,emp1.last_name,emp1.manager_id,
-> emp2.employee_id,emp2.last_name
-> FROM employees emp1 INNER JOIN employees emp2
-> ON emp1.manager_id=emp2.employee_id;
【示例9】多表连接三张表。
mysql> SELECT
-> emp1.employee_id,emp1.last_name,emp1.department_id,
-> dep.department_id,dep.location_id,loc.city,loc.state_province
-> FROM employees emp1 INNER JOIN departments dep
-> ON emp1.department_id=dep.department_id
-> INNER JOIN locations loc ON dep.location_id = loc.location_id;
2.4.2 左连接
左外连接又称为左连接,使用 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), 在之前已经提过。
mysql> SELECT
-> dep.department_id,dep.location_id,
-> emp.employee_id,emp.last_name,emp.department_id
-> FROM departments dep LEFT OUTER JOIN employees emp
-> ON dep.department_id=emp.department_id;
2.4.3 右连接
右外连接又称为右连接,右连接是左连接的反向连接。使用 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)。
mysql> SELECT
-> dep.department_id,dep.location_id,
-> emp.employee_id,emp.last_name,emp.department_id
-> FROM departments dep RIGHT OUTER JOIN employees emp
-> ON dep.department_id=emp.department_id;
多个表左/右连接时,在 ON 子句后连续使用 LEFT/RIGHT OUTER JOIN 或 LEFT/RIGHT JOIN 即可。使用外连接查询时,一定要分清需要查询的结果,是需要显示左表的全部记录还是右表的全部记录,然后选择相应的左连接和右连接。
2.4.4 满外连接(FULL OUTER JOIN)
满外连接的结果 = 左右表匹配的数据 + 左表没有匹配到的数据 + 右表没有匹配到的数据。
SQL99是支持满外连接的。使用 FULL JOIN 或 FULL OUTER JOIN 来实现。需要注意的是,MySQL 不支持 FULL JOIN,但是可以用 LEFT JOIN UNION RIGHT JOIN 代替。
2.4.5 补充:合并查询结果 UNION 使用
利用 UNION 关键字,可以给出多条 SELECT 语句,并将它们的结果组合成单个结果集。合并时,两个表对应的列数和数据类型必须相同。各个 SELECT 语句之间使用 UNION 或 UNION ALL 关键字分隔。UNION 不使用关键字 ALL,执行的时候删除重复的记录,所有返回的行都是唯一的;使用关键字 ALL 的作用是不删除重复行也不对结果进行自动排序。基本语法格式如下:
SELECT column,... FROM table1
UNION [ALL]
SELECT column,... FROM table2
注意: 执行 UNION ALL
语句时所需要的资源比 UNION
语句少。如果明确知道合并数据后的结果数据不存在重复数据,或者不需要去除重复的数据,则尽量使用 UNION ALL
语句,以提高数据查询的效率。【示例10】查询部门编号>90或邮箱包含a的员工信息。
mysql> SELECT * FROM employees WHERE email LIKE '%a%' OR department_id>90;
mysql> SELECT * FROM employees WHERE email LIKE '%a%' UNION
-> SELECT * FROM employees WHERE department_id>90;
mysql> SELECT * FROM employees WHERE email LIKE '%a%' UNION ALL#有重复数据
-> SELECT * FROM employees WHERE department_id>90;
2.5 7种 SQL JOINS 的实现
sql 示例:
mysql> SELECT employee_id,last_name,department_name FROM employees e LEFT JOIN departments d
-> ON e.`department_id` = d.`department_id`;
右连接:
sql 示例:
mysql> SELECT employee_id,last_name,department_name FROM employees e RIGHT JOIN departments d
-> ON e.`department_id` = d.`department_id`;
内连接:
sql 示例:
mysql> SELECT employee_id,last_name,department_name FROM employees e INNER JOIN departments d
-> ON e.`department_id` = d.`department_id`;
全连接:
sql 示例:
mysql> SELECT employee_id,last_name,department_name
-> FROM employees e LEFT JOIN departments d ON
-> e.`department_id` = d.`department_id` WHERE d.`department_id` IS NULL
-> UNION ALL #没有去重操作,效率高
-> SELECT employee_id,last_name,department_name
-> FROM employees e RIGHT JOIN departments d
-> ON e.`department_id` = d.`department_id`;
剩下的三个图:
sql 示例:
mysql> #左图
mysql> SELECT employee_id,last_name,department_name
-> FROM employees e LEFT JOIN departments d
-> ON e.`department_id` = d.`department_id`
-> WHERE d.`department_id` IS NULL
-> ;
+-------------+-----------+-----------------+
| employee_id | last_name | department_name |
+-------------+-----------+-----------------+
| 178 | Grant | NULL |
+-------------+-----------+-----------------+
1 row in set (0.00 sec)
mysql> #右图
mysql> SELECT employee_id,last_name,department_name
-> FROM employees e RIGHT JOIN departments d
-> ON e.`department_id` = d.`department_id`
-> WHERE e.`department_id` IS NULL;
+-------------+-----------+----------------------+
| employee_id | last_name | department_name |
+-------------+-----------+----------------------+
| NULL | NULL | Treasury |
| NULL | NULL | Corporate Tax |
| NULL | NULL | Control And Credit |
| NULL | NULL | Shareholder Services |
| NULL | NULL | Benefits |
| NULL | NULL | Manufacturing |
| NULL | NULL | Construction |
| NULL | NULL | Contracting |
| NULL | NULL | Operations |
| NULL | NULL | IT Support |
| NULL | NULL | NOC |
| NULL | NULL | IT Helpdesk |
| NULL | NULL | Government Sales |
| NULL | NULL | Retail Sales |
| NULL | NULL | Recruiting |
| NULL | NULL | Payroll |
+-------------+-----------+----------------------+
16 rows in set (0.00 sec)
mysql> #中图
mysql> SELECT employee_id,last_name,department_name
-> FROM employees e LEFT JOIN departments d
-> ON e.`department_id` = d.`department_id`
-> WHERE d.`department_id` IS NULL
-> UNION ALL
-> SELECT employee_id,last_name,department_name
-> FROM employees e RIGHT JOIN departments d
-> ON e.`department_id` = d.`department_id`
-> WHERE e.`department_id` IS NULL;
+-------------+-----------+----------------------+
| employee_id | last_name | department_name |
+-------------+-----------+----------------------+
| 178 | Grant | NULL |
| NULL | NULL | Treasury |
| NULL | NULL | Corporate Tax |
| NULL | NULL | Control And Credit |
| NULL | NULL | Shareholder Services |
| NULL | NULL | Benefits |
| NULL | NULL | Manufacturing |
| NULL | NULL | Construction |
| NULL | NULL | Contracting |
| NULL | NULL | Operations |
| NULL | NULL | IT Support |
| NULL | NULL | NOC |
| NULL | NULL | IT Helpdesk |
| NULL | NULL | Government Sales |
| NULL | NULL | Retail Sales |
| NULL | NULL | Recruiting |
| NULL | NULL | Payroll |
+-------------+-----------+----------------------+
17 rows in set (0.00 sec)
2.6 SQL99语法新特性
2.6.1 自然连接
SQL99 在 SQL92 的基础上提供了一些特殊语法,比如 NATURAL JOIN
用来表示自然连接。我们可以把自然连接理解为 SQL92 中的等值连接。它会帮你自动查询两张连接表中 所有相同的字段
,然后进行 等值连接
。在 SQL92 标准中:
mysql> SELECT employee_id,last_name,department_name FROM employees e Mysql笛卡尔积详解(附实现多表查询代码实现)