Django QuerySet 进阶
QuerySet 进阶
阅读本文你可以学习到什么
1. 查看 Django queryset 执行的 SQL(1部分)
2. 获得的查询结果直接以类似list方式展示(2,3 部分)
3. 如何在django中给一个字段取一个别名(4. 部分)
4. annotate 聚合 计数,求和,求平均数等(5. 部分)
5. 优化SQL,减少多对一,一对多,多对多时查询次数(6,7 部分)
6. 如何只取出需要的字段,排除某些字段(8,9部分)
7. 自定义一个自定义聚合功能,比如 group_concat(10. 部分)
本节属于进阶教程,初学者可以跳过这节,如果你觉得看不懂,也可以先跳过,后面再学。
准备阶段,可以直接在下面下载代码开始练习,建议看一下 models.py 的代码。
新建一个项目 zqxt ,建一个 app 名称是 blog
1
2
|
django-admin startproject zqxt python manage.py startapp blog |
把 blog 加入到 settings.py 中的 INSTALL_APPS 中
blog/models.py 代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2016-11-26 18:54:49 # @Author : Weizhong Tu (mail@tuweizhong.com) # @Link : http://www.tuweizhong.com # @Version : 0.0.1 from __future__ import unicode_literals from django.db import models from django.utils.encoding import python_2_unicode_compatible @python_2_unicode_compatible class Author(models.Model): name = models.CharField(max_length = 50 ) qq = models.CharField(max_length = 10 ) addr = models.TextField() email = models.EmailField() def __str__( self ): return self .name @python_2_unicode_compatible class Article(models.Model): title = models.CharField(max_length = 50 ) author = models.ForeignKey(Author) content = models.TextField() score = models.IntegerField() # 文章的打分 tags = models.ManyToManyField( 'Tag' ) def __str__( self ): return self .title @python_2_unicode_compatible class Tag(models.Model): name = models.CharField(max_length = 50 ) def __str__( self ): return self .name |
比较简单,假设一篇文章只有一个作者(Author),一个作者可以有多篇文章(Article),一篇文章可以有多个标签(Tag)。
创建 migrations 然后 migrate 在数据库中生成相应的表
1
2
|
python manage.py makemigrations python manage.py migrate |
tu@pro ~/zqxt $ python manage.py makemigrations
No changes detected
tu@pro ~/zqxt $ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying blog.0001_initial... OK
Applying sessions.0001_initial... OK
生成一些示例数据,运行 initdb.py(有疑问的可以参考 数据导入)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2016-11-26 19:10:14 # @Author : Weizhong Tu (mail@tuweizhong.com) # @Link : http://www.tuweizhong.com # @Version : 0.0.1 from __future__ import unicode_literals import random from zqxt.wsgi import * from blog.models import Author, Article, Tag author_name_list = [ 'WeizhongTu' , 'twz915' , 'dachui' , 'zhe' , 'zhen' ] article_title_list = [ 'Django 教程' , 'Python 教程' , 'HTML 教程' ] def create_authors(): for author_name in author_name_list: author, created = Author.objects.get_or_create(name = author_name) # 随机生成9位数的QQ, author.qq = ''.join( str (random.choice( range ( 10 ))) for _ in range ( 9 ) ) author.addr = 'addr_%s' % (random.randrange( 1 , 3 )) author.email = '%s@ziqiangxuetang.com' % (author.addr) author.save() def create_articles_and_tags(): # 随机生成文章 for article_title in article_title_list: # 从文章标题中得到 tag tag_name = article_title.split( ' ' , 1 )[ 0 ] tag, created = Tag.objects.get_or_create(name = tag_name) random_author = random.choice(Author.objects. all ()) for i in range ( 1 , 21 ): title = '%s_%s' % (article_title, i) article, created = Article.objects.get_or_create( title = title, defaults = { 'author' : random_author, # 随机分配作者 'content' : '%s 正文' % title, 'score' : random.randrange( 70 , 101 ), # 随机给文章一个打分 } ) article.tags.add(tag) def main(): create_authors() create_articles_and_tags() if __name__ = = '__main__' : main() print ( "Done!" ) |
tu@pro ~/zqxt $ python initdb.py
Done!
导入数据后,我们确认一下数据是不是已经导入。
tu@pro ~/zqxt $ python manage.py shell
In [1]: from blog.models import Article, Author, Tag
In [2]: Article.objects.all()
Out[2]: <QuerySet [<Article: Django 教程_1>, <Article: Django 教程_2>, <Article: Django 教程_3>, <Article: Django 教程_4>, <Article: Django 教程_5>, <Article: Django 教程_6>, <Article: Django 教程_7>, <Article: Django 教程_8>, <Article: Django 教程_9>, <Article: Django 教程_10>, <Article: Django 教程_11>, <Article: Django 教程_12>, <Article: Django 教程_13>, <Article: Django 教程_14>, <Article: Django 教程_15>, <Article: Django 教程_16>, <Article: Django 教程_17>, <Article: Django 教程_18>, <Article: Django 教程_19>, <Article: Django 教程_20>, '...(remaining elements truncated)...']>
In [3]: Author.objects.all()
Out[3]: <QuerySet [<Author: WeizhongTu>, <Author: twz915>, <Author: wangdachui>, <Author: xiaoming>]>
In [4]: Tag.objects.all()
Out[4]: <QuerySet [<Tag: Django>, <Tag: Python>, <Tag: HTML>]>
准备阶段结束,代码下载:zqxt_queryset_advance.zip
我们开始正式本节的学习,学习一些比较高级的查询方法
1. 查看 Django queryset 执行的 SQL
In [1]: print str(Author.objects.all().query)
SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author"
简化一下,就是:SELECT id, name, qq, addr, email FROM blog_author;
In [2]: print str(Author.objects.filter(name="WeizhongTu").query)
SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" WHERE "blog_author"."name" = WeizhongTu
简化一下,就是:SELECT id, name, qq, addr, email FROM blog_author WHERE name=WeizhongTu;
所以,当不知道Django做了什么时,你可以把执行的 SQL 打出来看看,也可以借助 django-debug-toolbar 等工具在页面上看到访问当前页面执行了哪些SQL,耗时等。
还有一种办法就是修改一下 log 的设置,后面会讲到。
2. values_list 获取元组形式结果
2.1 比如我们要获取作者的 name 和 qq
In [6]: authors = Author.objects.values_list('name', 'qq')
In [7]: authors
Out[7]: <QuerySet [(u'WeizhongTu', u'336643078'), (u'twz915', u'915792575'), (u'wangdachui', u'353506297'), (u'xiaoming', u'004466315')]>
In [8]: list(authors)
Out[8]:
[(u'WeizhongTu', u'336643078'),
(u'twz915', u'915792575'),
(u'wangdachui', u'353506297'),
(u'xiaoming', u'004466315')]
如果只需要 1 个字段,可以指定 flat=True
In [9]: Author.objects.values_list('name', flat=True)
Out[9]: <QuerySet [u'WeizhongTu', u'twz915', u'wangdachui', u'xiaoming']>
In [10]: list(Author.objects.values_list('name', flat=True))
Out[10]: [u'WeizhongTu', u'twz915', u'wangdachui', u'xiaoming']
2.2 查询 twz915 这个人的文章标题
In [11]: Article.objects.filter(author__name='twz915').values_list('title', flat=True)
Out[11]: <QuerySet [u'HTML \u6559\u7a0b_1', u'HTML \u6559\u7a0b_2', u'HTML \u6559\u7a0b_3', u'HTML \u6559\u7a0b_4', u'HTML \u6559\u7a0b_5', u'HTML \u6559\u7a0b_6', u'HTML \u6559\u7a0b_7', u'HTML \u6559\u7a0b_8', u'HTML \u6559\u7a0b_9', u'HTML \u6559\u7a0b_10', u'HTML \u6559\u7a0b_11', u'HTML \u6559\u7a0b_12', u'HTML \u6559\u7a0b_13', u'HTML \u6559\u7a0b_14', u'HTML \u6559\u7a0b_15', u'HTML \u6559\u7a0b_16', u'HTML \u6559\u7a0b_17', u'HTML \u6559\u7a0b_18', u'HTML \u6559\u7a0b_19', u'HTML \u6559\u7a0b_20']>
3. values 获取字典形式的结果
3.1 比如我们要获取作者的 name 和 qq
In [13]: Author.objects.values('name', 'qq')
Out[13]: <QuerySet [{'qq': u'336643078', 'name': u'WeizhongTu'}, {'qq': u'915792575', 'name': u'twz915'}, {'qq': u'353506297', 'name': u'wangdachui'}, {'qq': u'004466315', 'name': u'xiaoming'}]>
In [14]: list(Author.objects.values('name', 'qq'))
Out[14]:
[{'name': u'WeizhongTu', 'qq': u'336643078'},
{'name': u'twz915', 'qq': u'915792575'},
{'name': u'wangdachui', 'qq': u'353506297'},
{'name': u'xiaoming', 'qq': u'004466315'}]
3.2 查询 twz915 这个人的文章标题
In [23]: Article.objects.filter(author__name='twz915').values('title')
Out[23]: <QuerySet [{'title': u'HTML \u6559\u7a0b_1'}, {'title': u'HTML \u6559\u7a0b_2'}, {'title': u'HTML \u6559\u7a0b_3'}, {'title': u'HTML \u6559\u7a0b_4'}, {'title': u'HTML \u6559\u7a0b_5'}, {'title': u'HTML \u6559\u7a0b_6'}, {'title': u'HTML \u6559\u7a0b_7'}, {'title': u'HTML \u6559\u7a0b_8'}, {'title': u'HTML \u6559\u7a0b_9'}, {'title': u'HTML \u6559\u7a0b_10'}, {'title': u'HTML \u6559\u7a0b_11'}, {'title': u'HTML \u6559\u7a0b_12'}, {'title': u'HTML \u6559\u7a0b_13'}, {'title': u'HTML \u6559\u7a0b_14'}, {'title': u'HTML \u6559\u7a0b_15'}, {'title': u'HTML \u6559\u7a0b_16'}, {'title': u'HTML \u6559\u7a0b_17'}, {'title': u'HTML \u6559\u7a0b_18'}, {'title': u'HTML \u6559\u7a0b_19'}, {'title': u'HTML \u6559\u7a0b_20'}]>
注意:
1. values_list 和 values 返回的并不是真正的 列表 或 字典,也是 queryset,他们也是 lazy evaluation 的(惰性评估,通俗地说,就是用的时候才真正的去数据库查)
2. 如果查询后没有使用,在数据库更新后再使用,你发现得到在是新内容!!!如果想要旧内容保持着,数据库更新后不要变,可以 list 一下
3. 如果只是遍历这些结果,没有必要 list 它们转成列表(浪费内存,数据量大的时候要更谨慎!!!)
4. extra 实现 别名,条件,排序等
extra 中可实现别名,条件,排序等,后面两个用 filter, exclude 一般都能实现,排序用 order_by 也能实现。我们主要看一下别名这个
比如 Author 中有 name, Tag 中有 name 我们想执行
SELECT name AS tag_name FROM blog_tag;
这样的语句,就可以用 select 来实现,如下:
In [44]: tags = Tag.objects.all().extra(select={'tag_name': 'name'})
In [45]: tags[0].name
Out[45]: u'Django'
In [46]: tags[0].tag_name
Out[46]: u'Django'
我们发现 name 和 tag_name 都可以使用,确认一下执行的 SQL
In [47]: Tag.objects.all().extra(select={'tag_name': 'name'}).query.__str__()
Out[47]: u'SELECT (name) AS "tag_name", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag"'
我们发现查询的时候弄了两次 (name) AS "tag_name" 和 "blog_tag"."name"
如果我们只想其中一个能用,可以用 defer 排除掉原来的 name (后面有讲)
In [49]: Tag.objects.all().extra(select={'tag_name': 'name'}).defer('name').query.__str__()
Out[49]: u'SELECT (name) AS "tag_name", "blog_tag"."id" FROM "blog_tag"'
也许你会说为什么要改个名称,最常见的需求就是数据转变成 list,然后可视化等,我们在下面一个里面讲。
5. annotate 聚合 计数,求和,平均数等
5.1 计数
我们来计算一下每个作者的文章数(我们每个作者都导入的Article的篇数一样,所以下面的每个都一样)
In [66]: from django.db.models import Count
In [66]: Article.objects.all().values('author_id').annotate(count=Count('author')).values('author_id', 'count')
Out[66]: <QuerySet [{'count': 20, 'author_id': 1}, {'count': 20, 'author_id': 2}, {'count': 20, 'author_id': 4}]>
这是怎么工作的呢?
In [67]: Article.objects.all().values('author_id').annotate(count=Count('author')).values('author_id', 'count').query.__str__()
Out[67]: u'SELECT "blog_article"."author_id", COUNT("blog_article"."author_id") AS "count" FROM "blog_article" GROUP BY "blog_article"."author_id"'
简化一下SQL: SELECT author_id, COUNT(author_id) AS count FROM blog_article GROUP BY author_id
我们也可以获取作者的名称 及 作者的文章数
In [72]: Article.objects.all().values('author__name').annotate(count=Count('author')).values('author__name', 'count')
Out[72]: <QuerySet [{'count': 20, 'author__name': u'WeizhongTu'}, {'count': 20, 'author__name': u'twz915'}, {'count': 20, 'author__name': u'xiaoming'}]>
细心的同学会发现,这时候实际上查询两张表,因为作者名称(author__name)在 blog_author 这张表中,而上一个例子中的 author_id 是 blog_article 表本身就有的字段
5.2 求和 与 平均值
5.2.1 求一个作者的所有文章的得分(score)平均值
In [6]: from django.db.models import Avg
In [7]: Article.objects.values('author_id').annotate(avg_score=Avg('score')).values('author_id', 'avg_score')
Out[7]: <QuerySet [{'author_id': 1, 'avg_score': 86.05}, {'author_id': 2, 'avg_score': 83.75}, {'author_id': 5, 'avg_score': 85.65}]>
执行的SQL
In [8]: Article.objects.values('author_id').annotate(avg_score=Avg('score')).values('author_id', 'avg_score').qu
...: ery.__str__()
Out[8]: u'SELECT "blog_article"."author_id", AVG("blog_article"."score") AS "avg_score" FROM "blog_article" GROUP BY "blog_article"."author_id"'
5.2.2 求一个作者所有文章的总分
In [12]: from django.db.models import Sum
In [13]: Article.objects.values('author__name').annotate(sum_score=Sum('score')).values('author__name', 'sum_score')
Out[13]: <QuerySet [{'author__name': u'WeizhongTu', 'sum_score': 1721}, {'author__name': u'twz915', 'sum_score': 1675}, {'author__name': u'zhen', 'sum_score': 1713}]>
执行的SQL
In [14]: Article.objects.values('author__name').annotate(sum_score=Sum('score')).values('author__name', 'sum_sco
...: re').query.__str__()
Out[14]: u'SELECT "blog_author"."name", SUM("blog_article"."score") AS "sum_score" FROM "blog_article" INNER JOIN "blog_author" ON ("blog_article"."author_id" = "blog_author"."id") GROUP BY "blog_author"."name"'
6. select_related 优化一对一,多对一查询
开始之前我们修改一个 settings.py 让Django打印出在数据库中执行的语句
settings.py 尾部加上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
LOGGING = { 'version' : 1 , 'disable_existing_loggers' : False , 'handlers' : { 'console' : { 'class' : 'logging.StreamHandler' , }, }, 'loggers' : { 'django.db.backends' : { 'handlers' : [ 'console' ], 'level' : 'DEBUG' if DEBUG else 'INFO' , }, }, } |
这样当 DEBUG 为 True 的时候,我们可以看出 django 执行了什么 SQL 语句
tu@pro ~/zqxt $ python manage.py shell
In [1]: from blog.models import *
In [2]: Author.objects.all()
Out[2]: (0.001) SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" LIMIT 21; args=()
<QuerySet [<Author: WeizhongTu>, <Author: twz915>, <Author: dachui>, <Author: zhe>, <Author: zhen>]>
标记背景为 黄色的部分就是打出的 log。
假如,我们取出10篇Django相关的文章,并需要用到作者的姓名
In [13]: articles = Article.objects.all()[:10]
In [14]: a1 = articles[0] # 取第一篇
(0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 1; args=()
In [15]: a1.title
Out[15]: u'Django \u6559\u7a0b_1'
In [16]: a1.author_id
Out[16]: 5
In [17]: a1.author.name # 再次查询了数据库,注意!!!
(0.000) SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" WHERE "blog_author"."id" = 5; args=(5,)
Out[17]: u'zhen'
这样的话我们遍历查询结果的时候就会查询很多次数据库,能不能只查询一次,把作者的信息也查出来呢?
当然可以,这时就用到 select_related,我们的数据库设计的是一篇文章只能有一个作者,一个作者可以有多篇文章。
现在要查询文章的时候连同作者一起查询出来,“文章”和“作者”的关系就是多对一,换句说说,就是一篇文章只可能有一个作者。
In [18]: articles = Article.objects.all().select_related('author')[:10]
In [19]: a1 = articles[0] # 取第一篇
(0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score", "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_article" INNER JOIN "blog_author" ON ("blog_article"."author_id" = "blog_author"."id") LIMIT 1; args=()
In [20]: a1.title
Out[20]: u'Django \u6559\u7a0b_1'
In [21]: a1.author.name # 嘻嘻,没有再次查询数据库!!
Out[21]: u'zhen'
7. prefetch_related 优化一对多,多对多查询
和 select_related 功能类似,但是实现不同。
select_related 是使用 SQL JOIN 一次性取出相关的内容。
prefetch_related 用于 一对多,多对多 的情况,这时 select_related 用不了,因为当前一条有好几条与之相关的内容。
prefetch_related是通过再执行一条额外的SQL语句,然后用 Python 把两次SQL查询的内容关联(joining)到一起
我们来看个例子,查询文章的同时,查询文章对应的标签。“文章”与“标签”是多对多的关系。
In [24]: articles = Article.objects.all().prefetch_related('tags')[:10]
In [25]: articles
Out[25]: (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 10; args=()
(0.001) SELECT ("blog_article_tags"."article_id") AS "_prefetch_related_val_article_id", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); args=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
<QuerySet [<Article: Django 教程_1>, <Article: Django 教程_2>, <Article: Django 教程_3>, <Article: Django 教程_4>, <Article: Django 教程_5>, <Article: Django 教程_6>, <Article: Django 教程_7>, <Article: Django 教程_8>, <Article: Django 教程_9>, <Article: Django 教程_10>]>
遍历查询的结果:
不用 prefetch_related 时
In [9]: articles = Article.objects.all()[:3]
In [10]: for a in articles:
...: print a.title, a.tags.all()
...:
(0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 3; args=()
(0.000) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 1 LIMIT 21; args=(1,)
Django 教程_1 <QuerySet [<Tag: Django>]>
(0.000) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 2 LIMIT 21; args=(2,)
Django 教程_2 <QuerySet [<Tag: Django>]>
(0.000) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 3 LIMIT 21; args=(3,)
Django 教程_3 <QuerySet [<Tag: Django>]>
用 prefetch_related 我们看一下是什么样子
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000; background-color: #ffffff}
p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000; background-color: #ffffff; min-height: 15.0px}
p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #34a327; background-color: #ffffff}
span.s1 {font-variant-ligatures: no-common-ligatures; color: #34a327}
span.s2 {font-variant-ligatures: no-common-ligatures; color: #2ee721}
span.s3 {font-variant-ligatures: no-common-ligatures}
span.s4 {font-variant-ligatures: no-common-ligatures; color: #cd7923}
span.s5 {font-variant-ligatures: no-common-ligatures; color: #d03bff}
span.s6 {font-variant-ligatures: no-common-ligatures; color: #000000}
-->
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000; background-color: #ffffff}
p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000; background-color: #ffffff; min-height: 15.0px}
p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #34a327; background-color: #ffffff}
span.s1 {font-variant-ligatures: no-common-ligatures; color: #34a327}
span.s2 {font-variant-ligatures: no-common-ligatures; color: #2ee721}
span.s3 {font-variant-ligatures: no-common-ligatures}
span.s4 {font-variant-ligatures: no-common-ligatures; color: #cd7923}
span.s5 {font-variant-ligatures: no-common-ligatures; color: #d03bff}
span.s6 {font-variant-ligatures: no-common-ligatures; color: #000000}
-->
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000; background-color: #ffffff}
p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #000000; background-color: #ffffff; min-height: 15.0px}
p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 13.0px Menlo; color: #34a327; background-color: #ffffff}
span.s1 {font-variant-ligatures: no-common-ligatures; color: #34a327}
span.s2 {font-variant-ligatures: no-common-ligatures; color: #2ee721}
span.s3 {font-variant-ligatures: no-common-ligatures}
span.s4 {font-variant-ligatures: no-common-ligatures; color: #cd7923}
span.s5 {font-variant-ligatures: no-common-ligatures; color: #d03bff}
span.s6 {font-variant-ligatures: no-common-ligatures; color: #000000}
-->
In [11]: articles = Article.objects.all().prefetch_related('tags')[:3]
In [12]: for a in articles:
...: print a.title, a.tags.all()
...:
(0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 3; args=()
(0.000) SELECT ("blog_article_tags"."article_id") AS "_prefetch_related_val_article_id", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" IN (1, 2, 3); args=(1, 2, 3)
Django 教程_1 <QuerySet [<Tag: Django>]>
Django 教程_2 <QuerySet [<Tag: Django>]>
Django 教程_3 <QuerySet [<Tag: Django>]>
我们可以看到第二条 SQL 语句,一次性查出了所有相关的内容。
8. defer 排除不需要的字段
在复杂的情况下,表中可能有些字段内容非常多,取出来转化成 Python 对象会占用大量的资源。
这时候可以用 defer 来排除这些字段,比如我们在文章列表页,只需要文章的标题和作者,没有必要把文章的内容也获取出来(因为会转换成python对象,浪费内存)
In [13]: Article.objects.all()
Out[13]: (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 21; args=()
<QuerySet [<Article: Django 教程_1>, <Article: Django 教程_2>, <Article: Django 教程_3>, <Article: Django 教程_4>, <Article: Django 教程_5>, <Article: Django 教程_6>, <Article: Django 教程_7>, <Article: Django 教程_8>, <Article: Django 教程_9>, <Article: Django 教程_10>, <Article: Django 教程_11>, <Article: Django 教程_12>, <Article: Django 教程_13>, <Article: Django 教程_14>, <Article: Django 教程_15>, <Article: Django 教程_16>, <Article: Django 教程_17>, <Article: Django 教程_18>, <Article: Django 教程_19>, <Article: Django 教程_20>, '...(remaining elements truncated)...']>
In [14]: Article.objects.all().defer('content')
Out[14]: (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."score" FROM "blog_article" LIMIT 21; args=() # 注意这里没有查 content 字段了
<QuerySet [<Article: Django 教程_1>, <Article: Django 教程_2>, <Article: Django 教程_3>, <Article: Django 教程_4>, <Article: Django 教程_5>, <Article: Django 教程_6>, <Article: Django 教程_7>, <Article: Django 教程_8>, <Article: Django 教程_9>, <Article: Django 教程_10>, <Article: Django 教程_11>, <Article: Django 教程_12>, <Article: Django 教程_13>, <Article: Django 教程_14>, <Article: Django 教程_15>, <Article: Django 教程_16>, <Article: Django 教程_17>, <Article: Django 教程_18>, <Article: Django 教程_19>, <Article: Django 教程_20>, '...(remaining elements truncated)...']>
9. only 仅选择需要的字段
和 defer 相反,only 用于取出需要的字段,假如我们只需要查出 作者的名称
In [15]: Author.objects.all().only('name')
Out[15]: (0.000) SELECT "blog_author"."id", "blog_author"."name" FROM "blog_author" LIMIT 21; args=()
<QuerySet [<Author: WeizhongTu>, <Author: twz915>, <Author: dachui>, <Author: zhe>, <Author: zhen>]>
细心的同学会发现,我们让查 name , id 也查了,这个 id 是 主键,能不能没有这个 id 呢?
试一下原生的 SQL 查询
In [26]: authors = Author.objects.raw('select name from blog_author limit 1')
In [27]: author = authors[0]
(0.000) select name from blog_author limit 1; args=()
---------------------------------------------------------------------------
InvalidQuery Traceback (most recent call last)
<ipython-input-27-51c5f914fff2> in <module>()
----> 1author = authors[0]
/usr/local/lib/python2.7/site-packages/django/db/models/query.pyc in __getitem__(self, k)
1275
1276 def __getitem__(self, k):
-> 1277 return list(self)[k]
1278
1279 @property
/usr/local/lib/python2.7/site-packages/django/db/models/query.pyc in __iter__(self)
1250 if skip:
1251 if self.model._meta.pk.attname in skip:
-> 1252 raise InvalidQuery('Raw query must include the primary key')
1253 model_cls = self.model
1254 fields =[self.model_fields.get(c)for c in self.columns]
InvalidQuery: Raw query must include the primary key
报错信息说 非法查询,原生SQL查询必须包含 主键!
再试试直接执行 SQL
tu@pro ~/zqxt $ python manage.py dbshell
SQLite version 3.14.0 2016-07-26 15:17:14
Enter ".help" for usage hints.
sqlite> select name from blog_author limit 1;
WeizhongTu <--- 成功!!!
虽然直接执行SQL语句可以这样,但是 django queryset 不允许这样做,一般也不需要关心,反正 only 一定会取出你指定了的字段。
10. 自定义聚合功能
我们前面看到了 django.db.models 中有 Count, Avg, Sum 等,但是有一些没有的,比如 GROUP_CONCAT,它用来聚合时将符合某分组条件(group by)的不同的值,连到一起,作为整体返回。
我们来演示一下,如果实现 GROUP_CONCAT 功能。
新建一个文件 比如 my_aggregate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from django.db.models import Aggregate, CharField class GroupConcat(Aggregate): function = 'GROUP_CONCAT' template = '%(function)s(%(distinct)s%(expressions)s%(ordering)s%(separator)s)' def __init__( self , expression, distinct = False , ordering = None , separator = ',' , * * extra): super (GroupConcat, self ).__init__( expression, distinct = 'DISTINCT ' if distinct else '', ordering = ' ORDER BY %s' % ordering if ordering is not None else '', separator = ' SEPARATOR "%s"' % separator, output_field = CharField(), * * extra ) |
代码来自:http://stackoverflow.com/a/40478702/2714931(我根据一个回复改写的增强版本)
使用时先引入 GroupConcat 这个类,比如聚合后的错误日志记录有这些字段 time, level, info
我们想把 level, info 一样的 聚到到一起,按时间和发生次数倒序排列,并含有每次日志发生的时间。
1
2
3
|
ErrorLogModel.objects.values( 'level' , 'info' ).annotate( count = Count( 1 ), time = GroupConcat( 'time' , ordering = 'time DESC' , separator = ' | ' ) ).order_by( '-time' , '-count' ) |
参考资源:
QuerySet API: https://docs.djangoproject.com/en/dev/ref/models/querysets/
聚合相关:https://docs.djangoproject.com/en/dev/topics/db/aggregation/
转载自https://code.ziqiangxuetang.com/django/django-queryset-advance.html
Django QuerySet 进阶的更多相关文章
- Python之路,Day15 - Django适当进阶篇
Python之路,Day15 - Django适当进阶篇 本节内容 学员管理系统练习 Django ORM操作进阶 用户认证 Django练习小项目:学员管理系统设计开发 带着项目需求学习是最有趣 ...
- Python之路【第十七篇】:Django【进阶篇 】
Python之路[第十七篇]:Django[进阶篇 ] Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接 ...
- 合并多个python list以及合并多个 django QuerySet 的方法
在用python或者django写一些小工具应用的时候,有可能会遇到合并多个list到一个 list 的情况.单纯从技术角度来说,处理起来没什么难度,能想到的办法很多,但我觉得有一个很简单而且效率比较 ...
- Python之路【第十七篇】:Django【进阶篇】
Python之路[第十七篇]:Django[进阶篇 ] Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接 ...
- Python开发【第二十二篇】:Web框架之Django【进阶】
Python开发[第二十二篇]:Web框架之Django[进阶] 猛击这里:http://www.cnblogs.com/wupeiqi/articles/5246483.html 博客园 首页 ...
- Django 2.0 学习(07):Django 视图(进阶-续)
接Django 2.0 学习(06):Django 视图(进阶),我们将聚焦在使用简单的表单进行处理和精简代码. 编写简单表单 我们将用下面的代码,来替换之前的detail模板("polls ...
- Django orm进阶查询(聚合、分组、F查询、Q查询)、常见字段、查询优化及事务操作
Django orm进阶查询(聚合.分组.F查询.Q查询).常见字段.查询优化及事务操作 聚合查询 记住用到关键字aggregate然后还有几个常用的聚合函数就好了 from django.db.mo ...
- Django web 进阶
.路由系统 .模板引擎 simple_tag .Form .Ajax请求 -简单数据 -复杂数据 内容: -作业 model xss.csrf(安全方面的内容) 分页(公共的模块) 内容复习和今日内容 ...
- Django model进阶
Django-model进阶 QuerySet 可切片 使用Python 的切片语法来限制查询集记录的数目 .它等同于SQL 的LIMIT 和OFFSET 子句. >>> Ent ...
随机推荐
- 【Kth Smallest Element in a BST 】cpp
题目: Given a binary search tree, write a function kthSmallest to find the kth smallest element in it. ...
- HTML5新增属性学习笔记
1.form属性 表单内的从属元素,可以写在表单外部.可以通过指定元素的form属性来声明元素所属表单.form的属性值为表单的id. <form id="testForm" ...
- CSU-2110 Keeping Cool
题目链接 http://acm.csu.edu.cn:20080/csuoj/problemset/problem?pid=2110 题目 Description Kevin has just got ...
- STL之queue&stack使用简介
queue 队列也是一个线性存储表,与后进先出的堆栈不同,元素数据的插入在表的一端进行,在另一端删除,从而构成了一个先进先出(First In First Out) 表.插入一端称为队尾,删除一 ...
- HDU 3887 Counting Offspring (树状数组+人工模拟栈)
对这棵树DFS遍历一遍,同一节点入栈和出栈之间访问的节点就是这个节点的子树. 因此节点入栈时求一次 小于 i 的节点个数 和,出栈时求一次 小于 i 的节点个数 和,两次之差就是答案. PS.这题直接 ...
- GYM - 101147 F.Bishops Alliance
题意: 一个n*n的棋盘,有m个主教.每个主教都有自己的权值p.给出一个值C,在棋盘中找到一个最大点集.这个点集中的点在同一条对角线上且对于点集中任意两点(i,j),i和j之间的主教数(包括i,j)不 ...
- hihocoder 后缀自动机五·重复旋律8 求循环同构串出现的次数
描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 小Hi发现旋律可以循环,每次把一段旋律里面最前面一个音换到最后面就成为了原旋律的“循环相似旋律”,还可以 ...
- Linux命令之Nano基础指南
Nano基础指南 1. Nano基础 目的 这是篇nano简明使用指南,帮助你快速了解nano的基本用法.如想获取更多的帮助信息,请参阅: http://www.nano-editor.org 打开 ...
- php 字符串重要函数
1.chop() 从字符串右端移除字符 chop(string,charlist) $str="hello world~"; echo chop($str,"ld~&qu ...
- 转 Android_开源框架_AndroidUniversalImageLoader网络图片加载
转自:http://www.cnblogs.com/wanqieddy/p/3836485.html 1.功能概要 Android-Universal-Image-Loader是一个开源的UI组件程序 ...