MySQL4️⃣多表查询(❗)
1、多表查询
1.1、简介
多表查询:从多个存在关联关系的表中查询记录。
- 使用:
- 使用逻辑外键,不使用 FOREIGN KEY。
- 通常会为表起别名,简化书写。
- 若多表查询涉及同名字段,起别名加以区分。
- 说明:在实际业务场景中,建议使用单表查询为主。
- 原因:多表查询影响数据库性能。
- 解决:在代码层面,将涉及多表查询的业务拆分为多次单表查询,拼接查询结果。
1.2、多表关系
外键位置 | 外键要求 | 示例 | |
---|---|---|---|
One to One | 任意表 | 另一方的主键,UNIQUE 约束 | 用户 - 用户详情 |
One to Many | Many 表 | One 表的主键 | 部门 - 员工 |
Many to Many | 中间表 | 两张表的主键 | 学生 - 课程 |
1.3、连接查询
1.3.1、笛卡尔积
关键字
FROM
:计算笛卡尔积。
- 在多表查询中,需要消除无效的笛卡尔积,仅保留关联部分(交集)。
- 使用连接查询,即可达成以上目的。
1.3.2、连接类型(❗)
- 连接查询:
- 内连接:交集(
INNER
) - 外连接(
OUTER
):- 左外连接:左表所有数据 + 交集(
LEFT [OUTER]
) - 右外连接:右表所有数据 + 交集(
RIGHT [OUTER]
)
- 左外连接:左表所有数据 + 交集(
- 自连接:同一张表连接多次。
- 内连接:交集(
- 联合查询:合并多个查询结果,形成新的结果集(
UNION [ALL]
) - 子查询:同一张表连接多次(👎 性能差,不推荐)
- 需要指定别名,可以是内/外连接。
- 常用于数据库表维护的树状结构,即包含“父 id”字段。
2、连接查询
2.1、格式
join_type 的取值如下
-
INNER:内连接,可省略。
-
LEFT OUTER:左外连接,可省略 OUTER。
-
RIGHT OUTER:右外连接,可省略 OUTER。
SELECT 字段列表 FROM 表1 别名 [join_type] JOIN 表2 别名 ON 连接条件;
2.2、示例
2.2.1、内连接
交集
示例:查询员工姓名及其部门名称。
(没有部门的员工不会被查出)
SELECT e.name AS '姓名',
d.name AS '部门'
FROM t_emp AS e
INNER JOIN t_dept AS d
ON e.dept_id = d.id;
# 等价写法
SELECT e.name AS '姓名',
d.name AS '部门'
FROM t_emp AS e, t_dept AS d
WHERE e.dept_id = d.id;
2.2.2、外连接
左/右表 + 交集
示例:查询所有员工姓名及其部门名称。
(没有部门的员工也会查出,其部门名称为 null)
# 左外连接
SELECT e.name AS '姓名',
d.name AS '部门'
FROM t_emp AS e
LEFT JOIN t_dept AS d
ON e.dept_id = d.id;
# 右外连接
SELECT e.name AS '姓名',
d.name AS '部门'
FROM t_dept AS d
RIGHT JOIN t_emp AS e
ON d.id = e.dept_id;
2.2.3、自连接
同一张表连接多次
示例:查询员工姓名及其领导姓名。
# 内连接:顶级领导不会被查出
SELECT a.name '姓名',
b.name '领导'
FROM t_emp a
JOIN t_emp b
ON a.managerid = b.id;
# 外连接:所有员工都会查出
SELECT a.name '姓名',
b.name '领导'
FROM t_emp a
LEFT JOIN t_emp b
ON a.managerid = b.id;
3、联合查询
合并多个查询结果,形成新的结果集。
-
格式:
-
UNION:合并,去重。
-
UNION ALL:仅合并。
SELECT 字段列表 FROM 表1 UNION [ALL] SELECT 字段列表 FROM 表2;
-
-
说明:
- 待合并的查询结果集,字段列表需一致(列数,类型)。
- 通常可使用多条件查询(逻辑运算符
OR
)等价实现。
示例:查询工资低于 5000 或年龄高于 40 的员工
-
联合查询:
SELECT * FROM t_tmp WHERE salary < 5000 UNION [ALL] SELECT * FROM t_tmp WHERE age > 40;
-
多条件查询:
SELECT * FROM t_tmp WHERE salary < 5000 OR age > 40;
4、子查询
嵌套查询:SQL 语句中嵌套
SELECT
语句,以内层查询结果作为外层字段列表。
- 使用位置:
- WHERE
- FROM
- SELECT
- 分类:根据子查询返回结果。
- 标量子查询:单个值
- 列子查询:一列
- 行子查询:一行
- 表子查询:多行多列
- 说明:大部分操作可通过连接查询等价实现。
4.1、标量子查询
4.1.1、常用运算符
>
、<
、>=
、<=
、=
、<=>
、!=
或 <>
4.1.2、示例
-
查询【研发部】的员工信息:
(👉 子查询 dept 研发部 id,外层查询 emp 员工信息)
SELECT * FROM t_emp WHERE dept_id = ( # 子查询 SELECT id FROM t_dept WHERE name = '研发部');
-
查询比【7 号员工】晚入职的员工信息:
(👉 子查询 7 号员工的入职日期,外层查询条件查询)
SELECT * FROM t_emp WHERE entrydate > ( # 子查询 SELECT entrydate FROM t_emp WHERE id = 7);
4.2、列子查询
常用运算符
含义 | 备注 | |
---|---|---|
IN | 集合范围内,任意一个 | 后接小表(外层表 > 内层表) |
EXISTS | 对外层表进行逐行循环,子查询结果集非空时取出当前记录 | 后接大表(外层表 < 内层表) |
ANY(SOME) | 子查询返回列表中,有任意一个满足 | 可用 MAX/MIN 等价 |
ALL | 子查询返回列表中,所有值都需满足 | 可用 MAX/MIN 等价 |
4.2.1、IN & EXISTS
① 示例
示例:查询【研发部】和【质量部】的员工信息。
-
IN:子查询 dept 部门 id,外层查询 emp 员工信息。
SELECT * FROM t_emp WHERE dept_id IN ( # 子查询 SELECT id FROM t_dept WHERE name = '研发部' OR name = '质量部');
-
EXISTS:遍历外层表,每轮循环向子查询传入 dept_id。
SELECT * FROM t_emp AS a WHERE EXISTS ( SELECT id FROM t_emp WHERE a.dept_id = ( SELECT id FROM t_dept WHERE name = '研发部' OR name = '质量部'));
② 对比
IN 与 EXISTS 可等价替换,但原理不同。
IN | EXISTS | |
---|---|---|
场景 | 子表数据量小 | 子表数据量大 |
原理 | 1. 查询子表所有数据并保存到内存; 2. 循环子表,嵌套循环主表,逐条匹配记录。 |
1. 查询主表所有数据并保存到内存; 2. 循环主表,嵌套循环子表,逐条匹配记录。 |
核心 | 先从后主,子表驱动主表 | 先主后从,主表驱动子表 |
示例分析:主表有 10000 条数据,子表有 200 条记录
(即子表是小表)
-
使用 IN:先从后主,访问 200 次数据库(👍)
for(int i = 0; i < 200; i++) { for (int j = 0; j < 10000; j++) { ... } }
-
使用 EXISTS:先主后从,访问 10000 次数据库。
for(int i = 0; i < 10000; i++) { for (int j = 0; j < 200; j++) { ... } }
4.2.2、ALL & ANY
通常可与 MAX/MIN 等价替换。
① ALL
查询高于【研发部】所有员工工资都高的员工信息:
-
列子查询:子子查询 dept 研发部 id,子查询 emp 工资,外层查询 ALL 比较。
SELECT * FROM t_emp WHERE salary > ALL( # 子查询 SELECT salary FROM t_emp WHERE dept_id = ( # 子子查询 SELECT id FROM t_dept WHERE name = '研发部'));
-
等价:子子查询 dept 研发部 id,子查询 emp MAX(工资),外层查询 > 比较。
SELECT * FROM t_emp WHERE salary > ( # 子查询 SELECT MAX(salary) FROM t_emp WHERE dept_id = ( # 子子查询 SELECT id FROM t_dept WHERE name = '研发部'));
② ANY
查询高于【研发部】任一员工工资的员工信息:
-
列子查询:相比例 2,改为 ANY
SELECT * FROM t_emp WHERE salary > ANY( # 子查询 SELECT salary FROM t_emp WHERE dept_id = ( # 子子查询 SELECT id FROM t_dept WHERE name = '研发部'));
-
等价:相比 2,改为 MIN
SELECT * FROM t_emp WHERE salary > ( # 子查询 SELECT MIN(salary) FROM t_emp WHERE dept_id = ( # 子子查询 SELECT id FROM t_dept WHERE name = '研发部'));
4.3、行子查询
常用操作符
=
、!=
或 <>
、[NOT] IN
示例:查询与【3 号员工】的工资、部门都相同的员工信息。
Hint:此写法包括 3 号员工本身。
SELECT *
FROM t_emp
WHERE (salary, dept_id) = (
# 子查询
SELECT salary, dept_id
FROM t_emp
WHERE id = '3');
4.4、表子查询
常用操作符
IN
示例:查询与【1号,3 号员工】的工资,部门相同的员工。
SELECT *
FROM t_emp
WHERE (salary, dept_id) IN (
# 子查询
SELECT salary, dept_id
FROM t_emp
WHERE id = '1' OR id = '3');