Django进阶-QuerySet篇

1.查看 Django queryset 执行的 SQL

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;

 

2.values_list 获取元组形式结果

获取作者的 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']

 

3. values 获取字典形式的结果

获取作者的 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'}]


注意:

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"'

 

5.annotate:聚合 计数,求和,平均数等

1.计算一下每个作者的文章数(我们每个作者都导入的Article的篇数一样,所以下面的每个都一样)

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

2.求一个作者的所有文章的得分(score)平均值
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"'

3.求一个作者所有文章的总分
执行的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"'
Out[13]: <QuerySet [{'author__name': u'WeizhongTu', 'sum_score': 1721}, {'author__name': u'twz915', 'sum_score': 1675}, {'author__name': u'zhen', 'sum_score': 1713}]>
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 语句


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=()
    'out[2]'就是打出的 log。

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 优化一对多,多对多查询

prefetch_related和 select_related 功能类似,但是实现不同。

select_related 是使用 SQL JOIN 一次性取出相关的内容。

prefetch_related 用于 一对多,多对多 的情况,这时 select_related 用不了,因为当前一条有好几条与之相关的内容。

prefetch_related是通过再执行一条额外的SQL语句,然后用 Python 把两次SQL查询的内容关联(joining)到一起

我们来看个例子,查询文章的同时,查询文章对应的标签。“文章”与“标签”是多对多的关系。


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>]>
遍历查询的结果:

不用 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>]>

 

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=()

# 注意这里没有查 content 字段了
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=()  

 

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)的不同的值,连到一起,作为整体返回。
新建一个文件 比如 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        )
使用时先引入 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')

 

11.Model.objects.bulk_create() 更快更方便

#使用Model.objects.create()
    for line in f:
        title,content = line.split('****')
        Blog.objects.create(title=title,content=content)

# 使用Model.objects.bulk_create()
    BlogList = []
    for line in f:
        title,content = line.split('****')
        blog = Blog(title=title,content=content)
        BlogList.append(blog)
     Blog.objects.bulk_create(BlogList)
由于Blog.objects.create()每保存一条就执行一次SQL,而bulk_create()是执行一条SQL存入多条数据,做会快很多!当然用列表解析代替 for 循环会更快!!


# 使用列表
BlogList = [Blog(title=line.split('****')[0], content=line.split('****')[1]) for line in f]
     
Blog.objects.bulk_create(BlogList)

12.合并QuerySet

当你想要让两个或者多个 queryset 合并为一个 queryset 的时候, 并且希望使用 list, 而且想要保留对象的filter, count, distinct等 queryset 方法。

查看以下模型:
Python

class Story(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField(blank=True)
    category = models.ForeignKey(Category, related_name='stories')
    author = models.ForeignKey(User, related_name='stories')

class Medium(models.Model):
    name = models.CharField(max_length=30, unique=True)
    stories = models.ManyToManyField(Story)
假设你想显示特定的 Medium 中发布的故事以及属于特定作者的所有故事。那么一般情况下, 你用以下方法获取了两个 queryset。

Python

medium = Medium.objects.get(name='Django Blog')
user = User.objects.get(username='vitor')

django_stories = medium.stories.all()
vitor_stories = user.stories.filter(category__name='django')
此时我们有两个查询集(QuerySets) ,我们可以通过| 运算符来合并这两个查询集。

Python

stories = django_stories | vitor_stories  # merge querysets
此时你依然可以使用 queryset 的一些操作方法.

Python

recent_stories = stories.distinct().order_by('-date')[:10]
  注意: 合并运算符仅适用于同一类型的 queryset, 并且数据为切片前数据集.

 

posted @ 2020-09-28 11:00  傻白甜++  阅读(1078)  评论(0编辑  收藏  举报
TOP