情感测试
情感测试

您现在的位置: 情感测试简介_情感测试玩法 > 情感测试玩法 > Django框架模型3查询操

Django框架模型3查询操

发布时间:2021-8-28 13:19:32   点击数:
黑色素生成能力检测 http://m.39.net/pf/a_6210222.html

要检索数据库中的对象,就要为model类构造一个查询集QuerySet,一个QuerySet就代码数据库中的一组数据,它可以有一个或很多个,也可以通过filter根据给定的参数对数据集进行筛选。在SQL术语中,QuerySet相当于SELECT语句,filter相当于where或limit这样的限定从句。

查询对象

all()查询所有结果,返回QuerySet。

article_list=Article.objects.all()article_listQuerySet[Article:python数据类型,Article:Python快速入门,Article:MySQL快速入门,Article:MySQL快速入门,Article:中间件通讯,Article:最近,火遍朋友圈的地摊经济,Article:create]

我们可以使用for循环遍历一个个文章对象:

forarticleinarticle_list:print(article,type(article))python数据类型classarticle.models.ArticlePython快速入门classarticle.models.ArticleMySQL快速入门classarticle.models.ArticleMySQL快速入门classarticle.models.Article中间件通讯classarticle.models.Article最近,火遍朋友圈的地摊经济classarticle.models.Articlecreateclassarticle.models.Article

上面打印出标题,是因为我们在model中通过__str__方法返回标题:

def__str__(self):returnself.title

get(**kwargs)查询单个数据,只会返回一个实例对象,如果所有条件都不满足或者是满足条件的有多个,将抛出DoesNotExist异常,所以一般情况下,我们会这么用:

try:article=Article.objects.get(pk=1)#在django的ORM查询中,数据库的主键可以用PK代替,官方推荐使用pkarticle=Article.objects.get(id=1)#等同于select*fromhello_articlewhereid=1;exceptArticle.DoesNotExist:print(异常处理)

filter(**kwargs)返回满足筛选条件的QuerySet。

Article.objects.filter(status=0)#select*fromarticlewherestatus=0

exclude(**kwargs)排除符合条件的数据,返回一个QuerySet。

Article.objects.exclude(status=0)#select*fromarticlewherestatus0

*values(field)返回一个ValueQuerySet,一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列。

articles=Article.objects.all()#获取所有Article对象articlesQuerySet[Article:python数据类型,Article:Python快速入门,Article:MySQL快速入门,Article:MySQL快速入门,Article:中间件通讯,Article:最近,火遍朋友圈的地摊经济,Article:create]articles_value=Article.objects.values(title,tags)articles_valueQuerySet[{title:python数据类型,tags:None},{title:Python快速入门,tags:None},{title:MySQL快速入门,tags:None},{title:MySQL快速入门,tags:4},{title:中间件通讯,tags:1},{title:中间件通讯,tags:2},{title:最近,火遍朋友圈的地摊经济,tags:None},{title:create,tags:None}]

同样我们可以使用for循环进行遍历:

forarticle_dictinarticles_value:print(article_dict){title:python数据类型,tags:None}{title:Python快速入门,tags:None}{title:MySQL快速入门,tags:None}{title:MySQL快速入门,tags:4}{title:中间件通讯,tags:1}{title:中间件通讯,tags:2}{title:最近,火遍朋友圈的地摊经济,tags:None}{title:create,tags:None}

从上面可以看出遍历出来的是字典。

*values_list(field)它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列。

articles_values_list=Article.objects.values_list(title,tags)#输出结果:QuerySet[(python数据类型,None),(Python快速入门,None),(MySQL快速入门,None),(MySQL快速入门,4),(中间件通讯,1),(中间件通讯,2),(最近,火遍朋友圈的地摊经济,None),(create,None)]

遍历获取的是元组:

forarticle_tupleinarticles_values_list:print(article_tuple)(python数据类型,None)(Python快速入门,None)(MySQL快速入门,None)(MySQL快速入门,4)(中间件通讯,1)(中间件通讯,2)(最近,火遍朋友圈的地摊经济,None)(create,None)

*order_by(args)根据给定的参数进行排序。

#正序article_list=Article.objects.order_by(id)#输出结果:QuerySet[Article:python数据类型,Article:Python快速入门,Article:MySQL快速入门,Article:MySQL快速入门,Article:中间件通讯,Article:最近,火遍朋友圈的地摊经济,Article:create]#倒序,字段前面加-article_list=Article.objects.order_by(-id)#输出结果:#QuerySet[Article:create,Article:最近,火遍朋友圈的地摊经济,Article:中间件通讯,Article:MySQL快速入门,Article:MySQL快速入门,Article:Python快速入门,Article:python数据类型]#可以根据多个#首先根据阅读量进行排序,如果阅读量相同,则根据点赞量进行排序article_list=Article.objects.order_by(read_num,like_num)

一定要注意的一点是,多个order_by,会把前面排序的规则给打乱,而使用最后面的排序方式,例如:

article_list=Article.objects.order_by(read_num).order_by(like_num)

它会根据点赞数排序,而不是两者,因为前面一个排序得到一个queryset,然后又来排序了。

distinct()

给查询结果去重,一般都是在values和values_list方法后面,因为他们两个可以或取出指定字段数据,然后再去重。

这里需要注意的是,当模型类中指定了默认排序字段,那么当我们使用distinct方法进行去重时,默认会按照我们指定的排序字典进行去重,会导致去重结果不是我们想要的,所以要么我们在模型类中不指定排序字段,如果需要指定排序,则在查询集中,在distinct方法前面加上order_by方法,并且order_by方法中的字段就是我们要去重的字段数据。

count()将返回当前查询到的数据的总数。

article_count=Article.objects.count()#7,相当于selectcount(*)fromtb_article

first()获取查询到的数据的第一条数据,如果使用用了order_by排序,那么将获取排序后的第一条,如果没有用order_by,那么将按主键进行默认排序。

article=Article.objects.first()#Article:python数据类型

last()与first方法一样,只不过是获取的是最后一条的数据。

article=Article.objects.last()#Article:create

latest(field_name=None)根据提供的参数field_name来进行排序,然后提取离现在最近的一条数据。

latest_article=Article.objects.latest(id)#Article:create

field_name通常是DateField、DateTimeField或IntegerField一起使用。应该避免和其他字段一起使用,因为语义上是错误的。这两种方法主要是提供代码的方便性和可读性,如果和非时间字段一起使用,会带来新的混淆。

还可以在模型的Meta类中先定义get_latest_by属性:

classArticle(models.Model):...public_date=models.DateTimeField(blank=True,null=True)classMeta:db_table=tb_articleget_latest_by=public_date#用来排序的字段

然后直接使用:

latest_article=Article.objects.latest()

earliest(field_name=None)用法和latest一样,只是这个是获取最久远的一个。

exists()返回True或者False,在数据库层面执行select(1)asafromtable_namelimit1的查询。判断QuerySet是否有数据,用这个接口是最合适的,不要用count或者len(queryset)这样的操作来判断是否存在。相反,如果可以预期接下来会用到Query的方式来做判断,这样可以减少一次查询请求。

Article.objects.exists()#True

update(**kwargs)更新数据方法,这个方法可以对查询出来的QuerySet里面的所有元素进行更新,并且更新参数的个数也是不限的。另外要注意的是,因为get方法返回的不是QuerySet对象,因此使用get方法提取出来的数据不能使用update方法。出于这种情况,建议应该使用filter(pk=xx)来替代get(pk=xxx)方法。并且,使用get出来的模型,修改数据后再save,会更新所有的数据,比update的效率更低。

Article.objects.filter(id=2).update(title="文章标题")

**delete():**删除QuerySet中的模型。

查询条件

我们可以在调用这些方法的时候传递不同的参数来实现查询需求。相当于是SQL语句中的where语句后面的条件,在ORM层面,这些查询条件都是使用field__condition方式使用。

exact使用精确的=进行查找。如果提供的是一个None,那么在SQL层面就是被解释为NULL。示例代码如下:

article=Article.objects.get(id=2)#Article:python数据类型article=Article.objects.get(id__exact=2)#Article:python数据类型article_list=Article.objects.filter(id=2)#QuerySet[Article:python数据类型]article_list=Article.objects.filter(id__exact=2)#QuerySet[Article:python数据类型]

**iexact:**同exact,只是忽略大小写。

contains判断某个字段是否包含了某个数据。

articles=Article.objects.filter(title__contains=快速)#QuerySet[Article:Python快速入门,Article:MySQL快速入门,Article:MySQL快速入门]#相当于selecttitlefromtb_articlewheretitlelike%快速%;#我们可以使用查看执行的SQL语句:查询结果集.query

要注意的是,在使用contains的时候,翻译成的sql语句左右两边是有百分号的,意味着使用的是模糊查询。而exact翻译成sql语句左右两边是没有百分号的,意味着使用的是精确的查询。

**icontains:**同contains,只是忽略大小写。

in提取那些给定的field的值是否在给定的容器中。容器可以为list、tuple或者任何一个可以迭代的对象,包括QuerySet对象。示例如下:

articles=Article.objects.filter(id__in=[2,3,4,5])#QuerySet[Article:python数据类型,Article:Python快速入门]#相当于selecttitlefromtb_articlewhereidin(2,3,4,5)

**gt:**某个field的值要大于给定的值。示例代码如下:

articles=Article.objects.filter(id__gt=4)#将所有id大于4的文章全部都找出来。#select...whereid4;

**gte:**类似于gt,是大于等于。

**lt:**小于。

**lte:**小于等于。

startswith字段的值以某个值开始,大小写敏感。

articles=Article.objects.filter(title__startswith=py)#QuerySet[Article:python数据类型]#相当于select...wheretitlelikepy%

**istartswith:**同startswith,但忽略大小写。

endswith以某个字符串结尾。

articles=Article.objects.filter(title__endswith=入门)#QuerySet[Article:Python快速入门,Article:MySQL快速入门,Article:MySQL快速入门]#相当于select...wheretitlelike%入门

**iendswith:**同startswith,但忽略大小写。

range范围查询,多用于时间范围。

fromarticle.modelsimportCategory,Article,Tagfromdatetimeimportdatetimestart_date=datetime(,7,4,10)end_date=datetime(,7,4,11)articles=Article.objects.filter(create_time__range=(start_date,end_date))#QuerySet[Article:python数据类型,Article:Python快速入门,Article:MySQL快速入门,Article:MySQL快速入门,Article:中间件通讯,Article:最近,火遍朋友圈的地摊经济,Article:create]start_date=datetime(,7,4,2)end_date=datetime(,7,4,3)articles=Article.objects.filter(update_time__range=(start_date,end_date))#QuerySet[Article:python数据类型,Article:Python快速入门,Article:MySQL快速入门,Article:MySQL快速入门,Article:中间件通讯,Article:最近,火遍朋友圈的地摊经济,Article:create]#print(create_time)#-07-:35:04.#print(update_time)#-07-:25:53.

以上代码的意思是提取所有发布时间在start_date到end_date之间的文章。

需要注意的是,以上提取数据,不会包含最后一个值。也就是不会包含end_date的文章。

date针对某些date或者datetime类型的字段。可以指定date的范围。并且这个时间过滤,还可以使用链式调用。

fromarticle.modelsimportCategory,Article,Tagfromdatetimeimportdatearticles=Article.objects.filter(create_time__date=date(,7,4))#相当于select*fromtb_articlewheredate(create_time)=-07-04

year根据年份进行查找,代码如下:

fromarticle.modelsimportCategory,Article,Tagfromdatetimeimportdatetimearticles=Article.objects.filter(create_time__year=)#QuerySet[Article:python数据类型,Article:Python快速入门,Article:MySQL快速入门,Article:MySQL快速入门,Article:中间件通讯,Article:最近,火遍朋友圈的地摊经济,Article:create]#相当于select*fromtb_articlewherecreate_timebetween-01-:00:00and-12-:59:59.articles=Article.objects.filter(create_time__year__gte=)#select*fromtb_articlewherecreate_time=-01-:00:00

关于日期类的查询还有很多,比如month、day等,具体等需要时查文档即可。

isnull判断是否为空。

articles=Article.objects.filter(create_time__isnull=True)#相当于select*fromtb_articlewherecreate_timeisnull;

**切片:**可以用python的数组切片语法来限制你的QuerySet以得到一部分结果。它等价于SQL中的LIMIT和OFFSET。

articles=Article.objects.all()[:2]#返回前2个对象。#QuerySet[Article:python数据类型,Article:Python快速入门]

Django不支持对查询集做负数索引。

切片可以用来实现翻页显示内容,比如每一页显示10条内容可以利用切片,第page页,如下:

articles=Article.objects.all()#获取Article的QuerySet集counts=articles.count()#总记录数per_page=4#每页显示多少条记录forpageinrange(counts/per_page+counts%per_page):forarticleinarticles[page*per_page:per_page(page+1)]:print(article.title)F和Q查询F查询

之前的查询都是对象的属性与常量值比较,2个属性怎么比较呢?

Django中提供F来做这样的比较。F的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值。

#F语法形式filter(字段名__运算符=F(字段名))

例如,要查询阅读量大于等于点赞数的文章。可以这样实现:

articles=Article.objects.all()forarticleinarticles:ifarticle.read_num=article.like_num:print(article)

如果使用F表达式,那么一行代码就可以搞定。示例代码如下:

fromdjango.db.modelsimportF#要查询阅读量大于等于点赞数的文章articles=Article.objects.filter(read_num__gte=F(like_num))#要查询阅读量大于等于2倍点赞数的文章articles=Article.objects.filter(read_num__gte=F(like_num)*2)

F对象还可以对自身属性进行运算。例如,将公司全体员工薪水都增加元,如果按照一般流程,应该是先从数据库中提取所有的员工工资到内存中,然后进行增加,最后保存数据库中。

employees=Employee.objects.all()foremployeeinemployees:employee.salary+=employee.save()

这个需要先将数据从数据库中提取出来,再进行计算,最后再保存到数据库。

而使用F对象,可以来优化ORM操作数据库的,F对象并不会马上从数据库中获取数据,而是在生成SQL语句的时候,动态的获取传给F表达式的值。

fromdjang.db.modelsimportFEmployee.object.update(salary=F(salary)+)Q查询

现在,我们要查询阅读量大于20,并且点赞大于10的文章。

#使用链式调用,2个filterarticles=Article.objects.filter(read_num__gte=20).filter(like_num__gte=10)#使用一个filterarticles=Article.objects.filter(read_num__gte=20,like_num__gte=10)

以上是一个并集查询,可以简单的通过传递多个条件进去来实现。

但是,如果想要实现一些复杂的查询语句,比如要查询阅读量大于20,或者是点赞大于10的文章。那就没有办法通过传递多个条件进去实现了。这时候就需要使用Q表达式来实现了。

#Q语法形式:filter(Q(字段名__运算符=值))fromdjango.db.modelsimportQarticles=Article.objects.filter(Q(read_num__gte=20)

Q(like_num__gte=10))

以上是进行运算,当然还可以进行其他的运算,比如有和~(非)等。

或Q()

Q()并且Q()Q()非~Q()fromdjango.db.modelsimportQ#查询阅读量大于20,并且点赞大于10的文章articles=Article.objects.filter(Q(read_num__gte=20)Q(like_num__gte=10))#获取id不等于3的文章articles=Article.objects.exclude(id=3)#或articles=Article.objects.filter(~Q(id=3))聚合查询aggregate

在SQL中,我们可以使用聚合函数来提取数据。比如提取某个商品销售的数量,那么可以使用Count,如果想要知道商品销售的平均价格,那么可以使用Avg。

在ORM中聚合函数是通过aggregate方法来实现的,返回的是一个字典。

Avg,Max,Min,Count,Sum语法形式:aggregate(Xxx(字段))fromdjango.db.modelsimportAvg,Max,Min,Count,Sum#Avg:求平均值。求文章平均阅读量read_avg=Article.objects.aggregate(Avg(read_num))#{read_num__avg:20.0}#其中read_num__avg的结构是按照构成的,如果想要修改默认的名字,那么可以将Avg赋值给一个关键字参数。read_avg=Article.objects.aggregate(read_avg=Avg(read_num))#{read_avg:20.0}#Count:统计格数。统计总共有多少篇文章article_num=Article.objects.aggregate(article_num=Count(id))#{article_num:7}#如果需要去重,使用distinct=True,例如,获取用户表中所有的不重复的密码总共有多少个pwd_num=Account.objects.aggregate(pwd_num=Count("password",distinct=True))#其他Max,Min,Sum类似#多个聚合result=Article.objects.aggregate(Count(id),Sum(read_num),Max(read_num),Min(read_num))#{id__count:7,read_num__sum:,read_num__max:20,read_num__min:20}分组查询annotate

我们有时候查看一些博客,侧边栏有分类列表,显示博客已有的全部文章分类。

现在想在分类名后显示该分类下有多少篇文章,即统计每个分类下有多少篇文章,该怎么做呢?

这个时候,我们就可以用到annotate,annotate为QuerySet中的每个对象添加一个独立的汇总值。先按类名进行分组,然后再统计每个组分别为多少数量即可。

fromdjango.db.modelsimportAvg,Max,Min,Count,Sum#统计每个分类下有多少篇文章。#这里先对Category进行分类,使用annotate分组,再使用Count统计每个分组下的数量,同时给每一个对象增加这个数量。#这相当于给Category动态增加了属性articles__count,而这个属性的值来源于Count("articles")category_list=Category.objects.annotate(Count("articles"))#类似反向查询,使用related_name定义的名称来查#QuerySet[Category:Python基础,Category:数据库知识]相当于SQLselecttb_category.id,tb_category.name,count(tb_article.id)asarticles__countfromtb_categoryleftjointb_articleontb_category.id=tb_article.category_idgroupbytb_category.idforcategoryincategory_list:#annotate为QuerySet中每一个对象都增加了一个独立的articles__count值。print(category.name,category.articles__count)#Python基础2#数据库知识1#统计每个分类阅读最多的文章#方式一:category_list=Category.objects.annotate(article_read_max=Max("articles__read_num"))#QuerySet[Category:Python基础,Category:数据库知识]相当于SQLselecttb_category.id,tb_category.name,MAX(tb_article.read_num)asarticle_read_maxfromtb_categoryleftjointb_articleon(tb_category.id=tb_article.category_id)groupbytb_category.idforcategoryincategory_list:print(category.name,category.article_read_max)#Python基础22#数据库知识25#方式2:#values就等同于groupby,返回的是一个字典,values_list就等同于groupby,,返回的是一个元祖#正向查询,外键属性__字段Article.objects.values(category__name).annotate(article_read_max=Max("read_num"))#QuerySet[{category__name:Python基础,article_read_max:22},{category__name:数据库知识,article_read_max:25},{category__name:None,article_read_max:33}]相当于SQLselecttb_category.name,max(tb_article.read_num)asarticle_read_maxfromtb_articleleftjointb_categoryontb_article.category_id=tb_category.idgroupbytb_category.name#给每个文章对象实例,添加一个category__name属性,用来显示对应分类的名称,这里使用F取对应的分类名称。articles=Article.objects.annotate(category_name=F("category__name"))print(articles.query)#输出对应的SQL语句selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_num,tb_category.nameascategory_namefromtb_articleleftjointb_categoryON(tb_article.category_id=tb_category.id)aggregate和annotate的区别:aggregate:返回使用聚合函数后的字段和值,返回的是一个字典,执行后就是最终结果。annotate:返回的是一个QuerySet,可以继续进行查询。annotate的聚合结果是针对每行数据的,而不是整个查询结果。在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组groupby。

Django的aggregate和annotate方法属于高级查询方法,主要用于组合查询,可以大大提升数据库查询效率。当你需要对查询集(queryset)的某些字段进行聚合操作时(比如Sum,Avg,Max),请使用aggregate方法。如果你想要对数据集先进行分组(GroupBy)然后再进行某些聚合操作或排序时,请使用annotate方法。

value与annotate

value与annotate的顺序不同时,查询结果大相径庭。

1)value在annotate前面时,相当于groupby。

ret=Student.objects.values(gender).annotate(count=Count(sid)).order_by(-count)print(ret)

等价于以下sql语句:

selectgender,COUNT(sid)AScountfromstudentgroupbygenderorderbycountDESC

执行结果:

QuerySet[{gender:女,count:6},{gender:男,count:5}]

可以看到结果是按性别分组的。

2)value放在annotate后面时,相当于select。

ret=Student.objects.annotate(count=Count(sid)).values(gender).order_by(-count)print(ret)

等价于以下sql语句:

SELECTgenderFROMstudentGROUPBYsidORDERBYCOUNT(sid)DESC

执行结果:

QuerySet[{gender:女},{gender:男},{gender:女},{gender:女},{gender:男},{gender:女},{gender:男},{gender:男},{gender:女},{gender:女},{gender:男}]

annotate前面没有value时,默认按当前表的主键分组。

可以看到结果并没有按性别分组,而是每个学生作为一组,说明上述结论是正确的

关联查询

关联查询有两种方式:一种是创建类对象,通过类对象查询。另外一种是通过模型类查询。

例如:

①查询分类名字包含“Python”的所有文章信息;

②查询文章标题中包含“python”的文章所属的分类信息;

③查询标签含有“快速入门”的文章;

④查询标签为“快速入门”的文章所属的分类信息;

我们分别使用2种方式处理。

方式一:类对象查询

①通过查询,即根据一类,查询多类的信息:

#创建一类的对象#一类对象.多类类名小写_set.all()#或者使用related_name设置的名字

②通过查询,即根据多类,查询一类的信息:

#创建多类的对象#多类对象.关联属性(外键)方式二:模型类查询

Django提供一种强大而又直观的方式来处理查询中的关联关系,它能自动处理join联系。要做跨关系查询,就使用双下划线来连接模型(model)间关联字段的名称,直到想要的model为止。

①已知条件是一类信息,查询多类信息。(正向查询):

#多类类名.objects.filter(多类中的外键属性__一类属性名__条件)#例如,查询分类名字包含‘Python’的所有文章信息articles=Article.objects.filter(category__name__icontains=python)#QuerySet[Article:python数据类型,Article:Python快速入门]

正向查询按字段,比如上面Article中的category。

②已知条件为多类信息,查询一类信息。(反向查询):

#一类类名.objects.filter(多类类名小写__多类属性名__条件)#若使用related_name设置了名字,则需要使用设置的名字#查询文章标题中包含python的文章所属的分类信息categories=Category.objects.filter(articles__title__icontains=python)#QuerySet[Category:Python基础,Category:Python基础]

反向查询按子表的模型类名小写,如果设置了related_name,则使用related_name替换表名。

③多个表进行查询:

#查询标签为‘快速入门’的文章所属的分类信息categories=Category.objects.filter(articles__tags__name__icontains=快速入门)#QuerySet[Category:Python基础]

多对多和一对多类似。

最后对模型类查询来个小总结:**

1、想查询哪个类中的信息,就以哪个类名.object.filter开头

2、多类类名开头,filter括号内关联属性开头。一类类名开头,filter括号内多类类名小写开头。后面都是条件判断(如上面的一类有条件判断或多类有条件判断)。

查询集QuerySet

Django的ORM中存在查询集的概念,也称查询结果集QuerySet,表示从数据库中获取的对象集合。

当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):

all:返回所有数据;filter:返回满足条件的数据;exclude:返回满足条件之外的数据;order_by:对结果进行排序;andsoon....

对查询集可以再次调用过滤器进行过滤,即链式调用方式,如:

articles=Article.objects.filter(read_num__gte=20).filter(like_num__gte=10)

在每个方法的执行结果上可以继续调用同样的方法,因为每个方法的返回值都是它自己,也就是QuerySet。

两大特性

1)QuerySet是惰性的

Django中的QuerySet本质上是一个懒加载的对象,创建查询集QuerySet的动作不涉及任何数据库操作,只是返回一个QuerySet对象,你可以一直添加过滤器,在这个过程中,Django不会执行任何数据库查询,除非QuerySet被执行,等真正调用它时才会执行查询。

fromarticle.modelsimportCategory,Article,Tagfromdjango.dbimportconnectionarticle_list=Article.objects.all()#此时还未访问数据库操作print(connection.queries)#[]该方法会打印出所有执行过的sql语句

我们可以看到在打印connection.quries的时候打印的是一个空的列表。说明上面的QuerySet并没有真正的执行。

在以下情况下QuerySet会被转换为SQL语句执行:

#1.遍历forarticleinArticle.objects.all():#第一次遍历的时候查询,后面再次遍历就不会查询了。print(article)#执行相关SQL:selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_numfromtb_articleforarticleinArticle.objects.filter(title__icontains=快速):print(article)forsqlinconnection.queries:#第一次遍历的时候查询,后面再次遍历就不会查询了。print(=*30)print(sql)selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_numfromtb_articlewheretb_article.titlelike%快速%#2.使用步长做切片操作#3.调用len函数用来获取QuerySet中总共有多少条数据#4.调用list函数用来将一个QuerySet对象转换为list对象#5.对某个QuerySet进行判断

2)缓存机制

每个QuerySet都包含一个缓存,以减少对数据库的访问。要编写高效代码,就要理解缓存是如何工作的。

使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询时会使用缓存的数据,减少了数据库的查询次数。

场景一:如下是两个QuerySet,这2个是无法使用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。

[article.idforarticleinArticle.objects.all()]print(connection.queries)#[{sql:SELECT

SQL_AUTO_IS_NULL,time:0.},{sql:SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED,time:0.},{sql:SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`content`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`FROM`tb_article`,time:0.}][article.idforarticleinArticle.objects.all()]print(connection.queries)#[{sql:SELECT

SQL_AUTO_IS_NULL,time:0.},{sql:SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED,time:0.},{sql:SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`content`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`FROM`tb_article`,time:0.},{sql:SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`content`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`FROM`tb_article`,time:0.}]

通过上面输出所执行的SQL记录,我们可以看出第二次执行同样的语句,明显比第一次多了一条SQL语句,即第二次又进行数据库查询操作。

如何优化,减少对数据库的操作呢??

我们先来测试几个数据,看看效果:

articles=Article.objects.all()print(connection.queries)[][article.idforarticleinarticles]#访问数据库#[2,5,6,7,8,9,10]print(connection.queries)#[{sql:SELECT

SQL_AUTO_IS_NULL,time:0.},{sql:SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED,time:0.},{sql:SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`content`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`FROM`tb_article`,time:0.}][article.idforarticleinarticles]#使用缓存#[2,5,6,7,8,9,10]print(connection.queries)#[{sql:SELECT

SQL_AUTO_IS_NULL,time:0.},{sql:SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED,time:0.},{sql:SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`content`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`FROM`tb_article`,time:0.}]

通过上面测试可知,只要简单的保存QuerySet,然后重用即可,上面第二个就使用了缓存,只要查询一次数据库即可,提高了性能。

但是,查询集并不总是缓存结果,当查询集执行部分查询时,会先检查缓存,如果它没有被填充,部分查询返回的结果不会被缓存。这意味着,使用切片查询不会填充缓存。

articles=Article.objects.all()#没有访问数据库print(connection.queries)#[]#重复的切片查询每次都会访问数据库,例如下面相当于只查询了一条记录,没有覆盖全部articles[1]#访问数据库articles[2]#再次访问数据库#但是,如果整个查询集已经被求值,切片查询会使用缓存articles=Article.objects.all()[article.idforarticleinarticles]#查询数据库articles[1]#使用缓存articles[2]#使用缓存#下面是一些会填充缓存的操作,因为会全部查询,即覆盖所有[article.idforarticleinarticles]#列表推导式,会遍历queryset所有的bool(articles)articleinarticleslist(articles)几个提升性能的接口

在优化Django项目,提高性能时,尤其要考虑这几种接口的用法。

**defer接口:**把不需要展示的字段做延迟加载,比如我们在获取文章列表的时候,文章的内容我们是不需要的,因此这时候我们就可以使用defer来过滤掉一些字段,但是当我们需要用到这个字段时,在使用时回去加载。这个字段跟values有点类似,只不过defer返回的不是字典,而是模型。

fromarticle.modelsimportCategory,Article,Tagfromdjango.dbimportconnectionarticles=Article.objects.all().defer(content)#没有访问数据库print(connection.queries)#[]forarticleinarticles:#这里遍历的时候会执行一次数据库查询#因为在上面提取的时候过滤了content#这个地方重新获取content,会执行数据库查询,for遍历多少次,即执行多少次数据库查询,加上上面一次,即要查询N+1次print(article.content)forsqlinconnection.queries:print(=*30)print(sql)#输出结果:[]本篇将针对Python做一个简单入门,帮助小白迅速了解Python。本篇将针对Mysql做一个简单入门,帮助小白迅速了解MySQL。本篇将针对Mysql做一个简单入门,帮助小白迅速了解MySQL。开始学习通讯领域,我们从CTI开始吧相信,最近朋友圈都在讨论各种有关摆地摊的话题,你是否有种冲动也去试下呢?添加不存在的数据,将数据直接存入数据库,创建一个新的对象,将它保存并放在新创建的对象。(一对一,多对多)=============================={sql:SELECT

SQL_AUTO_IS_NULL,time:0.}=============================={sql:SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED,time:0.}==============================#下面一条除了content字段,其他字段值都查出来,因为前面过滤此字段{sql:SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`FROM`tb_article`,time:0.}==============================#后面查询content字段和id字段{sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=2,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=5,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=6,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=7,time:0.}==============================

上面代码会产生N+1的查询问题,在实际使用时千万要注意!

defer虽然能过滤字段,但是有些字段是不能过滤的,比如id,即使你过滤了,也会提取出来。

**only接口:**与defer刚好相反,only提取指定的字段。如果只想获取文章标题title的内容。

fromarticle.modelsimportCategory,Article,Tagfromdjango.dbimportconnectionarticles=Article.objects.all().only(title)#没有访问数据库print(connection.queries)#[]forarticleinarticles:#这里遍历的时候会执行一次数据库查询,只查询title字段值#因为在上面提取的时候过滤了title#这个地方重新获取title,会执行数据库查询,for遍历多少次,即执行多少次数据库查询,加上上面一次,即要查询N+1次print(article.content)forsqlinconnection.queries:print(=*30)print(sql)#[]本篇将针对Python做一个简单入门,帮助小白迅速了解Python。本篇将针对Mysql做一个简单入门,帮助小白迅速了解MySQL。本篇将针对Mysql做一个简单入门,帮助小白迅速了解MySQL。开始学习通讯领域,我们从CTI开始吧相信,最近朋友圈都在讨论各种有关摆地摊的话题,你是否有种冲动也去试下呢?添加不存在的数据,将数据直接存入数据库,创建一个新的对象,将它保存并放在新创建的对象。(一对一,多对多)=============================={sql:SELECT

SQL_AUTO_IS_NULL,time:0.}=============================={sql:SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`title`FROM`tb_article`,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=2,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=5,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=6,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=7,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=8,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=9,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`content`FROM`tb_article`WHERE`tb_article`.`id`=10,time:0.}

**select_related:**在提取某个模型的数据的同时,也提前将相关联的数据提取出来。比如提取文章数据,可以使用select_related将category信息提取出来,以后再次使用article.category的时候就不需要再次去访问数据库了。可以减少数据库查询的次数。示例代码如下:

fromarticle.modelsimportCategory,Article,Tagfromdjango.dbimportconnectionarticles=Article.objects.all()#没有访问数据库print(connection.queries)#[]forarticleinarticles:#查询数据库print(article.category)#再次查询数据库Python基础Python基础NoneNoneNone数据库知识Noneforsqlinconnection.queries:print(=*30)print(sql)=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`content`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`FROM`tb_article`,time:0.}=============================={sql:SELECT`tb_category`.`id`,`tb_category`.`name`FROM`tb_category`WHERE`tb_category`.`id`=2,time:0.}=============================={sql:SELECT`tb_category`.`id`,`tb_category`.`name`FROM`tb_category`WHERE`tb_category`.`id`=2,time:0.}=============================={sql:SELECT`tb_category`.`id`,`tb_category`.`name`FROM`tb_category`WHERE`tb_category`.`id`=4,time:0.}

上面外键产生了N+1查询问题,我们可以使用select_related来解决此类问题。

articles=Article.objects.all().select_related("category")#没有访问数据库print(connection.queries)#[]print(articles.query)selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_num,tb_category.id,tb_category.namefromtb_articleleftouterjointb_categoryon(tb_article.category_id=tb_category.id)forarticleinarticles:#查询数据库,category数据也会一次性查出来print(article.category)#使用缓存forsqlinconnection.queries:print(=*30)print(sql)[]=============================={sql:SELECT

SQL_AUTO_IS_NULL,time:0.}=============================={sql:SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED,time:0.}=============================={sql:SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`content`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`,`tb_category`.`id`,`tb_category`.`name`FROM`tb_article`LEFTOUTERJOIN`tb_category`ON(`tb_article`.`category_id`=`tb_category`.`id`),time:0.}

selected_related只能用在一对多或者一对一中,不能用在多对多或者多对一中。比如可以提前获取文章的作者,即正向获取关联对象时,关联对象会被缓存,后续根据外键访问时这个实例,就会从缓存中获得。

但是不能通过作者获取这个作者的文章(逆向关联),或者是通过某篇文章获取这个文章所有的标签(多对多的关系)。这个还得使用下面的接口解决。

**prefetch_related:**这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法可用于多对多关系字段,也可用于反向外键关系(related_name)的查询问题。例如:

#获取标题中带有`python`字符串的文章以及它的所有标签,articles=Article.objects.prefetch_related("tags").filter(title__icontains="python")print(articles.query)#通过这条命令查看在底层的SQL语句selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_numfromtb_articlewheretb_article.titlelike%python%forarticleinarticles:#查询article文章对象的同时提取tag标签对象print(article.title)#使用缓存print(article.tags.all())#使用缓存python数据类型QuerySet[Tag:MySQL,Tag:快速入门]Python快速入门QuerySet[Tag:快速入门]forsqlinconnection.queries:print(=*30)print(sql)触发的相关SQL,for循环时产生访问数据库的操作,查询article文章对象的同时提取tag标签对象==============================selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_numfromtb_articlewheretb_article.titlelike%python%==============================select(tb_article_tags.article_id)as_prefetch_related_val_article_id,tb_tag.id,tb_tag.namefromtb_taginnerjointb_article_tagson(tb_tag.id=tb_article_tags.tag_id)wheretb_article_tags.article_idIN(2,5)

第一条SQL查询仅仅是获取标题中带有python字符串的Article对象,第二条比较关键,它选取中间表tb_article_tags中article_id带有python字符串的行,然后和tb_tag表内联(INNERJOIN)得到结果表。

现在问题来了,如果我们获取tags对象时只希望获取带有入门的tag对象怎么办呢?

articles=Article.objects.prefetch_related(tags).filter(title__icontains="python")print(articles.query)selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_numfromtb_articlewheretb_article.titlelike%python%forarticleinarticles:#for循环时产生访问数据库的操作,查询article文章对象的同时提取tag标签对象print(article.title)#使用缓存print(article.tags.filter(name__icontains=入门))#之前用prefetch_related缓存的数据将会被忽略掉python数据类型QuerySet[Tag:快速入门]Python快速入门QuerySet[Tag:快速入门]forsqlinconnection.queries:print(-*30)print(sql)触发的相关SQL,for循环时产生访问数据库的操作,查询article文章对象的同时提取tag标签对象------------------------------selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_numfromtb_articlewheretb_article.titlelike%python%------------------------------select(tb_article_tags.article_id)as_prefetch_related_val_article_id,tb_tag.id,tb_tag.namefromtb_taginnerjointb_article_tagson(tb_tag.id=tb_article_tags.tag_id)wheretb_article_tags.article_idin(2,5)------------------------------#但是这里多了2条查询语句,一个为查询id=2对应的文章和id=5对应文章。selecttb_tag.id,tb_tag.namefromtb_taginnerjointb_article_tagson(tb_tag.id=tb_article_tags.tag_id)where(tb_article_tags.article_id=2andtb_tag.namelike%入门%)limit21------------------------------selecttb_tag.id,tb_tag.namefromtb_taginnerjointb_article_tagson(tb_tag.id=tb_article_tags.tag_id)where(tb_article_tags.article_id=5andtb_tag.namelike%入门%)limit21

注意:在使用QuerySet的时候,一旦在链式操作中改变了数据库请求,之前用prefetch_related缓存的数据将会被忽略掉,这会导致Django重新请求数据库来获得相应的数据,从而造成性能问题,这里提到的改变数据库请求指各种filter()、exclude()等等最终会改变SQL代码的操作。而all()并不会改变最终的数据库请求,因此是不会导致重新请求数据库的。

那如果确实是想要在查询的时候指定过滤条件该如何做呢,这时候我们可以使用django.db.models.Prefetch来实现,Prefetch这个可以提前定义好queryset,给prefect_related方法添加条件和属性,示例代码如下:

fromdjango.db.modelsimportPrefetcharticles=Article.objects.prefetch_related(Prefetch(tags,queryset=Tag.objects.filter(name__icontains=入门))).filter(title__icontains="python")print(articles.query)#通过这条命令查看在底层的SQL语句forarticleinarticles:#产生两条查询语句,分别查询article和tagsprint(article.title)#使用缓存print(article.tags.filter(name__icontains=入门))selecttb_article.id,tb_article.title,tb_article.content,tb_article.category_id,tb_article.create_time,tb_article.update_time,tb_article.read_num,tb_article.like_numfromtb_articlewheretb_article.titlelike%python%forarticleinarticles:print(article.title)tags=article.tags.filter(name__icontains=入门)fortagintags:print(tag)python数据类型快速入门Python快速入门快速入门forsqlinconnection.queries:print(=*30)print(sql)=============================={sql:SELECT

SQL_AUTO_IS_NULL,time:0.}=============================={sql:SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED,time:0.}=============================={sql:"SELECT`tb_article`.`id`,`tb_article`.`title`,`tb_article`.`content`,`tb_article`.`category_id`,`tb_article`.`create_time`,`tb_article`.`update_time`,`tb_article`.`read_num`,`tb_article`.`like_num`FROM`tb_article`WHERE`tb_article`.`title`LIKE%python%",time:0.}=============================={sql:"SELECT(`tb_article_tags`.`article_id`)AS`_prefetch_related_val_article_id`,`tb_tag`.`id`,`tb_tag`.`name`FROM`tb_tag`INNERJOIN`tb_article_tags`ON(`tb_tag`.`id`=`tb_article_tags`.`tag_id`)WHERE(`tb_tag`.`name`LIKE%入门%AND`tb_article_tags`.`article_id`IN(2,5))",time:0.}=============================={sql:"SELECT`tb_tag`.`id`,`tb_tag`.`name`FROM`tb_tag`INNERJOIN`tb_article_tags`ON(`tb_tag`.`id`=`tb_article_tags`.`tag_id`)WHERE(`tb_tag`.`name`LIKE%入门%AND`tb_article_tags`.`article_id`=2AND`tb_tag`.`name`LIKE%入门%)",time:0.}=============================={sql:"SELECT`tb_tag`.`id`,`tb_tag`.`name`FROM`tb_tag`INNERJOIN`tb_article_tags`ON(`tb_tag`.`id`=`tb_article_tags`.`tag_id`)WHERE(`tb_tag`.`name`LIKE%入门%AND`tb_article_tags`.`article_id`=5AND`tb_tag`.`name`LIKE%入门%)",time:0.}预览时标签不可点收录于话题#个上一篇下一篇

转载请注明:http://www.zmax-alibaba.com/qgwf/138101.html

网站简介 | 发布优势 | 服务条款 | 隐私保护 | 广告合作 | 合作伙伴 | 版权申明 | 网站地图

当前时间: