模型层之多表操作
一、创建模型
各模型字段
图书表book:id,title,price,publish_date
作者表author:id,name,age
出版社表publish:id,name,addr,email
作者详情表:id,phone,addr
表之间的关系
图书表和作者表是多对多关系
图书表和出版社表是多对一关系
作者表和作者详情表是一对一关系
class Book(models.Model): # 主键不指定默认创建id字段为主键 title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) publish_date = models.DateField(auto_now_add=True) # auto_now:每次操作改数据都会自动更新时间,auto_now_add:新增数据的时候会将当前时间自动添加,后续的修改该字段不会自动更新 # 外键关系 publish = models.ForeignKey(to='Publish') authors = models.ManyToManyField(to='Author') # 虚拟字段, 信号字段 def __str__(self): return '书籍对象的名字:%s'%self.title class Publish(models.Model): name = models.CharField(max_length=32) addr = models.CharField(max_length=32) email = models.EmailField() # 对应就是varchar类型 def __str__(self): return '出版社对象的名字:%s'%self.name class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() authordetail = models.OneToOneField(to='AuthorDetail') def __str__(self): return '作者对象的名字:%s'%self.name class AuthorDetail(models.Model): phone = models.CharField(max_length=32) addr = models.CharField(max_length=32)
二、多对一(一对一)表关系记录操作
2.1 新增表记录
# 1.直接写数据:publish_id=1 models.Book.objects.create(title='红楼梦',price=66.66,publish_id=1) # 2.传数据对象:publish=publish_obj publish_obj = models.Publish.objects.filter(pk=2).first() # pk指主键 models.Book.objects.create(title='三国演义',price=199.99,publish=publish_obj)
2.2 修改表数据
# 基于对象操作 book_object = models.Book.objects.get(pk = 1) publish_object = models.Book.objects.get(pk = 2) book.publish = publish_object # 或者book.publish_id = 2 book.save() # 基于Queryset操作 publish_object = models.Book.objects.filter(pk = 2).first() book_object = models.Book.objects.filter(pk = 1).update(publish = publish_object) # book_object = models.Book.objects.filter(pk = 1).update(publish_id = 2)
2.3 删除表记录
同单表操作里的删除操作
三、多对多表关系记录操作
3.1 新增表记录
# 数字id,可传多个 表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表1_object.与表2的关联字段.add(数字(id),……) # 对象,可传多个 表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表2_object = models.对应表2的类名.objects.filter(字段名 = 值[查找条件]).first() 表1_object.与表2的关联字段.add(表2_object,……)
3.2 删除表记录
# 数字id,可传多个 表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表1_object.与表2的关联字段.remove(数字(id),……) # 对象,可传多个 表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表2_object = models.对应表2的类名.objects.filter(字段名 = 值[查找条件]).first() 表1_object.与表2的关联字段.remove(表2_object,……)
3.3 清空表记录
表1_object.与表2的关联字段.clear() # 不需要传参数
3.4 修改表记录
本质上就是先清空,再新增
# set:传的必须是可迭代对象(用列表或元祖形式) # 数字id,可传多个 表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表1_object.与表2的关联字段.set([数字(id),……]) # 对象,可传多个 表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表2_object = models.对应表2的类名.objects.filter(字段名 = 值[查找条件]).first() 表1_object.与表2的关联字段.set([表2_object,……])
四、基于对象的跨表查询(SQL的子查询)
正向:关联字段在表内
反向:关联字段不在表内
4.1 一对一
正向:按字段查询
表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表2_object = 表1_object.与表2的关联字段 表2_object.表2需查询字段 # 查询结果
反向:按表名小写查询
表2_object = models.对应表2的类名.objects.filter(字段名 = 值[查找条件]).first() 表1_object = 表2_object.表1名小写 表1_object.表1需查询字段 # 查询结果
4.2 多对一
正向:按字段查询
表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表2_object = 表1_object.与表2的关联字段 表2_object.表2需查询字段 # 查询结果
反向:按表名小写_set.all()查询
表2_object = models.对应表2的类名.objects.filter(字段名 = 值[查找条件]).first() 表1数据 = 表2_object.表1名小写_set.all() # Queryset对象
若只想要拿出查询记录的某一列,因为是一个Queryset对象,Queryset对象可以无限用句点符.查询,所以可以用.values('字段')得到。
4.3 多对多
正向:按字段.all()查询
表1_object = models.对应表1的类名.objects.filter(字段名 = 值[查找条件]).first() 表2数据 = 表1_object.与表2的关联字段.all() # Queryset对象
反向:按表名小写_set.all()查询
表2_object = models.对应表2的类名.objects.filter(字段名 = 值[查找条件]).first() 表1数据 = 表2_object.表1名小写_set.all() # Queryset对象
4.4 连续跨表
栗子
#查询三国演义这本书所有作者的手机号 book_obj = models.Book.objects.filter(name ='三国演义').first() authors = book_obj.authors.all() for author in authors: authordetail = author.authordetail print(authordetail.phone)
五、基于双下划线的跨表查询(SQL的连表查询)
- 确定以哪张表为查询基表来捋清楚正反向,正向按关联字段__查询字段,反向按表名小写__查询字段。
- 确定查询字段是否在基表里,不在的话用双下划线转到目标表。
- 跨表可以在filter,也可以在values里
栗子:查询moonzier作者的性别和手机号
# 正向 result = Author.objects.filter(name = 'moonzier').values('sex','authordetail__phone') # 反向 result = AuthorDetail.objects.filter(author__name = 'moonzier').values('author__sex','phone')
related_name
反向查询时,如果定义了related_name ,则用related_name替换表名小写。
六、聚合查询与分组查询
6.1 聚合(aggregate(*args, **kwargs))
from django.db.models import Avg,Count,Max,Min,Sum # 计算所有图书的平均价格 Book.objects.all().aggregate(Avg('price'))
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。
from django.db.models import Avg,Count,Max,Min,Sum Book.objects.aggregate(average_price=Avg('price'), Max('price'), Min('price'))
6.2 分组(annotate())
annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。以谁为分组(group by)就以谁为基表。跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询
栗子
from django.db.models import Avg,Count,Max,Min,Sum # 统计每本书的作者数 # 法一 ret= Book.object.all().annotate(num = Count('authors')) # annotate的返回值是querySet for result in ret: print(result.name,result.num) # 法二 result= Book.object.all().annotate(num = Count('authors')).values('name','num') print(result)
方法总结
- values在前,表示 group by;values在后,表示取值。
- filter在前,表示 where条件;filter在后,表示having(分组之后的条件筛选)。
from django.db.models import Avg,Count,Max,Min,Sum # 统计以三开头的书籍的作者数 result = Book.object.all().filter(name__startwith='三').annotate(num=Count('authors')).values('name','num') # 套用方法总结写法 result = Book.object.all().values('pk').filter(name__startwith='三').annotate(num=Count('authors')).values('name','num')
# 前面的values不写,默认以id,group by,可指定字段
七、F查询与Q查询
7.1 F查询
以往我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
# 查询评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commentNum__gt=F('keepNum'))
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
# 查询评论数大于收藏数2倍的书籍 Book.objects.filter(commnetNum__gt=F('keepNum')*2) # 将每一本书的价格提高30元: Book.objects.all().update(price=F("price")+30)
F对象的字符串操作
# 所有书名字后面加上'限时特价' from django.db.models.functions import Concat from django.db.models import Value models.Book.objects.all().update(name=Concat(F("name")+Value('限时特价')))
7.2 Q查询
filter() 等方法中的关键字参数查询条件都是“AND” 。 如果你需要执行更复杂的查询(例如OR或者NOT),你可以使用Q对象。
from django.db.models import Q # 与:筛选价格等于88并且出版社为东京出版社的书 Book.objects.filter(Q(price=88) ,Q(publisher='东京出版社')) # 或(|):筛选价格等于88或者出版社为东京出版社的书 Book.objects.filter(Q(price=88) | Q(publisher='东京出版社')) # 非(~):筛选价格不等于88或者出版社为东京出版社的书 Book.objects.filter(~Q(price=88) | Q(publisher='东京出版社'))
查询函数可以混合使用Q对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。
Q对象查询字段是字符串
from django.db.models import F, Q q = Q() q.connector = 'or' # 通过这个参数可以将Q对象默认的and关系变成or q.children.append(('price',88.88)) q.children.append(('name','西游记')) res = models.Product.objects.filter(q) # Q对象查询默认也是and print(res)