前言:在数据库设计中,数据通常分散存储在多个表中,而联合查询通过连接多张表来实现复杂的数据提取和分析需求。它不仅是关联表数据的核心工具,还能高效解决多表协作的问题,是数据库开发中不可或缺的重要技能。
在正式开始讲解之前,先让我们看一下本文大致的讲解内容
1.为什么使用联合查询
在开始学习联合查询之前,先让我们了解一下为什么要使用联合查询,在数据库设计中,为了满足范式的要求,数据通常被拆分存储在多个表中,这样的设计能够提高数据的规范性和可维护性,但也带来了数据分散的问题。
比如说有这样一个例子,当需要查询学生的基本信息和班级信息时,这些数据分别存储在 student
表和 class
表中,需要从多个表中提取数据才能获取完整信息。
而联合查询正是为了解决这一问题而存在的,通过将多个表关联起来,联合查询可以将分散存储的数据整合到一个结果集中。
——所以根据上边我们所讲,联合查询的出现本质上就是为了一个目的——整合多个表的内容。
这样我们也就对为什么要使用联合查询有了初步的理解了,那么接下来就让我们学习一下有哪些联合查询的操作吧!
2.内连接
首先先让我们学习一下内连接。
内连接的基本概念:
内连接返回两个表中满足连接条件的所有记录,通常用于关联主表与外键表的数据。
内连接的基本语法:
SELECT 字段名
FROM 表1 [INNER] JOIN 表2
ON 表1.字段名 = 表2.字段名
WHERE 其他条件;
了解完了内连接的基本语法之后,让我们使用一个案例来进一步对内连接进行理解:
场景:获取学生及其班级信息
在学校管理系统中,学生的基本信息和班级信息存储在不同的表中,通过内连接,我们可以查询学生的详细信息及其所属班级名称。
SELECT -- 选择查询的字段
AS 学生姓名, -- 学生表中的姓名字段,别名为 "学生姓名"
s.sno AS 学号, -- 学生表中的学号字段,别名为 "学号"
AS 班级名称 -- 班级表中的名称字段,别名为 "班级名称"
FROM
student s -- 从学生表(s)查询数据
INNER JOIN
class c -- 使用 INNER JOIN 连接班级表(c)
ON
s.class_id = c.id; -- 学生表的 class_id 与 班级表的 id 匹配
解释:
s.class_id = c.id
指定连接条件,确保学生与其班级正确匹配。- 查询结果包括学生姓名、学号及班级名称,便于管理者快速查看学生的基本信息。
这样我们就对内连接有了一定的了解了!
2.外连接
在了解完了内连接之后,在让我们看一下外连接。
外连接的基本概念:
外连接分为左外连接、右外连接,用于返回一张表的所有记录,以及另一张表中与之匹配的数据(如果有的话)。
外连接的基本语法:
-- 左外连接
SELECT 字段名
FROM 表1 LEFT JOIN 表2
ON 表1.字段名 = 表2.字段名;
-- 右外连接
SELECT 字段名
FROM 表1 RIGHT JOIN 表2
ON 表1.字段名 = 表2.字段名;
当然,光看上述的代码可能有点晦涩难懂,那么接下来让我们使用两个案例来讲解一下:
案例1:查询所有学生及其考试成绩
在成绩管理系统中,需要获取所有学生的成绩信息,包括未参加考试的学生。
SELECT -- 选择查询的字段
AS 学生姓名, -- 学生表中的姓名字段,别名为 "学生姓名"
sc.score AS 成绩 -- 成绩表中的分数字段,别名为 "成绩"
FROM
student s -- 从学生表(s)查询数据
LEFT JOIN
score sc -- 使用 LEFT JOIN 连接成绩表(sc)
ON
s.id = sc.student_id; -- 学生表的 id 与 成绩表的 student_id 匹配
解释:
LEFT JOIN
确保学生表中的所有记录都出现在结果中,即使成绩表中没有匹配的记录。- 未匹配的成绩字段将显示为
NULL
,表明该学生未参加考试。
案例2:查询未被分配学生的班级
当某些班级尚未分配学生时,使用右外连接可快速定位这些班级。
SELECT -- 选择查询的字段
AS 班级名称 -- 班级表中的名称字段,别名为 "班级名称"
FROM
student s -- 学生表(s)
RIGHT JOIN
class c -- 使用 RIGHT JOIN 连接班级表(c)
ON
s.class_id = c.id -- 学生表的 class_id 与 班级表的 id 匹配
WHERE
s.id IS NULL; -- 筛选没有学生的班级
解释:
RIGHT JOIN
以班级表为基准,确保所有班级信息都显示在结果中。s.id IS NULL
用于过滤没有学生分配的班级。
至此,我们就了解了Mysql中的外连接了!
3. 自连接
接下来让我们看一下自连接。
自连接的基本概念:
自连接是对同一张表进行连接操作,通过为同一张表设置不同的别名,自连接可以将表看作两张表进行连接,用于行间数据的比较和关联。
自连接的基本语法:
SELECT 字段列表
FROM 表名 别名1, 表名 别名2
WHERE 别名1.字段名 = 别名2.字段名
AND 其他条件;
对于自连接,有两个注意事项:
表别名:
自连接必须为表设置别名,便于区分同一表的不同引用。连接条件:
使用WHERE
子句指定连接条件,例如两个字段相等或满足其他关系。
了解完了自连接的基本语法之后,让我们使用一个案例来进一步对自连接进行理解:
案例:比较相同学生的两门课程成绩
要求查询出“MySQL”成绩高于“Java”成绩的学生信息。
SELECT -- 选择查询的字段
s1.student_id AS 学生ID, -- 从 score 表 s1 中选取 student_id 字段,别名为 "学生ID"
s1.score AS MySQL成绩, -- 从 score 表 s1 中选取 score 字段,别名为 "MySQL成绩"
s2.score AS Java成绩 -- 从 score 表 s2 中选取 score 字段,别名为 "Java成绩"
FROM
score s1, score s2 -- 从同一个 score 表(使用别名 s1 和 s2)查询
WHERE
s1.student_id = s2.student_id -- 连接条件:选取同一学生的记录
AND s1.course_id = 3 -- MySQL 课程的课程ID为 3
AND s2.course_id = 1 -- Java 课程的课程ID为 1
AND s1.score > s2.score; -- 筛选条件:MySQL 成绩高于 Java 成绩
解释:
s1
和s2
是score
表的两个别名,分别表示 MySQL 和 Java 课程的成绩记录。s1.student_id = s2.student_id
确保比较的是同一个学生的成绩。- 其他条件过滤出符合需求的记录(如课程 ID 和成绩差异)。
4.子查询
学习完自连接之后,在让我们学习一下子查询,首先先让我们了解一下子查询的基本概念:
子查询是一个嵌套在主查询(外部查询)中的查询,可以帮助你对复杂查询进行分解,获取主查询所需的数据,子查询的执行通常在主查询之前完成,并且其结果可以被用作主查询的一部分。
当然子查询一般出现在一下的位置:
- WHERE 子句:在
WHERE
条件中使用子查询,通常用于筛选符合某条件的记录。- FROM 子句:子查询作为一个虚拟表使用,提供给主查询的数据源。
- SELECT 子句:子查询可以作为一个列,用来返回特定的值。
- INSERT、UPDATE、DELETE 子句:子查询可以用来提供插入、更新或删除操作的数据。
通过上述的讲解,我们就对子查询有了一定的了解了,那么接下来就让我们学习一下子查询的类型,并通过这些类型中的例子,来进一步理解如何去使用子查询。
子查询的类型:
【1】标量子查询
——标量子查询返回单一的单个值,通常用于SELECT
或WHERE
子句中。
例子:
SELECT name -- 选择查询员工姓名
FROM employees -- 从 employees 表中查询数据
WHERE salary > ( -- 筛选条件:工资大于子查询结果
SELECT AVG(salary) -- 子查询:计算所有员工的平均工资
FROM employees -- 从 employees 表中计算平均工资
);
在这个例子中,子查询 (SELECT AVG(salary) FROM employees)
计算了所有员工的平均工资,主查询返回那些工资高于平均工资的员工。
【2】列子查询
——列子查询返回单列的多个值,通常用于IN
、NOT IN
等操作符。
例子:
SELECT name -- 选择查询员工姓名
FROM employees -- 从 employees 表中查询数据
WHERE department_id IN ( -- 筛选条件:员工的部门ID在子查询结果中
SELECT department_id -- 子查询:选择符合条件的部门ID
FROM departments -- 从 departments 表中查询数据
WHERE location = 'New York' -- 筛选部门所在地为 'New York' 的部门
);
这里,子查询返回所有位于“New York”位置的部门ID,主查询返回属于这些部门的员工。
【3】行子查询
——行子查询返回单行多列的多个值,通常用于= (subquery)
的方式进行比较。
例子:
SELECT name, salary -- 选择查询员工姓名和工资
FROM employees -- 从 employees 表中查询数据
WHERE (department_id, salary) = ( -- 筛选条件:部门ID和工资匹配子查询的结果
SELECT department_id, MAX(salary) -- 子查询:每个部门的最高工资
FROM employees -- 从 employees 表查询数据
GROUP BY department_id -- 按部门分组,计算每个部门的最高工资
);
这个例子中,子查询 (SELECT department_id, MAX(salary) FROM employees GROUP BY department_id)
返回每个部门的最大工资和部门ID,主查询用于查找这些工资最高的员工。
【4】表子查询
——表子查询返回多行多列数据,通常用在FROM
子句中,作为虚拟表来参与联接操作。
例子:
SELECT , e.salary -- 选择查询员工姓名和工资
FROM employees e -- 从 employees 表中查询数据,并给表起别名 e
JOIN ( -- 将主查询与子查询连接
SELECT department_id, MAX(salary) AS max_salary -- 子查询:按部门分组并获取每个部门的最高工资,命名为 max_salary
FROM employees -- 从 employees 表中查询数据
GROUP BY department_id -- 按部门ID分组
) AS dept_max_salary -- 给子查询结果取别名 dept_max_salary
ON e.department_id = dept_max_salary.department_id -- 将员工表和子查询结果按部门ID连接
WHERE e.salary = dept_max_salary.max_salary; -- 筛选员工工资等于该部门的最高工资
在这个查询中,子查询 (SELECT department_id, MAX(salary) FROM employees GROUP BY department_id)
生成了一个包含部门ID和最大工资的临时表,并与employees
表联接,返回每个部门工资最高的员工。
至此我们就大致的了解了有关子查询的类型了!希望读者可以根据上述的案例对子查询有自己的理解!
5.合并查询
最后在让我们学习一下合并查询,合并查询允许你将两个或多个查询的结果合并成一个结果集,每个查询的结果必须有相同数量的列,并且列的数据类型应该兼容。UNION
主要有两种形式:
UNION
:默认情况下,UNION
会去除重复的结果。UNION ALL
:UNION ALL
不会去重,会返回所有的结果,包括重复的行。
了解完了合并查询的基本概念之后,在让我们学习一下合并查询的语法:
SELECT column1, column2 FROM table1
UNION
SELECT column1, column2 FROM table2;
这样我们就大致的了解了什么是联合查询了,那么接下来让我们使用一个案例来讲解一下合并查询该如何去使用:
例子:
假设有两个表:employees_2019
和 employees_2020
,你想查询这两年中所有员工的姓名和职位:
SELECT name, position -- 选择查询员工姓名和职位
FROM employees_2019 -- 从 2019 年的员工表中获取数据
UNION -- 合并两个查询的结果,并去重
SELECT name, position -- 选择查询员工姓名和职位
FROM employees_2020; -- 从 2020 年的员工表中获取数据
这段 SQL 查询使用了 UNION
,将两个表 (employees_2019
和 employees_2020
) 中的员工姓名和职位合并成一个结果集。
——当然如果你希望保留重复的员工记录,可以使用 UNION ALL
SELECT name, position -- 选择查询员工姓名和职位
FROM employees_2019 -- 从 2019 年的员工表中获取数据
UNION ALL -- 合并两个查询的结果,不去重
SELECT name, position -- 选择查询员工姓名和职位
FROM employees_2020; -- 从 2020 年的员工表中获取数据
这段 SQL 查询使用了 UNION ALL
,将两个表 (employees_2019
和 employees_2020
) 中的员工姓名和职位合并成一个结果集,只不过与 UNION
不同,UNION ALL
不会去重,所以重复的员工记录会保留。
这样我们就了解了联合查询的全部内容了!