一些前期工作
- 上章回顾:上一章讲到,创建了四大表,其中中间表不需要手动创建。本章将延续上一章节表模型的使用,并将进行数据插入以待演示。
- 插入数据。写一个用于数据插入的函数,使用 get_or_create 的方法插入,该方法的好处是当数据库中存在与待插入数据相同的数据时,该数据将不会被插入到数据库中。就是怕手抖按多了几次插了一大堆数据。
def insert(request):
# 学院表
College.objects.get_or_create(college_name='计算机')
College.objects.get_or_create(college_name='经济管理')
College.objects.get_or_create(college_name='土木工程')
College.objects.get_or_create(college_name='数学')
# 学生表
Student.objects.get_or_create(s_id=201901, student_name='小叶', c_id_id=1)
Student.objects.get_or_create(s_id=201902, student_name='小黄', c_id_id=2)
Student.objects.get_or_create(s_id=201903, student_name='小嘉', c_id_id=2)
Student.objects.get_or_create(s_id=201904, student_name='小琳', c_id_id=3)
Student.objects.get_or_create(s_id=201905, student_name='小巧', c_id_id=3)
Student.objects.get_or_create(s_id=201906, student_name='小伏', c_id_id=4)
# 学生详情表
StudentDetail.objects.get_or_create(student_age=12, student_phone=186612345, student_id=201901)
StudentDetail.objects.get_or_create(student_age=13, student_phone=186634674, student_id=201902)
StudentDetail.objects.get_or_create(student_age=11, student_phone=186634655, student_id=201903)
StudentDetail.objects.get_or_create(student_age=15, student_phone=186633463, student_id=201904)
StudentDetail.objects.get_or_create(student_age=12, student_phone=186634421, student_id=201905)
StudentDetail.objects.get_or_create(student_age=14, student_phone=186634678, student_id=201906)
# 课程表
Course.objects.get_or_create(course_name='python')
Course.objects.get_or_create(course_name='经济学')
Course.objects.get_or_create(course_name='土木工程制图')
Course.objects.get_or_create(course_name='应用数学')
Course.objects.get_or_create(course_name='毛概')
Course.objects.get_or_create(course_name='大英')
- 小结
虽然表中有一对多、一对一关系表,但是仍然可以使用普通的数据插入方式作为表的操作。需要注意的是,关于一对多和一对一的插入,外键名需为数据库内实际的字段名。如在一对多关系中,学生表的外键命名的是 c_id, 而在数据库中字段名为 c_id_id,想要使用普通方法进行数据插入,则必须相应地使用实际的字段进行插入。
主从表的概念
主表:被作为外键引用的表。
从表:有外键引用的表。
on_delete的三种用法
以学院表和学生表为例,学生表外键 c_id 关联了学院表 College,on_delete 代表了删除时候的操作,删除时有三种操作,CASCADE、SET_NULL、PROTECT
# 学院表
class College(models.Model):
c_id = models.AutoField(primary_key=True)
college_name = models.CharField(max_length=30)
# 学生表
class Student(models.Model):
s_id = models.IntegerField(primary_key=True)
student_name = models.CharField(max_length=20)
c_id = models.ForeignKey(College, on_delete=models.CASCADE)
- CASCADE(级联删除):从表引用的外键在主表中被删除时(主表的主键删除时),从表中对应的数据也被删除。
c_id = models.ForeignKey(College, on_delete=models.CASCADE)
- SET_NULL(设为空值):从表引用的外键在主表中被删除时(主表的主键删除时),从表中对应的键的字段设为空,前提条件为该字段可以为空。
c_id = models.ForeignKey(College, on_delete=models.SET_NULL, null=True)
- PROTECT(限制删除):如果主表中的一个主键被删除时,当从表中仍有外键引用这个主键时,那么不允许直接删除主表的这条记录,必须先删除或修改引用该主键的外键才能删除。
c_id = models.ForeignKey(College, on_delete=models.PROTECT)
表关系访问操作
一对一与一对多关系支持一些普通方法的操作,在此不多说了,详情可参照上文“一些前期工作”。此处主要介绍一些富有关系表特征的数据操作方法。
一对多关系操作
关系表数据的操作主要在于主从表实例的使用。
+--------+--------------+---------+
| s_id | student_name | c_id_id |
+--------+--------------+---------+
| 201901 | 小叶 | 1 |
| 201902 | 小黄 | 2 |
| 201903 | 小嘉 | 2 |
| 201904 | 小琳 | 3 |
| 201905 | 小巧 | 3 | 未修改的学生表
| 201906 | 小伏 | 4 |
+--------+--------------+---------+
(使用上述学生表以及学院表进行演示)
- add 方法
- 一个主表或者从表的实例,可以使用 add 方法进行数据操作
- add 方法使用时传入一个实例化对象,当该实例化对象的外键列为空时,为外键列增加一个调用 add 方法的实例的对应值;当该实例化外键列值不为空时,则对应修改外键列的值。
c1 = College.objects.get(c_id=1) # 一个学院的实例
s2 = Student.objects.get(s_id=201902) # 一个学生的实例
c1.student_set.add(s2) # 使用add为从表添加或修改外键值
# 结果如下
+--------+--------------+---------+
| s_id | student_name | c_id_id |
+--------+--------------+---------+
| 201902 | 小黄 | 1 |
+--------+--------------+---------+
- create 主表增从表记录
c1.student_set.create(s_id=201907, student_name='小明')
# 结果如下
+--------+--------------+---------+
| s_id | student_name | c_id_id |
+--------+--------------+---------+
| 201907 | 小明 | 1 |
+--------+--------------+---------+
- (补充) related_name:在表创建阶段,可以为两表之间的关系命名。关系名默认为表名的小写,所以在上述示例中c1.student_set 的关系名为 student,如果使用了 related_name=‘college’,则为 c1.college_set。
- 查询
每个从表都一个外键对应主表,通过从表与主表的主外键对应关系,可以查询到主表中对应的记录。在从表查询主表数据,称为正向查询,而从主表查询从表数据,称为反向查询。- 正向查询:通过访问外键,可直接访问主表字段值
- 反向查询:通过 relate 关系,调用方法访问从表记录
# 正向查询 从表查主表
print(s2.c_id.college_name)
print(s2.c_id.c_id) # C_id 为外键名,详情可在上一章查看我的详细表结构
# 反向查询 主表查从表
print(c1.student_set.all()) # 查询与c1对应的所有记录
print(c1.student_set.filter(s_id=201901)) # 查询与c1对应的指定记录
- 删除
与反向查询同理,只是调用的方法有不同。删除时从表的外键列必须可以为空 (null=True),否则将无法从Manage里获得 remove、clear 方法。
c1.student_set.remove(s2) # s1 为一个实例化对象
c1.student_set.clear() # 清空
一对一关系操作
- 查询
一对一关系的表关系访问与一对多关系同理,最大的区别是对应的记录数量。一对多的反向查询对应了多的数量,所以需要调用方法筛选,一对一关系的两表,由于相互的数据有且仅有一条在两表中相互对应,所以不管是主表还是从表,都可以直接使用关系名访问对方的字段值。
# 正向查询
print(stu_detail.student.student_name)
# 反向查询
print(stu1.studentdetail.student_phone)
print(stu1.studentdetail.student_age)
多对多关系操作
按照上述定好的课程与学生中间表进行课程分配
- add 方法添加,在多对多关系中不存在主从表关系,所以从哪一个表操作效果都是殊途同归。如下,从学生表添加课程,就像是在选课;从课程表添加学生,就像是必修课分配给对应学生。
# 六门课程实例
co1 = Course.objects.get(id=1)
co2 = Course.objects.get(id=2)
co3 = Course.objects.get(id=3)
co4 = Course.objects.get(id=4)
co5 = Course.objects.get(id=5)
co6 = Course.objects.get(id=6)
# 六个学生实例
s1 = Student.objects.get(s_id=201901)
s2 = Student.objects.get(s_id=201902)
s3 = Student.objects.get(s_id=201903)
s4 = Student.objects.get(s_id=201904)
s5 = Student.objects.get(s_id=201905)
s6 = Student.objects.get(s_id=201906)
# 从学生表添加课程
s1.course_set.add(co1)
s1.course_set.add(co2)
s2.course_set.add(co3, co4)
s3.course_set.add(co4, co1)
# 从课程表添加学生
co2.student.add(s4, s5, s6)
- 查询
多对多的查询还可以使用更多的常用查询,详情可查看《06-常用查询、字段、参数》
print(s1.course_set.all()) # 查询学生s1报名的所有课程
print(co1.student.all()) # 查询课程co1报名的所有学生
print(s1.course_set.filter(co_id__lt=3)) # 查询学生s1学习的所有co_id小于3的课程
print(s3.course_set.filter(co_id__gt=3)) # 查询学生s3学习的所有co_id大于3的课程
......
- 多对多的删除
# 删除
s1.course_set.remove(co1) # 移除课程co1
s1.course_set.clear() # 清空学生s1的所有课程
co3.student.clear() # 清空学习课程co3的所有学生
多表联查
- 双下划线的查询:在django中,提供了一种以双下划线来链接模型的快速查询方法。使用方法简单,只需要基于最终需要查询到的信息的表来不断进行过滤即可。过滤方法中,以表名的全小写的形式代表某个表。
- 查询包含小叶的学院
- 查询计算机学院名字以叶为结尾的学生详情:首先过滤到名字为以小叶为结尾的,再补充过滤条件计算机学院,其中计算机学院是通过表 student 的外键 c_id 进一步使用双下划线访问到 college 表的 college_name 字段值
- 查询土木工程学院学号大于201901的所有学生所报名的课程信息:与上述同理。
# 查询包含小叶的学院
rs = College.objects.filter(student__student_name='小叶')
# 查询计算机学院名字以叶结尾的学生详情信息
rs = StudentDetail.objects.filter(student__student_name__endswith='叶', student__c_id__college_name='计算机')
# 查询土木工程学院学号大于201901的所有学生所报名的课程信息
rs = Course.objects.filter(student__s_id__gt=201901, student__c_id__college_name='土木工程')
print(rs)
END
参考文献
原文标题及链接 | 作者 |
---|---|
数据库的主表,从表,主键,外键等之间的关系 | Yubaba丶 |
Django中related_name作用 | 哀乐之巅写年华 |