在数据库操作里,单表查询常无法满足复杂业务需求,多表查询则至关重要。MySQL 作为常用关系型数据库,其多表查询功能强大,能关联整合不同表的数据。本文将深入探讨 MySQL 多表查询,从基础到实现,助力掌握核心要点。
1. 多表查询了解
多表查询,也叫关联查询,是指将两个或更多表联合起来完成查询操作。参与查询的表之间存在一对一
或一对多
的关系,且表间有用于关联的字段,这些关联字段可能建立了外键,也可能未建立外键。
- 比如:员工表和部门表,这两个表依靠“部门编号”进行关联。
1.1 笛卡尔积(或交叉连接)的理解
笛卡尔乘积是一个数学运算。假设有两个集合 X 和 Y,那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合,也就是第一个对象来自于 X,第二个对象来自于 Y 的所有可能。
笛卡尔积组合的个数即为两个集合中元素个数的乘积数。(笛卡尔积的组合:无脑的将两个集合的数据组合)
SQL标准中,笛卡尔积也称为 交叉连接
,英文是 CROSS JOIN
。它的作用就是可以把任意表进行连接,即使这两张表不相关。
- 在MySQL中什么情况会出现笛卡尔积?
- 缺少连接条件:在进行多表查询时,如果没有指定表之间的连接条件,数据库就会将表中所有行进行两两组合,从而产生笛卡尔积。
- 连接条件无效:如果连接条件设置不合理,无法有效关联表中的行,也会产生类似笛卡尔积的效果。
- 使用错误的连接类型:在使用连接操作时,如果使用了错误的连接类型,也可能导致笛卡尔积。例如,在需要使用内连接(INNER JOIN)时,错误地使用了交叉连接(CROSS JOIN)。
为了避免笛卡尔积, 可以在 WHERE
加入有效的连接条件。加入连接条件后,查询语法:
SELECT table1.column, table2.column
FROM table1, table2
WHERE table1.column1 = table2.column2; #连接条件
# 在表中有相同列时,在列名之前加上表名前缀。
SELECT last_name, department_name
FROM employees, departments
WHERE employees.department_id = departments.department_id;
- 注意事项:
- 如果在查询语句中出现了多个表中都存在的列时,则必须指明此列所在的表(在列名之前加上表名前缀)。
- 建议:从sql优化的角度,建议多表查询时,每个字段前都指明其所在的表。
- 可以给表起别名,在
select
和where
中使用表的别名。但是,如果给表起了别名,一旦使用就只能使用别名,不能再使用原表名了。- 如果有
n
个表实现多表查询,则需要至少n-1
个连接条件。
1.2 多表查询分类
1.2.1 等值连接 vs 非等值连
- 等值连接(连接条件是等于)
SELECT employees.employee_id, employees.last_name,
employees.department_id, departments.department_id,
departments.location_id
FROM employees, departments
WHERE employees.department_id = departments.department_id;
- 非等值连接(连接条件是不等于)
SELECT e.last_name, e.salary, j.grade_level
FROM employees e, job_grades j
WHERE e.salary BETWEEN j.lowest_sal AND j.highest_sal;
SELECT e.last_name, e.salary, j.grade_level
FROM employees e, job_grades j
WHERE e.salary >= j.lowest_sal AND e.salary <= j.highest_sal;
1.2.2 自连接 vs 非自连接
- 自连接(自我引用的)
当table1和table2本质上是同一张表,只是用取别名的方式虚拟成两张表以代表不同的意义。然后两个表再进行内连接,外连接等查询。
# 查询员工id,员工姓名及其管理者的id和姓名
SELECT emp.employee_id,emp.last_name,mgr.employee_id,mgr.last_name
FROM employees emp ,employees mgr
WHERE emp.`manager_id` = mgr.`employee_id`;
- 非自连接(多个表之间)后续了解。
1.2.3 内连接 vs 外连接
- 内连接(交集)
内连接合并具有同一列的两个以上的表的行,结果集中不包含一个表与另一个表不匹配的行。结果集内只有交集结果,即只会显示有关联的数据。
SELECT employee_id,department_name
FROM employees e,departments d
WHERE e.`department_id` = d.department_id; #只有106条记录
- 外连接(并集)
两个表在连接过程中除了返回满足连接条件的行以外还返回左(或右)表中不满足条件的行 ,这种连接称为左(或右) 外连接。没有匹配的行时, 结果表中相应的列为空(NULL)。
- 如果是左外连接,除了返回满足连接条件的行以外还返回左表中不满足条件的行,连接条件中左边的表也称为主表 ,右边的表称为从表 。
- 如果是右外连接,除了返回满足连接条件的行以外还返回右表中不满足条件的行,连接条件中右边的表也称为主表 ,左边的表称为从表 。
2. MySQL进行多表查询
2.1 基本语法
使用 JOIN...ON
子句创建连接的语法结构:
SELECT table1.column, table2.column,table3.column
FROM table1
JOIN table2 ON (table1 和 table2 的连接条件)
JOIN table3 ON (table2 和 table3 的连接条件)
嵌套逻辑类似我们使用的 FOR 循环
。采用的这种嵌套结构非常清爽、层次性更强、可读性更强,即使再多的表进行连接也都清晰可见。
语法说明:
- 可以使用
ON 子句
指定额外的连接条件。- 这个连接条件是与其它条件分开的。
ON 子句
使语句具有更高的易读性。- 关键字
JOIN
、INNER JOIN
、CROSS JOIN
的含义是一样的,都表示内连接。
2.2 内连接(INNER JOIN)的实现
内连接(INNER JOIN)是 MySQL 中用于合并多个表的数据的常用操作,它会返回多个表中满足连接条件的行的组合。
- 语法:
SELECT 字段列表
FROM A表 (INNER) JOIN B表
ON 关联条件
WHERE 等其他子句;
- 举例:
SELECT e.employee_id, e.last_name, e.department_id,
d.department_id, d.location_id
FROM employees e JOIN departments d
ON (e.department_id = d.department_id);
SELECT employee_id, city, department_name
FROM employees e
JOIN departments d
ON d.department_id = e.department_id
JOIN locations l
ON d.location_id = l.location_id;
2.3 外连接(OUTER JOIN)的实现
外连接(OUTER JOIN)用于在连接多个表时,除了返回满足连接条件的行,还会返回部分或全部表中未匹配的行。
2.3.1 左外连接(LEFT OUTER JOIN)
左外连接会返回左表中的所有行,以及右表中匹配的行。如果右表中没有匹配的行,则右表的列值为 NULL。
- 语法:
#实现查询结果是A
SELECT 字段列表
FROM A表 LEFT JOIN B表
ON 关联条件
WHERE 等其他子句;
- 举例:
SELECT e.last_name, e.department_id, d.department_name
FROM employees e
LEFT OUTER JOIN departments d
ON (e.department_id = d.department_id) ;
2.3.2 右外连接(RIGHT OUTER JOIN)
右外连接会返回右表中的所有行,以及左表中匹配的行。如果左表中没有匹配的行,则左表的列值为 NULL。
- 语法
#实现查询结果是B
SELECT 字段列表
FROM A表 RIGHT JOIN B表
ON 关联条件
WHERE 等其他子句
- 举例
SELECT e.last_name, e.department_id, d.department_name
FROM employees e
RIGHT OUTER JOIN departments d
ON (e.department_id = d.department_id) ;
2.3.3 满外连接(FULL OUTER JOIN)
全外连接会返回两个表中的所有行,匹配的行正常组合,未匹配的行对应的另一表的列值为 NULL。
- 不过 MySQL 本身不直接支持 FULL OUTER JOIN,可以使用 UNION 操作符将左外连接和右外连接的结果合并来模拟全外连接,即
LEFT JOIN UNION RIGHT JOIN
。
- 满外连接的结果 = 左右表匹配的数据 + 左表没有匹配到的数据 + 右表没有匹配到的数据。
3. UNION的使用
在 MySQL 里,UNION
操作符用于把多个 SELECT 语句的结果合并成一个结果集。
- 合并时,两个表对应的列数和数据类型必须相同,并且相互对应。
- 各个SELECT语句之间使用
UNION
或UNION ALL
关键字分隔。
- 语法格式:
SELECT column,... FROM table1
UNION [ALL]
SELECT column,... FROM table2
UNION
操作符:返回两个查询的结果集的并集,去除重复记录。UNION ALL
操作符:返回两个查询的结果集的并集。对于两个结果集的重复部分,不去重。
注意:
- 执行UNION ALL语句时所需要的资源比UNION语句少。
- 如果明确知道合并数据后的结果数据不存在重复数据,或者不需要去除重复的数据,则尽量使用UNION ALL语句,以提高数据查询的效率。
- 举例:查询部门编号>90或邮箱包含a的员工信息
#方式1
SELECT * FROM employees WHERE email LIKE '%a%' OR department_id>90;
#方式2
SELECT * FROM employees WHERE email LIKE '%a%'
UNION
SELECT * FROM employees WHERE department_id>90;
- 举例:查询中国用户中男性的信息以及美国用户中年男性的用户信息
SELECT id,cname FROM t_chinamale WHERE csex='男'
UNION ALL
SELECT id,tname FROM t_usmale WHERE tGender='male';
4. 七种SQL JOINS的实现
在 SQL 中,连接(JOIN)用于根据两个或多个表中的列之间的关系,从这些表中查询数据。
- 常见的有七种 SQL JOIN
连接类型 | 描述 |
---|---|
内连接(INNER JOIN) | 返回两个表中满足连接条件的所有行 |
左外连接(LEFT JOIN/LEFT OUTER JOIN) | 返回左表中的所有行,以及右表中匹配的行。右表无匹配时,对应列值为 NULL |
右外连接(RIGHT JOIN/RIGHT OUTER JOIN) | 返回右表中的所有行,以及左表中匹配的行。左表无匹配时,对应列值为 NULL |
全外连接(FULL JOIN/FULL OUTER JOIN) | 返回两个表中的所有行,一方无匹配时,对应列值为 NULL 。MySQL 中用 UNION 模拟 |
交叉连接(CROSS JOIN) | 返回两个表的笛卡尔积,即左表每一行与右表每一行组合 |
左半连接(LEFT SEMI JOIN) | 返回左表中在右表有匹配行的行,不返回右表列。MySQL 用 IN 或 EXISTS 实现 |
左反连接(LEFT ANTI JOIN) | 返回左表中在右表无匹配行的行,不返回右表列。MySQL 用 NOT IN 或 NOT EXISTS 实现 |
- 代码实现
#中图:内连接 A∩B
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`;
#左上图:左外连接
SELECT employee_id,last_name,department_name
FROM employees e LEFT JOIN departments d
ON e.`department_id` = d.`department_id`;
#右上图:右外连接
SELECT employee_id,last_name,department_name
FROM employees e RIGHT JOIN departments d
ON e.`department_id` = d.`department_id`;
#左中图:左半外连接 A - A∩B
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
#右中图:右半外连接 B-A∩B
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 NUL
#左下图:满外连接 = 左中图 + 右上图 A∪B
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`;
# 右下图 = 左中图 + 右中图 A∪B- A∩B 或者 (A - A∩B) ∪ (B - A∩B)
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
- 语法格式小结
# 左中图
#实现A - A∩B
select 字段列表
from A表 left join B表
on 关联条件
where 从表关联字段 is null and 等其他子句;
# 右中图
#实现B - A∩B
select 字段列表
from A表 right join B表
on 关联条件
where 从表关联字段 is null and 等其他子句;
# 左下图
#实现查询结果是A∪B
#用左外的A,union 右外的B
select 字段列表
from A表 left join B表
on 关联条件
where 等其他子句
union
select 字段列表
from A表 right join B表
on 关联条件
where 等其他子句;
# 右下图
#实现A∪B - A∩B 或 (A - A∩B) ∪ (B - A∩B)
#使用左外的 (A - A∩B) union 右外的(B - A∩B)
select 字段列表
from A表 left join B表
on 关联条件
where 从表关联字段 is null and 等其他子句
union
select 字段列表
from A表 right join B表
on 关联条件
where 从表关联字段 is null and 等其他子句
5. 多表连接新特性
SQL99
标准的一些新特性。
5.1 自然连接 NATURAL JOIN
自然连接(NATURAL JOIN)是一种特殊的连接方式,它会自动根据两个表中具有相同名称和数据类型的列来进行连接操作。在使用自然连接时,无需显式指定连接条件,数据库会自动匹配这些同名同类型的列,并返回匹配的行。
可以把自然连接理解为
SQL92
中的等值连接。它会帮你自动查询两张连接表中所有相同的字段 ,然后进行 等值连接 。
- 在
SQL92
标准中:
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
ON e.`department_id` = d.`department_id`
AND e.`manager_id` = d.`manager_id`;
- 在
SQL99
中你可以写成:
SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;
- 列名和数据类型:自然连接依赖于列名和数据类型的匹配。如果两个表中存在多个同名同类型的列,数据库会以这些列作为连接条件。
- 结果列的唯一性:在自然连接的结果中,同名的列只会出现一次。
- 可能存在的问题:由于自然连接自动匹配列,可能会导致意外的结果。因此,在使用自然连接时,要确保表结构和列名的设计是合理的,避免出现不必要的连接。
5.2 USING连接
USING
连接是 SQL99
标准中引入的一种连接方式,它可以简化多表连接的语法,尤其适用于两个表中存在同名且含义相同的列时。
当我们进行连接的时候,SQL99
还支持使用 USING
指定数据表里的 同名字段 进行等值连接。但是只能配合JOIN
一起使用。比如:
SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);
与自然连接
NATURAL JOIN
不同的是,USING
指定了具体的相同的字段名称,你需要在USING
的括号()
中填入要指定的同名字段。
同时使用JOIN...USING
可以简化JOIN ON
的等值连接。它与下面的 SQL 查询结果是相同的:SELECT employee_id,last_name,department_name FROM employees e ,departments d WHERE e.department_id = d.department_id;
- USING 子句中指定的列名必须在两个表中都存在,并且数据类型要兼容。
- USING 子句可以指定多个列,用逗号分隔,以实现更复杂的连接条件。
6. 多表查询小结
表连接的约束条件可以有三种方式:WHERE
, ON
, USING
WHERE
:适用于所有关联查询ON
:只能和JOIN一起使用,只能写关联条件。虽然关联条件可以并到WHERE中和其他条件一起写,但分开写可读性更好。USING
:只能和JOIN一起使用,而且要求两个关联字段在关联表中名称一致,而且只能表示关联字段值相等
#关联条件
#把关联条件写在where后面
SELECT last_name,department_name
FROM employees,departments
WHERE employees.department_id = departments.department_id;
#把关联条件写在on后面,只能和JOIN一起使用
SELECT last_name,department_name
FROM employees INNER JOIN departments
ON employees.department_id = departments.department_id;
SELECT last_name,department_name
FROM employees CROSS JOIN departments
ON employees.department_id = departments.department_id;
SELECT last_name,department_name
FROM employees JOIN departments
ON employees.department_id = departments.department_id;
#把关联字段写在using()中,只能和JOIN一起使用
#而且两个表中的关联字段必须名称相同,而且只能表示=
#查询员工姓名与基本工资
SELECT last_name,job_title
FROM employees INNER JOIN jobs USING(job_id);
#n张表关联,需要n-1个关联条件
#查询员工姓名,基本工资,部门名称
SELECT last_name,job_title,department_name FROM employees,departments,jobs
WHERE employees.department_id = departments.department_id
AND employees.job_id = jobs.job_id;
SELECT last_name,job_title,department_name
FROM employees INNER JOIN departments INNER JOIN jobs
ON employees.department_id = departments.department_id
AND employees.job_id = jobs.job_id;
- 多表连接就相当于嵌套 for 循环一样,非常消耗资源,会让 SQL 查询性能下降得很严重,因此不要连接不必要的表。在许多 DBMS 中,也都会有最大连接表的限制。
- 需要 join 的字段,数据类型保持绝对一致;
- 多表关联查询时, 保证被关联的字段需要有索引。
总之,MySQL 多表查询实用但有性能挑战。要理解概念、掌握连接方式和新特性,同时注意性能优化,如避免连接不必要的表、保证关联字段数据类型一致并建立索引。