【Django QuerySet进阶 010】

阅读本文你可以学习到什么

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部分)

新建一个项目zqxt_blog,建一个app名称是blog

django-admin startproject zqxt
python manage.py startapp blog

把blog加入到setting.py中的INSTALLED_APPS中

blog/models.py代码如下

'''
|--一篇文章只有一个作者,一个作者可有有多篇文章,一篇文章会有多个标签
'''
from __future__ import  unicode_literals

from django.db import models
from django.utils.encoding import python_2_unicode_compatible
# Create your models here.

#定义作者类
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,)
    context = 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在数据库中生成相应的表

python manage.py makemigrations
python manage.py migrate

生成一些实例数据,运行initdb.py()

# -*- coding: utf-8 -*-
from __future__ import unicode_literals  # 将模块中显式出现的所有字符串转为unicode类型
import random
# (Web Server Gateway Interface,Web 服务器网关接口)则是Python语言中所定义的Web服务器和Web应用程序之间或框架之间的通用接口标准。
from zqxt_blog.wsgi import *  #
from blog.models import Author, Article, Tag

author_name_list = ['wufuqiang', 'test005', 'jinjin', 'xue', 'wei']
article_title_list = ['Django教程', 'Python教程', 'HTML教程']


def create_authors():
    for author_name in author_name_list:
        #首先尝试获取,不存在就创建,可以防止重复
        author, create = Author.objects.get_or_create(name=author_name)
        # 随机生成9位数的qq
        # ''join()用于字符串连接操作
        author.qq = ''.join(
            str(random.choice(range(1,10))) for _ in range(9)
        )
        print("author.qq=",author.qq)

        author.addr = 'addr_%s' % (random.randrange(1, 3))
        print("author.addr=",author.addr)

        author.email = '%s@csii.com.cn' % (author.addr)
        print("author.email=",author.email)
        author.save()


def create_article_and_tags():
    # 随机生成文章
    for article_title in article_title_list:
        # 从文章标题中得到tag
        # split()就是将一个字符串分隔成多个字符串组成的列表
        # 已空格为分隔符,只分隔一次,从第一个空格进行分隔
        tag_name = article_title.split(' ', 1)[0]
        print("tag_name = ",tag_name)
        tag, created = Tag.objects.get_or_create(name=tag_name)
        random_author = random.choice(Author.objects.all())
        print('random_author= ',random_author)

        for i in range(1, 21):
            title = '%s_%s' % (article_title, i)
            print('title',title)
            article, created = Article.objects.get_or_create(
                title=title, defaults={
                    'author': random_author, #随机分配作者
                    'context': '%s 正文' %(title),
                    'score': random.randrange(70, 101)}  #随机给文章打分
            )
            article.tags.add(tag)


def main():
    create_authors()
    create_article_and_tags()


if __name__ == '__main__':
    main()
    print("done")

(csiiDjango) MacBook-Pro-2:zqxt_blog wufq$ python initdb.py
done


导入数据后,我们确认数据是不是真的导入了

>>> from blog.models import Article, Author, Tag
>>> Author.objects.all()
<QuerySet [<Author: wufuqiang>, <Author: test005>, <Author: jinjin>, <Author: xue>, <Author: wei>]>
>>> Tag.objects.all()
<QuerySet [<Tag: Django教程>, <Tag: Python教程>, <Tag: HTML教程>]>
>>> Article.objects.all()
<QuerySet [<Article: Django教程_1>, <Article: Django教程_2>, <Article: Django教程_3>, <Article: Django教程_4>, <Article: Django教程_5>, <Article: DjanArticle: Django教程_7>, <Article: Django教程_8>, <Article: Django教程_9>, <Article: Django教程_10>, <Article: Django教程_11>, <Article: Django教程_12>Django教程_13>, <Article: Django教程_14>, <Article: Django教程_15>, <Article: Django教程_16>, <Article: Django教程_17>, <Article: Django教程_18>, <Art教程_19>, <Article: Django教程_20>, '...(remaining elements truncated)...']>
>>> 

以上都是准备阶段,已经结束

正式开始学习本节课,学习一些比较高级的查询方法

1、查看Django queryset 执行的SQL

>>> print(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

>>> print(Author.objects.filter(name="wufuqiang").query)
SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" WHERE "blog_author"."name" = wufuqiang
简化一下就是:SELECT id, name, qq, addr, email FROM blog_author WHERE name=wufuqiang;

所以,当不知道Django做了什么时,你可以把执行的SQL打出来看看,也可以借助https://github.com/jazzband/django-debug-toolbar  等工具在页面上看到访问当前页面执行了那些SQL,耗时等。

还有一种办法就是修改一下log的设置,后面会讲到。

2、values_list获取元组形式结果

2.1 比如我们要获取作者的name和qq

>>> authors = Author.objects.values_list('name','qq','addr')
>>> authors
<QuerySet [('wufuqiang', '263621251', 'addr_1'), ('test005', '437644648', 'addr_1'), ('jinjin', '394793579', 'addr_2'), ('xue', '712858356', 'addr_2'), ('wei', '743657397', 'addr_1')]>
>>> list(authors)
[('wufuqiang', '263621251', 'addr_1'), ('test005', '437644648', 'addr_1'), ('jinjin', '394793579', 'addr_2'), ('xue', '712858356', 'addr_2'), ('wei', '743657397', 'addr_1')]

如果只需要1个字段,可以指定flat = True

>>> Author.objects.values_list('name',flat=True)
<QuerySet ['wufuqiang', 'test005', 'jinjin', 'xue', 'wei']>
>>> list(Author.objects.values_list('name',flat=True))
['wufuqiang', 'test005', 'jinjin', 'xue', 'wei']

2.2查询wufuqiang这个人的文章标题

>>> Article.objects.filter(author__name='wufuqiang').values_list('title',flat=True)
<QuerySet ['Django教程_1', 'Django教程_2', 'Django教程_3', 'Django教程_4', 'Django教程_5', 'Django教程_6', 'Django教程_7', 'Django教程_8', 'Django教程, 'Django教程_11', 'Django教程_12', 'Django教程_13', 'Django教程_14', 'Django教程_15', 'Django教程_16', 'Django教程_17', 'Django教程_18', 'Django教程_]>

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

3.1  比如我们要获取作者的name和qq

>>> authors = Author.objects.values('name','qq')
>>> authors
<QuerySet [{'name': 'wufuqiang', 'qq': '263621251'}, {'name': 'test005', 'qq': '437644648'}, {'name': 'jinjin', 'qq': '394793579'}, {'name': 'xue', 'qq': '712858356'}, {'name': 'wei', 'qq': '743657397'}]>
>>> list(authors)
[{'name': 'wufuqiang', 'qq': '263621251'}, {'name': 'test005', 'qq': '437644648'}, {'name': 'jinjin', 'qq': '394793579'}, {'name': 'xue', 'qq': '712858356'}, {'name': 'wei', 'qq': '743657397'}]

3.2 查询wufuqiang这个人的文章标题
>>> Article.objects.filter(author__name='wufuqiang').values('title')
<QuerySet [{'title': 'Django教程_1'}, {'title': 'Django教程_2'}, {'title': 'Django教程_3'}, {'title': 'Django教程_4'}, {'title': 'Django教程_5'}, {'tingo教程_6'}, {'title': 'Django教程_7'}, {'title': 'Django教程_8'}, {'title': 'Django教程_9'}, {'title': 'Django教程_10'}, {'title': 'Django教程_11'}, jango教程_12'}, {'title': 'Django教程_13'}, {'title': 'Django教程_14'}, {'title': 'Django教程_15'}, {'title': 'Django教程_16'}, {'title': 'Django教程_e': 'Django教程_18'}, {'title': 'Django教程_19'}, {'title': 'Django教程_20'}]>


注意:

1、values_list和values返回的并不是真正的列表或字典,也是queryset,他们也是lazyevaluation的(懒惰评估,通俗地说,就是用的时候才真正的去数据库查)

2、如果查询后没有使用,在数据库更新会在使用,你发现得到在新内容!!!如果想要就内容保持着,数据库更新后不要变,可以list一下

3、如果只是遍历这些结果,没有必要list它们转成列表(浪费内存,数据量大的时候要更谨慎!!!)

4、extra实现别名,条件,排序等

extra中可实现别名,条件,排序等,后面两个用filter,exclude一般都能实现,排序用order__by也能实现。我们主要看一下别名这个

比如Author中有name,Tag中有name我们想执行

SELECT name AS tag_name from blog_tag;

这样的语句,就可以用select来实现,如下:

>>> tags = Tag.objects.all().extra(select={'tag_name':'name'})
>>> tags[0].name
'Django教程'
>>> tags[2].name
'HTML教程'

我们发现name和tag_name都可以使用,确认一下执行的sql

>>> Tag.objects.all().extra(select={'tag_name':'name'}).query.__str__()
'SELECT (name) AS "tag_name", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag"'


我们发现查询的时候弄了两次(name)AS "tag_name"和“blog_tag”."name",

如果我们只想要其中的一个,可以用defer拍出原来的name

>>> Tag.objects.all().extra(select={'tag_name':'name'}).defer('name').query.__str__()
'SELECT (name) AS "tag_name", "blog_tag"."id" FROM "blog_tag"'

也许你会说为什么要改个名称,最常见的需求就是数据转变成 list,然后可视化等,我们在下面一个里面讲

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

5.1计数

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

>>> from django.db.models import Count
>>>Article.objects.all().values('author_id').annotate(count=Count('author')).values('author_id','count')
<QuerySet [{'author_id': 1, 'count': 20}, {'author_id': 3, 'count': 20}, {'author_id': 4, 'count': 20}]>
语句分析:
|-- Article.objects.all().values('author_id'):获取文章的作者ID字典

|-- annotate(count=Count('author')):对author进行计数

|-- values('author_id','count'):字典输出的格式:author_id:count

怎么工作的,转换成SQL语句

>>>Article.objects.all().values('author_id').annotate(count=Count('author')).values('author_id','count').query.__str__()
'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

我们也可以获取作者的名称及作者的文章数

>>>Article.objects.all().values('author__name').annotate(count=Count('author')).values('author__name','count')
<QuerySet [{'author__name': 'jinjin', 'count': 20}, {'author__name': 'wufuqiang', 'count': 20}, {'author__name': 'xue', 'count': 20}]>
>>>Article.objects.all().values('author__name').annotate(count=Count('author')).values('author__name','count').query.__str__()
'SELECT "blog_author"."name", COUNT("blog_article"."author_id") AS "count" FROM "blog_article" INNER JOIN "blog_author" ON ("blog_article"."author_id" = "blog_author"."id") GROUP BY "blog_author"."name"'

这时候实际上查询两张表,因为作者名称(author__name)在 blog_author 这张表中,而上一个例子中的 author_id 是 blog_article 表本身就有的字段



原文地址:https://www.cnblogs.com/frankruby/p/10880403.html