Django之QuerySet 查询

Posted 一张红枫叶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django之QuerySet 查询相关的知识,希望对你有一定的参考价值。

首先来看下如何查询。我们在网页中增加书名的查询链接

后端的查询处理代码:这里由于authors是manytomanyfiled,因此我们这里用r.authors.all().first()来查询符合条件的第一个

def books_inquery_result(request):
ret=[]
if request.method == \'POST\':
bookname=request.POST[\'bookname\']
result=Book.objects.filter(title=bookname)
for r in result:
ret.append(r.authors.all().first())
return HttpResponse(ret)

但是当输入书名flask1的时候,我们查出来的确实下面这些代码。这是为什么呢。

我们回头看下之前添加books的操作:

ret3.publisher=Publisher.objects.all()[1]
ret3.publish_date=publish_date
ret3.save()
ret3.authors.add(Author.objects.all()[1])

我们查询对应的Author.objects.all()[1]发现也是同样的结果。

def result2(request):
result=Author.objects.all()[1]
return HttpResponse(result)

但是我们想要到的是书的作者和在网页上输入的对象关联起来。那么我们添加的代码就需要更新如下:这样就把之前的输入给加了进来

ret3=Book()
ret3.title=title
ret3.publisher=Publisher.objects.get(name=publish)
ret3.publish_date=publish_date
ret3.save()
ret3.authors.add(Author.objects.get(email=email))

此时再进行查询,就可以得到正确的结果了。而且我们还可以通过

r.authors.all().first().email
r.authors.all().first().first_name
r.authors.all().first().last_name
接下来看下如何查询出版商的信息,出版商是ForeignKey的关系。因此查询也很简单,通过下面的方式就可以查出出版商的信息。
r.publisher.address 
r.publisher. name
r.publisher. city
r.publisher. state_province
r.publisher. country
r.publisher. website

select_related查询优化,首先我们在setting.py的末尾加上下面的配置:
LOGGING = {
    \'version\': 1,
    \'disable_existing_loggers\': False,
    \'handlers\': {
        \'console\':{
            \'level\':\'DEBUG\',
            \'class\':\'logging.StreamHandler\',
        },
    },
    \'loggers\': {
        \'django.db.backends\': {
            \'handlers\': [\'console\'],
            \'propagate\': True,
            \'level\':\'DEBUG\',
        },
    }
}


这个logging的配置作用是在终端查询的时候自动打印出sql语句,这样我们就能很直观的看到查询的方式。比如前面的Book查询。我们来看下
Python manage.py shell
In [1]: from site_prj.models import Publisher,Book,Author
 
In [2]: books=Book.objects().all
In [4]: b1=books[0]
(0.002) SELECT "site_prj_book"."id", "site_prj_book"."title", "site_prj_book"."publisher_id", "site_prj_book"."publish_date" FROM "site_prj_book" LIMIT 1; args=()
In [6]: b1.title
Out[6]: u\'flask\'
 
In [7]: b1.publisher
(0.000) SELECT "site_prj_publisher"."id", "site_prj_publisher"."name", "site_prj_publisher"."address", "site_prj_publisher"."city", "site_prj_publisher"."state_province", "site_prj_publisher"."country", "site_prj_
publisher"."website" FROM "site_prj_publisher" WHERE "site_prj_publisher"."id" = 2; args=(2,)
Out[7]: <Publisher: æ
我们看到在查询publisher的时候又调用了sql语句,原因在于Book中publisher 和Book的关系是ForeignKey的关系,也就是多对一的关系。可能一本书有多个出版商都在出版,那么我们能不能在查询的时候将这些出版商全都查出来呢。这就需要用到select_related的方法了。
ret2=Book.objects.all().select_related(\'publisher\')  #得到所有的文章
r2=ret2[10]  #选择第10篇
return HttpResponse(r2.publisher.state_province)  #得到出版次文章的所有出版商的省份
 
In [9]: book2=Book.objects.all().select_related(\'publisher\')
 
In [10]: b2=book2[2]
(0.000) SELECT "site_prj_book"."id", "site_prj_book"."title", "site_prj_book"."publisher_id", "site_prj_book"."publish_date", "site_prj_publisher"."id", "site_prj_publisher"."name", "site_prj_publisher"."address",
 "site_prj_publisher"."city", "site_prj_publisher"."state_province", "site_prj_publisher"."country", "site_prj_publisher"."website" FROM "site_prj_book" INNER JOIN "site_prj_publisher" ON ("site_prj_book"."publish
er_id" = "site_prj_publisher"."id") LIMIT 1 OFFSET 2; args=()
In [11]: b2.publisher.name
Out[11]: u\'\\xe6\\x88\\x90\\xe9\\x83\\xbd\\xe6\\x97\\xa5\\xe6\\x8a\\xa5\'
 
从下面的调试可以看到,没有再进行SQL查询。

那么我们现在来看下发现查询。什么是反向查询呢,之前的查询是引用方查询被引用方

比如在Book中publisher 和Book的关系是ForeignKey的关系,也就是多对一的关系.如果我想查出这本书的作者还写了哪些书。就需要用到反向查询。
代码如下:
author=Author.objects.filter(first_name=first,last_name=second)
for a in author:
ret.append(a.book_set.all().first())
得到每个作者的对象后用author.book_set.all().first()得到该作者写过的所有书。
同样的通过输入出版商也可以查出该出版商的所有书籍。
publish=Publisher.objects.filter(name=publishname)
for p in publish:
ret_publish.append(a.book_set.all().first())


下面介绍下Values获取字典形式的结果。
def result3(request):
ret=[]
result=Book.objects.values(\'title\',\'authors\',\'publisher\',\'publish_date\')
for r in result:
ret.append(r)
return HttpResponse(ret)
这样能将所有的查询结果以字典的形式呈现出来。我们看到其中publisher和authro都是数字。而不是具体的名称。

我们来看下对应的sql语句:可以看到在sql语句中就是选用的是Publisher和Author的id作为查询条件且使用的是左链接的方式。
SELECT "site_prj_book"."title", "site_prj_book_authors"."author_id", "site_prj_book"."publisher_id", "site_prj_book"."publish_date" FROM "site_prj_book" LEFT OUTER JOIN "site_prj_book_authors" ON ("site_prj_book"."id" = "site_prj_book_authors"."book_id"); args=()
如果不想看字典的形式,而只是想看列表的方式,可以用values_list的方式
result=Book.objects.values_list(\'title\',\'authors\',\'publisher\',\'publish_date\')

Annotate进行聚合,计数,平均数,求和等
下面首先来计算作者的个数:
首先引入模块:django.db.models里面包含所有的计算函数
from django.db.models import *
def result2(request):
result=Author.objects.all().values(\'email\').annotate(count=Count(\'email\')).values(\'email\',\'count\')
return HttpResponse(result)
这个函数的作用是统计作者中的email使用总次数。计每个作者的email出现的次数。Annotate代表以某个元素进行聚类的意思。这里是以每个名字的次数进行聚类。返回结果如下。
我们还可以通过聚类求平均,但是目前的表中没有整数字段。我们在Author中添加一个age的字段。添加方法如下,进入python manage.py shell执行如下的语句。
In [1]: from django.db import connection
 
In [2]: cursor=connection.cursor()
 
In [3]: cursor.execute(\'Alter TABLE Authro add age Integer\')
In [8]: cursor.execute(\'Alter TABLE site_prj_Author add age Integer\')
(0.014) Alter TABLE site_prj_Author add age Integer; args=None
Out[8]: <django.db.backends.sqlite3.base.SQLiteCursorWrapper at 0x3050a80>
In [3]: print Author.objects.all().query
SELECT "site_prj_author"."id", "site_prj_author"."first_name", "site_prj_author"."last_name", "site_prj_author"."email", "site_prj_author"."age" FROM "site_prj_author"
 
添加后我们就可以通过下面的方式进行作者平均年龄的计算了                                                                                                                          
result=Author.objects.all().values(\'age\').annotate(average=Avg(\'age\')).values(\'age\',\'average\')
对应的SQL语句:
SELECT "site_prj_author"."age", AVG("site_prj_author"."age") AS "average" FROM "site_prj_author" GROUP BY "site_prj_author"."age"; args=()
 
链式查询:
过滤成都日报出版且城市是成都的出版商
result=Publisher.objects.filter(name=\'成都日报\').filter(city=\'成都\')
过滤成都出版社且城市不是成都出版商
result=Publisher.objects.filter(name=\'成都日报\').exclude(city=\'成都\')
对应的SQL语句:
SELECT "site_prj_publisher"."id", "site_prj_publisher"."name", "site_prj_publisher"."address", "site_prj_publisher"."city", "site_prj_publisher"."state_province", "site_prj_publisher"."country", "site_prj_publisher"."website" FROM "site_prj_publisher" WHERE (NOT ("site_prj_publisher"."name" = \'成都日报\') AND "site_prj_publisher"."city" = \'成都\');
 
Defer排除不需要的字段:有些模型的字段太多,我们只想看其中几种可以用defer来排查某些字段
result=Author.objects.all().defer(\'age\').query
对应的SQL:可以看到没有查询age字段
SELECT "site_prj_author"."id", "site_prj_author"."first_name", "site_prj_author"."last_name", "site_prj_author"."email" FROM "site_prj_author"
 
相对应的only就是选择需要的字段。
result=Author.objects.all().only(\'age\').query
对应的SQL:
SELECT "site_prj_author"."id", "site_prj_author"."age" FROM "site_prj_author"
 
最后补充model的所有类型描述:

AutoField:一个自动递增的整型字段,添加记录时它会自动增长。你通常不需要直接使用这个字段;如果你不指定主键的话,系统会自动添加一个主键字段到你的model。(参阅自动主键字段)

BooleanField:布尔字段,管理工具里会自动将其描述为checkbox。

CharField:字符串字段,单行输入,用于较短的字符串,如要保存大量文本, 使用 TextField,CharField有一个必填参数:

TextField:一个容量很大的文本字段, admin 管理界面用 <textarea>多行编辑框表示该字段数据。

CommaSeparatedIntegerField:用于存放逗号分隔的整数值。类似 CharField,必须maxlength 参数。

DateField:日期字段,admin 用一个文本框 <input type=”text”> 来表示该字段数据(附带一个 javascript 日历和一个”Today”快捷按键。有下列额外的可选参数:

auto_now:当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 “last-modified” 时间戳
auto_now_add:当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间。

FilePathField:选择指定目录按限制规则选择文件,有三个参数可选, 其中”path”必需的,这三个参数可以同时使用, 参数描述:

path:必需参数,一个目录的绝对文件系统路径。 FilePathField 据此得到可选项目。 Example: “/home/images”;
match:可选参数, 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名。 注意这个正则表达式只会应用到 base filename 而不是路径全名。 Example: “foo。*\\。txt^”, 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif;
recursive:可选参数, 是否包括 path 下全部子目录,True 或 False,默认值为 False。

FloatField:浮点型字段。 必须提供两个 参数, 参数描述:

max_digits:总位数(不包括小数点和符号)
decimal_places:小数位数。如:要保存最大值为 999 (小数点后保存2位),你要这样定义字段:models.FloatField(…,max_digits=5, decimal_places=2),要保存最大值一百万(小数点后保存10位)的话,你要这样定义:models.FloatField(…,max_digits=19, decimal_places=10)

ImageField:类似 FileField, 不过要校验上传对象是否是一个合法图片。它有两个可选参数:height_field 和 width_field,如果提供这两个参数,则图片将按提供的高度和宽度规格保存。 该字段要求 Python Imaging 库。

IntegerField:用于保存一个整数。

IPAddressField:一个字符串形式的 IP 地址, (如 “202.1241.30″)。

NullBooleanField:类似 BooleanField, 不过允许 NULL 作为其中一个选项。 推荐使用这个字段而不要用 BooleanField 加 null=True 选项。 admin 用一个选择框 <select> (三个可选择的值: “Unknown”, “Yes” 和 “No” ) 来表示这种字段数据。

PhoneNumberField:一个带有合法美国风格电话号码校验的 CharField(格式:XXX-XXX-XXXX)。

PositiveIntegerField:类似 IntegerField, 但取值范围为非负整数(这个字段应该是允许0值的…可以理解为无符号整数)

PositiveSmallIntegerField:

正小整型字段,类似 PositiveIntegerField, 取值范围较小(数据库相关)SlugField“Slug” 是一个报纸术语。 slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符。它们通常用于URLs。 若你使用 Django 开发版本,你可以指定 maxlength。 若 maxlength 未指定, Django 会使用默认长度: 50,它接受一个额外的参数:

SlugField:是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.它们通常用于URLs。

SmallIntegerField:类似 IntegerField, 不过只允许某个取值范围内的整数。(依赖数据库)

TimeField:时间字段,类似于 DateField 和 DateTimeField。

URLField:用于保存 URL。 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在(即URL是否被有效装入且没有返回404响应)。

USStateField:美国州名缩写,由两个字母组成(天朝人民无视)。

XMLField:XML字符字段,校验值是否为合法XML的 TextField,必须提供参数:

schema_path:校验文本的 RelaxNG schema 的文件系统路径。


 


 

以上是关于Django之QuerySet 查询的主要内容,如果未能解决你的问题,请参考以下文章

Django框架 之 querySet详解

Django框架 之 querySet详解

重修课程day61(django之补充)

Django之查询知识点总结

Django之ORM查询复习与cookie

Django之models之查询表