关于本站
1、基于Django+Bootstrap开发
2、主要发表本人的技术原创博客
3、本站于 2015-12-01 开始建站
之前刚开始学习和使用Django时,遇到需要多个表联合一起查询的情况。
碰到这种情况,每次都使用原始SQL语句的方法去实现。使用模型的raw方法执行SQL语句。
该方法唯一的好处是灵活!只要会写SQL语句就可以轻松实现你想要的功能。
缺点也很明显,需要打开数据库,查询具体的表名、字段名等等。而且若模型修改了,又需要重新写一个复杂的SQL语句。
Django的ORM已经帮我们实现这个功能。多看Django文档,还有很多东西等待被挖掘。
如下两个简单模型:
#coding:utf-8 from django.db import models #标签模型 class Tag(models.Model): tag_name = models.CharField(max_length=20) #博客模型 class Blog(models.Model): caption = models.CharField(max_length=50) tags = models.ManyToManyField(Tag, blank=True)
其中,博客模型有个多对多字段tags和Tag模型关联。
一个博客可能有多个标签,一个标签可能对应多个博客。
那么,问题来了。我如何在获取Tag对象列表的时候,同时获取博客的个数呢?
Tag标签模型中没有和相关字段和Blog模型对象。
这种情况很常见,例如我需要显示一个标签列表给访客点击。顺便把该标签中有多少篇文章也显示出来。下面列举有3种方法,你就可以看出优劣。
#coding:utf-8 from models import Blog, Tag def get_tag_blogs_count(): tags = [] for tag in Tag.objects.all(): #tag反向获取blogs,再用count方法获取个数 blogs_count = tag.blog_set.count() #把数量赋给tag临时添加的属性 tag.blogs_count = blogs_count #添加到tags集合中 tags.append(tag) return tags
利用该方法可以获取一个带有blog数量的tags列表。将数据返回给前端页面。
可能你会说,你可以在模版文件直接用{{tag.blog_set.count}}得到数据,无须这么费劲。
那么,再加个需求:我还需要按照博客的个数对Tag标签排序。
这种情况,就无法直接在模版文件中处理。需要在后台代码中排序处理。
#coding:utf-8 from .models import Blog, Tag def get_tag_blogs_count(): tags = [] for tag in Tag.objects.all(): #tag反向获取blogs,再用count方法获取个数 blogs_count = tag.blog_set.count() #把数量赋给tag临时添加的属性 tag.blogs_count = blogs_count #添加到tags集合中 tags.append(tag) #倒序排序处理 tags = sorted(tags, key=lambda x: x.blogs_count, reverse=True) return tags
这段代码,怎么看怎么都觉得执行效率低。
使用原始SQL语句查询,需要打开数据库查看表名和字段名。
和这两个模型相关的表有3个表。因为多对多字段生成了一个blog_blog_tags表。
测试SQL语句没问题之后,在代码中使用。这里排序需求也实现了。
#coding:utf-8 from .models import Tag def get_tag_blogs_count(): sql = """ select blog_tag.*, count(blog_blog_tags.id) as blogs_count from blog_tag left join blog_blog_tags on blog_tag.id = blog_blog_tags.tag_id group by blog_tag.id order by blogs_count desc """ #使用raw执行sql语句 tags = Tag.objects.raw(sql) return tags
该tags中的每个对象都有blogs_count属性。
annotate这个单词本身是注释的意思。可理解为我们可以给该模型加个字段作为注释,如下代码。
#coding:utf-8 from .models import Tag from django.db import models def get_tag_blogs_count(): tags = Tag.objects.annotate(blogs_count=models.Count('blog')).order_by('-blogs_count') return tags
此处的annotate只用了一个参数。用Count方法关联到Blog模型,并计数统计。
同样,tags中的每个对象都多出了一个blogs_count属性。
我们可以如下代码输出SQL语句:
print(str(tags.query))
得到如下SQL语句:
SELECT "blog_tag"."id", "blog_tag"."tag_name", COUNT("blog_blog_tags"."blog_id") AS "blogs_count" FROM "blog_tag" LEFT OUTER JOIN "blog_blog_tags" ON ("blog_tag"."id" = "blog_blog_tags"."tag_id") GROUP BY "blog_tag"."id", "blog_tag"."tag_name" ORDER BY "blogs_count" DESC
发现该SQL和上面第2种方法的SQL差不多,相当的酷!
使用annotate需要用django.db.models的统计方法。除了Count统计方法之外,还有Max、Min、Sum、Avg等方法。这么看来annotate相当于SQL语句的Group by分组统计。
统计方法的参数是一个模型名称小写。该模型一定要和当前模型有关联的字段(一对多外键或多对多外键等)。
可通过 Tag._meta.get_fields() 方法查得有哪些字段。相关知识可以参考外键的QuerySet查询。
那么,annotate也可以对当前模型进行分组统计。
ps,我自己网站的标签之前是默认排序:
按照每个标签对应博客数量降序排序如下:
看起来舒服一些,有些发文量少的分类会慢慢地排到后面。
相关专题: Django QuerySet查询
杨仕航
这里讲的应用是比较基本的。还可以结合Blog.objects.values方法进行表内的分组统计
2017-02-17 14:51 回复