MySQL学习 - 多表查询
数据表信息说明:
SQL
下载地址:点击下载employees
表 有107
条记录,包括一条department_id
为NULL
departments
表有27
条记录employees
表和departments
有两个同名字段department_id
和manager_id
需求分析
员工信息在 A
表,包括部门编号,部门详细信息在 B
表,包括编号和地址,想要知道某个员工的姓名和部门地址需要先查询 A
表然后再通过获取的部门编号去 B
表查询部门地址。
初步探索
尝试在 FROM
中同时使用 employees
表和 departments
表直接查询
mysql> SELECT COUNT(*) count
-> FROM employees,departments;
+-------+
| count |
+-------+
| 2889 |
+-------+
1 row in set (0.00 sec)
共获取到 2889
条记录,因为上面的查询中没有限制条件,将两张所有的记录进行了 交叉连接,产生了 笛卡尔积错误,返回的记录数 =
表 A
记录数 ×
表 B
记录数
使用有效的约束条件可以避免 笛卡尔积错误
mysql> SELECT COUNT(*) count
-> FROM employees e,departments d
-> WHERE e.department_id=d.department_id;
+-------+
| count |
+-------+
| 106 |
+-------+
1 row in set (0.00 sec)
但是,这里统计出来的是 106
条数据,而 employees
表中是 107
条数据,因为 employees
表中有一条数据的 department_id
为 NULL
,无法与 departments
表中任何一条数据匹配,所以只有 106
条数据,这里使用的多表查询是 SQL-92
标准的 内连接。
之前学习过 可以在 SELECT
语句中给字段名取别名,同样的也可以给表名取别名,上面示例中,分别给 employees
和 departments
取了别名,然后可以在 WHERE
语句中使用,以及后面学习的 JOIN...ON
语句,同时一旦给表名取了别名,就无法使用表名的名字了。
多表查询不仅仅局限连接两张表
SELECT *
FROM table_1,table_2,talbe_3
WHERE table_1.field_1=table_2.field_1,table_2.field_1=table_3.field_1;
对 n
个表进行多表查询,至少需要 n-1 个条件。
不同角度对多表查询的分类
具体可以分为三大类:
- 等值连接和非等值连接;
- 自连接和非自连接;
- 内连接 和 外连接;
等值连接和非等值连接
就是约束条件是否可以使用等号相等起来的,上面的 e.department_id=d.department_id
就是一个 等值连接,而比如 e.department_id IN(1,2,3)
是 非等值连接。非重点
自连接和非自连接
多表查询时,连接的其他表是自己本身的表还是非自身的表。上面的通过 A
表的部门编号再查询到 B
表中的部门地址是 非自连接,而比如查询自己的上级,可能就可能还是在本表查询,是 自连接。非重点
内连接和外连接
内连接
合并具有同一列两个以上的表,结构集中不包括不满足另一表的记录;
这就是内连接,红色部分表示结果集。
外连接
合并具有同一列两个以上的表,结果集中包括不满足另一表的记录;
上面 内连接 的示意图中,只要有结果集的记录是示意图红色之外的,就是 外连接。
根据返回的不同的结果集,还分为 左外连接、右外连接 和 满外连接,固定的是左边的表是原来的表,右边的表是其他被连接的表。
-
左外连接是以左边的表为主表,右边的表为从表,结果是主表的数据,在从表没有匹配到的结果设为
NULL
-
右外连接是以右边的表为主表,左边的表为从表,结果是主表的数据,主表在从表没有匹配到的结果设为
NULL
-
满外连接的结果是主从表匹配的结果 + 主表没有匹配到的结果 + 从表没有匹配到的结果
上面这三种 外连接,内连接是它们的 子集。
不同 SQL
下的实现
SQL
标准主要分为 SQL92
和 SQL99
SQL92
,又叫做SQL-2
,语法简单,但是语句长;SQL99
,又叫做SQL-3
,语法复杂,但是可读性强;
SQL92
对于内连接,就是简单的在 FROM
语句选择多个表,然后 WHERE
语句中写全约束条件。就是上面一些示例中的使用这种实现。
对于外连接,使用 +
实现,但是MySQL
不支持这种标准的外连接写法,就不深入探究了。
SQL99
使用 JOIN...ON...
关键字实现多表查询
SQL99
语法的 内连接、外连接 的关键字如下:
-
INNER JOIN...ON...
内连接INNER
关键字可以省略INNER JOIN ...ON...
-
LEFT OUTER JOIN...ON...
左外连接OUTER
关键字可以省略LEFT JOIN...ON...
-
RIGHT OUTER JOIN...ON...
右外连接OUTER
关键字可以省略RIGHT JOIN...ON...
-
FULL OUTER JOIN...ON...
满外连接OUTER
关键字可以省略FULL JOIN...ON...
可惜的是,
MySQL
不支持这种写法的满外连接,但是可以结合UNION
关键字实现 满外连接
SQL99
语法的 内连接、外连接 具体实现在下面单独列出,并且再结合排除的形式组成七种连接。
SQL99
JOIN...ON...
关键字的七种连接
需用到的数据表下载:点击下载
七种连接示意图
前置知识:UNION
和 UNION ALL
关键字
使用这两个关键字可以将两个 SELECT
语句的结果集,合并为一个结果集。其中,两个 SELECT
语句的结果集的 列数 和 数据类型 必须相同。
这两个关键字之间的差别在合并后的结果集,UNION
关键字可以实现两个结果集的的去重,但是这样子会导致效率低,而 UNION ALL
关键字不会对去重,但是所需的资源比 UNION
少。
内连接
mysql> SELECT table_a.`PK` "index",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> JOIN table_b b
-> ON table_a.PK=b.PK;
+-------+------------+----------+
| index | one | two |
+-------+------------+----------+
| 1 | FOX | TROT |
| 2 | COP | CAR |
| 3 | TAXI | CAB |
| 6 | WASHINGTON | MONUMENT |
| 7 | DELL | PC |
+-------+------------+----------+
5 rows in set (0.00 sec)
左外连接
mysql> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> LEFT JOIN table_b b
-> ON table_a.PK=b.PK;
+-------+---------+------------+----------+
| index | index_2 | one | two |
+-------+---------+------------+----------+
| 1 | 1 | FOX | TROT |
| 2 | 2 | COP | CAR |
| 3 | 3 | TAXI | CAB |
| 4 | NULL | LINCOLN | NULL |
| 5 | NULL | ARIZONA | NULL |
| 6 | 6 | WASHINGTON | MONUMENT |
| 7 | 7 | DELL | PC |
| 10 | NULL | LUCENT | NULL |
+-------+---------+------------+----------+
8 rows in set (0.00 sec)
右外连接
mysql> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> RIGHT JOIN table_b b
-> ON table_a.PK=b.PK;
+-------+---------+------------+-----------+
| index | index_2 | one | two |
+-------+---------+------------+-----------+
| 1 | 1 | FOX | TROT |
| 2 | 2 | COP | CAR |
| 3 | 3 | TAXI | CAB |
| 6 | 6 | WASHINGTON | MONUMENT |
| 7 | 7 | DELL | PC |
| NULL | 8 | NULL | MICROSOFT |
| NULL | 9 | NULL | APPLE |
| NULL | 11 | NULL | SCOTCH |
+-------+---------+------------+-----------+
8 rows in set (0.00 sec)
满外连接
UNION ALL
实现
mysql> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> LEFT JOIN table_b b
-> ON table_a.PK=b.PK
-> UNION ALL
-> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> RIGHT JOIN table_b b
-> ON table_a.PK=b.PK;
+-------+---------+------------+-----------+
| index | index_2 | one | two |
+-------+---------+------------+-----------+
| 1 | 1 | FOX | TROT |
| 2 | 2 | COP | CAR |
| 3 | 3 | TAXI | CAB |
| 4 | NULL | LINCOLN | NULL |
| 5 | NULL | ARIZONA | NULL |
| 6 | 6 | WASHINGTON | MONUMENT |
| 7 | 7 | DELL | PC |
| 10 | NULL | LUCENT | NULL |
| 1 | 1 | FOX | TROT |
| 2 | 2 | COP | CAR |
| 3 | 3 | TAXI | CAB |
| 6 | 6 | WASHINGTON | MONUMENT |
| 7 | 7 | DELL | PC |
| NULL | 8 | NULL | MICROSOFT |
| NULL | 9 | NULL | APPLE |
| NULL | 11 | NULL | SCOTCH |
+-------+---------+------------+-----------+
16 rows in set (0.00 sec)
UNION
实现
mysql> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> LEFT JOIN table_b b
-> ON table_a.PK=b.PK
-> UNION
-> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> RIGHT JOIN table_b b
-> ON table_a.PK=b.PK;
+-------+---------+------------+-----------+
| index | index_2 | one | two |
+-------+---------+------------+-----------+
| 1 | 1 | FOX | TROT |
| 2 | 2 | COP | CAR |
| 3 | 3 | TAXI | CAB |
| 4 | NULL | LINCOLN | NULL |
| 5 | NULL | ARIZONA | NULL |
| 6 | 6 | WASHINGTON | MONUMENT |
| 7 | 7 | DELL | PC |
| 10 | NULL | LUCENT | NULL |
| NULL | 8 | NULL | MICROSOFT |
| NULL | 9 | NULL | APPLE |
| NULL | 11 | NULL | SCOTCH |
+-------+---------+------------+-----------+
11 rows in set (0.00 sec)
左外连接不包括内连接
mysql> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> LEFT JOIN table_b b
-> ON table_a.PK=b.PK
-> WHERE b.`PK` IS NULL;
+-------+---------+---------+------+
| index | index_2 | one | two |
+-------+---------+---------+------+
| 4 | NULL | LINCOLN | NULL |
| 5 | NULL | ARIZONA | NULL |
| 10 | NULL | LUCENT | NULL |
+-------+---------+---------+------+
3 rows in set (0.00 sec)
右外连接不包括内连接
mysql> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> RIGHT JOIN table_b b
-> ON table_a.PK=b.PK
-> WHERE table_a.`PK` IS NULL;
+-------+---------+------+-----------+
| index | index_2 | one | two |
+-------+---------+------+-----------+
| NULL | 8 | NULL | MICROSOFT |
| NULL | 9 | NULL | APPLE |
| NULL | 11 | NULL | SCOTCH |
+-------+---------+------+-----------+
3 rows in set (0.00 sec)
满外连接不包括内连接
mysql> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> LEFT JOIN table_b b
-> ON table_a.PK=b.PK
-> WHERE b.`PK` IS NULL
-> UNION ALL
-> SELECT table_a.`PK` "index",b.`PK` "index_2",table_a.`VALUE` "one",b.`VALUE` "two"
-> FROM table_a
-> RIGHT JOIN table_b b
-> ON table_a.PK=b.PK
-> WHERE table_a.`PK` IS NULL;
+-------+---------+---------+-----------+
| index | index_2 | one | two |
+-------+---------+---------+-----------+
| 4 | NULL | LINCOLN | NULL |
| 5 | NULL | ARIZONA | NULL |
| 10 | NULL | LUCENT | NULL |
| NULL | 8 | NULL | MICROSOFT |
| NULL | 9 | NULL | APPLE |
| NULL | 11 | NULL | SCOTCH |
+-------+---------+---------+-----------+
6 rows in set (0.00 sec)
上面的实现的,使用的两个结果集没有重复的记录,使用 UNION
或者 UNION ALL
的结果集是一样的,所以这里使用 UNION ALL
更高效。
SQL99
语言的新特性
自然连接:NATURAL JOIN...
自动查询两张表同名字段,然后进行 等值连接
用于 内连接
mysql> SELECT e.employee_id,last_name,d.department_name
-> FROM employees e
-> NATURAL JOIN departments d;
+-------------+------------+-----------------+
| employee_id | last_name | department_name |
+-------------+------------+-----------------+
| 202 | Fay | Marketing |
| 115 | Khoo | Purchasing |
| 116 | Baida | Purchasing |
-- ... ...
| 112 | Urman | Finance |
| 113 | Popp | Finance |
| 206 | Gietz | Accounting |
+-------------+------------+-----------------+
32 rows in set (0.00 sec)
等效操作:
mysql> SELECT e.employee_id,last_name,d.department_name
-> FROM employees e
-> JOIN departments d
-> ON d.department_id=e.department_id
-> AND d.manager_id=e.manager_id;
+-------------+------------+-----------------+
| employee_id | last_name | department_name |
+-------------+------------+-----------------+
| 202 | Fay | Marketing |
| 115 | Khoo | Purchasing |
| 116 | Baida | Purchasing |
-- ... ...
| 112 | Urman | Finance |
| 113 | Popp | Finance |
| 206 | Gietz | Accounting |
+-------------+------------+-----------------+
32 rows in set (0.00 sec)
这种操作不够灵活,有些时候想查询的并不是自己想要的结果,就比如上面这种,想要查询的是每个员工对应的是哪个部门。
USING
:JOIN...USING()
与上面不同的是,我们手动选择比较哪些同名字段,然后进行 等值连接
用于 内连接
mysql> SELECT e.employee_id,last_name,d.department_name
-> FROM employees e
-> JOIN departments d
-> USING(department_id);
+-------------+-------------+------------------+
| employee_id | last_name | department_name |
+-------------+-------------+------------------+
| 200 | Whalen | Administration |
| 201 | Hartstein | Marketing |
| 202 | Fay | Marketing |
-- ... ...
| 113 | Popp | Finance |
| 205 | Higgins | Accounting |
| 206 | Gietz | Accounting |
+-------------+-------------+------------------+
106 rows in set (0.00 sec)
等效操作:
mysql> SELECT e.employee_id,last_name,d.department_name
-> FROM employees e
-> JOIN departments d
-> ON e.department_id=d.department_id;
+-------------+-------------+------------------+
| employee_id | last_name | department_name |
+-------------+-------------+------------------+
| 200 | Whalen | Administration |
| 201 | Hartstein | Marketing |
| 202 | Fay | Marketing |
-- ... ...
| 113 | Popp | Finance |
| 205 | Higgins | Accounting |
| 206 | Gietz | Accounting |
+-------------+-------------+------------------+
106 rows in set (0.00 sec)
相对灵活,但无法 自连接
练习
1. 显示所有员工的姓名,部门号和部门名称
mysql> SELECT e.last_name,e.department_id,d.department_name
-> FROM employees e
-> LEFT JOIN departments d
-> ON d.department_id=e.department_id;
+-------------+---------------+------------------+
| last_name | department_id | department_name |
+-------------+---------------+------------------+
| King | 90 | Executive |
| Kochhar | 90 | Executive |
| De Haan | 90 | Executive |
| Hunold | 60 | IT |
| Ernst | 60 | IT |
| Austin | 60 | IT |
-- ... ...
| Baer | 70 | Public Relations |
| Higgins | 110 | Accounting |
| Gietz | 110 | Accounting |
+-------------+---------------+------------------+
107 rows in set (0.00 sec)
2. 查询90号部门员工的 job_id
和 90
号部门的 location_id
mysql> SELECT e.last_name,e.job_id,d.location_id,e.department_id
-> FROM employees e
-> JOIN departments d
-> ON e.department_id=d.department_id
-> WHERE e.department_id=90;
+-----------+---------+-------------+---------------+
| last_name | job_id | location_id | department_id |
+-----------+---------+-------------+---------------+
| King | AD_PRES | 1700 | 90 |
| Kochhar | AD_VP | 1700 | 90 |
| De Haan | AD_VP | 1700 | 90 |
+-----------+---------+-------------+---------------+
3 rows in set (0.00 sec)
3. 选择所有有奖金的员工的 last_name
、department_name
、location_id
、city
需要考虑有的员工没有没有分配部门
mysql> SELECT e.last_name,d.department_name,l.location_id,l.city
-> FROM employees e
-> LEFT JOIN departments d
-> ON d.department_id=e.department_id
-> LEFT JOIN locations l
-> ON l.location_id=d.location_id
-> WHERE e.commission_pct IS NOT NULL;
+------------+-----------------+-------------+--------+
| last_name | department_name | location_id | city |
+------------+-----------------+-------------+--------+
| Russell | Sales | 2500 | Oxford |
| Partners | Sales | 2500 | Oxford |
| Errazuriz | Sales | 2500 | Oxford |
-- ... ...
| Taylor | Sales | 2500 | Oxford |
| Livingston | Sales | 2500 | Oxford |
| Grant | NULL | NULL | NULL |
| Johnson | Sales | 2500 | Oxford |
+------------+-----------------+-------------+--------+
35 rows in set (0.00 sec)
4. 选择 city
在 Toronto
工作的员工的 last_name
、job_id
、department_id
、department_name
mysql> SELECT e.last_name,e.job_id,d.department_id,d.department_name
-> FROM employees e
-> JOIN departments d
-> ON e.department_id=d.department_id
-> JOIN locations l
-> ON d.location_id=l.location_id
-> AND l.city='Toronto';
+-----------+--------+---------------+-----------------+
| last_name | job_id | department_id | department_name |
+-----------+--------+---------------+-----------------+
| Hartstein | MK_MAN | 20 | Marketing |
| Fay | MK_REP | 20 | Marketing |
+-----------+--------+---------------+-----------------+
2 rows in set (0.00 sec)
5. 查询员工所在的部门名称、部门地址、姓名、工作、工资,其中员工所在部门的部门名称为 Executive
mysql> SELECT e.last_name,d.department_name,e.salary,l.street_address,j.job_id
-> FROM employees e
-> JOIN departments d
-> ON d.department_id=e.department_id
-> AND d.department_name='Executive'
-> LEFT JOIN locations l
-> ON l.location_id=d.location_id
-> LEFT JOIN jobs j
-> ON e.job_id=j.job_id;
+-----------+-----------------+----------+-----------------+---------+
| last_name | department_name | salary | street_address | job_id |
+-----------+-----------------+----------+-----------------+---------+
| King | Executive | 24000.00 | 2004 Charade Rd | AD_PRES |
| Kochhar | Executive | 17000.00 | 2004 Charade Rd | AD_VP |
| De Haan | Executive | 17000.00 | 2004 Charade Rd | AD_VP |
+-----------+-----------------+----------+-----------------+---------+
3 rows in set (0.00 sec)
6. 选择指定员工的姓名、员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式
employees Emp# manager Mgr#
kochhar 101 king 100
mysql> SELECT e.last_name employees,e.employee_id "Emp#",m.last_name manager,m.employee_id "Mgr#"
-> FROM employees e
-> LEFT JOIN employees m
-> ON e.manager_id=m.employee_id;
+-------------+------+-----------+------+
| employees | Emp# | manager | Mgr# |
+-------------+------+-----------+------+
| King | 100 | NULL | NULL |
| Kochhar | 101 | King | 100 |
| De Haan | 102 | King | 100 |
-- ... ...
| Baer | 204 | Kochhar | 101 |
| Higgins | 205 | Kochhar | 101 |
| Gietz | 206 | Higgins | 205 |
+-------------+------+-----------+------+
107 rows in set (0.00 sec)
7. 查询哪些部门没有员工
mysql> SELECT d.department_name,d.department_id
-> FROM departments d
-> LEFT JOIN employees e
-> ON d.department_id=e.department_id
-> WHERE e.department_id IS NULL;
+----------------------+---------------+
| department_name | department_id |
+----------------------+---------------+
| Treasury | 120 |
| Corporate Tax | 130 |
| Control And Credit | 140 |
| Shareholder Services | 150 |
| Benefits | 160 |
| Manufacturing | 170 |
| Construction | 180 |
| Contracting | 190 |
| Operations | 200 |
| IT Support | 210 |
| NOC | 220 |
| IT Helpdesk | 230 |
| Government Sales | 240 |
| Retail Sales | 250 |
| Recruiting | 260 |
| Payroll | 270 |
+----------------------+---------------+
16 rows in set (0.00 sec)
8. 查询哪个城市没有部门
mysql> SELECT l.city
-> FROM locations l
-> LEFT JOIN departments d
-> ON d.location_id=l.location_id
-> WHERE d.location_id IS NULL;
+-----------------+
| city |
+-----------------+
| Roma |
| Venice |
| Tokyo |
| Hiroshima |
| South Brunswick |
| Whitehorse |
| Beijing |
| Bombay |
| Sydney |
| Singapore |
| Stretford |
| Sao Paulo |
| Geneva |
| Bern |
| Utrecht |
| Mexico City |
+-----------------+
16 rows in set (0.00 sec)
9. 查询部门名为 Sales
或 IT
的员工信息
mysql> SELECT e.employee_id,e.last_name
-> FROM departments d
-> JOIN employees e
-> ON e.department_id=d.department_id
-> WHERE d.department_name IN('Sales','IT');
+-------------+------------+
| employee_id | last_name |
+-------------+------------+
| 103 | Hunold |
| 104 | Ernst |
| 105 | Austin |
-- ... ...
| 176 | Taylor |
| 177 | Livingston |
| 179 | Johnson |
+-------------+------------+
39 rows in set (0.00 sec)
参考连接
- 需求分析
- 初步探索
- 不同角度对多表查询的分类
- 不同 SQL 下的实现
- SQL99 JOIN...ON... 关键字的七种连接
- SQL99 语言的新特性
- 练习
- 1. 显示所有员工的姓名,部门号和部门名称
- 2. 查询90号部门员工的 job_id 和 90 号部门的 location_id
- 3. 选择所有有奖金的员工的 last_name 、department_name、location_id、city
- 4. 选择 city 在 Toronto 工作的员工的 last_name、job_id、department_id、department_name
- 5. 查询员工所在的部门名称、部门地址、姓名、工作、工资,其中员工所在部门的部门名称为 Executive
- 6. 选择指定员工的姓名、员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式
- 7. 查询哪些部门没有员工
- 8. 查询哪个城市没有部门
- 9. 查询部门名为 Sales 或 IT 的员工信息
- 参考连接
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
怎么还不更新!!!!!!!
搞这么快?
18章,这才4、5章
大佬的意思是基础要打牢
666