一、关联查询
1、关联关系
(1)关联关系概述
关联关系三种:
- 一对一关系:一个实体仅仅关联一个实体。
- 一对多关系:一个实体可以关联多个实体。关系在多的一方
- 多对多关系:多对多关系是指多个实体之间是多对多的关系,需要建立一个中间表进行连接。例如,一个学生可能同时选修多个课程,而一个课程也可能被多个学生选修,需要建立一个中间表来记录学生与课程之间的关联关系。
(2)笛卡尔积
笛卡尔积是指两个集合中所有可能的组合,如果两个表一起查,不加以限制,那么得到的结果就是笛卡尔积。
2、关联查询
关联查询的目的是解决笛卡尔积。
(1)过滤笛卡尔积
SELECT *
FROM teacher,
subject
WHERE teacher.subject_id = subject.id;
(2)使用别名简化SQL
SELECT *
FROM teacher t,
subject s
WHERE t.subject_id = s.id;
(3)设计关联查询
关联关系的最少数量 = 查询的表的数量 - 1
关联查询步骤:
1、首先确定数据的来源表,将表写到 FROM 子句中,可以适当设定别名,方便简写SQL;
2、再找到两个表有关系的属性,作为连接条件,将连接条件写到WHERE子句中,这样就可以获得有意义的查询结果集。
-- 关联查询
-- 查看班级的名称和对应的班主任(老师)是谁?
SELECT t.name,c.name
FROM teacher t,
class c
WHERE c.teacher_id = t.id;
-- 查看每个学生的名字,年龄,以及其所在的班级名称和所在楼层。
SELECT s.name,s.age,c.name,c.floor
FROM student s,
class c
WHERE s.class_id = c.id;
(4)关联查询中使用过滤条件
-- 查看姓李的学生的名字,年龄,以及其所在的班级名称和所在楼层。
SELECT s.name, s.age, c.name, c.floor
FROM student s,
class c
WHERE s.class_id = c.id
AND s.name LIKE '李%';
-- 王克晶是哪个班的班主任?
SELECT t.name, t.salary, c.name, c.floor
FROM teacher t,
class c
WHERE c.teacher_id = t.id
AND t.name = '王克晶';
-- 查看三年级的班级班主任都是谁?要列出班级名称,所在楼层,班主任名字和工资
SELECT c.name, c.floor, t.name, t.salary
FROM teacher t,
class c
WHERE c.teacher_id = t.id
AND c.name LIKE '3年级%';
-- 查看来自南京的学生都有谁?要列出城市名字,学生名字,年龄,性别
SELECT l.name, s.name, s.age, s.gender
FROM student s,
location l
WHERE l.id = s.location_id
AND l.name = '南京';
-- 查看5年级的中队长都有谁?要列出学生名字,年龄,性别,职位和所在班级的名字以及楼层
SELECT s.name, s.age, s.gender, s.job, c.name, c.floor
FROM student s,
class c
WHERE s.class_id = c.id
AND c.name LIKE '5年级%'
AND s.job = '中队长';
二、连接查询
1、内连接
(1)什么是连接查询
连接查询(Join)是一种SQL查询方式,用于将两个或多个表格中的数据联接在一起。
连接查询主要包括:
- 内连接(Inner Join)
- 外连接(Outer Join)
- 全连接(Full Join)
-- 关联查询 用关联关系
-- 查询5年级的所有中队长
SELECT s.name, s.age, s.gender, s.job, c.name, c.floor
FROM student s,
class c
WHERE s.class_id = c.id
AND c.name LIKE '5年级%'
AND S.job = '中队长'
-- 使用连接查询完成操作,解决关联关系和查询条件混在一起的问题
SELECT s.name, s.age, s.gender, s.job, c.name, c.floor
FROM student s
JOIN class c ON s.class_id = c.id
WHERE s.job = '中队长'
AND c.name LIKE '5年级%'
(2)内连接查询INNER JOIN
用于将两个或多个表格中的数据进行联接,只返回那些在两个表中都有匹配的行。
SELECT column_names
FROM left_table
[INNER] JOIN right_table
ON left_table.column_name = right_table.column_name;
- left_table 和 right_table 是要连接的两个表格的名称
- column_names 是表格中要选择的列的名称
- JOIN 是关键字,[INNER] 可以省略不写
- ON 是连接条件
-- 王克晶是哪个班的班主任?列出:班级名称,楼层,老师名称,工资
SELECT c.name, c.floor, t.name, t.salary
FROM teacher t
JOIN class c on t.id = c.teacher_id
WHERE t.name = '王克晶'
(3)多表连接查询
- 先确定第一个数据来源表,并将其作为左表,写在FROM子句中
- 找到第二个数据来源表作为右表,写在JOIN子句中
- 编写第一个表和第二个表的连接条件并写在ON子句中
- 将前两个表连接后的结果作为新的左表,继续找到下一个数据来源表作为右表,写在JOIN子句中
- 编写下一个连接条件的ON子句,写在JOIN子句后面
- 最后编写WHERE过滤条件
-- 查询"范传奇"所带班级的学生姓名、年龄、班级名称及老师名字。
SELECT s.name, s.age, c.name, t.name
FROM teacher t
JOIN class c on t.id = c.teacher_id
JOIN student s on c.id = s.class_id
WHERE t.name = '范传奇';
-- 查看1年级1班的同学的名字和来自的城市
SELECT c.name, s.name, l.name
FROM student s
JOIN class c on c.id = s.class_id
JOIN location l on l.id = s.location_id
WHERE c.name = '1年级1班';
(4)多表连接查询练习
-- 查看来自北京的学生都是谁?
SELECT s.name, l.name
FROM student s
JOIN location l on l.id = s.location_id
WHERE l.name = '北京';
-- 教"英语"的老师都是谁?
SELECT t.name, s.name
FROM teacher t
JOIN subject s on s.id = t.subject_id
WHERE s.name = '英语';
-- 刘苍松所带班级的学生都有谁?
SELECT t.name, s.name
FROM student s
JOIN class c on c.id = s.class_id
JOIN teacher t on t.id = c.teacher_id
WHERE t.name = '刘苍松';
-- 教语文的老师所带的班级有哪些?
SELECT t.name, s.name, c.name
FROM teacher t
JOIN subject s on s.id = t.subject_id
JOIN class c on t.id = c.teacher_id
WHERE s.name = '语文';
-- 王克晶所带的班级学生都来自哪些城市(去重)?
SELECT DISTINCT l.name
FROM teacher t
JOIN class c on t.id = c.teacher_id
JOIN student s on c.id = s.class_id
JOIN location l on l.id = s.location_id
WHERE t.name = '王克晶';
-- 3年级的几个班主任都教哪些课程?
SELECT t.name, s.name, c.name
FROM class c
JOIN teacher t on t.id = c.teacher_id
JOIN subject s on s.id = t.subject_id
WHERE c.name LIKE '3年级%';
-- 工资高于10000的老师所带班里的大队长都是谁?
SELECT t.name, t.salary, s.name, s.job
FROM teacher t
JOIN class c on t.id = c.teacher_id
JOIN student s on c.id = s.class_id
WHERE t.salary > 10000
AND s.job = '大队长';
-- "李费水"的班主任教哪门课?
SELECT t.name, s2.name
FROM student s
JOIN class c on c.id = s.class_id
JOIN teacher t on t.id = c.teacher_id
JOIN subject s2 on t.subject_id = s2.id
WHERE s.name = '李费水';
-- 所在4楼的班里的大队长和中队长以及班主任都是谁?
SELECT c.floor, c.name, s.name, s.job, t.name
FROM student s
JOIN class c on c.id = s.class_id
JOIN teacher t on t.id = c.teacher_id
WHERE c.floor = 4
AND s.job IN ('大队长', '中队长');
-- 全校最小的同学的班主任是谁?
SELECT s.name, s.age, s.birth, t.name
FROM student s
JOIN class c on c.id = s.class_id
JOIN teacher t on t.id = c.teacher_id
ORDER BY s.birth DESC
LIMIT 1;
2、连接查询分组聚合
(1)连接查询中使用聚合函数
-- 连接查询使用聚合函数
-- 查看范传奇所带班级的学生共多少人?
SELECT t.name, c.name, COUNT(*) `人数`
FROM teacher t
JOIN class c on t.id = c.teacher_id
JOIN student s on c.id = s.class_id
WHERE t.name = '范传奇';
-- 教语文的老师平均工资是多少
SELECT s.name, AVG(salary)
FROM teacher t
JOIN subject s on s.id = t.subject_id
WHERE s.name = '语文';
-- 每门课的平均工资
SELECT s.name, AVG(salary) `平均工资`
FROM teacher t
JOIN subject s on s.id = t.subject_id
GROUP BY s.name
(2)连接查询分组统计
-- 仅查看平均工资高于6000的那些科目的老师平均工资是多少?
SELECT s.name, AVG(salary) `avg`
FROM teacher t
JOIN subject s on s.id = t.subject_id
GROUP BY s.name
HAVING AVG(salary) > 6000
ORDER BY avg DESC;
3、主键和外键
(1)概述
主键是用于唯一标识数据库表中每一行数据的字段或字段组合。主键的值必须是唯一的,且不能为NULL。
外键是用于建立两个表之间关联关系的字段。外键可以定义在一个表中,它的值对应着另一个表中的主键值。通过使用外键,可以在多个表之间建立起关系,从而实现数据的关联查询和更新操作。外键可以用来限制数据的完整性,以避免不一致和错误的数据操作。外键的值必须与对应主键的值相同,或为NULL。
(3)多对多关系
-- 查看'李费水'都学了哪些课程以及成绩?
SELECT s.name, s2.name `课程`, tsss.score `成绩`
FROM student s
JOIN t_stu_subject_score tsss on s.id = tsss.stu_id
JOIN subject s2 on s2.id = tsss.subject_id
WHERE s.name = '李费水';
-- 查看学习语文的学生都有谁
SELECT s.name, s2.name, tsss.score
FROM student s
JOIN t_stu_subject_score tsss on s.id = tsss.stu_id
JOIN subject s2 on s2.id = tsss.subject_id
WHERE s2.name = '语文'
4、外连接
(1)查询类型
- 内连接(INNER JOIN):返回两个表中匹配的行。只有在两个表中都存在匹配的数据时,才会返回结果。
- 左连接(LEFT JOIN):返回左表中的所有行,以及右表中匹配的行。如果右表中没有匹配的行,则返回NULL值。
- 右连接(RIGHT JOIN):返回右表中的所有行,以及左表中匹配的行。如果左表中没有匹配的行,则返回NULL值。
- 全连接(FULL OUTER JOIN):返回左表和右表中的所有行,如果没有匹配的行,则返回NULL值。(MySQL 没有提供支持)
- 自连接(SELF JOIN):在一个表中进行连接查询,可以将表看作两个表,其中一个表是源表,另一个表是目标表。
- 交叉连接(CROSS JOIN):返回两个表中所有可能的组合,也称为笛卡尔积。
(2)左外连接查询
返回左表中的所有行,以及右表中匹配的行。如果右表中没有匹配的行,则返回NULL值。
-- 查看所有的班级,以及该班级对应的班主任,没有班主任的也要将班级列出来
SELECT c.name, t.name
FROM class c
LEFT JOIN teacher t on t.id = c.teacher_id
(3)内连接与外连接对比
(4)右外连接
-- 查看所有的班级,以及该班级对应的班主任,没有班级的老师也要列出来
SELECT c.name, t.name
FROM class c
RIGHT JOIN teacher t on t.id = c.teacher_id
(5)全连接
如果希望得到这样的结果:查看所有的班级,以及该班级对应的班主任,不满足连接条件的班级和老师也都要体现,也就是既要显示没有班主任的班级,也要显示没有代班的老师。
SELECT c.name, t.name
FROM class c
RIGHT JOIN teacher t on t.id = c.teacher_id
UNION
SELECT c.name, t.name
FROM class c
LEFT JOIN teacher t on t.id = c.teacher_id
(6)自连接
在自连接中,表被视为两个不同的表,其中一个表包含源表中的数据,而另一个表包含目标表中的数据。通过自连接,可以将表中的数据与它们自身进行比较或匹配,以便执行某些特定的操作。
-- 查看每个老师以及他的领导信息
SELECT t.name, m.name
FROM teacher t
LEFT JOIN teacher m
ON t.manager = m.id;
-- 查看刘苍松的下属都是谁?
SELECT t.name, m.name
FROM teacher t
JOIN teacher m
on t.manager = m.id
WHERE m.name = '刘苍松';
-- 查看3年级2班的班长是谁?(student表中team_leader记录班长的学生id)
SELECT s.name
FROM student s
JOIN class c on c.id = s.class_id
WHERE s.id = s.team_leader
AND c.name = '3年级2班';
-- 年龄最大的学生所在班的班主任的上司是谁?
SELECT s.name, s.birth `生日`, t.name `老师`, m.name `上司`
FROM student s
JOIN class c on c.id = s.class_id
JOIN teacher t on t.id = c.teacher_id
JOIN teacher m on t.manager = m.id
WHERE s.birth = (SELECT MIN(birth) FROM student)
三、练习
1 查询练习1
实现如下查询:
-- 查看来自香港和澳门的学生都是谁?
SELECT student.name, age, gender, location.name `地址`
FROM student,
location
WHERE location.name IN ('香港', '澳门')
AND location_id = location.id;
-- 教“数学”、“物理”、“化学”的老师都是谁?
SELECT t.name, s.name
FROM teacher t,
subject s
WHERE s.id = t.subject_id
AND s.name IN ('数学', '物理', '化学');
-- “1年级3班” 班级的学生都有谁?
SELECT s.name, c.name
FROM student s,
class c
WHERE c.name = '1年级3班'
AND s.class_id = c.id;
-- 数学的老师所带的班级有哪些?
SELECT t.name, c.name, s.name
FROM teacher t,
class c,
subject s
WHERE c.teacher_id = t.id
AND t.subject_id = s.id
AND s.name = '数学';
-- 范传奇所带的班级学生都来自哪些城市(去重)?
SELECT DISTINCT l.name `城市`
FROM teacher t,
student s,
location l,
class c
WHERE c.teacher_id = t.id
AND s.class_id = c.id
AND s.location_id = l.id
AND t.name = '范传奇';
-- 2年级的几个班主任都教哪些课程?
SELECT t.name, c.name `班级`, s.name `所教课程`
FROM teacher t,
class c,
subject s
WHERE c.teacher_id = t.id
AND t.subject_id = s.id
AND c.name LIKE '2年级%';
-- 工资高于8000的老师所带班里的中队长都是谁?
SELECT t.name `老师姓名`, t.salary `工资`, s.name `学生姓名`, s.job `学生职位`
FROM teacher t,
student s,
class c
WHERE c.teacher_id = t.id
AND s.class_id = c.id
AND t.salary > 8000
AND s.job = '中队长';
-- "黄喻"的班主任教哪门课?
SELECT s.name, t.name, s2.name
FROM student s,
teacher t,
class c,
subject s2
WHERE c.teacher_id = t.id
AND s.class_id = c.id
AND t.subject_id = s2.id
AND s.name = '黄喻';
-- 所在3楼的班里的大队长和中队长以及班主任都是谁?
SELECT c.floor, s.name, s.job, t.name
FROM student s,
class c,
teacher t
WHERE s.class_id = c.id
AND c.teacher_id = t.id
AND c.floor = '3'
AND s.job IN ('大队长', '中队长');
-- 全校最大的同学的班主任是谁?
SELECT s.name, s.birth, s.age, t.name
FROM teacher t,
student s,
class c
WHERE c.teacher_id = t.id
AND s.class_id = c.id
HAVING s.birth = (SELECT MIN(birth) FROM student);
2 查询练习2
实现如下查询:
-- 查询"王克晶"所带班级的学生姓名、年龄、班级名称及老师名字
SELECT s.name, s.age, c.name, t.name
FROM student s,
teacher t,
class c
WHERE c.teacher_id = t.id
AND s.class_id = c.id
AND t.name = '王克晶';
-- 查看1年级的同学的名字和来自的城市
SELECT s.name, c.name, l.name
FROM student s,
class c,
location l
WHERE c.id = s.class_id
AND s.location_id = l.id
AND c.name LIKE '1年级%';
-- 查看教语文课老师的平均工资是多少
SELECT AVG(t.salary)
FROM teacher t
JOIN subject s on t.subject_id = s.id
WHERE s.name = '语文';
-- 每个科目的平均分是多少
SELECT DISTINCT s.name, AVG(tsss.score)
FROM subject s
JOIN t_stu_subject_score tsss on s.id = tsss.subject_id
GROUP BY s.name;
-- 计算每个学生的全部科目总分,并且按照分数从高到低排名
SELECT s2.name, SUM(tsss.score)
FROM t_stu_subject_score tsss
JOIN subject s on s.id = tsss.subject_id
JOIN student s2 on tsss.stu_id = s2.id
GROUP BY s2.name
ORDER BY SUM(tsss.score) DESC;
-- 找到总分最高的学生和班级
SELECT s.name,c.name,SUM(S2.score)
FROM student s
JOIN class c on c.id = s.class_id
JOIN t_stu_subject_score s2 on s2.stu_id = s.id
GROUP BY s.name
ORDER BY SUM(S2.score) DESC
LIMIT 1;
-- 计算每位学生的平均分
SELECT s2.name, AVG(tsss.score)
FROM t_stu_subject_score tsss
JOIN subject s on s.id = tsss.subject_id
JOIN student s2 on tsss.stu_id = s2.id
GROUP BY s2.name
ORDER BY AVG(tsss.score) DESC;
-- 找到还没有填写城市信息的学员
SELECT s.name,l.name
FROM student s JOIN location l on l.id = s.location_id
WHERE l.name IS NULL ;
3 查询练习3
实现如下查询:
-- 查询每个城市的学生数量以及该城市的平均年龄。
SELECT l.name, count(s.location_id) `学生数量`, AVG(S.age) `平均年龄`
FROM student s
JOIN location l on l.id = s.location_id
GROUP BY l.name;
-- 查询每个科目的平均分数以及最高和最低分数的学生姓名和分数。
SELECT s2.name `科目`,
AVG(tsss.score) `平均分`,
MAX(tsss.score) `最高分`,
MAX(s.name) `学生姓名`,
MIN(tsss.score) `最低分`,
MIN(s.name) `学生姓名`
FROM t_stu_subject_score tsss
JOIN student s on s.id = tsss.stu_id
JOIN subject s2 on s2.id = tsss.subject_id
GROUP BY s2.name;
-- 查询每个老师的工资和每个老师所教科目的数量。
SELECT t.name, t.salary, COUNT(subject_id)
FROM teacher t
JOIN subject s on s.id = t.subject_id
GROUP BY t.name;
-- 查询每个城市的学生数量及平均年龄。
SELECT l.name, COUNT(location_id), AVG(s.age)
FROM student s
JOIN location l on l.id = s.location_id
GROUP BY s.location_id;
-- **查询每个班级的学生数量、平均年龄和平均成绩。
SELECT c.name, COUNT(DISTINCT s.id) `学生数量`, AVG(s.age) `平均年龄`, AVG(tsss.score) `平均成绩`
FROM student s
JOIN class c on c.id = s.class_id
LEFT JOIN t_stu_subject_score tsss on tsss.stu_id = s.id
GROUP BY c.name;
-- 查询每个城市的学生数量、平均年龄和工资最高的老师信息。
SELECT l.name, COUNT(s.location_id) `学生数量`, AVG(s.age) `平均年龄`, MAX(t.name), MAX(t.salary)
FROM student s
JOIN location l on l.id = s.location_id
JOIN class c on c.id = s.class_id
JOIN teacher t on t.id = c.teacher_id
GROUP BY l.name;
-- 查询每个老师的学生数量、平均年龄和平均成绩,并按照平均成绩降序排列。
SELECT t.name, COUNT(DISTINCT s.id) `学生数量`, AVG(s.age) `平均年龄`, AVG(tsss.score) `平均成绩`
FROM teacher t
JOIN class c on t.id = c.teacher_id
JOIN student s on c.id = s.class_id
JOIN t_stu_subject_score tsss on s.id = tsss.stu_id
GROUP BY t.name
ORDER BY AVG(tsss.score) DESC;
-- 查询每个老师所教授的科目数量、平均工资和平均成绩,并按照平均成绩降序排列。
SELECT t.name, COUNT(DISTINCT t.subject_id) '科目数量', AVG(salary) `平均工资`, AVG(tsss.score) `平均成绩`
FROM teacher t
JOIN subject s on s.id = t.subject_id
join t_stu_subject_score tsss on s.id = tsss.subject_id
GROUP BY t.name
ORDER BY AVG(tsss.score) DESC;
-- 查询每个科目的平均成绩,并按照平均成绩降序排列。
SELECT s.name, AVG(tsss.score)
FROM subject s
JOIN t_stu_subject_score tsss on s.id = tsss.subject_id
GROUP BY s.name
ORDER BY AVG(tsss.score) DESC ;