目录
[1.1 概念](#1.1 概念)
[1.2 设计原则](#1.2 设计原则)
[1.3 实例:部门与员工](#1.3 实例:部门与员工)
[1.4 添加物理外键约束](#1.4 添加物理外键约束)
[2.1 概念](#2.1 概念)
[2.2 设计原则](#2.2 设计原则)
[2.3 实例:用户与身份证信息](#2.3 实例:用户与身份证信息)
[3.1 概念](#3.1 概念)
[3.2 设计原则](#3.2 设计原则)
[3.3 实例:学生与课程](#3.3 实例:学生与课程)
[4.1 物理外键](#4.1 物理外键)
[4.2 逻辑外键](#4.2 逻辑外键)
[5.1 表关系设计要点](#5.1 表关系设计要点)
[5.2 外键选择建议](#5.2 外键选择建议)
[1.内连接(INNER JOIN)](#1.内连接(INNER JOIN))
[1.1 概念](#1.1 概念)
[1.2 隐式内连接](#1.2 隐式内连接)
[1.3 显式内连接](#1.3 显式内连接)
[1.4 带条件的内连接](#1.4 带条件的内连接)
[2. 外连接(OUTER JOIN)](#2. 外连接(OUTER JOIN))
[2.1 概念](#2.1 概念)
[2.2 左外连接(LEFT JOIN)](#2.2 左外连接(LEFT JOIN))
[2.3 右外连接(RIGHT JOIN)](#2.3 右外连接(RIGHT JOIN))
[2.4 外连接的实际应用](#2.4 外连接的实际应用)
[3. 子查询(Subquery)](#3. 子查询(Subquery))
[3.1 概念](#3.1 概念)
[3.2 标量子查询](#3.2 标量子查询)
[3.3 列子查询](#3.3 列子查询)
[3.4 行子查询](#3.4 行子查询)
[3.5 表子查询](#3.5 表子查询)
[4. 三种连接方式的区别](#4. 三种连接方式的区别)
[5. 实战需求练习](#5. 实战需求练习)
[5.1 需求1:查询"教研部"性别为男,且在"2011-05-01"之后入职的员工信息](#5.1 需求1:查询"教研部"性别为男,且在"2011-05-01"之后入职的员工信息)
[5.2 需求2:查询工资低于公司平均工资的且性别为男的员工信息](#5.2 需求2:查询工资低于公司平均工资的且性别为男的员工信息)
[5.3 需求3:查询部门人数超过10人的部门名称](#5.3 需求3:查询部门人数超过10人的部门名称)
[5.4 需求4:查询在"2010-05-01"后入职,且薪资高于10000的"教研部"员工信息,按薪资倒序排序](#5.4 需求4:查询在"2010-05-01"后入职,且薪资高于10000的"教研部"员工信息,按薪资倒序排序)
[5.5 需求5:查询工资低于本部门平均工资的员工信息](#5.5 需求5:查询工资低于本部门平均工资的员工信息)
[6. 总结](#6. 总结)
[1. 过滤条件的区别(WHERE、ON、HAVING)](#1. 过滤条件的区别(WHERE、ON、HAVING))
[1.1WHERE 子句](#1.1WHERE 子句)
[1.2 ON 子句](#1.2 ON 子句)
[1.3 HAVING 子句](#1.3 HAVING 子句)
[1.4 三者对比](#1.4 三者对比)
[1.5 ON 和 WHERE 在外连接中的区别](#1.5 ON 和 WHERE 在外连接中的区别)
[1.6 SQL 的常见执行顺序](#1.6 SQL 的常见执行顺序)
[2 GROUP BY 的使用](#2 GROUP BY 的使用)
[2.1 基本用法](#2.1 基本用法)
[2.2 GROUP BY 的规则](#2.2 GROUP BY 的规则)
[2.3 GROUP BY 可以按照多个字段分组。](#2.3 GROUP BY 可以按照多个字段分组。)
[2.4 GROUP BY 与聚合函数](#2.4 GROUP BY 与聚合函数)
前言
今天我们来学习MySQL关于多表关系和查询的具体实现,这些SQL语句会最终写在xml文件或者Mapper接口上,有着至关重要性。
引言
在数据库设计中,表与表之间的关系是核心概念。合理的表关系设计不仅能保证数据的完整性和一致性,还能提高查询效率。
多表关系:
1.一对多关系
1.1 概念
一对多关系是数据库中最常见的关系类型。一个表中的一条记录可以对应另一个表中的多条记录,但反过来不成立。
1.2 设计原则
在"多"的一方添加外键字段,关联"一"方的主键。
1.3 实例:部门与员工
-- 部门表(一方)
create table dept(
id int unsigned primary key auto_increment comment 'ID, 主键',
name varchar(10) not null unique comment '部门名称',
create_time datetime comment '创建时间',
update_time datetime comment '修改时间'
) comment '部门表';
-- 员工表(多方)
create table emp(
id int unsigned primary key auto_increment comment 'ID,主键',
username varchar(20) not null unique comment '用户名',
password varchar(32) not null comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 1:男, 2:女',
phone char(11) not null unique comment '手机号',
job tinyint unsigned comment '职位',
salary int unsigned comment '薪资',
entry_date date comment '入职日期',
dept_id int unsigned COMMENT '关联的部门ID',
create_time datetime comment '创建时间',
update_time datetime comment '修改时间'
) comment '员工表';
1.4 添加物理外键约束
alter table子表 add constraint fk__表名__引用字段名 foreign key (字段名)references 父表(父表的关联);
alter table emp add constraint fk_emp_dept_id foreign key (dept_id)references dept(id);
也可以基于图形化页面操作
右键-修改表-外键- 目标表:父表名,列名:1,目标名称:父表的主键
说明:
- 一个部门可以有多个员工
- 一个员工只能属于一个部门
- 在员工表的dept_id字段上建立外键,引用部门表的主键id
2.一对一关系
2.1 概念
一对一关系表示两个表之间的记录是一一对应的。这种关系相对少见,通常用于:
- 拆分大表,提高查询效率
- 存储可选的扩展信息
2.2 设计原则
在任意一方添加外键字段,并设置为unique,确保一对一的约束。
2.3 实例:用户与身份证信息
-- 用户信息表
create table tb_user(
id int unsigned primary key auto_increment comment 'ID',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 1 男 2 女',
phone char(11) comment '手机号',
degree varchar(10) comment '学历'
) comment '用户信息表';
-- 用户身份证信息表
create table tb_user_card(
id int unsigned primary key auto_increment comment 'ID',
nationality varchar(10) not null comment '民族',
birthday date not null comment '生日',
idcard char(18) not null comment '身份证号',
issued varchar(20) not null comment '签发机关',
expire_begin date not null comment '有效期限-开始',
expire_end date comment '有效期限-结束',
user_id int unsigned not null unique comment '用户ID',
constraint fk_user_id foreign key (user_id) references tb_user(id)
) comment '用户身份证表';
说明:
- user_id字段设置了unique约束,确保一个身份证信息只能对应一个用户
- 外键约束保证了数据的完整性
3.多对多关系
3.1 概念
多对多关系表示两个表之间的记录可以相互对应多条。例如学生与课程的关系:一个学生可以选修多门课程,一门课程可以被多个学生选修。
3.2 设计原则
需要创建一个中间表(也叫关联表),包含两个外键分别指向两个表的主键。
3.3 实例:学生与课程
-- 学生表
create table tb_student(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '姓名',
no varchar(10) comment '学号'
) comment '学生表';
-- 课程表
create table tb_course(
id int auto_increment primary key comment '主键ID',
name varchar(10) comment '课程名称'
) comment '课程表';
-- 学生课程中间表
create table tb_student_course(
id int auto_increment comment '主键' primary key,
student_id int not null comment '学生ID',
course_id int not null comment '课程ID',
constraint fk_courseid foreign key (course_id) references tb_course(id),
constraint fk_studentid foreign key (student_id) references tb_student(id)
) comment '学生课程中间表';
说明:
- 中间表存储了学生与课程的关联关系
- 每个关联记录表示一个学生选修了一门课程
- 通过两个外键分别约束学生表和课程表
4.物理外键与逻辑外键的区别
4.1 物理外键
定义:通过FOREIGN KEY约束在数据库层面强制建立的外键关系。
特点:
-- 约束强制关联:在数据库表结构中显式定义外键关系,并通过 FOREIGN KEY 约束强制确保数据完整性
-- 物理外键示例
alter table emp add constraint fk_emp_dept_id
foreign key (dept_id) references dept(id);
优势:
- 数据完整性:数据库自动保证引用完整性
- 级联操作:支持级联删除、级联更新
- 自动校验:插入或更新时自动检查引用是否存在
劣势:
- 性能开销:外键约束会降低写入性能
- 灵活性差:修改外键关系需要DDL操作
- 分布式困难:在分布式系统中难以维护
4.2 逻辑外键
定义:不在数据库层面建立外键约束,而是通过业务逻辑来维护表之间的关联关系。
特点:
-- 逻辑外键:仅定义字段,不添加FOREIGN KEY约束
create table emp(
id int unsigned primary key auto_increment,
dept_id int unsigned comment '关联的部门ID', -- 逻辑外键
...
);
优势:
- 性能优秀:避免外键约束的性能开销
- 灵活性高:修改关联关系无需DDL操作
- 分布式友好:易于在分布式系统中实现
- 便于测试:测试时无需维护复杂的外键关系
劣势:
- 数据风险:需要业务代码保证数据完整性
- 无自动校验:数据库不会自动检查引用有效性
- 开发负担:增加了业务层的复杂度
5.总结
5.1 表关系设计要点
- 一对多:在多方添加外键,关联一方主键
- 一对一:在任意一方添加外键,并设置unique约束
- 多对多:创建中间表,包含两个外键分别指向两个表
5.2 外键选择建议
- 物理外键:适合数据一致性要求高、写入频率相对较低的场景
- 逻辑外键:适合高并发、分布式、需要灵活扩展的场景
多表查询
1.内连接(INNER JOIN)
1.1 概念
内连接是最常用的连接方式,它只返回两个表中满足连接条件的记录,相当于两个集合的 交集 。
1.2 隐式内连接
通过 WHERE 子句指定连接条件:
-- 查询所有员工的ID、姓名及所属部门名称
select emp.id, emp.name, dept.name
from emp, dept
where dept_id = dept.id;
1.3 显式内连接
使用 JOIN 关键字明确指定连接类型:
-- 显式内连接语法
select emp.id, emp.name
from emp inner join dept on dept_id = dept.id;
-- INNER 关键字可省略
select emp.id, emp.name
from emp join dept on dept_id = dept.id;
1.4 带条件的内连接
-- 查询男性且工资大于8000的员工信息
select emp.id, emp.name, dept.name
from emp, dept
where dept_id = dept.id and salary > 8000 and gender = 1;
-- 使用表别名
select e.id, e.name, d.name
from emp e, dept d
where e.dept_id = d.id and e.salary > 8000 and e.gender = 1;
2. 外连接(OUTER JOIN)
2.1 概念
外连接返回满足条件的所有记录,即使一方没有匹配的记录也会返回(用 NULL 填充)。
2.2 左外连接(LEFT JOIN)
返回左表的所有记录,以及右表中满足条件的记录。如果右表没有匹配,则右表字段为 NULL。
-- 查询所有员工及其部门(包括没有部门的员工)
select e.name as 员工姓名, d.name as 部门名称
from emp e
left outer join dept d on e.dept_id = d.id;
2.3 右外连接(RIGHT JOIN)
返回右表的所有记录,以及左表中满足条件的记录。如果左表没有匹配,则左表字段为 NULL。
-- 查询所有部门及其员工(包括没有员工的部门)
select d.name as 部门名称, e.name as 员工姓名
from emp e
right outer join dept d on e.dept_id = d.id;
2.4 外连接的实际应用
-- 查询工资高于8000的所有员工姓名及其部门名称
select *
from emp e
left outer join dept d on e.dept_id = d.id
where salary > 8000;
3. 子查询(Subquery)
3.1 概念
子查询是嵌套在其他 SQL 语句中的查询,也称为嵌套查询。
3.2 标量子查询
返回单个值(一行一列)的子查询。
-- 查询最早入职的员工信息
select name
from emp
where entry_date = (select min(entry_date) from emp);
-- 查询在"阮小五"入职之后入职的员工信息
select emp.name
from emp
where entry_date > (select emp.entry_date from emp where name = '阮小五');
3.3 列子查询
返回一列多行的子查询,通常与 IN 操作符配合使用。
-- 查询"教研部"和"咨询部"的所有员工信息
select *
from emp
where dept_id in (select id from dept where name = '教研部' or name = '咨询部');
3.4 行子查询
返回一行多列的子查询,用于比较多个字段。
-- 查询与"李忠"薪资及职位都相同的员工信息
select *
from emp
where (salary, job) = (select salary, job from emp where name = '李忠');
3.5 表子查询
返回多行多列的子查询,作为临时表使用。
-- 查询与"李忠"薪资及职位都相同的员工信息
select *
from emp
where (salary, job) = (select salary, job from emp where name = '李忠');
4. 三种连接方式的区别
- 内连接:取两个表的交集,只返回匹配的记录
- 左外连接:保留左表全部记录,右表无匹配时用 NULL 填充
- 右外连接:保留右表全部记录,左表无匹配时用 NULL 填充
5. 实战需求练习
5.1 需求1:查询"教研部"性别为男,且在"2011-05-01"之后入职的员工信息
select *
from emp e, dept d
where e.dept_id = d.id
and gender = 1
and entry_date > '2011-05-01'
and d.name = '教研部';
5.2 需求2:查询工资低于公司平均工资的且性别为男的员工信息
select *
from emp
where salary < (select avg(salary) from emp)
and gender = 1;
5.3 需求3:查询部门人数超过10人的部门名称
select *
from emp
where salary < (select avg(salary) from emp)
and gender = 1;
5.4 需求4:查询在"2010-05-01"后入职,且薪资高于10000的"教研部"员工信息,按薪资倒序排序
select *
from emp e, dept d
where d.id = e.dept_id
and e.entry_date > '2010-05-01'
and salary > 10000
and d.name = '教研部'
order by e.salary desc;
5.5 需求5:查询工资低于本部门平均工资的员工信息
select e.*
from emp e, (select dept_id, avg(salary) avg_sal from emp group by dept_id) d
where e.dept_id = d.dept_id
and e.salary < d.avg_sal;
注意:交叉连接(笛卡尔积)
-- 不推荐:返回两个表的笛卡尔积
select * from emp, dept;
-- 正确:应始终使用连接条件
select * from emp join dept on emp.dept_id = dept.id;
6. 总结
| 查询类型 | 关键字 | 特点 |
|---|---|---|
| 内连接 | JOIN / INNER JOIN | 返回交集 |
| 左外连接 | LEFT JOIN / LEFT OUTER JOIN | 保留左表全部 |
| 右外连接 | RIGHT JOIN / RIGHT OUTER JOIN | 保留右表全部 |
| 子查询 | () | 嵌套分步计算,逻辑清晰 |
过滤条件补充说明:
1. 过滤条件的区别(WHERE、ON、HAVING)
在 SQL 查询中,`WHERE`、`ON` 和 `HAVING` 都可以用于设置条件,但它们的作用对象和执行阶段并不相同。
如果混用不当,尤其是在多表连接和分组统计时,很容易得到错误结果。
1.1WHERE 子句
`WHERE` 用于过滤原始数据行,通常在分组之前生效。
适用场景:
- 单表条件筛选
- 多表查询后的行级过滤
- 不涉及聚合结果的条件判断
特点:
-
过滤的是"行"
-
一般不能直接使用聚合函数
-
常用于 `SELECT`、`UPDATE`、`DELETE` 等语句中
-- 查询工资大于 8000 的男性员工
select *
from emp
where salary > 8000 and gender = 1;
1.2 ON 子句
ON 用于指定表与表之间的连接条件,决定两张表如何匹配。
适用场景:
- JOIN、LEFT JOIN、RIGHT JOIN 等连接查询
- 指定两表之间的关联关系
- 在外连接中控制"匹配条件"
特点:
-
过滤的是"连接关系"
-
主要用于 JOIN 后面
-
在外连接中,ON 和 WHERE 的写法不同,结果也可能不同
-- 左外连接:只关联部门名称为"教研部"的部门信息 select e.name, d.name from emp e left join dept d on e.dept_id = d.id and d.name = '教研部';
说明:
上面这条 SQL 中,left join 会保留左表 emp 的所有记录。
如果某个员工所在部门不是"教研部",那么右表 dept 对应字段会显示为 NULL,但员工记录仍然会保留。
1.3 HAVING 子句
HAVING 用于分组之后再过滤分组结果,通常与 GROUP BY 一起使用。
适用场景:
- 对分组统计结果进行筛选
- 使用聚合函数作为条件
- 例如筛选"人数大于 10 的部门"
特点:
-
过滤的是"分组后的结果"
-
可以使用聚合函数
-
一般配合 GROUP BY 使用
-- 查询人数超过 10 人的部门 select d.name, count(*) as 人数 from emp e join dept d on e.dept_id = d.id group by d.name having count(*) > 10;
1.4 三者对比
| 子句 | 作用阶段 | 过滤对象 | 是否可使用聚合函数 | 典型用途 |
|---|---|---|---|---|
| WHERE | 分组前 | 原始数据行 | 否 | 过滤基础数据 |
| ON | 连接时 | 表之间的匹配关系 | 一般不用于聚合条件 | 指定连接条件 |
| HAVING | 分组后 | 分组结果 | 是 | 过滤聚合后的结果 |
1.5 ON 和 WHERE 在外连接中的区别
ON 和 WHERE 在内连接中很多时候效果相近,但在外连接中差别非常明显。
写法一:条件写在 ON 中
select *
from emp e
left join dept d
on e.dept_id = d.id and d.name = '教研部';
这条语句会保留 emp 表中的所有员工。
如果员工所在部门不是"教研部",右表字段显示为 NULL。
写法二:条件写在 WHERE 中
select *
from emp e
left join dept d
on e.dept_id = d.id
where d.name = '教研部';
这条语句会先完成左外连接,再通过 WHERE 过滤掉 d.name 不等于"教研部"的记录。
由于 NULL 不满足条件,最终效果往往接近内连接。
结论:
-
ON 更适合写连接条件
-
WHERE 更适合写结果过滤条件
-
在外连接中,条件放在 ON 还是 WHERE,会直接影响最终结果
1.6 SQL 的常见执行顺序
虽然 SQL 的书写顺序是 SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY,
但数据库的常见执行逻辑通常是下面这样的:
-
FROM
-
JOIN
-
ON
-
WHERE
-
GROUP BY
-
HAVING
-
SELECT
-
ORDER BY
-
LIMIT
示例结构如下:
select 选择的列
from 表名
join 另一张表 on 连接条件
where 行级过滤条件
group by 分组字段
having 分组过滤条件
order by 排序字段
limit 限制数量;
理解这个顺序后,就更容易明白:
-
为什么 WHERE 不能直接过滤聚合结果
-
为什么 HAVING 要放在 GROUP BY 后面
-
为什么外连接中 ON 和 WHERE 的作用不同
2 GROUP BY 的使用
GROUP BY 用于将数据按照某个字段或多个字段进行分组,通常与聚合函数一起使用。
2.1 基本用法
按部门分组,统计每个部门的人数和平均工资
select dept_id, count(*) as 人数, avg(salary) as 平均工资
from emp
group by dept_id;
2.2 GROUP BY 的规则
- SELECT 中出现的非聚合列,通常都应该写在 GROUP BY 中。
正确:dept_id 和 name 都参与分组
select dept_id, name, count(*)
from emp
group by dept_id, name;
错误:name 没有出现在 group by 中
select dept_id, name, count(*)
from emp
group by dept_id;
说明:
不同数据库对这类写法的容忍度不同,但在规范写法中,非聚合字段应当出现在 GROUP BY 中。
2.3 GROUP BY 可以按照多个字段分组。
按部门和性别分组
select dept_id, gender, count(*)
from emp
group by dept_id, gender;
2.4 GROUP BY 与聚合函数
分组统计通常会搭配聚合函数一起使用,常见聚合函数如下:
| 函数 | 作用 | 示例 |
|---|---|---|
| COUNT() | 统计行数 | count(*) |
| SUM() | 求和 | sum(salary) |
| AVG() | 求平均值 | avg(salary) |
| MAX() | 求最大值 | max(salary) |
| MIN() | 求最小值 | min(salary) |
总结
今天我们学习了一堆SQL语句,真的是累炸了。后面我们将继续学习crud的操作