数据库表的关联

后端系统开发中, 数据库设计是重中之重。

而关系型数据库,设计的一个难点就是各种表之间的关联关系。

常见的几种关联关系是: 多对一,一对一,多对多

多对一

表之间一对多的关系,就是外键

比如,我们的 BYSMS 系统中, 现在已经定义了客户这张表对应的Model。如下所示

class Customer(models.Model):
    # 客户名称
    name = models.CharField(max_length=200)

    # 联系电话
    phonenumber = models.CharField(max_length=200)

    # 地址
    address = models.CharField(max_length=200)



我们还需要定义 药品 这张表,包括药品名称、编号和描述,这个也很简单,添加如下的类定义

class Medicine(models.Model):
    # 药品名
    name = models.CharField(max_length=200)
    # 药品编号
    sn = models.CharField(max_length=200)
    # 描述
    desc = models.CharField(max_length=200)



接下来我们要定义订单这张表,这个订单表 包括 创建日期、客户、药品、数量。

其中:

客户字段对应的客户只能是 客户表中的某个客户记录

所以可以说:

一条订单记录里面的客户 只能对应一个客户记录

多条 订单记录里面的客户可以对应 同一个客户记录

这就是一对多的关系,可以用如下图片表示

image


同样,药品字段也是这样

药品 字段对应的客户只能是 药品表中的某个药品记录,

所以可以说:

一条订单记录里面的药品 只能对应一个药品记录

多条 订单记录里面的药品可以对应 同一个药品记录



像这种一对多的关系,数据库中是用 外键 来表示的。

如果一个表中 的 某个字段是外键,就是说这个字段指向另外一个表的主键。 外键字段的值,只能是它指向的那个表的主键某个记录的值。

Django定义Model的时候,如果没有指定一个字段为主键,Django 会为该Model对应的数据库表自动生成一个id字段,作为主键。

比如,我们这里,客户、药品表均没有主键,但是在migrate之后,查看数据库记录就可以发现有一个id字段,且该字段是 主键 (primary key)

image


现在我们要定义 订单 表的时候,

客户字段就应该是一个外键,对应客户表的主键,也就是id字段

药品字段也应该是一个外键,可以对应药品表的主键,也是id字段

Django中定义外键 的方法就是 在Model类的属性字段使用 ForeignKey对象,如下所示

class Order(models.Model):
    # 创建日期
    create_date = models.DateField()

    # 客户,外键 指向 Customer表 的主键
    customer = models.ForeignKey(Customer,on_delete=models.PROTECT)

    # 药品,外键 指向 Medicine表 的主键
    medicine = models.ForeignKey(Medicine,on_delete=models.PROTECT)

    # 数量
    amount = models.PositiveIntegerField()

大家可以发现, customer字段 和 medicine字段 都是外键,分别指向 Customer 和 Medicine 类。

其中另外一个参数 on_delete 指定了 当我们想 删除 外键指向的主键 记录时, 系统的行为。

比如 我们要删除客户记录, 那么 订单表中 对应这个客户的订单记录 该如何处理呢?

on_delete 不同取值对应不同的做法,常见的做法如下

  • CASCADE

    删除主键记录和 相应的外键表记录。

    比如,我们要删除客户记录,在删除了客户记录同时,也删除订单表中所有这个客户的订单记录

  • PROTECT

    禁止删除记录。

    比如,我们要删除客户记录时,如果订单表中有该客户的订单记录。Django系统 抛出ProtectedError类型的异常,当然也就禁止删除 客户记录和相关的订单记录了。

    除非我们将订单表中所有该客户的订单记录都先删除掉,才能删除该客户记录。

  • SET_NULL

    删除主键记录,并且将外键记录中外键字段的值置为null。 当然前提是外键字段要设置为值允许是null。

    比如,我们要删除客户记录时,在删除了客户记录同时,将订单表里面的 customer字段值置为 null。 但是上面我们并没有设置 customer 字段有 null=True 的参数设置,所以,是不能取值为 SET_NULL的。



一对一

外键是多对一的关系。

有的时候,表之间是一对一的关系。

比如,某个学校的学生 表 和学生的地址表,就形成一对一的关系,即 一条主键所在表的记录 只能对应一条 外键所在表的记录。

Django 中 用 OneToOneField 类型 实现 一对一的关系,如下

class Student(models.Model):
    # 姓名
    name = models.CharField(max_length=200)
    # 班级
    classname = models.CharField(max_length=200)
    # 描述
    desc = models.CharField(max_length=200)


class ContactAddress(models.Model):
    # 一对一 对应学生 
    student = models.OneToOneField(Student, on_delete=models.PROTECT)
    # 家庭
    homeaddress = models.CharField(max_length=200)
    # 电话号码
    phone = models.CharField(max_length=200)

其实一对一的关系,在数据库中就是, 定义为外键的同时 加上 unique=True 约束,表示在此表中,所有记录的该字段 取值必须唯一,不能重复。



多对多

数据库表还有一种 多对多 的关系。

在我们的 BYSMS系统中, 如果 一个订单可以对应 药品表里面的多种药品的话, 那么订单表 和 药品表 之间就形成了多对多的关系。

在django中, 是通过manytomany类型 表示 多对多的关系的。

如下所示:

class Order(models.Model):
    # 创建日期
    create_date = models.DateField()

    # 客户,外键 指向 Customer表 的主键
    customer = models.ForeignKey(Customer,on_delete=models.PROTECT)

    # 订单购买的药品,和药品表是多对多 的关系
    medicine = models.ManyToManyField(Medicine)

    # 数量
    amount = models.PositiveIntegerField()

其实,像上面这样指定的多对多的关系后,migration的时候,Django会自动产生的一张新表 (这里就是 common_order_medicine)来 实现 order 表 和 medicine 表之间的多对多的关系。

如下:

image

可以发现这张表中有 order_id 和 medicine_id 两个字段。

比如一个order表的订单记录id 为 1, 如果该订单中对应的药品记录的id 为 3,4,5。 那么就会有类似这样的这样3条记录在 common_order_medicine 表中。

order_id medicine_id
1 3
1 4
1 5



目前为止,我们系统的完整代码,点击这里下载



上一页 下一页