表关联对象及多表查询
关系表的数据操作
前面我们在models.py文件中创建了一些关系表 , 接下来我们就来操作这写表里的数据
为了能方便学习,我们进入项目的idle中去执行我们的操作,
通过控制台输入python manage.py shell
就能进入当前目录下的IDLE
导入关系表
from firstapp.models import Student, StudentDetail, Grade, Course, Enroll
设置__str__
通过设置str方法可以使我们在查询某个模型对象的时候, 直观的看到其简要内容, 例如, id和name等:
def __str__(self): return '{}-{}'.format(self.name, self.num)
表关联对象的访问
相互关联的表之间的访问方式分为 正向 和 反向 , 下面我们来详细了解一下
什么是正向or反向?
以ForeignKey
/OneToManyField
/ManyToManyField
这种类型的字段为参照物, 凡是担有这种字段的表 , 通过其来访问这个外键链接的表, 这种方式就是正向访问 , 反过来就是反向访问
OneToMany 正向
正向:一个模型如果有外键字段,通过这个模型对外键进行操作叫做正向(eg:student模型有grade外键字段)
反向:一个模型如果被另一个模型外键关联,通过这个模型对关联它的模型进行操作叫做反向(eg:grade模型被student模型的grade外键关联,通过grade对student进行操作)
增&改
正向的增和改是一样的,
通过属性赋值的方式
In [19]: g1=Grade.objects.get(name='django框架') In [20]: s1 = Student.objects.get(name='Jack') In [21]: s1.grade = g1 In [22]: s1.save() # s1 -> g1 , 将学生Jack(s1)分到django框架班(s2)中, 学生表中的`grade_id`变为了'django框架'的班级id
通过主键的方式
In [24]: g2=Grade.objects.get(name='python爬虫基础') In [26]: s2 = Student.objects.get(name='Jennf') In [28]: s2.grade_id = g2.id In [29]: s2.save() # s2 -> g2 , 与上同理
改的方式是一样的, 通过赋值来完成改操作
In [35]: s2.grade_id = g1.id In [36]: s2.save() # s2 -> g1 , 学生Jennf(s2)被改到django框架(g1)班
查
In [3]: s1.grade # 可以直接获取grade对象, 这里设置了__str__方法所以才这样显示 Out[3]: <Grade: django框架-8> In [4]: s1.grade.name # 也可以通过获取对象的属性,这就涉及到了联合查询 Out[4]: 'django框架' # 联合查询 In [5]: Student.objects.filter(grade__name='django框架') Out[38]: <QuerySet [<Student: Micker>, <Student: Jack>, <Student: Jennf>]> # 外键字段对象的属性可以通过两个下划线来获取
ɾ
In [39]: s1.grade = None In [40]: s1.save()
OneToMany 反向
上面的正向增删改查用到的管理器对象是objects , 而反向则与其不同 , 反向的增删改查用的管理器是 app小写名_set
来操作, 例如, g2.student__set.all()
# 实际访问情况:如果一个模型有外键(eg:student模型),那么这个外键模型的实例(eg:grade模型的实例g2)将可以通过一个管理器(返回的是Student模型的所有实例的管理器),默认情况下管理名为(student_set),其中student是源模型名,小写。
如果觉得student_set
使用麻烦, 也可以用这个related_name='student'
重命名 , 之后就可以用student
来访问了, 如下设置在Student的外键中
grade = models.ForeignKey('Grade', on_delete=models.SET_NULL, null=True, related_name='student')
增
In [2]: g1=Grade.objects.get(name='django框架') In [4]: new_student = g1.student_set.create(name='Fet', age=17, sex=0) # new_student -> g1 , 为django框架(g1)创建一个新的学生Fet
批量增
In [18]: s1,s2,s3 = Student.objects.filter(id__lte=3) In [19]: g1.student_set.add(s1,s2,s3) # 通过add传入多个对象, 即可实现批量增加
改
In [9]: g1.student_set.set([s1,s2]) # g1(s1,s3,s5) -> g1(s1,s2) , 会将django框架(g1)班中的人替换为s1和s2两人
ɾ
In [20]: g1.student_set.remove(s1) # 指定删除 In [21]: g1.student_set.clear() # 全部删除
查
查的方法和普通查询类似 , 这里就不再赘述
In [10]: g1.student_set.all() Out[10]: <QuerySet [<Student: Micker>, <Student: Jack>]> In [12]: g1.student_set.filter(name='Micker') Out[12]: <QuerySet [<Student: Micker>]>
ManyToMany
基于以字段为参照物的准则 , 这里的正向是从Course -> Student , Course模型:
class Course(models.Model): name = models.CharField('课程名', max_length=20) student = models.ManyToManyField("Student", through='Enroll')
Course里面有ManyToManyField
字段,模型在正向访问时就使用该字段本身的属性名student
, 而不是student_set
(这里注意student
是自定义的)
# 创建连接 In [5]: s1 = Student.objects.get(pk=1) In [6]: c1 = Course.objects.create(name='Python基础') In [8]: e = Enroll() In [9]: e.course = c1 In [11]: e.student = s1 In [12]: e.save()
正向查询
In [15]: c1.student.all() Out[15]: <QuerySet [<Student: Micker>]>
反向查询
在反向访问时使用的属性名是, 原始模型的小写模型名加上_set
, 即course_set
In [21]: s1.course_set.all() Out[21]: <QuerySet [<Course: Python基础>]>
OneToOne
正向
通过属性访问
In [17]: d1 = StudentDetail(college = '潭州大学') In [18]: d1.student = s1 In [19]: d1.save() # 这就完成了正向操作
反向
通过原始模型的小写模型名访问
In [10]: d2 = StudentDetail(college='清华大学') In [11]: s2 = Student.objects.get(pk=2) In [12]: s2.studentdetail = d2 In [13]: s2.save()
多表查询
正向查询 当表进行关联后 , 就可以通过 StudentDetail
直接访问到 Student
的内容
In [16]: StudentDetail.objects.values('college', 'student__name') Out[16]: <QuerySet [{'college': '北京大学', 'student__name': 'Jesse'}, {'college': '清华大学', 'student__name': 'Jack'}]>
通过这种双下滑线的方式, 可以获得student中所有的属性
反向查询 同样适用
In [17]: Student.objects.values('name', 'studentdetail__college') Out[17]: <QuerySet [{'name': 'Jesse', 'studentdetail__college': '北京大学'}, {'name': 'Jack', 'studentdetail__college': '清华大学'}]>
使用这种查询可以完成更深的数据挖掘(仅在当前数据库内)
Course.objects.filter(student__sex=1) # 男生报了什么课程 Student.objects.filter(course__name='python') # 报了’python‘的学生 Student.objects.filter(course__name='python',grade__num='8') # 所有报了python,8期的学员 Student.objects.filter(enroll__pay__lt=3000) # 缴费小于3000的学员 Grade.objects.filter(student__course__name = 'python').distinct() # 学员报名Python课程的班级有哪些。distinct()去重