操作系统: windows
IDE: Pycharm
后端系统开发中, 数据库设计是重中之重。特别是前后端分离的系统, 后端的职责基本就是数据管理, 开发的代码几乎都是围绕数据操作的。
因此,常用的数据库表和表之间的关系的设计就很重要。
目前使用的数据库系统主要还是关系型数据库(建立在关系模型基础上的数据库) 。
而关系型数据库,设计的一个难点就是各种表之间的关联关系 。
常见的3种关联关系就是: 一对多 , 一对一 , 多对多
表之间一对多的关系,就是外键关联关系
现在系统中, 已经定义了客户(Customer)这张表 :
class Customer(models.Model):
# 客户名称
name = models.CharField(max_length=200)
# 联系电话
phonenumber = models.CharField(max_length=200)
# 地址
address = models.CharField(max_length=200)
还需要定义药品(Medicine)这张表,包括药品名称、编号和描述 这些信息。
添加如下的类定义:
class Medicine(models.Model):
# 药品名
name = models.CharField(max_length=200)
# 药品编号
sn = models.CharField(max_length=200)
# 描述
desc = models.CharField(max_length=200)
接下来我们还要定义 订单(Order)这张表,这个Order表包括创建日期、客户、药品、数量。
其中:
客户字段对应的客户 只能是 Customer 中的某个客户记录,也就是说Order表里面的一条订单记录的客户对应 Customer表里面的一条客户记录。
而且一个客户记录可以对应多条订单记录。
这就是一对多的关系:
像这种一对多的关系,数据库中是用外键来表示的。
如果一个表中的某个字段是外键,那就意味着这个外键字段的记录的取值,只能是它关联表的某个记录的主键的值。
我们定义表的 Model类的时候,如果没有指定主键字段,migrate
的时候 Django 会为该Model对应的数据库表自动生成一个id
字段,作为主键。
比如,我们这里,Customer、Medicine表均没有主键,但是在migrate
之后,查看数据库记录就可以发现有一个id字段,且该字段是主键 (primary key)。
Django中用models.ForeignKey()
方法实现一对一的关系,如下:
common/models.py:
from django.db import models
import datetime
# Create your models here.
#存放数据库表对象
class Customer(models.Model):
#客户名称
name = models.CharField(max_length=200) #对应数据库中的varchar
#联系电话
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)
class Order(models.Model):
# 订单名
name = models.CharField(max_length=200, null=True, blank=True)
# 创建日期
create_date = models.DateTimeField(default=datetime.datetime.now)
# 客户 customer字段是外键, 和Customer类的主键关联
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
补充:
on_delete
参数指定了当我们删除外键指向的主键记录时, 系统的行为。
on_delete 不同取值对应不同的做法,常见的做法如下:
比如,我们要删除客户张三,在删除了客户表中张三记录同时,也删除Order表中所有这个张三的订单记录。
比如,我们要删除客户张三,如果Order表中还有张三的订单记录,Django系统就会抛出ProtectedError
类型的异常,当然也就禁止删除客户记录和相关的订单记录了。
除非我们将Order表中所有张三的订单记录都先删除掉,才能删除该客户表中的张三记录。
前提是外键字段要设置为值允许是null。
比如,我们要删除客户张三时,在删除了客户张三记录同时,会将Order表里面所有的 张三记录里面的customer字段值置为 null。 但是上面我们并没有设置 customer 字段有 null=True 的参数设置,所以,是不能取值为 SET_NULL的。
定义完之后,还是老方法,在根目录cmd执行:
注意:
customer_id
字段,比定义的时候多了一个_id
,因为这是作为外键和common_customer表的主键id关联了外键是一对多的关系,有的时候,表之间是一对一的关系。
比如,某个学校的学生表和学生的地址表,就形成一对一的关系,即一条主键所在表的记录只能对应一条外键所在表的记录。
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)
Django发现这样一对一定定义,它会在migrate
的时候,在数据库中定义该字段为外键的同时, 加上unique=True
约束(在底层数据库实现),表示在此表中,所有记录的该字段取值必须唯一,不能重复。
数据库表还有一种多对多的关系。
在当前系统中, 一个订单可以采购多种药品,就对应Medicine表里面的多种药品;
而一种药品也可以被多个订单采购, 那么Order表和Medicine表 之间就形成了多对多的关系。
Django是通过 ManyToManyField
对象 表示 多对多的关系。
修改models.py中的Order类:
class Order(models.Model):
# 订单名
name = models.CharField(max_length=200, null=True, blank=True)
# 创建日期
create_date = models.DateTimeField(default=datetime.datetime.now)
# 客户
customer = models.ForeignKey(Customer, on_delete=models.PROTECT)
# 订单购买的药品,和Medicine表是多对多的关系
medicines = models.ManyToManyField(Medicine, through='OrderMedicine')
class OrderMedicine(models.Model):
order = models.ForeignKey(Order, on_delete=models.PROTECT)
medicine = models.ForeignKey(Medicine, on_delete=models.PROTECT)
# 订单中药品的数量
amount = models.PositiveIntegerField()
像这样:
medicines = models.ManyToManyField(Medicine, through='OrderMedicine')
指定Order表和 Medicine 表的多对多关系, 其实Order表中并不会产生一个叫 medicines 的字段。
Order表和 Medicine 表的多对多关系是通过另外一张表, 也就是through参数 指定的 OrderMedicine表来确定的,
migrate
的时候,Django会自动产生一张新表 (这里就是 common_ordermedicine)来 实现 order 表 和 medicine 表之间的多对多的关系。
同样的,在根目录cmd执行:
python manage.py makemigrations common
python manage.py migrate
可以看到数据库中多了一张common_ordermedicine表,用来实现订单表和药品表的多对多关系。
外键medicine_id关联了common_medicine表,外键order_id关联了common_order表。
比如一个order表的订单id 为 1, 如果该订单中对应的药品有3种,它们的id分别 为 3,4,5。 那么就会有类似这样的这样3条记录在 common_order_medicine 表中。
order_id | medicine_id |
---|---|
1 | 3 |
1 | 4 |
1 | 5 |
因为要多一个字段,例如药品的数量,所以要用through='OrderMedicine'
,也就是说如果不需要额外字段的话,through可以省略