Django——模型基础(多表)
实际应用中,数据之间的关系往往都比较复杂,多表之间的约束关系为我们提供了数据管理以及查询的便利。来简单看下django中如何通过调用相应的API来实现多表的查询功能。
1.模型建立
简单描述下本模型的关系:
- 一个用户拥有一个博客:一对一
- 一个用户可以写多篇博文:一对多
- 一个博客下有多个标签:一对多
- 一个标签下可以有多篇文章,一篇文章可以添加多个标签:多对多
模型:
class User(models.Model): """用户表""" username = models.CharField(max_length=32) password = models.CharField(max_length=64) at_blog = models.OneToOneField(to='Blog',on_delete=models.CASCADE) class Blog(models.Model): """博客表""" title = models.CharField(max_length=32) class Tag(models.Model): """标签表""" title = models.CharField(max_length=32, default='') in_blog = models.ForeignKey(to='Blog',on_delete=models.CASCADE) class Article(models.Model): """博文表""" by_user = models.ForeignKey( to='User', on_delete=models.CASCADE) title = models.CharField(max_length=100) content = models.TextField( default='') create_time = models.DateTimeField( default=timezone.now) for_tag = models.ManyToManyField(Tag)
2.添加数据
2.1一对多
以Tag表为例:
# 方式一: blog_obj = models.Blog.objects.get(id=2) models.Tag.objects.create(title="名著品鉴",in_blog=blog_obj) # 方式二: models.Tag.objects.create(title="名著品鉴", in_blog_id=2)
2.2多对多
# 新建文章对象 art_obj = models.Article.objects.create(by_user_id=2,title="论金屏梅的艺术价值",content="未完待续...") # 为文章添加的标签 tag1 = models.Tag.objects.filter(title='名著品鉴').first() tag2 = models.Tag.objects.filter(title='文学艺术').first() # 绑定多对多关系,向文章中添加标签 art_obj.for_tag.add(tag1,tag2) #将某些 model 对象添加到被关联对象集合中。
所以art_obj.objects.all()就是一个该文章包含所有标签对象的QuerySet对象
多对多关系其它常用API:
art_obj.for_tag.remove() # 将某个特定的对象从被关联对象集合中去除。 ====== art_obj.tag.remove(*[]) art_obj.for_tag.clear() #清空被关联对象集合 art_obj.for_tag.set() #先清空再设置
3.基于对象的跨表查询
3.1一对多(Blog和Tag)
1.正向查询(按字段:in_blog)
查询方法:直接通过圆点加属性,访问外键对象:
tag_obj = models.Tag.objects.get(id=1) b = tag_obj.in_blog # b即为tag_obj关联的Blog对象
要注意的是,对外键的修改,必须调用save方法进行保存:
tag_obj = models.Tag.objects.get(id=1) anther_blog = models.Blog.objects.get(id=3) tag_obj.in_blog = anther_blog tag_obj.save() # 修改后要保存
2.反向查询(按小写表名:tag)
查询方法:通过一个管理器进行反向查询,返回源模型的所有实例。默认情况下,这个管理器的名字为FOO_set,其中FOO是要查找模型的小写名称(本例为Tag的小写)。该管理器返回的查询集还可以进行过滤和操作:
blog_obj = models.Blog.objects.get(id=2) t = blog_obj.tag_set.all() # 返回所有Tag对象(与Blog表中id=2关联的对象)
还可以在ForeignKey字段的定义中,通过设置related_name来重写FOO_set的名字,例如:把Tag表的in_blog字段修改为:in_blog = models.ForeignKey( to='Blog', on_delete='models.CASCADE', related_name='blogs'),那么上面的查询将变成:
obj2 = models.Blog.objects.get(id=2) t = obj2.blogs.all()
3.2一对一(User和Blog)
1.正向查询(按字段:at_blog)
user_obj = models.User.objects.filter(name="Alex").first() b = user_obj.at_blog # 返回与user_obj关联的Blog对象
2.反向查询(按小写表名:user)
blog_obj = models.Blog.objects.filter(title="无情的帅比").first() u = blog_obj.user # 返回与blog_obj关联的User对象
3.3多对多(Tag和Article)
1.正向查询(按字段:for_tag)
art_obj = models.Article.objects.filter(title="灯草和尚的前世今生").first() tag_list = art_obj.for_tag.all() # 与art_obj有关的所有标签 for tag_obj in tag_list: print(tag_obj.title)
2.反向查询(按小写表名:article)
tag_obj = models.Tag.objects.filter(title="时尚").first() article_list = tag_obj.article_set.all() # 所有与tag_obj有关的Article对象 for article_obj in article_list: print(article_obj.title)
在ManyToMany字段中也可以通过设置related_name来重写FOO_set的名字,与ForeignKey类似。
3.基于双下划线的表查询
Django提供了强大并且直观的方式解决跨越关联的查询,它在后台自动执行包含JOIN的SQL语句。要跨越某个关联,只需使用关联的模型字段名称,并使用双下划线分隔,直至你想要的字段(可以链式跨越,无限跨度)。
一对多查询
一、正向查询:字段名 # 查询所有username为Eric的Article对象 results = models.Article.objects.filter(by_user__username="Eric").values_list('title','create_time') 二、反向查询:小写表名 # 查询所有在2016年创建文章的User对象 results = models.User.objects.filter(article__create_time__year='2016').values_list('pk','username')
多对多查询
# 和一对多类似 一、正向查询:字段名 # 查询标签“四大名著”下的所有文章的题目 models.Article.objects.filter(for_tag__title="四大名著").values_list('by_user','title') 二、反向查询:小写表名 # 查询文章“社会主义好”的所有标签的名字 models.Tag.objects.filter(article__title="社会主义好").values_list('pk','title')
4.聚合函数和分组查询
聚合 aggregate(*args, **kwargs),聚合函数是SQL基本函数,聚合函数对一组值执行计算,并返回单个值。除了 COUNT 以外,聚合函数都会忽略空值。 常见的聚合函数有AVG / COUNT / MAX / MIN /SUM 等。
Django的django.db.models
模块提供一些聚合函数。
# 计算所有图书的平均价格 >>> from django.db.models import Avg >>> Book.objects.all().aggregate(Avg('price')) {'price__avg': 34.35}
aggregate()是QuerySet 的一个终止子句,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
>>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35}
如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
>>> from django.db.models import Avg, Max, Min >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
通过上面的介绍,我们可以知道,aggregate的逻辑比较简单,应用场景比较窄,如果你想要对数据进行分组(GROUP BY)后再聚合的操作,则需要使用annotate来实现。
分组annotate
为调用的QuerySet中每一个对象都生成一个独立的统计值
# 统计每个标签下有几篇文章 from django.db.models import Count tag_list = Tag.objects.all().annotate(artnums=Count('article')) for num in tag_list: print(num.artnums)
更多例子
# 统计标签下文章数量大于1的标签个数 res=Tag.objects.annotate(num_article=Count('article')).filter(num_article__gt=1) print(res) # 根据文章标签的数量多少进行排序 res=Article.objects.annotate(numtag=Count('for_tag')).order_by('numtag') print(res) # 统计标签名字中含有“文”字的有多少 res=Tag.objects.filter(title__contains=('文')).annotate(num=Count('title')) print(res) #统计每个作者有几篇文章 res=User.objects.annotate(num=Count('article__title')).values('num') print(res)
5.F查询和Q查询
F查询
到目前为止的例子中,我们都是将模型字段与常量进行比较。但是,如果你想将模型的一个字段与同一个模型的另外一个字段进行比较该怎么办?
使用Django提供的F表达式!
如我们要统计评论数大于收藏数的文章:
from django.db.models import F models.Article.objects.filter(comment_nums__gt=F('keep_nums'))
Django支持对F()对象进行加、减、乘、除、取模以及幂运算等算术操作。两个操作数可以是常数和其它F()对象。还可以在F()中使用双下划线来进行跨表查询。
# 统计点赞数大于评论数2被的文章 models.Article.objects.filter(praise_num__gt=F('comment_num')*2) # 统计用户名和博客标题一样的用户 models.User.objects.filter(username=F('at_blog__title'))
Q查询
普通filter函数里的条件都是“and”逻辑,如果你想实现“or”逻辑怎么办?用Q查询!
from django.db.models import Q Q(question__startswith='What')
可以使用“&”或者“|”或“~”来组合Q对象,分别表示与或非逻辑。它将返回一个新的Q对象。
models.Article.objects.filter(Q(by_user__username='Alex')|Q(by_user__username='Eric'))
当关键字参数和Q对象组合使用时,Q对象必须放在前面,如下例子:
Article.objects.get( Q(create_time=date(2016, 5, 2)) | Q(create_time=date(2016, 5, 6)),title__startswith='Who',)