您现在的位置是:网站首页 > 心得笔记

关系型数据库中的外键

盛悦2019-05-24576人围观
简介关系数据库通过外键可以实现一对多、多对多和一对一的关系。

1、主键的概念

对于关系表,有个很重要的约束,就是任意两条记录不能重复。不能重复不是指两条记录不完全相同,而是指能够通过某个字段唯一区分出不同的记录,这个字段被称为主键


例如,假设我们把name字段作为主键,那么通过名字小明小红就能唯一确定一条记录。但是,这么设定,就没法存储同名的同学了,因为插入相同主键的两条记录是不被允许的。


所以,选取主键的一个基本原则是:不使用任何业务相关的字段作为主键。

因此,身份证号、手机号、邮箱地址这些看上去可以唯一的字段,均不可用作主键。


id(主键)
name
sex
age
1
红红

20
2
蓝蓝

22



2、联合主键的概念

关系数据库实际上还允许通过多个字段唯一标识记录,即两个或更多的字段都设置为主键,这种主键被称为联合主键


对于联合主键,允许一列有重复,只要不是所有主键列都重复即可:

id
type
sex
name
1
A
20
test1
2
A
20
test2
2
B
22
test3

如果我们把上述表的idtype这两列作为联合主键,那么上面的3条记录都是允许的,因为没有两列主键组合起来是相同的。

没有必要的情况下,我们尽量不使用联合主键,因为它给关系表带来了复杂度的上升。


3、外键

3.1、一对多

当我们用主键唯一标识记录时,我们就可以在students表中确定任意一个学生的记录:

id
name
1
test1
2
test2
3
test3

我们还可以在classes表中确定任意一个班级记录:

id
name
1
一班
2
二班

但是我们如何确定students表的一条记录,例如,id=1的test1,属于哪个班级呢?

由于一个班级可以有多个学生,在关系模型中,这两个表的关系可以称为“一对多”,即一个classes的记录可以对应多个students表的记录。


为了表达这种一对多的关系,我们需要在students表中加入一列class_id,让它的值与classes表的某条记录相对应:

id
class_id
name
1
1
test1
2
2
test2
3
2
test3



这样,我们就可以根据class_id这个列直接定位出一个students表的记录应该对应到classes的哪条记录。

例如:

  • test1的class_id1,因此,对应的classes表的记录是id=1的一班;

  • test2的class_id2,因此,对应的classes表的记录是id=1的二班;

  • test3的class_id2,因此,对应的classes表的记录是id=2的二班。

students表中,通过class_id的字段,可以把数据与另一张表关联起来,这种列称为外键


3.2、多对多

通过一个表的外键关联到另一个表,我们可以定义出一对多关系。有些时候,还需要定义“多对多”关系。例如,一个老师可以对应多个班级,一个班级也可以对应多个老师,因此,班级表和老师表存在多对多关系。

多对多关系实际上是通过两个一对多关系实现的,即通过一个中间表,关联两个一对多关系,就形成了多对多关系


teachers表:

id
name
1
赵老师
2
钱老师
3
孙老师


classes表:

id
name
1
一班
2
二班

中间表teacher_class关联两个一对多关系:

id
teacher_id
class_id
1
1
1
2
1
2
3
2
1
4
2
2
5
3
2


通过中间表teacher_class可知teachersclasses的关系:

  • id=1的赵老师对应id=1,2的一班和二班;

  • id=2的钱老师对应id=1,2的一班和二班;

  • id=3的孙老师对应id=2的二班;


同理可知classesteachers的关系:

  • id=1的一班对应id=1,2的赵老师、钱老师;

  • id=2的二班对应id=1,2,3的赵老师、钱老师和孙老师;

因此,通过中间表,我们就定义了一个“多对多”关系。


3.3、一对一


一对一关系是指,一个表的记录对应到另一个表的唯一一个记录。

例如,students表的每个学生可以有自己的联系方式,如果把联系方式存入另一个表contacts,我们就可以得到一个“一对一”关系:

id
name
phone
1
赵赵
15655252352
2
钱钱
17641525523

既然是一对一关系,其实可以直接给students表增加一个phone列,这样就能合二为一了? 如果业务允许,完全可以把两个表合为一个表。


还有一些应用会把一个大表拆成两个一对一的表,目的是把经常读取和不经常读取的字段分开,以获得更高的性能。例如,把一个大的用户表分拆为用户基本信息表user_info和用户详细信息表user_profiles,大部分时候,只需要查询user_info表,并不需要查询user_profiles表,这样就提高了查询速度。


4、条件查询

使用SELECT * FROM <表名>可以查询到一张表的所有记录。但是,很多时候,我们并不希望获得所有记录,而是根据条件选择性地获取指定条件的记录,例如,查询分数在80分以上的学生记录。在一张表有数百万记录的情况下,获取所有记录不仅费时,还费内存和网络带宽。


条件查询的语法:

SELECT * FROM <表名> WHERE <条件表达式>

条件表达式第一种条件:可以用<条件1> AND <条件2>表达满足条件1并且满足条件2。例如,符合条件“分数在80分或以上”,并且还符合条件“男生”,把这两个条件写出来:

  • 条件1:根据score列的数据判断:score >= 80

  • 条件2:根据gender列的数据判断:gender = 'M',注意gender列存储的是字符串,需要用单引号括起来。


条件表达式第二种条件:<条件1> OR <条件2>,表示满足条件1或者满足条件2。例如,把上述AND查询的两个条件改为OR,查询结果就是“分数在80分或以上”或者“男生”,满足任意之一的条件即选出该记录

条件表达式第三种条件:NOT <条件>,表示“不符合该条件”的记录。例如,写一个“不是2班的学生”这个条件,可以先写出“是2班的学生”:class_id = 2,再加上NOTNOT class_id = 2

上述NOT条件NOT class_id = 2其实等价于class_id <> 2,因此,NOT查询不是很常用。


优先级:如果不加括号,条件运算按照NOTANDOR的优先级进行,即NOT优先级最高,其次是AND,最后是OR。加上括号可以改变优先级。