项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在各种联系,基本上分为三种:一对一、多对一、一对多。
一、多表设计
(一)一对多表
需求:根据页面原型及需求文档,完成部门及员工模块的表结构设计。(一个部门对应多个员工)
设计部门表和员工表(父表和子表)
create table tb_emp (
id int unsigned primary key auto_increment comment 'ID',
username varchar(20) not null unique comment '用户名',
password varchar(32) default '123456' comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
image varchar(300) comment '图像',
job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管',
entrydate date comment '入职时间',
dept_id int unsigned comment '归属部门的id',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '员工表';
create table tb_dept(
id int unsigned primary key auto_increment comment 'ID',
name varchar(10) not null unique comment '部门名称',
create_date datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
)comment '部门表';
插入数据:
insert into tb_dept (id, name, create_date, update_time) values
(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()),
(4,'就业部',now(),now()),(5,'人事部',now(),now());
INSERT INTO tb_emp
(id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES
(1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()),
(2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()),
(3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()),
(4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()),
(5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()),
(6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()),
(7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()),
(8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()),
(9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()),
(10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()),
(11,'luzhangke','123456','鹿杖客',1,'11.jpg',1,'2007-02-01',1,now(),now()),
(12,'hebiweng','123456','鹤笔翁',1,'12.jpg',1,'2008-08-18',1,now(),now()),
(13,'fangdongbai','123456','方东白',1,'13.jpg',2,'2012-11-01',2,now(),now()),
(14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()),
(15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()),
(16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2010-01-01',2,now(),now()),
(17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());
问题:部门数据可以直接删除,然而还有员工归属于该部门下,此时就出现了数据的不完整不一致的问题。
原因:此时这两张表在数据库层面并没有建立关联,所以无法保证数据的一致性和完整性。
解决方案:使用外键约束
(二)物理外键
外键约束(物理外键):使用foreign key定义外键关联另一张表
缺点:影响增删改的效率,仅用于单节点数据库,不适用于分布式、集群场景,容易引发数据库的死锁问题。
图形化界面的方式:
(三)逻辑外键
在业务逻辑层,解决外键关联,通过逻辑外键可以很方便的解决物理外键的问题。
(四)一对一
一对一关系多用于单表拆分,将一张表的基础字段放在一张表中,其他字段放在另一张表中,以提升操作效率。
实现:在任意一方加入外键,关联另一方的主键,并且设置外键为唯一的(UNIQUE)
(五)多对多
实例:一个学生可以选择多门课,一门课也可以供多个学生选择。
实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键。
二、多表查询
从多张表中查询数据,并且根据业务需要消除无效信息。
建表:
create table tb_emp (
id int unsigned primary key auto_increment comment 'ID',
username varchar(20) not null unique comment '用户名',
password varchar(32) default '123456' comment '密码',
name varchar(10) not null comment '姓名',
gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
image varchar(300) comment '图像',
job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管',
entrydate date comment '入职时间',
dept_id int unsigned comment '归属部门的id',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '员工表';
create table tb_dept(
id int unsigned primary key auto_increment comment 'ID',
name varchar(10) not null unique comment '部门名称',
create_date datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
)comment '部门表';
insert into tb_dept (id, name, create_date, update_time) values
(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()),
(4,'就业部',now(),now()),(5,'人事部',now(),now());
INSERT INTO tb_emp
(id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES
(1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()),
(2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()),
(3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()),
(4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()),
(5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()),
(6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()),
(7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()),
(8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()),
(9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()),
(10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()),
(11,'luzhangke','123456','鹿杖客',1,'11.jpg',1,'2007-02-01',1,now(),now()),
(12,'hebiweng','123456','鹤笔翁',1,'12.jpg',1,'2008-08-18',1,now(),now()),
(13,'fangdongbai','123456','方东白',1,'13.jpg',2,'2012-11-01',2,now(),now()),
(14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()),
(15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()),
(16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2010-01-01',2,now(),now()),
(17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());
查询:
通过这种查询,会造成不正常的链接,也就是笛卡尔积
消除了无效的笛卡尔积:
(一)内连接
相当于查询A、B交集部分的数据。
# 查询员工的姓名,及所属的部门名称(隐式内连接)
select tb_emp.name,tb_dept.name from tb_emp,tb_dept where tb_emp.dept_id=tb_dept.id;
#查询员工姓名及所属部门名称(显示内连接)
select tb_emp.name,tb_dept.name from tb_emp inner join tb_dept on dept_id=tb_dept.id;
(二)外连接
左外连接,查询左表所有数据(包含两张表交集部分的数据);右外连接,查询右表所有数据(包含两张表交集部分的数据)。
#查询 所有员工的姓名,及所属的部门名称(左外连接)
select * from tb_emp e left join tb_dept d on e.dept_id=d.id;
#查询部门表 所有 部门的名称,和对应的员工名称(右外连接)
select e.name,d.name from tb_emp e right join tb_dept d on e.dept_id=d.id;
(三)子查询
1、标量子查询
子查询返回的是单个值(数值、字符串、日期等),最简单的形式。常用的操作符是:= <> > >= <= <
#子查询
#标量子查询
#A——查询“教研部”的所有员工信息 查教研部ID+查询部门ID下的员工信息
select tb_dept.id from tb_dept where name='教研部';
select * from tb_emp where id = 2;
# 合并
select * from tb_emp where dept_id =(select tb_dept.id from tb_dept where name='教研部');
# 在方东白入职之后的员工信息 入职时间+该时间之后的员工信息
select entrydate from tb_emp where name='方东白';
select * from tb_emp where entrydate>='2012-11-01';
# 合并
select * from tb_emp where entrydate>=(select entrydate from tb_emp where name='方东白');
2、列子查询
子查询返回的结果是一列(可以是多行),常用的操作符in 、not in
等。
#列子查询
#查询 教研部 和 咨询部 的所有员工信息 查询ID+查询该部门下的员工信息
select tb_dept.id from tb_dept where name='教研部' or name = '咨询部';
select * from tb_emp where dept_id in (2,3);
# 合并
select * from tb_emp where dept_id in (select tb_dept.id from tb_dept where name='教研部' or name = '咨询部');
3、行子查询
子查询返回的结果是一行(可以是多列),常用的操作符:= 、<>、 in 、not in
#行子查询
#查询与“韦一笑”这个员工的 入职日期 和 职位 都相同你的员工 =查询“韦一笑”的 入职日期 和职位 + 查询和他入职日期和职位都相同的人
select entrydate,job from tb_emp where name='韦一笑';
select * from tb_emp where entrydate = '2007-01-01' and job = 2;
#合并
select * from tb_emp where entrydate = (select entrydate from tb_emp where name='韦一笑') and job = (select job from tb_emp where name='韦一笑');
#优化
select * from tb_emp where (entrydate,job)= ('2007-01-01' ,job = 2);
select * from tb_emp where (entrydate,job)= (select entrydate,job from tb_emp where name='韦一笑');
4、表子查询
子查询返回的是多行多列,常作为临时表,常用的操作符in
。
#表子查询
#查询入职日期是“2006-01-01”之后的员工信息,及其部门信息
#先查询“2006-01-01”之后的员工信息
#在查询部门信息
select * from tb_emp where entrydate>'2006-01-01';
select e.*,d.name from (select * from tb_emp where entrydate>'2006-01-01') e,tb_dept d where e.dept_id =d.id;