zoukankan      html  css  js  c++  java
  • 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(13))
            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 in range(121):
                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(70101),  # 随机给文章一个打分
                    }
                )
                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 u6559u7a0b_1', u'HTML u6559u7a0b_2', u'HTML u6559u7a0b_3', u'HTML u6559u7a0b_4', u'HTML u6559u7a0b_5', u'HTML u6559u7a0b_6', u'HTML u6559u7a0b_7', u'HTML u6559u7a0b_8', u'HTML u6559u7a0b_9', u'HTML u6559u7a0b_10', u'HTML u6559u7a0b_11', u'HTML u6559u7a0b_12', u'HTML u6559u7a0b_13', u'HTML u6559u7a0b_14', u'HTML u6559u7a0b_15', u'HTML u6559u7a0b_16', u'HTML u6559u7a0b_17', u'HTML u6559u7a0b_18', u'HTML u6559u7a0b_19', u'HTML u6559u7a0b_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 u6559u7a0b_1'}, {'title': u'HTML u6559u7a0b_2'}, {'title': u'HTML u6559u7a0b_3'}, {'title': u'HTML u6559u7a0b_4'}, {'title': u'HTML u6559u7a0b_5'}, {'title': u'HTML u6559u7a0b_6'}, {'title': u'HTML u6559u7a0b_7'}, {'title': u'HTML u6559u7a0b_8'}, {'title': u'HTML u6559u7a0b_9'}, {'title': u'HTML u6559u7a0b_10'}, {'title': u'HTML u6559u7a0b_11'}, {'title': u'HTML u6559u7a0b_12'}, {'title': u'HTML u6559u7a0b_13'}, {'title': u'HTML u6559u7a0b_14'}, {'title': u'HTML u6559u7a0b_15'}, {'title': u'HTML u6559u7a0b_16'}, {'title': u'HTML u6559u7a0b_17'}, {'title': u'HTML u6559u7a0b_18'}, {'title': u'HTML u6559u7a0b_19'}, {'title': u'HTML u6559u7a0b_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 u6559u7a0b_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 u6559u7a0b_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

    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

  • 相关阅读:
    Cannot find module 'webpack'
    Failed to read auto-increment value from storage engine
    laravel框架查看执行过的sql语句
    windows与虚拟机共享文件夹设置
    csv导出文件中有html
    特定中文字符串正则匹配
    运动跟踪等算法的解释
    自控力这本书的读后感
    提取肤色信息原理及操作——opencv
    那些年我们排过的序之希尔排序
  • 原文地址:https://www.cnblogs.com/zzqit/p/9754733.html
Copyright © 2011-2022 走看看