目录
方式二、获取外键表对象id,给类定义的关系变量,赋值查询到的对象
六、聚合查询(aggregate() + 聚合函数(Avg,Max,Min…))
七、分组查询(annotate() + 聚合函数(Avg,Max,Min…))
- filter、annotate、values的使用顺序,及不同
八、F查询(对同一个model实例中的两个不同字段进行比较)
- models新增(book标下的阅读数字段和评论数字段)
一、建立多表关系结构
总结:
- 一对一关系:OneToOneField
- 一对多关系:ForeignKey
- 多对多关系(自动生成中间表):ManyToManyField
- 用了OneToOneField和ForeignKey,模型表的外键字段,默认末尾添加_id
- 表的名字 myapp_modelName 自动生成,但是可以覆写为其他名字。
- 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。
- 主键字段使用primary_key,调用时可以使用简写‘pk’
from django.db import models # 出版社数据表 class Publish(models.Model): # id如果不写,会自动生成,名字叫nid,并且自增 id = models.AutoField(primary_key=True) name = models.CharField(max_length=32) addr = models.CharField(max_length=64) email = models.EmailField() # 作者表,与作者详细信息表建立一对一的关系(OneToOneField) class Author(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=32) # 数字类型 sex = models.IntegerField() # 可以用 ForeignKey(如下),但是得设置唯一性约束,会报警告,不建议用,建议用OneToOneField # authordetail= models.ForeignKey(unique=True) # to='AuthorDetail' 使用引号,表存在即可;不适用引号,数据表类必须在此类之前定义,才能被找到 # 与表 AuthorDetail 的 id字段建立一对一的关系 authordetail = models.OneToOneField(to='AuthorDetail', to_field='id') def __str__(self): return self.name # 作者信息详细表,被作者表建立一对一的关系 class AuthorDetail(models.Model): id = models.AutoField(primary_key=True) phone = models.CharField(max_length=32) addr = models.CharField(max_length=64) # 书籍表,和作者表建立多对多的关系(ManyToManyField) - 自动生成中间表 book_authors # 与出版社表建立一对多关系(ForeignKey) class Book(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=32) price = models.DecimalField(max_digits=5, decimal_places=2) # 一对多 publish = models.ForeignKey(to=Publish, to_field='id') # 多对多; to 后不加引号,指引类必须在此类前定义 authors = models.ManyToManyField(to=Author) # 重写此类被print的输出内容 def __str__(self): return self.name
二、一对多 and 一对一 表关系的记录操作
注意:一对多和一对一的数据记录操作方式相同
1、 增(两种方式)
方式一、create方式创建,指定字段名传输数据
# creat创建数据,将值传输给表内字段名publish_id ret=Book.objects.create(name='红楼梦',price=34.5,publish_id=1)
方式二、获取外键表对象id,给类定义的关系变量,赋值查询到的对象
# 获取出版社表,id为1的对象 publish=Publish.objects.get(id=1) # 同上获取 出版社表对象 pk是主键,通过主键查找 publish=Publish.objects.get(pk=1) # 获取出版社表对象,主键id=2的第一条数据对象,因为filter返回queryset对象 publish = Publish.objects.filter(pk=2).first() # creat对象,将获取的出版社表对象赋予 book类内提前建立表关系的变量名 # publish = models.ForeignKey(to=Publish, to_field='id') ret = Book.objects.create(name='西游记', price=34.5, publish=publish) print(ret.name)
2、改(两种方式)
方式一、获取对象,修改后save方法保存
# 获取book对象 book=Book.objects.get(pk=1) # book.publish=出版社对象 --- 同下 book.publish_id=2 book.save()
方式二、update方法更新
book=Book.objects.filter(pk=1).update(publish = 出版社对象) book=Book.objects.filter(pk=1).update(publish_id = 1)
三、多对多 表关系的记录操作
1、增 add()
# 为红楼梦这本书新增一个叫n1,n2的作者 # 获取author表内的数据对象 n1 对应id = 1,n2 对应id =2 n1=Author.objects.filter(name='n1').first() n2=Author.objects.filter(name='n2').first() # 获取书籍对象 book=Book.objects.filter(name='红楼梦').first() # add 添加单个对象 # book类.authore变量(建立多对多联系的变量).add(对象1) book.authors.add(n1) # add 添加多个对象 # book类.authore变量(建立多对多联系的变量).add(对象1,对象2) book.authors.add(n1,n2) # add 添加作者id # book类.authore变量(建立多对多联系的变量).add(准确内容) book.authors.add(1,2)
2、删除 remove()
#可以传对象,可以传id,可以传多值(,分割),不建议混用 book.authors.remove(n1) book.authors.remove(2) book.authors.remove(1,2)
3、清空 clear()
book.authors.clear()
4、先清空后创建 set() -- 必须传值列表
# set,先清空,再新增 # 必须传一个列表 -- 列表内数据 -- [id, 对象,] book.authors.set([n1,]) # 不允许*[n1,]进行传值 # 打散了传过去了,相当于book.authors.set(n1) book.authors.set(*[lqz,])
四、基于对象的跨表查询
总结:
- 表A类内-关联a 建立与 表B-id字段的关系
- 正向查询:通过表A查询表B内字段内容
- 反向查询:通过表B查询表A内字段内容
- 基于对象的查询,是子查询也就是多次查询
1、一对一的跨表查询
1)正向查询(类内联系变量名查询)
# 查询n1作者的手机号 # 获取指定作者对象 author=Author.objects.filter(name='n1').first() # author.authordetail -- 上方指定对象.类内联系变量名 -- 作者表对应详细信息表的对象 # author类内的authordetail联系变量名 authordetail=author.authordetail # 通过对象获取对象内字段信息 print(authordetail.phone)
2)反向查询(表名小写查询)
# 查询地址是 :山东 的作者名字 # 获取AuthorDetail详情表内的指定对象 authordetail=AuthorDetail.objects.filter(addr='山东').first() # authordetail.author -- 上方指定对象.关联表名小写 -- 获取关联作者对象 author=authordetail.author print(author.name)
2、一对多的跨表查询
1)正向查询(类内联系变量名查询)
# 查询红楼梦这本书的出版社邮箱 # 获取指定书籍对象 book=Book.objects.filter(name='红楼梦').first() # book.publish -- 上方获取指定对象.类内联系名 --就是出版社对象 pulish=book.publish # 出版社对象.字段名 print(pulish.email)
2)反向查询(表名小写_set.all())
# 查询地址是北京 的出版社出版的图书 # 获取指定地址对象 publish=Publish.objects.filter(addr='北京').first() # publish.book_set.all() 拿出所有的图书 # 获取指定对象 建立联系的表内容 books=publish.book_set.all() # 统计一下条数 books=publish.book_set.all().count() print(books)
3、多对多的跨表查询
1)正向查询(类内联系变量名.all())
# 查询红楼梦这本书所有的作者 # 获取指定书本的对象 book=Book.objects.filter(name='红楼梦').first() # 获取指定对象联系表的所有数据 指定对象.类内联系变量名 book.authors.all() #是所有的作者,是一个queryset对象,可以继续点 print(book.authors.all())
2)反向查询(表名小写_set.all())
# 查询n1写的所有书 # 获取n1对象 n1=Author.objects.filter(name='n1').first() # n1.联系表名小写_set.all() 获取对应联系表内数据 books=n1.book_set.all() print(books)
4、连续跨表查询
# 查询红楼梦这本书所有的作者的手机号 # 获取对象 book=Book.objects.filter(name='红楼梦').first() # 获取所有联系表对象 authors=book.authors.all() # 循环取联系表对象 for author in authors: # 获取联系表对象对应详细信息表对象 authordetail=author.authordetail print(authordetail.phone) ''' book表 -- authors表 -- authordetail表 '''
五、基于双下划线的查询:联系表__联系表内字段名
总结:
- 三者表联系,对于基于上下划线的查询本质不变:跨表使用下划线
- 从纯生sql代码来看,只是from的表不同
1、一对一的查询
# 查询n1的手机号(一个作者对应一个具体信息) # 正向查询(作者表对象,跨表字段) ret=Author.objects.filter(name="n1").values("authordetail__telephone") # 反向查询(作者详细信息跨表对象,本地字段) ret=AuthorDetail.objects.filter(author__name="n1").values("telephone")
2、一对多的查询
# 查询出版社为北京出版社出版的所有图书的名字,价格(一个出版社对应多个图书信息) # 正向 (出版社表对象跨表字段) ret=Publish.objects.filter(name='北京出版社').values('book__name','book__price') print(ret) # 反向 (图书表跨表对象的本地字段) ret=Book.objects.filter(publish__name='北京出版社').values('name','price') print(ret) # 查询北京出版社出版的价格大于19的书 (一个出版社对应多个图书信息) # 正向(出版社表对象的跨表字段) ret=Publish.objects.filter(name='北京出版社',book__price__gt=19).values('book__name','book__price') print(ret)
3、多对多的查询
# 查询红楼梦的所有作者名字 (一本图书可能对应多个作者) # 正向(图书表对象,跨表字段) ret=Book.objects.filter(name='红楼梦').values('authors__name') print(ret) # 反向(作者表跨表对象,本地字段) ret=Author.objects.filter(book__name='红楼梦').values('name') print(ret) # 查询图书价格大于30的所有作者名字(一本图书对应多个作者) # 正向(图书表对象,跨表字段) ret=Book.objects.filter(price__gt=30).values('authors__name') print(ret)
4、连续跨表查询
# 基于双下划綫的 “连续跨表” # 查询北京出版社出版过的所有书籍的名字以及作者的姓名 # 出版社表 - 书籍表 - 作者表 # 正向(出版社表对象,书本跨表字段,书本下作者跨表字段) ret=Publish.objects.filter(name='北京出版社').values('book__name','book__authors__name') print(ret) # 反向(书本表跨表出版社对象,本地书名字段,跨表作者表字段) ret=Book.objects.filter(publish__name='北京出版社').values('name','authors__name') print(ret) # 手机号以151开头的作者出版过的所有书籍名称以及出版社名称 # 作者详细信息表(手机号) - 作者表 - 书籍表(书名) - 出版社表(出版社表) # 正向(作者信息表对象,作者表-书籍表字段,作者表-书籍表-出版社表字段) ret=AuthorDetail.objects.filter(phone__startswith='13').values('author__book__name','author__book__publish__name') print(ret) # 反向(书籍表跨表 作者表-详细信息表 对象,本地字段,跨表出版社字段) ret=Book.objects.filter(authors__authordetail__phone__startswith='13').values('name','publish__name') print(ret)
六、聚合查询(aggregate() + 聚合函数(Avg,Max,Min…))
总结:
- aggregate():QuerySet 的一个终止子句,它返回一个包含一些键值对的字典。
- 键的名称是聚合值的标识符(自动生成或者手动命名),值是计算出来的聚合值。
- key(自动生成):‘字段名_聚合函数名’
- value:聚合函数计算返回值
# 导入所需 聚合函数 from django.db.models import Avg,Count,Max,Min,Sum # 计算所有图书的平均价格 ret=Book.objects.all().aggregate(Avg('price')) print(ret) # {'price__avg': 202.896} # 计算图书的最高价格 (自定义命名key) ret=Book.objects.all().aggregate(my_max = Max('price')) print(ret) # {'my_max': Decimal('99.00')} # 计算图书的最高价格,最低价格,平均价格,总价 ret=Book.objects.all().aggregate(Max('price'),Min('price'),Avg('price'),Sum('price')) print(ret) # {'price__max':Decimal('81.20'),'price__min':Decimal('12.99'),'price__avg':34.35,'price__sum': Decimal('121.00'),}
七、分组查询(annotate() + 聚合函数(Avg,Max,Min…))
总结:
annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。
# 统计每一本书作者个数 ret=Book.objects.all().annotate(c=Count('authors')) print(ret) # 返回的是一个queryset对象 # <QuerySet [<Book:红楼梦>,<Book:西游记>,<Book:水浒传>]> # 循环取出列表内对象 for r in ret: print(r.name,'-->',r.c) # 红楼梦--> 1 # 红楼梦--> 2 # 红楼梦--> 3 ret=Book.objects.all().annotate(c=Count('authors')).values('name','c') print(ret) # 返回queryset对象 # <QuerySet [{'name':红楼梦,'c':1},{'name':西游记,'c':2},{'name':水浒传,'c':3}]> # 统计每一个出版社的最便宜的书(以谁group by分组 就以谁为基表) # 出版社表跨表对象(双下划线的使用) ret=Publish.objects.all().annotate(m=Min('book__price')).values('name','m') print(ret) # <QuerySet [{'name':北京出版社,'m':Decimal('1.00')},]> ret=Publish.objects.all().annotate(m=Min('book__price')).values('name','book__name','m') print(ret) # <QuerySet [{'name':北京出版社,'book_name':'红楼梦','m':Decimal('1.00')},]>
- filter、annotate、values的使用顺序,及不同
总结:
- filter:在前表示where条件删选,在后表示having
- values:在前表示group by分组,在后表示取对应字段的值
- annotate:取分组
# 统计每一本以py开头的书籍的、作者个数 # filter:where筛选,annotate:join,values:取值 ret1=Book.objects.all().filter(name__startswith='py').annotate(c=Count('authors')).values('name','c') print(ret1) # <QuerySet [{'name':'python','c':2}]> # 统计每一本以py开头的书籍的作者个数--套用模板 # valuse:group by分组,fitter:having限定条件,annotate:join,values:取值 ret2=Book.objects.all().values('name').filter(name__startswith='py').annotate(c=Count('authors')).values('name','c') print(ret2) # <QuerySet [{'name':'python','c':2}]> # 查询各个作者出的书的总价格 ret=Author.objects.all().values('name').annotate(s=Sum('book__price')).values('name','s') ret=Author.objects.all().annotate(s=Sum('book__price')).values('name','s') print(ret) # <QuerySet [{'name':'n1','s':Decimal('123')}]> # 查询名字叫n1作者书的总价格 ,pk同主键 ret=Author.objects.all().values('pk').filter(name='n1').annotate(s=Sum('book__price')).values('name','s') print(ret) # 查询所有作者写的书的总价格大于30 ret=Author.objects.all().values('pk').annotate(s=Sum('book__price')).filter(s__gt=2).values('name','s') ret=Author.objects.all().annotate(s=Sum('book__price')).filter(s__gt=30).values('name','s') print(ret) # 统计不止一个作者的图书 ret = Book.objects.all().values('pk').annotate(c=Count('authors')).filter(c__gt=1).values('name', 'c') # ret = Book.objects.annotate(author_num=Count("authors")).filter(author_num__gt=1).values('name','author_num') print(ret)
八、F查询(对同一个model实例中的两个不同字段进行比较)
总结:
- F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
- 支持F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作
- 支持F()对字段值的修改操作
- models新增(book标下的阅读数字段和评论数字段)
class Book(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=32,db_index=True) price = models.DecimalField(max_digits=5, decimal_places=2) # 阅读数 reat_num=models.IntegerField(default=0) # 评论数 commit_num=models.IntegerField(default=0) publish = models.ForeignKey(to=Publish, to_field='id',on_delete=models.CASCADE) authors = models.ManyToManyField(to=Author) # test=models.PositiveSmallIntegerField() def __str__(self): return self.name
- 同一个model实力类中的字段比较
from django.db.models import F # 查询评论数大于阅读数的书 # 错误方式 # ret = Book.objects.all().filter(commit_num__gt=reat_num) # 正确方式 评论数 >(gt) 阅读数 ret = Book.objects.all().filter(commit_num__gt=F('reat_num')) print(ret)
- 字段值的修改(加减乘除取模等操作)
from django.db.models import F # 把所有书的评论数加1 # 错误方式1 # ret = Book.objects.all().update(commit_num += 1) # 错误方式2 # ret = Book.objects.all().update(commit_num=commit_num + 1) # 正确方式 ret = Book.objects.all().update(commit_num=F('commit_num') + 1) print(ret) # 把python这本书的阅读数减5 ret = Book.objects.all().filter(name='python').update(reat_num=F('reat_num') - 5) print(ret)
九、Q查询(操作复杂的逻辑运算(与或非))
总结:
- and (&):Q(字段名1=‘查询名1’) & Q(字段名1=‘查询名1’)
- or(|):Q(字段名1=‘查询名1’) | Q(字段名1=‘查询名1’)
- not(~):Q(字段名1=‘查询名1’) ~ Q(字段名1=‘查询名1’)
- 当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
- 可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。
from django.db.models import Q # 查询作者名字是n1或者名字是n2的书 # 错误方式1(使用的是and) # ret=Book.objects.all().filter(authors__name='n1',authors__name='n2') # 错误方式 同上 # ret = Book.objects.all().filter(Q(authors__name='n1') & Q(authors__name='n2')) # print(ret) # 正确方式 ret=Book.objects.all().filter(Q(authors__name='n1')|Q(authors__name='n2')) print(ret) # 查询作者不是n1的书(~ 非) ret=Book.objects.filter(~Q(authors__name='n1')) print(ret) # 构建很复杂的逻辑,需要用括号来区分优先级 ret = Book.objects.filter((Q(name='红楼梦') & Q(price__gt=100)) | Q(pk__gt=2)) print(ret)