自强学堂
自强学堂:学习、分享、让你更强!
Django 教程HTMLCSSJAVASCRIPTJQUERYSQLPHPBOOTSTRAPANGULARXML
 

Django 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

django-admin startproject zqxt
python manage.py startapp blog

把 blog 加入到 settings.py 中的 INSTALL_APPS 中

blog/models.py 代码如下

# -*- coding: utf-8 -*-
# @Date    : 2019-11-24 14:18:12
# @Author  : Weizhong Tu (mail@tuweizhong.com)
# @Link    : http://www.tuweizhong.com
# @Version : 0.0.1

from django.db import models


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


class Article(models.Model):
    title = models.CharField(max_length=50)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    content = models.TextField()
    score = models.IntegerField()  # 文章的打分
    tags = models.ManyToManyField('Tag')

    def __str__(self):
        return self.title


class Tag(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

比较简单,假设一篇文章只有一个作者(Author),一个作者可以有多篇文章(Article),一篇文章可以有多个标签(Tag)。

创建 migrations 然后 migrate 在数据库中生成相应的表

python3 manage.py makemigrations
python3 manage.py migrate

tu@pro ~/zqxt $ python3 manage.py makemigrations

Migrations for 'blog':

  blog/migrations/0001_initial.py

    - Create model Author

    - Create model Tag

    - Create model Article


tu@pro ~/zqxt $ python3 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 admin.0003_logentry_add_action_flag_choices... 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 auth.0009_alter_user_last_name_max_length... OK

  Applying auth.0010_alter_group_name_max_length... OK

  Applying auth.0011_update_proxy_permissions... OK

  Applying blog.0001_initial... OK

  Applying sessions.0001_initial... OK


生成一些示例数据,运行 initdb.py(有疑问的可以参考 数据导入

# @Date    : 2019-11-24 14:19:00
# @Author  : Weizhong Tu (mail@tuweizhong.com)
# @Link    : http://www.tuweizhong.com
# @Version : 0.0.1

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 $ python3 initdb.py               

Done!


导入数据后,我们确认一下数据是不是已经导入。

tu@pro ~/zqxt $ python3 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 尾部加上

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 我们看一下是什么样子

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

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 一样的 聚到到一起,按时间和发生次数倒序排列,并含有每次日志发生的时间。

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/2.2/ref/models/querysets/


Django视频教程,极客时间出口,质量有保证

↑ 上方为自强学堂赞助商,非常荣幸有他们支持自强学堂的发展,感兴趣的了解一下他们的产品。