25 MySql多表、外键和数据库设计

381 阅读11分钟

1 多表

在实际开发中,单表存储项目数据是不现实的,所以一个项目通常都需要很多张表,并且单表进场会存在冗余问题,也就是同一字段出现大量重复数据,这显然就是不合理的,解决方案就是设计成多张表。

下面就以部门表和员工表为例,介绍多表: 创建两张表

create table department(
    id int primary key auto_increment,
    dept_name varchar(20),
    dept_location varchar(20)
);
create table employee(
    eid int primary key auto_increment ,
    ename varchar(20),
    age int,
    dept_id int
);

添加部门和员工数据

insert into department values (1,'研发部','广州'),(2,'销售部','深圳');
insert into employee(ename, age, dept_id) values ('张百万',20,1);
insert into employee(ename, age, dept_id) values ('赵四',21,1);
insert into employee(ename, age, dept_id) values ('广坤',20,1);
insert into employee(ename, age, dept_id) values ('小冰',20,2);
insert into employee(ename, age, dept_id) values ('艳秋',22,2);
insert into employee(ename, age, dept_id) values ('大玲子',18,2);

表关系分析:员工表中有一个字段dept_id与部门表中的主键对应,员工表的这个字段就叫做外键,拥有外键的表就是从表,与外键对应的主键所在表就是主表。

多表存在的问题:还是以上面的例子为例,当我们在员工表中插入一条不存在的部门id的数据,数据依然可以添加成功,这显然是不合理的,因为实际上我们要做保证插入员工表中的dept_id必须在部门表中存在,解决方案就是使用外键约束。

2 外键约束

外键约束可以让两张表之间产生一个对应的关系,从而保证主从表的引用的完整性。添加外键约束的语法格如下: 新建表时添加

[constraint]  [外键约束名] foreign key(外键字段名) references 主表名(主键字段名)

已有表添加

alter table 从表 add [constraint] [外键约束名] foreign key(外键字段名) references 主表名(主键字段名)

实例代码:

alter table employee add constraint dept_employee foreign key (dept_id) references department(id);

这时候添加的外键约束会产生强制性的外键数据检查,从而保证了数据的完整性和一致性,从而在员工中插入一条不存在的部门id的数据就无法插入成功。

删除外键约束的语法格式:

alter table 从表 drop foreign key 外键约束名称;

实例代码:

alter table employee drop foreign key dept_employee;

外键约束的注意事项:

  • 从表外键约束类型必须与主表主键类型一致,否则创建失败

  • 添加数据时,应该先添加主表中的数据

  • 删除数据时,应该先删除从表中的数据

级联删除操作:若果想实现删除主表中的数据的同时,会自动的删除从表中对应的数据,就需要使用级联删除操作,具体语法是:

on delete cascade;

在员工表中中添加级联删除。实例代码:

create table employee(
    eid int primary key auto_increment ,
    ename varchar(20),
    age int,
    dept_id int,constraint dept_employee foreign key (dept_id) references department(id)
    on delete  cascade
);

3 多表关系设计

表与表之间的三种关系:

  • 一对一关系:身份证号与个人
  • 一对多关系:学生与班级,房子与业主
  • 多对多关系:学生与课程,用户与角色

一对多关系(常见)

一对多关系(1:n),例如班级和学生,部门和员工·,客户和订单。

一对多关系的建表原则:在从表中创建一个字段,字段作为外键指向主表的主键

一对多.jpg

多对多关系(常见)

多对多关系(m:n),例如学生和课程,老师和学生等等。

多对多关系的建表原则:需要创建第三张表,第三张表就是中间表,中间表至少两个字段,这两个字段分别作为外键指向各自一方的主键。

多对多.jpg

一对一关系(不常见)

一对一关系,在实际开发中不常见,因为一对一其实就是一张表。

一对一的建表原则:主表的主键和从表的外键,形成主外键关系,并且外键是唯一的uniqueq。

一对一.jpg

4 多表查询

多表查询DQL就是查询多张表,获取到需要的数据。

接下来以分类表和商品表为例,先准备数据,具体代码如下:

#分类表 (一方 主表)
CREATE TABLE category (
  cid VARCHAR(32) PRIMARY KEY ,
  cname VARCHAR(50)
);

#商品表 (多方 从表)
CREATE TABLE products(
  pid VARCHAR(32) PRIMARY KEY ,
  pname VARCHAR(50),
  price INT,
  flag VARCHAR(2),		#是否上架标记为:1表示上架、0表示下架
  category_id VARCHAR(32),
  -- 添加外键约束
  FOREIGN KEY (category_id) REFERENCES category (cid)
);

#分类数据
INSERT INTO category(cid,cname) VALUES('c001','家电');
INSERT INTO category(cid,cname) VALUES('c002','鞋服');
INSERT INTO category(cid,cname) VALUES('c003','化妆品');
INSERT INTO category(cid,cname) VALUES('c004','汽车');


#商品数据
INSERT INTO products(pid, pname,price,flag,category_id) VALUES('p001','小米电视机',5000,'1','c001');
INSERT INTO products(pid, pname,price,flag,category_id) VALUES('p002','格力空调',3000,'1','c001');
INSERT INTO products(pid, pname,price,flag,category_id) VALUES('p003','美的冰箱',4500,'1','c001');

INSERT INTO products (pid, pname,price,flag,category_id) VALUES('p004','篮球鞋',800,'1','c002');
INSERT INTO products (pid, pname,price,flag,category_id) VALUES('p005','运动裤',200,'1','c002');
INSERT INTO products (pid, pname,price,flag,category_id) VALUES('p006','T恤',300,'1','c002');
INSERT INTO products (pid, pname,price,flag,category_id) VALUES('p007','冲锋衣',2000,'1','c002');

INSERT INTO products (pid, pname,price,flag,category_id) VALUES('p008','神仙水',800,'1','c003');
INSERT INTO products (pid, pname,price,flag,category_id) VALUES('p009','大宝',200,'1','c003');

笛卡尔积

交叉连接查询会产生笛卡尔积,所以基本不会使用,具体语法格式:

select 字段名 from1, 表2;

使用交叉连接查询会得到很多无用的数据,因此这种用法很少出现在实际开发中。

笛卡尔积:集合1={1,2},集合2={a,b},那么集合1与2的笛卡尔积是{{1,a},{1,b},{2,a},{2,b}}

5 多表查询的分类

内连接查询

内连接的特点:通过指定的条件去匹配两张表中的数据,匹配得上就显示,否则不显示

隐式内连接

from子句 后面直接写多个表名,使用where指定连接条件,这种连接方式是隐式内连接(使用where条件过滤无用数据)

语法格式:

select 字段名 from 左表,右表 where 连接条件;

查询所有商品信息和对应的分类信息

select * from category,products where category_id=category.cid;

显式内连接

使用 inner join ...on这种方式,就是显式内连接,具体的语法格式是:

select 字段名 from 左表 	[inner] join 右表 on  条件;
-- inner 可以省略不写

查询所有商品信息和对应的分类信息,对应的显式连接代码是:

select * from products join category c on products.category_id = c.cid;
select p.pname,p.price
from products p join category c on p.category_id = c.cid where p.price>500 and c.cname='鞋服';

外连接查询

外连接查询有左外连接查询和右外连接查询两种。

左外连接查询,使用left outer join,其中outer是可以省略的,左外连接查询的特点是:

  • 以左表为基准,匹配右表中的数据,匹配得上就显示
  • 匹配不上,左表中的数据正常显示,右边的展示为null

左外连接查询语法格式:

select 字段名 from 左表 left [outer] join 右表 on 条件

左外连接查询每个分类下的商品个数:

select * from category c left join products p on c.cid = p.category_id;
select c.cname as '分类名称',count(p.pid) as '商品个数'from category c left join products p on c.cid = p.category_id group by c.cname;

右外连接查询,使用right outer join,其中outer是可以省略的,右外连接查询的特点是:

  • 以右表为基准,匹配左表中的数据,匹配得上就显示
  • 匹配不上,右表中的数据正常显示,左边的展示为null

右外连接查询语法格式:

select 字段名 from 左表 right [outer] join 右表 on 条件

右外连接查询每个分类下的商品个数:

select * from products p right join category c on c.cid = p.category_id;

各种连接方式的总结见下图:

多表查询总结.jpg

  • 内连接:inner join,只获取两张表中,交集部分的数据
  • 左外连接:left join ,以左表为基准,查询左表中的所有数据,以及与右表有交集的部分
  • 右外连接:right join,以右表为基准,查询右表中的所有数据,以及与左表有交集的部分

5 子查询

子查询:一条select查询语句的结果作为另一条select查询语句的条件

特点是:子查询必须放在小括号中;子查询一般作为父查询的查询条件使用

子查询分类:

  • where型子查询:将子查询结果作为父查询的比较条件
  • from型子查询:将子查询的结果作为一张表,提供给父层查询使用
  • exists型子查询:子查询的结果是单列多行,父层查询使用in函数,包含子查询的结果

where型子查询

语法格式:

select 查询字段 fromwhere 字段=(子查询);

查询价格最高的商品信息

select * from products where price=(select max(price) from products);

from型子查询

语法格式:

select 查询字段 from (子查询) 表别名 where 条件;

查询商品中,价格大于500的商品信息,包括商品名称,价格,商品所属分类名称

select p.pname,p.price,c.cname from products p inner join (select * from category) c on p.category_id=c.cid where p.price>500;

注意:当子查询作为一张表的时候,必须起表别名,否则无法访问表中的字段

exists型子查询

语法格式:

select 查询字段 from 表名 where 字段 in (子查询);

查询价格小于两千的商品,来自哪些分类

select * from category where cid in(select distinct category_id from products where price<2000);

子查询总结:

  • 子查询结果查出来的结果是一个字段(单列),那就在where后面作为条件使用
  • 子查询结果查出来的是多个字段(多列),就当作一张表使用

6 数据库设计

设计数据库要遵循三范式的规则,这样就可以建立冗余小、结构合理的数据库,所以设计数据库必须遵循规则。满足最低要求的范式是第一范式(1NF)。在此基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以此类推。一般来说数据库只需要满足第三范式。

第一范式

概念:具有原子性,每一列都不可拆分,第一范式是最基本的范式。数据库表里面的字段都是单一属性的,不可拆分,如果数据表中每个字段都是不可再分的最小数据单元,则满足第一范式。

第二范式

概念:第二范式在第一范式的基础上,增加了一张表只能描述一件事,这样可以确保表中的每列都和主键相关。

第三范式

概念;表的信息,如果能被推导出来,就不应该单独的设计一个字段来存放,这样可以消除传递依赖

当然除了上面提到的数据库三范式,还有数据库反三范式,上面提到的数据库三范式是以时间换空间的做法,简单来说按照数据库三范式设计的数据库非常节省空间,但是每次查询数据的耗时很长,当时在现在实际开发中,更加注重时间,这样可以提高用户体验,也就有了现在的数据库反三范式,虽然有冗余的部分,但是数据库的查询会非常的快,这就是用空间换时间。

举个数据库反三范式的例子,两张表:用户表有id,name,sex,订单表有id,number,price,name,可以看出看来订单表中的name已经在用户表中存在了,这显然是冗余字段,那么为何还要如此设计,试想当我们要查询订单信息,要求有用户姓名,假如订单表中没有name字段的话,我们需要join连接用户表,当表中的数据非常大时,那么连接查询就会耗费非常大的内存消耗,这时添加name这个冗余字段的优势就体现出来了,这时只需要查一张表即可。

**总结:**创建关系型数据库设计,我们有两种选择

  • 尽量遵循范式理论的规约,尽可能减少冗余字段,让数据库设计看起来更加合理,简约。
  • 合理的加入冗余字段,可以减少join操作,从而提高数据库的执行性能