MySQL联合查询详解
联合查询
设计数据表是把表进行拆分,为了消除表中的字段依赖关系(部分函数依赖,传递依赖…),这时会导致一条SQL查出来的数据,对于业务来说是不完整的,我们就可以使用联合查询把关系中的数据全部查出来,在一个数据行中显示详细信息
联合查询时MySQL是如何执行的
1.取多张表的笛卡尔积
表格一student
| 编号 | 姓名 | 性别 | 班级编号 |
|---|---|---|---|
| 1 | 张三 | 男 | 1 |
| 2 | 李四 | 女 | 1 |
| 3 | 王五 | 男 | 2 |
| 4 | 赵六 | 男 | 2 |
表格二class
| 编号 | 班级 |
|---|---|
| 1 | 1班 |
| 2 | 2班 |
对多张表进行笛卡尔积的过程,
-
先从第一张表中取一条记录,然后再与第二张表中的第一条记录进行组合,生成一条新的记录
-
先从第一张表中取一条记录,然后再与第二张表中的第二条记录进行组合,生成一条新的记录 ……
最后得到的结果就是一个全排列结果集
| 编号 | 姓名 | 性别 | 编号 | 班级 |
|---|---|---|---|---|
| 1 | 张三 | 男 | 1 | 1班 |
| 1 | 张三 | 男 | 2 | 2班 |
| 2 | 李四 | 女 | 1 | 1班 |
| 2 | 李四 | 女 | 2 | 2班 |
| 3 | 王五 | 男 | 1 | 1班 |
| 3 | 王五 | 男 | 2 | 2班 |
| 4 | 赵六 | 男 | 1 | 1班 |
| 4 | 赵六 | 男 | 2 | 2班 |
语法:
select * from 表名,表名;
示例:
select * from student,class;

从查询的结果可以看见,两张表取笛卡尔积之后,有些数据是无效数据
2.通过连接条件过滤掉无效数据
两个表之间是有主外键关系,只需要判断两个表中的主外键字段是否相等即可
select * from student,class where student.class_id = class.class_id;

**注意:**不能写成select * from student,class where class_id = class_id;,因为class_id在两张表中都存在,MySQL分不清当前语句中的class_id应该取自哪张表。可以通过表名.列名的方式来解决这个问题。
3.通过指定列查询,精简结果集
查询列表中通过表名.列名的方式指定要查询的字段
select student.id,student.name,class.name from student,class where student.class_id=class.class_id;
可以通过给表取别名的方式来精简代码
select s.id,s.name,c.name from student s,class c where s.class_id=c.class_id;
联合查询也叫表连接查询:
- 首先确定哪几张表要参与查询
- 根据表与表之间的主外键关系 确定过滤条件
- 精简查询字段,得到想要的结果
示例用表
创建表:
-- 课程表
CREATE TABLE course (id INT PRIMARY KEY AUTO_INCREMENT COMMENT '课程ID',name VARCHAR(50) NOT NULL COMMENT '课程名称'
)-- 班级表
CREATE TABLE class (id INT PRIMARY KEY AUTO_INCREMENT COMMENT '班级ID',name VARCHAR(50) NOT NULL COMMENT '班级名称'
)-- 学生表
CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生ID',name VARCHAR(20) NOT NULL COMMENT '姓名',sno VARCHAR(20) NOT NULL COMMENT '学号',age TINYINT UNSIGNED NOT NULL COMMENT '年龄',gender TINYINT COMMENT '性别(1男,0女)',enroll_date DATE COMMENT '入学日期',class_id INT COMMENT '班级ID',CONSTRAINT fk_student_class FOREIGN KEY (class_id) REFERENCES class (id)
)-- 成绩表
CREATE TABLE score (id INT PRIMARY KEY AUTO_INCREMENT COMMENT '成绩记录ID',score DECIMAL(5,2) COMMENT '成绩',student_id INT NOT NULL COMMENT '学生ID',course_id INT NOT NULL COMMENT '课程ID',CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student (id),CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course (id)
)
插入数据:
# 课程表
insert into course (name) values ('Java'), ('C++'), ('MySQL'), ('操作系统'), ('计算机⽹络'), ('数据结构');
# 班级表
insert into class(name) values ('Java001班'), ('C++001班'), ('前端001班');
# 学⽣表
insert into student (name, sno, age, gender, enroll_date, class_id) values
('唐三藏', '100001', 18, 1, '1986-09-01', 1),
('孙悟空', '100002', 18, 1, '1986-09-01', 1),
('猪悟能', '100003', 18, 1, '1986-09-01', 1),
('沙悟净', '100004', 18, 1, '1986-09-01', 1),
('宋江', '200001', 18, 1, '2000-09-01', 2),
('武松', '200002', 18, 1, '2000-09-01', 2),
('李逹', '200003', 18, 1, '2000-09-01', 2),
('不想毕业', '200004', 18, 1, '2000-09-01', 2);
# 成绩表
insert into score (score, student_id, course_id) values
(70.5, 1, 1),(98.5, 1, 3),(33, 1, 5),(98, 1, 6),
(60, 2, 1),(59.5, 2, 5),
(33, 3, 1),(68, 3, 3),(99, 3, 5),
(67, 4, 1),(23, 4, 3),(56, 4, 5),(72, 4, 6),
(81, 5, 1),(37, 5, 5),
(56, 6, 2),(43, 6, 4),(79, 6, 6),
(80, 7, 2),(92, 7, 6);
内连接
上面的示例便是一个内连接
语法:
-- 写法一:
select 字段 from 表1 别名1, 表2 别名2 where 连接条件 and 其他条件;
-- 写法二:
select 字段 from 表1 别名1 [inner] join 表2 别名2 on 连接条件 where 其他条件;
写法二中的inner是可选项,也就是说可以省略
示例:
select s.id,s.name,c.name from student s,class c where s.class_id=c.id;select s.id,s.name,c.name from student s inner join class c on s.class_id=c.id;select s.id,s.name,c.name from student s join class c on s.class_id=c.id;
联合查询详细步骤
- 确定查询中涉及到哪些表
- 对目标表取笛卡尔积
- 确定连接条件
- 确定对整个结果集的过滤条件
- 精简查询字段
内连接实例:
(1).查询“李逹”同学的成绩
SELECT s.name AS 学生姓名,c.name AS 课程名,sc.score AS 成绩
FROM student s,score sc,course c
WHERE s.id = sc.student_idAND sc.course_id = c.idAND s.name = '李逹';
或者
SELECT s.name AS 学生姓名,c.name AS 课程名,sc.score AS 成绩
FROM student s
JOIN score sc ON s.id = sc.student_id
JOIN course c ON sc.course_id = c.id
WHERE s.name = '李逹';
(2)查询所有同学的总成绩及个人信息
select st.id,st.name,SUM(sc.score) as 总分
from student st,score sc,course c
where st.id = sc.student_id AND sc.course_id = c.id
group by sc.student_id;
外连接
- 外连接分为左外连接、右外连接和全外连接三种类型,MySQL不⽀持全外连接。
- **左外连接:**返回左表的所有记录和右表中匹配的记录。如果右表中没有匹配的记录,则结果集中对 应字段会显示为NULL。
- **右外连接:**与左外连接相反,返回右表的所有记录和左表中匹配的记录。如果左表中没有匹配的记 录,则结果集中对应字段会显示为NULL。
- **全外连接:**结合了左外连接和右外连接的特点,返回左右表中的所有记录。如果某⼀边表中没有匹 配的记录,则结果集中对应字段会显示为NULL。
语法:
-- 左外连接,表1完全显⽰
select 字段名 from 表名1 left join 表名2 on 连接条件;
-- 右外连接,表2完全显⽰
select 字段 from 表名1 right join 表名2 on 连接条件;
示例:
(1)(左连接)查询没有参加考试的同学信息
在学生表中插入的('不想毕业', '200004', 18, 1, '2000-09-01', 2)这条数据并没有出现在score表中,使用内连接就无法查出这条数据。而使用外连接就可以查询。
# 左连接以JOIN左边的表为基准,左表显⽰全部记录,右表中没有匹配的记录⽤NULL填充
SELECT s.id, s.name, s.sno, s.age, sc.*
FROM student s
LEFT JOIN score sc ON sc.student_id = s.id;
| s.id | s.name | s.sno | s.age | sc.id | sc.score | sc.student_id | sc.course_id |
|---|---|---|---|---|---|---|---|
| 1 | 唐三藏 | 100001 | 18 | 1 | 70.5 | 1 | 1 |
| 1 | 唐三藏 | 100001 | 18 | 2 | 98.5 | 1 | 3 |
| 1 | 唐三藏 | 100001 | 18 | 3 | 33 | 1 | 5 |
| 1 | 唐三藏 | 100001 | 18 | 4 | 98 | 1 | 6 |
| 2 | 孙悟空 | 100002 | 18 | 5 | 60 | 2 | 1 |
| 2 | 孙悟空 | 100002 | 18 | 6 | 59.5 | 2 | 5 |
| 3 | 猪悟能 | 100003 | 18 | 7 | 33 | 3 | 1 |
| 3 | 猪悟能 | 100003 | 18 | 8 | 68 | 3 | 3 |
| 3 | 猪悟能 | 100003 | 18 | 9 | 99 | 3 | 5 |
| 4 | 沙悟净 | 100004 | 18 | 10 | 67 | 4 | 1 |
| 4 | 沙悟净 | 100004 | 18 | 11 | 23 | 4 | 3 |
| 4 | 沙悟净 | 100004 | 18 | 12 | 56 | 4 | 5 |
| 4 | 沙悟净 | 100004 | 18 | 13 | 72 | 4 | 6 |
| 5 | 宋江 | 200001 | 18 | 14 | 81 | 5 | 1 |
| 5 | 宋江 | 200001 | 18 | 15 | 37 | 5 | 5 |
| 6 | 武松 | 200002 | 18 | 16 | 56 | 6 | 2 |
| 6 | 武松 | 200002 | 18 | 17 | 43 | 6 | 4 |
| 6 | 武松 | 200002 | 18 | 18 | 79 | 6 | 6 |
| 7 | 李逹 | 200003 | 18 | 19 | 80 | 7 | 2 |
| 7 | 李逹 | 200003 | 18 | 20 | 92 | 7 | 6 |
| 8 | 不想毕业 | 200004 | 18 | NULL | NULL | NULL | NULL |
(2)(左连接)查询没有参加考试的同学(不在score表中,但在student表的数据)
-- 内连接
SELECT s.*
FROM student s
JOIN score sc ON sc.student_id = s.id
WHERE sc.score IS NULL;-- 外连接(左连接)
SELECT s.*
FROM student s
LEFT JOIN score sc ON sc.student_id = s.id
WHERE sc.score IS NULL;
| 特性 | LEFT JOIN 版本 | INNER JOIN 版本 |
|---|---|---|
| 查找的是 | 没有任何成绩记录的学生 | 有成绩记录但成绩为 NULL 的学生 |
| 学生必须在 score 表有记录吗? | 不需要 | 必须有 |
| 结果包含 | student 表中所有未出现在 score 表的学生 | score 表中成绩字段为 NULL 的学生 |
(3)(右连接)查询没有学⽣的班级
# 右连接以JOIN右边的表为基准,右表显⽰全部记录,左表中没有匹配的记录⽤NULL填充
select * from student s RIGHT JOIN class c on c.id = s.class_id;
| s.id | s.name | s.sno | s.age | s.gender | s.enroll_date | s.class_id | c.id | c.name |
|---|---|---|---|---|---|---|---|---|
| 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001 班 |
| 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001 班 |
| 3 | 猪悟能 | 100003 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001 班 |
| 4 | 沙悟净 | 100004 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001 班 |
| 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | 2 | 2 | C++001 班 |
| 6 | 武松 | 200002 | 18 | 1 | 2000-09-01 | 2 | 2 | C++001 班 |
| 7 | 李逹 | 200003 | 18 | 1 | 2000-09-01 | 2 | 2 | C++001 班 |
| 8 | 不想毕业 | 200004 | 18 | 1 | 2000-09-01 | 2 | 2 | C++001 班 |
| NULL | NULL | NULL | NULL | NULL | NULL | NULL | 3 | 前端 001 班 |
自连接
应用场景
⾃连接是⾃⼰与⾃⼰取笛卡尔积,可以把⾏转化成列,在查询的时候可以使⽤where条件对结果进⾏过滤,或者说实现⾏与⾏之间的⽐较。在做表连接时为表起不同的别名。
# 不为表指定别名
mysql> select * from score, score;
ERROR 1066 (42000): Not unique table/alias: 'score'
# 指定别名
mysql> select * from score s1, score s2;

示例:显示所有的“Java”成绩比“MySQL”成绩高的同学
左表这样的表设计,可以在一行中通过列与列的比较而得到查询结果,但是不便于维护和修改数据。
右表的表设计满足第⼀范式(1NF)、第⼆范式(2NF)、第三范式(3NF)但是无法做到行与行之间的比较
此时就能通过自连接完成需求。
分两步:
mysql> select * from course where name = 'Java' or name = 'MySQL';
+----+-------+
| id | name |
+----+-------+
| 1 | Java |
| 3 | MySQL |
+----+-------+
2 rows in set (0.00 sec)
select * from score s1, score s2
where s1.student_id=s2.student_id
and s1.course_id=1
and s2.course_id=3
and s1.score>s2.score;
结合在一起
select s1.* fromscore s1, score s2, course c1,course c2
wheres1.student_id = s2.student_id
ands1.course_id = c1.id
ands2.course_id = c2.id
ands1.score > s2.score
andc1.`name` = 'MySQL'
andc2.`name` = 'Java';+----+-------+------------+-----------+
| id | score | student_id | course_id |
+----+-------+------------+-----------+
| 2 | 98.5 | 1 | 3 |
| 8 | 68 | 3 | 3 |
+----+-------+------------+-----------+
练习:
显⽰所有"MySQL"成绩⽐"Java"成绩⾼的学⽣信息和班级以及成绩信息
select stu.name as 姓名, c.name as 班级, s1.score as MySQL分数, s2.score as Java分数
from student stu,score s1,score s2,course c1,course c2,class c
wheres1.student_id = s2.student_idand c1.id=s1.course_idand c2.id=s2.course_idand stu.id=s1.student_idand stu.class_id = c.idand c1.name="MySQL"and c2.name="Java"and s1.score>s2.score;
结果集
| 姓名 | 班级 | MySQL 分数 | Java 分数 |
|---|---|---|---|
| 唐三藏 | Java001 班 | 98.5 | 70.5 |
| 猪悟能 | Java001 班 | 68 | 33 |
子查询
子查询是把⼀个SELECT语句的结果当做别⼀个SELECT语句的条件,也叫嵌套查询
可以嵌套很多层

单行子查询
嵌套的查询中只返回一行数据
示例:查询与"不想毕业"同学的同班同学
select * from student where class_id=(select class_id from student where name='不想毕业');
| id | name | sno | age | gender | enroll_date | class_id |
|---|---|---|---|---|---|---|
| 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | 2 |
| 6 | 武松 | 200002 | 18 | 1 | 2000-09-01 | 2 |
| 7 | 李逹 | 200003 | 18 | 1 | 2000-09-01 | 2 |
| 8 | 不想毕业 | 200004 | 18 | 1 | 2000-09-01 | 2 |
多行子查询
嵌套的查询中返回多⾏数据,使⽤[NOT] IN关键字
示例:查询"MySQL"或"Java"课程的成绩信息
select * from score where course_id in (select id from course where name = 'Java' or name = 'MySQL');
使⽤NOT IN 可以查询除了"MySQL"或"Java"课程的成绩
select * from score where course_id not in (select id from course where name = 'Java' or name = 'MySQL');
多列子查询
单行子查询和多行子查询都只返回⼀列数据,多列⼦查询中可以返回多个列的数据,外层查询与嵌套的内层查询的列要匹配
**⽰例:**查询重复录⼊的分数
# 插⼊重复的分数:score, student_id, course_id列重复
insert into score(score, student_id, course_id) values (70.5, 1, 1),(98.5, 1, 3),(60, 2, 1);# ⼦查询中返回多个列
SELECT * FROM score WHERE (score, student_id, course_id )IN (SELECT score, student_id,course_id FROM score GROUP BY score, student_id, course_id HAVING count( 0 ) > 1);
在from子句中使用子查询
当⼀个查询产⽣结果时,MySQL⾃动创建⼀个临时表,然后把结果集放在这个临时表中,最终返回 给⽤⼾,在from⼦句中也可以使⽤临时表进⾏⼦查询或表连接操作
**⽰例:**查询所有⽐"Java001班"平均分⾼的成绩信息
#⾸先分步进⾏,第⼀步先查出Java001班的平均分
mysql> select avg(sc.score) score from student s join class c on s.class_id = c.id join score sc on s.id = sc.student_idwherec.name = 'Java001班';# 把以上查询做为临时表,与真实表进⾏⽐较
select * from score s, (select avg(sc.score) score from student s join class c on s.class_id = c.id join score sc on s.id = sc.student_idwherec.name = 'Java001班') tmp where s.score > tmp.score;
[NOT] EXISTS关键字
语法:select * from 表名 where [not] exists (select * from 表名1);
exists 后面括号中查询的语句,如果有结果的话,则执行外层的查询;如果返回的是一个空结果集,则不执行外层的查询。
**注意:**如果exists后面的括号的语句是select NULL;它也会执行外层的查询。因为类似于这要的查询语句他返回的结果集不为空,只不过列名为NULL,值也为NULL

合并查询
合并多个查询结果到一个结果集中,可以使用集合操作符unino,union all
创建示例用表
create table student1 like student;insert into student1 (name, sno, age, gender, enroll_date, class_id) values
('唐三藏', '100001', 18, 1, '1986-09-01', 1),
('刘备', '300001', 18, 1, '1993-09-01', 3),
('张⻜', '300002', 18, 1, '1993-09-01', 3),
('关⽻', '300003', 18, 1, '1993-09-01', 3);
注意:合并时两个SQL的查询字段要具有相同意义并且按照顺序一一对应
Union
该操作符用于取得两个结果集的并集。当使⽤该操作符时,会自动去掉结果集中的重复行。
**⽰例:**查询student表中 id < 3 的同学和student1表中的所有同学
# 结果集中有两张表中的数据,但是唐三藏是重复数据,所以只返回了⼀条记录
select *from student where id<3 union select * from student1;
结果集:
| id | name | sno | age | gender | enroll_date | class_id |
|---|---|---|---|---|---|---|
| 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 |
| 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 |
| 2 | 刘备 | 300001 | 18 | 1 | 1993-09-01 | 3 |
| 3 | 张⻜ | 300002 | 18 | 1 | 1993-09-01 | 3 |
| 4 | 关⽻ | 300003 | 18 | 1 | 1993-09-01 | 3 |
Union all
该操作符⽤于取得两个结果集的并集。当使⽤该操作符时,不会去掉结果集中的重复⾏。
# 结果集中有两张表中的数据,返回了所有唐三藏的记录
select * from student where id < 3 union all select * from student1;
| id | name | sno | age | gender | enroll_date | class_id |
|---|---|---|---|---|---|---|
| 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 |
| 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 |
| 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 |
| 2 | 刘备 | 300001 | 18 | 1 | 1993-09-01 | 3 |
| 3 | 张飞 | 300002 | 18 | 1 | 1993-09-01 | 3 |
| 4 | 关羽 | 300003 | 18 | 1 | 1993-09-01 | 3 |
插入查询结果
语法
INSERT INTO table_name [(column [, column ...])] SELECT ...
⽰例
将student表中C++001班的学⽣复制到student1表中
insert into student1 (name, sno, age, gender, enroll_date, class_id) select s.name, s.sno, s.age, s.gender, s.enroll_date, s.class_idfrom student s, class c where s.class_id = c.id and c.name = 'C++001班';
