MySQL 中数据完整性约束、外键管理(含级联策略) 和多表查询
一、表结构设计与数据完整性约束
数据完整性是数据库设计的核心目标,用于保证数据的准确性、一致性和有效性。代码中通过user
(用户表)、dept
(部门表)、emp
(员工表)的设计,体现了 MySQL 的 5 类核心约束:主键约束、唯一约束、非空约束、检查约束、默认约束。
1. 用户表(user
):约束的集中体现
sql
create table user(id int primary key auto_increment comment '主键',name varchar(10) not null unique comment '姓名',age int check (age >= 0 and age <= 120) comment '年龄',status char(1) default '1' comment '状态',gender char(1) comment '性别'
)comment '用户表';
该表是 MySQL约束语法的典型示例,需逐个解析约束的作用:
约束类型 | 字段 | 语法 / 逻辑 | 作用与注意事项 |
---|---|---|---|
主键约束 | id | primary key auto_increment | - 唯一标识每条用户记录,避免重复;- auto_increment (自增):插入时无需指定id ,MySQL 自动生成递增数值(默认从 1 开始),简化操作。 |
非空约束 | name | not null | 强制 “姓名” 必须填写,无法插入name 为null 的记录(如insert into user(age) values(20) 会报错)。 |
唯一约束 | name | unique | 强制 “姓名” 不重复,避免插入同名用户(如已插入Tom1 ,再插Tom1 会报错)。 |
检查约束 | age | check (age >= 0 and age <= 120) | 限制 “年龄” 范围在 0-120 之间,超出范围的插入会失败(如insert into user(name,age) values('Tom4',150) 报错)。⚠️ 注意:MySQL 5.7 及之前版本对check 仅语法支持,不实际生效;MySQL 8.0 + 完全支持。 |
默认约束 | status | default '1' | 若插入时未指定status 值,自动填充为1 (如代码中insert into user(name, age, gender) values ('Tom5', 32,'男') ,Tom5 的status 为1 )。 |
2. 部门表(dept
)与员工表(emp
):关联关系铺垫
部门表(
dept
):作为 “主表”,存储部门基础信息,id
为主键(自增),name
非空(部门名称必须存在,避免空部门)。sql
create table dept(id int primary key auto_increment comment 'ID' ,name varchar(50) not null comment '部门名称' -- 非空:部门必须有名称 )comment '部门表';
员工表(
emp
):作为 “从表”,与部门表关联(dept_id
字段),同时支持 “自关联”(managerid
字段,关联自身的id
,表示员工的直属领导)。sql
create table emp(id int primary key auto_increment comment 'ID' ,name varchar(50) not null comment '员工姓名' , -- 原代码注释笔误,应为“员工姓名”age int comment '年龄',job varchar(20) comment '职位',salary int comment '薪资',entrydate date comment '入职时间' , -- date类型:存储年月日(如'2000-01-01')managerid int comment '直属领导ID' , -- 自关联字段:关联emp.id(领导也是员工)dept_id int comment '部门ID' -- 外键字段:关联dept.id(员工所属部门) )comment '员工表';
关键字段说明:
entrydate
:date
类型,专门用于存储日期,格式为YYYY-MM-DD
,比varchar
更节省空间且支持日期函数(如计算入职年限)。managerid
:自关联设计的核心,为后续 “查询员工与领导关系” 的自连接查询埋下伏笔。
二、外键管理:维护表间数据一致性
外键(foreign key
)是主从表之间的 “纽带”,用于保证从表的外键字段值必须在主表中存在(避免无效关联,如 “员工所属部门不存在”)。代码中完整演示了外键的 “添加、删除、级联策略” 操作。
1. 基本概念:主表与从表
- 主表:被关联的表(如
dept
),其主键(dept.id
)被从表引用。 - 从表:引用主表的表(如
emp
),其外键(emp.dept_id
)关联主表主键。
2. 外键操作详解
(1)添加外键
sql
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
- 语法解析:
alter table 从表名
:修改从表结构。add constraint 外键名
:为外键命名(规范:fk_从表名_外键字段名
,如fk_emp_dept_id
,便于后续管理)。foreign key (从表外键字段)
:指定从表的外键字段(emp.dept_id
)。references 主表名(主表主键)
:指定关联的主表主键(dept.id
)。
- 作用:插入
emp
数据时,若dept_id
的值在dept.id
中不存在(如dept_id=6
,但dept
中最大id=5
),MySQL 会直接报错,拒绝插入。
(2)删除外键
sql
alter table emp drop foreign key fk_emp_dept_id;
- 语法:通过 “外键名” 删除(而非外键字段名),因为一个表可能有多个外键,需通过名称精准定位。
- 场景:当需要修改外键策略(如从 “无级联” 改为 “级联删除”)时,需先删除旧外键,再添加新外键。
(3)外键级联策略:主表数据变化时,从表如何联动?
默认情况下,若主表数据被修改 / 删除(如删除dept.id=1
的研发部),而从表有依赖该数据的记录(如emp.dept_id=1
的员工),MySQL 会直接报错,阻止操作。为解决此问题,需设置级联策略。
代码中演示了两种常用级联策略:
级联更新 / 删除(
on update cascade / on delete cascade
)sql
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on UPDATE cascade -- 主表主键更新时,从表外键同步更新 on DELETE cascade; -- 主表主键删除时,从表关联记录同步删除
- 示例:若将
dept.id=1
(研发部)更新为id=10
,则emp
中所有dept_id=1
的记录会自动同步为dept_id=10
;若删除dept.id=1
,则emp
中所有dept_id=1
的记录会被自动删除。 - 适用场景:主从表是 “强关联”(如部门删除,下属员工也应删除 / 迁移)。
- 示例:若将
更新 / 删除时设为 NULL(
on update set null / on delete set null
)sql
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on update set null -- 主表主键更新时,从表外键设为NULL on delete set null; -- 主表主键删除时,从表外键设为NULL
- 前提:从表外键字段必须允许为 NULL(
emp.dept_id
未设置not null
,符合条件)。 - 示例:若删除
dept.id=1
,则emp
中所有dept_id=1
的记录会自动将dept_id
设为NULL
(员工仍存在,但暂时无部门)。 - 适用场景:主从表是 “弱关联”(如部门删除,员工不应被删除,仅解除部门关联)。
- 前提:从表外键字段必须允许为 NULL(
三、多表查询:从关联表中提取有效数据
当需要从多个关联表(如emp
和dept
)中获取数据时,需使用多表查询。核心是消除笛卡尔积,并根据业务需求选择合适的连接方式(内连接、外连接、自连接)。
1. 基础:笛卡尔积与如何消除
- 笛卡尔积(Cartesian Product):若直接查询两个表(
select * from emp, dept
),MySQL 会将emp
的每条记录与dept
的每条记录强制组合,产生emp行数 × dept行数
条记录(如emp
有 6 条,dept
有 5 条,会产生 30 条无效组合)。 - 消除方法:通过
where
子句添加 “关联条件”(如emp.dept_id = dept.id
),只保留主从表关联匹配的记录。
代码示例:
sql
-- 消除笛卡尔积,只查员工与所属部门的匹配记录
select * from emp, dept where emp.dept_id = dept.id;
2. 内连接(Inner Join):查询两张表的 “交集”
内连接只返回主从表中关联条件匹配的记录(不匹配的记录会被过滤),是最常用的连接方式,分为 “隐式内连接” 和 “显式内连接”。
(1)隐式内连接(用where
关联)
sql
-- 查询员工姓名和所属部门名称(隐式内连接)
select emp.name as 员工姓名, dept.name as 部门名称
from emp, dept
where emp.dept_id = dept.id; -- 关联条件放在where后
- 特点:语法简洁,但当查询条件复杂时(如多表 + 过滤条件),
where
子句会混合 “关联条件” 和 “过滤条件”,可读性较差。
(2)显式内连接(用inner join ... on
关联)
sql
-- 查询员工姓名和所属部门名称(显式内连接)
select emp.name as 员工姓名, dept.name as 部门名称
from emp
inner join dept -- 明确指定“内连接”
on emp.dept_id = dept.id; -- 关联条件放在on后
- 特点:
- 关键字
inner join
明确连接类型,on
子句单独放关联条件,过滤条件(如where emp.age > 30
)可单独放在where
后,可读性更强。 inner
可省略(join
默认是内连接),即select ... from emp join dept on ...
。
- 关键字
- 结果:仅返回 “有部门的员工” 和 “有员工的部门”(如
dept
中的 “财务部”(id=3)若没有对应员工,不会出现在结果中)。
3. 外连接(Outer Join):查询 “一张表的全部 + 另一张表的匹配记录”
内连接只查交集,而外连接可查 “某张表的全部记录”(即使另一张表无匹配),分为 “左外连接” 和 “右外连接”。
(1)左外连接(Left Outer Join):左表全部 + 右表匹配
sql
-- 左外连接:查询所有员工(包括无部门的员工),及其所属部门
select emp.*, dept.name as 部门名称
from emp
left outer join dept on emp.dept_id = dept.id; -- left outer join 可简写为 left join
- 核心规则:
- 左表:
from
后第一个表(emp
),其所有记录都会被返回。 - 右表:
join
后的表(dept
),仅返回与左表匹配的记录;若不匹配,右表字段值为NULL
。
- 左表:
- 示例:若
emp
中有一条dept_id=NULL
的员工(如 “临时员工”),左外连接会显示该员工的所有信息,部门名称
为NULL
。 - 简写:
left outer join
可简化为left join
。
(2)右外连接(Right Outer Join):右表全部 + 左表匹配
sql
-- 右外连接:查询所有部门(包括无员工的部门),及其下属员工
select dept.name as 部门名称, emp.*
from emp
right join dept on emp.dept_id = dept.id; -- right outer join 可简写为 right join
- 核心规则:
- 右表:
join
后的表(dept
),其所有记录都会被返回。 - 左表:
from
后的表(emp
),仅返回与右表匹配的记录;若不匹配,左表字段值为NULL
。
- 右表:
- 示例:
dept
中的 “财务部”(id=3)若没有员工,右外连接会显示 “财务部”,emp
的所有字段为NULL
。 - 关键技巧:右外连接可转换为左外连接(交换表的顺序),如上述右外连接等价于:
sql
select dept.name as 部门名称, emp.* from dept left join emp on dept.id = emp.dept_id; -- 左表为dept,右表为emp
4. 自连接(Self Join):将一张表当作 “两张表” 查询
自连接是特殊的多表查询,适用于 “表内存在层级关系” 的场景(如员工与领导的关系,领导也是员工,都存储在emp
表中)。核心是给同一张表起两个不同的别名,当作 “两张表”(如 “员工表” 和 “领导表”)。
(1)基础自连接:查询有领导的员工
sql
-- 查询员工姓名及其直属领导姓名(仅显示有领导的员工)
select emp_emp.name as 员工姓名, emp_mgr.name as 领导姓名
from emp emp_emp, emp emp_mgr -- 同一张表起两个别名:emp_emp(员工表)、emp_mgr(领导表)
where emp_emp.managerid = emp_mgr.id; -- 关联条件:员工的managerid = 领导的id
- 别名意义:
emp_emp
:代表 “员工” 角色,查询其name
。emp_mgr
:代表 “领导” 角色,查询其name
。
- 结果:仅显示有领导的员工(如 “张无忌” 的
managerid=1
,对应 “金庸”;“金庸” 的managerid=null
,无匹配,不显示)。
(2)左外自连接:查询所有员工(包括无领导的)
sql
-- 查询所有员工姓名及其直属领导姓名(无领导的员工也显示)
select emp_emp.name as 员工姓名, emp_mgr.name as 领导姓名
from emp emp_emp
left join emp emp_mgr
on emp_emp.managerid = emp_mgr.id; -- 左外连接:保留所有员工
- 结果:“金庸”(
managerid=null
)会被显示,其领导姓名
为NULL
,符合 “总裁无领导” 的业务逻辑。
四、核心知识点总结
模块 | 核心知识点 | 应用场景 |
---|---|---|
数据完整性约束 | 主键(primary key)、自增(auto_increment)、唯一(unique)、非空(not null)、检查(check)、默认(default) | 保证数据准确(如年龄 0-120)、不重复(如唯一姓名)、不缺失(如非空姓名)。 |
外键与级联 | 外键添加 / 删除、级联更新 / 删除(cascade)、级联设为 NULL(set null) | 维护主从表一致性(如部门删除时,员工不丢失或同步删除)。 |
多表查询 | 消除笛卡尔积、内连接(交集)、外连接(左 / 右表全部)、自连接(表内层级) | 复杂业务查询(如查员工 + 部门 + 领导信息、查所有部门及下属员工)。 |