主题一:什么情况下会触发QuerySet计算
查询集Queryset可以生成、过滤、切片、传递,这些行为都不会引起数据库的操作,除非执行以下操作对查询集进行计算。
(1)迭代:Queryset是可以迭代的,当第一次对Queryset进行迭代操作时,会触发数据库查询操作。比如:
for e in Entry.objects.all(): print(e.headline)
注意:如果只是想要确认一个条目是否存在时,不要采用这种方法,用exists()方法更有效。
(2)切片:可以用python数组切片的语法对查询集进行切片处理。对一个未进行计算的查询集切片会得到另一个未计算的查询集,除非使用了切片中的步长这个参数,如果使用步长,Django会执行数据库查询,并返回一个列表,对一个已经计算过的查询集进行切片处理时也会返回一个列表。
>>> from blog.models import BlogPost
>>> QS_1 = BlogPost.objects.all()
>>> type(QS_1)
<class 'django.db.models.query.QuerySet'>
>>> QS_2 = BlogPost.objects.all()[:5]
>>> type(QS_2)
<class 'django.db.models.query.QuerySet'>
>>> List_1 = BlogPost.objects.all()[:5:2]
>>> type(List_1)
<class 'list'>
注意:虽然对未计算的查询集A进行切片可以得到一个未计算的查询集B,但是不允许对查询集B进行进一步修正,比如对B进行filter过滤和排序操作,因为再对切片后的查询集进行修正,并不能很好的转化成SQL操作指令。报错信息为:AssertionError: Cannot filter a query once a slice has been taken.
(3)序列化/缓存:在进行序列化查询集操作时,所有查询集结果在序列化之前会加载到内存中。
(4)Repr():repr() 方法将对象转化为供解释器读取的形式。在将查询集作为repr参数时,会显示查询集的所有值,同时也会计算查询集。
(5)len():在查询集调用len方法时,查询集会被计算。注意:如果只想确定模型中有多少实际的记录而不需要实际的对象时,可以对模型名字用count()方法,这样更高效,且不需要对数据库进行访问,这也是count函数存在的意义。
(6)Lsit():通过调用list()函数,可以使查询集强行进行计算,比如:
entry_list = list(Entry.objects.all())
(7)bool():如果对查询集使用bool()函数或者进行if操作,会导致查询集的计算,如果查询集至少有一个元素,则bool(QuerySet)为True,否则为False。比如:
if Entry.objects.filter(headline="Test"): print("There is at least one Entry with the headline Test")
注意:如果只想判断查询集中是否存在条目,可以使用exists函数。
主题二:QuerySet的API
一、查询集的公共属性
QuerySet中有两个可以使用的公共属性:第一个就是ordered,比如可以对QuerySet进行Queryset.ordered属性查询,如果结果为True,代表模型会默认排序或者对查询集使用了ordered_by方法。db代表查询集计算时使用的数据库。
注意:因为QuerySet的查询参数存在,所以查询集的子集可以重新制定查询集的内部状态。
二、返回查询集的方法
(1)filter(**kwargs):返回一个匹配给定查询参数的查询集,如果有多个参数,则通过and进行连接。如果想要表达更复杂的查询,可以使用Q对象;
(2)exclude(**kwargs):返回一个不匹配给定查询参数的查询集,如果有多个参数,通过and进行连接;
(3)annotate(*args,**kwargs):给QuerySet中每个对象都添加一个使用查询表达式(聚合函数,F表达式,Q表达式,Func表达式)的字段;
示例:
>>> from django.db.models import F
>>> q = BlogPost.objects.annotate(title_test=F("title"))
>>> print(q[1].title_test)
sgsa'
>>> print(q[1].title)
sgsa'
以上代码相当于给查询到的QuerySet中每一个对象添加一个新字段title_test,该字段是利用对象的title生成的,和title完全一样。
(4)Order_by(*fields):查询集按照选项进行排序后返回,注意:-fields代表降序排列,不加的话代表升序排列,如果有两个排序参数,则按照第一个排序参数进行处理。如果想要随机排序,可以使用order_by(‘?’)。
你可以使用-fields进行降序排序或者使用asc() and desc()函数进行,比如Entry.objects.order_by(Coalesce('summary', 'headline').desc())。
如果想要用一个多对一关联模型进行排序,比如:Entry.objects.order_by('blog'),但是blog并没有设置排序方式,那么会默认使用id进行排序。
此外,需要注意排序对应的参数必须是一个对象对应一个,否则会出错,比如:
class Event(Model): parent = models.ForeignKey( 'self', on_delete=models.CASCADE, related_name='children', ) date = models.DateField() Event.objects.order_by('children__date')
注意如果想让QuerySet不排序,可以使用order_by(),然后里面不加任何参数。
order_by()可以清除所有之前的排序,比如以下排序会按照pub_date排序而不是headline。
Entry.objects.order_by('headline').order_by('pub_date')
警告:排序并不是免费操作,你加入排序的每个字段都会导致数据库的访问占用。排序每次都一次必须要保证用来排序的字段每个对象都是唯一的,比如ID。否则可能同样的排序方式,排序结果可能不同。
(5) reverse(): reverse方法可以将QuerySet中所有的元素排列顺序反过来。使用两次reverse()方法又恢复正常顺序。如果想要访问QuerySet中最后五条顺序,可以这样使用:
my_queryset.reverse()[:5]
注意这和python中切片语句my_queryset[-5:]是不一样的,Python的切片语句最先访问的是倒数第五条记录。而my_queryset.reverse()[:5]最先访问的是最后一条记录。
注意:使用reverse()方法是基于QuerySet是有顺序的,否则reverse()是没有意义的。
(6) distinct(*fields):主要用来消除返回的查询集中相同的对象。
查询集出现相同对象的情况主要出现在一次查询牵涉到多个表。这个时候可以使用distinct方法。
对于不带参数distinct()调用,数据库会比较每一行所有的字段来确定这一行是不是没有重复的,只有在PostgreSQL型数据库中,distinct()中才能带有参数,数据库只会比较参数所代表的字段是不是没有重复。在指定distinct参数的时候,要注意一定要提供一个order_by函数给QuerySet,而且order_by的第一个参数必须是distinct参数中的一个,如果是distinct中的多个参数,则必须保证两个函数参数的顺序一致。
使用distinct也可能出现结果仍然有相同模型对象的情况,比如:
任何被用作order_by()方法参数的字段,该字段列被数据库选中作为排序参考列。这和distinct结合使用时可能会导致一些意外的结果。如果将关联模型的字段作为排序字段时,这些字段会被数据库选中,此时,如果存在两个模型对象是一样的,但是关联对象的字段不一样(也就是用来排序的关联对象字段不一样),因此返回查询集QuerySet的时候会返回两个相同的模型对象。
(7)values(*fields,**expressions):返回一个查询集,查询集中元素并不是模型实例,而是字典。每个字典都代表着一个模型实例,字典的keys就代表着模型的字段名。如果values方法不指定字段参数,则默认包含模型的所有字段。
另外,values还包含可选的**expressions参数,这个expressions是通过annotate()方法进行添加的新字段。比如:
>>> from django.db.models.functions import Lower
>>> Blog.objects.values(lower_name=Lower('name'))<QuerySet [{'lower_name': 'beatles blog'}]>
除了以上方法,还可以使用annotate方法增添一个新的显示字段。比如:
>>> from django.db.models import Count
>>> Blog.objects.values('entry__authors', entries=Count('entry'))<QuerySet [{'entry__authors': 1, 'entries': 20}, {'entry__authors': 1, 'entries': 13}]>
>>> Blog.objects.values('entry__authors').annotate(entries=Count('entry'))<QuerySet [{'entry__authors': 1, 'entries': 33}]>
一些细小但是需要注意的地方:
l 当模型中有一个多对一关联字段foo的时候,在调用values()方法的时候该字段的keys名字为foo或者foo_id(随机一个),值为关联对象的id。如下所示:
>>> Entry.objects.values()<QuerySet [{'blog_id': 1, 'headline': 'First Entry', ...}, ...]>
>>> Entry.objects.values('blog')<QuerySet [{'blog': 1}, ...]>
>>> Entry.objects.values('blog_id')<QuerySet [{'blog_id': 1}, ...]>
l 使用values()查询会限制被数据库选定的列。而order_by方法使用的字段参数(或者默认排序字段)仍会被选中,这可能会影响distinct方法的使用。
l 不能在使用values方法后又使用only或者defer方法,这样会报错。因为这样逻辑上存在问题。
注意:Values方法适用于在只需要一小部分字段而不需要整个模型实例对象时使用。
(8)values_list(*field,flat=Flase,named=False):和values方法类似,只不过该方法返回的是元组而不是字典。每个元组包含的是对应字段(作为values_list参数的字段)或者表达式expressions的值。
如果只传入一个字段作为参数,那么可以将flat参数写成true,values_list这样返回的并不是元素为元组的QuerySet,而是一个元素为单值的QuerySet。如果传入参数不止一个,不能将flat设置为True。
如果把named参数设为True,则返回一个可读性更高的namedtuple。
如果需要得到某个模型的某个字段的值,可以采用在values_list后面加一个get方法实现:
>>> Entry.objects.values_list('headline', flat=True).get(pk=1)'First entry'
注意:values()和values_list()两者都是建立在一行,一个对象的前提下,在遇到多对一的反向关联对象字段或者多对多关联对象字段的情况下,values()或values_list()方法中一个字段可能对应多个关联对象字段,这个时候元组的对应值为none。
(9)none():调用none()会返回一个空的查询集。
(10)all():返回查询集QuerySet的复制,通过对一个模型管理器(默认为objects)或者查询集使用all()方法,可以进一步对查询集进行操作。对一个已经计算过的QuerySet进行调用call方法,可以更新QuerySet的数据。
(11)union(*other_qs, all=False:将两个或者多个查询集合并,union方法会默认去除重复记录,如果要保留重复记录可以将all参数设为True。
注意:union()、intersection()和difference()都会返回调用者的QuerySet的实例模型组成的QuerySet,尽管使用的QuerySet参数是其它模型的查询集。将其它模型的查询集作为这三个函数(union()、intersection()和difference())的参数也是可以的,但是得确保两个QuerySet在数据库的选择单(select list)是一样的(这里的一样指的是字段类型和顺序一致,字段名字可以不相同)。
(12) intersection(*other_qs): 返回两个或者多个查询集的共有元素。限制和union是一致的。
(13) difference(*other_qs): 返回只存在于调用查询集中而不存在与查询集参数中的元素组成的QuerySet。
(14) select_related(*fields):返回一个查询集,该查询集中模型对象会包含与之多对一关联的模型对象数据。这样可以避免之后对多对一关联对象进行访问时再次查询数据库。
如果select_related()不加参数,会包含所有非空的关联对象。如果想要清除所有的由select_related添加的关联对象,可以传递None作为QuerySet参数。比如:
without_relations = queryset.select_related(None)
(15)prefetch_related(*lookups): 和select_related的目的类似,都是为了防止访问查询集对象的关联对象时再次向数据库查询,增加数据库的负担。但是两者实现策略完全不同。
select_related是通过SQL join方法(将多个表格合并成一个)来选择所有字段。这样,只需要在同一个数据库查询就可以得到所有相关联对象。为了避免join(合并)太多个表格,因此将select_related限制在一个多对一关联或者一对一关联的关联对象使用(因为这样最多合并一张表)。而prefetch_related在多个表中分别查询,然后使用Python的joining,这样就可以实现多对多关联、和一对多的关联对象的查询。
比如假设建立以下模型:
from django.db import models class Topping(models.Model): name = models.CharField(max_length=30) class Pizza(models.Model): name = models.CharField(max_length=50) toppings = models.ManyToManyField(Topping) def __str__(self): return "%s (%s)" % ( self.name, ", ".join(topping.name for topping in self.toppings.all()), )
然后运行以下指令将所有Pizza对象存在QuerySet中:
>>> Pizza.objects.all()["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...
问题是每次运行pizza.__str__()时,都需要请求self.topping.all(),这也需要对数据库进行查询,self.topping.all()会建立一个toppings表中每一项条目的数据库查询访问。为了减少数据库的查询工作,可以采用以下语句:
>>> Pizza.objects.all().prefetch_related('toppings')
这也意味着将pizza的所有topping对象数据都存在pizza模型对象中了,接下来每次调用self.toppings.all()时,并不会去访问数据库,而是从QuerySet缓存中找。
注意:任何后续的链式操作都会使得之前缓存的结果失效。并且会生成一个新的数据库查询指令。比如:
>>> pizzas = Pizza.objects.prefetch_related('toppings')
>>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]
这里虽然pizza.toppings.all()虽然已经已经被提前缓存了,但是pizza.toppings.filter()是一个新的查询指令。之前缓存的内容就不能用了。事实上这损害了性能,因为我们做了一个数据库查询,但是并没有使用它。
另外如果我们在关联对象的管理器上调用数据库更改方法(add,remove,clear等),任何关于关联对象的缓存都会清空。
(16) extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None):有时候直接用django语法不能简便地描述一个复杂的WHERE语句,在这种情况下可以使用extra()查询集修饰器:一个查询集的钩子,目的是完成特定SQL语句。
注意:这些额外的查询可能并不适用所有数据引擎,因为我们其实是显示的写数据库代码,这违背了DRY原则,所以我们应该尽量少用。
(17)defer(fields):在一些复杂的数据模型情况下,你的模型可能包含很多字段,其中有些字段可能包含大量的数据(比如textfields),或者需要大量处理能力将其转换成Python对象。如果你在使用一个QuerySet查询结果时,你并不知道你是否需要将这些特定字段,你可以告诉Django不要从数据库中把这些字段信息取出来。这可以通过将这些字段的名字当做参数传递给defer()实现,比如:
Entry.objects.defer("headline", "body")
如果一个查询集QuerySet中含有deferred(延缓、推迟)的字段,该查询集返回的还是模型实例。每个延迟的字段只有在你访问这些字段的时候才会被取出来,而且你访问哪个延迟字段就取出哪个字段。
你可以使用多个defer()方法进行链式操作。重复添加延迟字段也是允许的。同样,也可以延迟加载相关模型的字段(如果该相关模型是通过select_related()加载的),但是需要通过双下划线来区分相关模型字段。比如:
Blog.objects.select_related().defer("entry__headline", "entry__body")
如果想要清空延迟字段,只需要将None作为参数传递给defer().
注意:有些字段是不能延迟的,比如你不能延迟加载主键。如果你使用select_related()方法去获取关联模型,你不能延迟加载连接关联模型和原始模型的字段,这样做会报错。
defer()和only()需要在你明确明白需要什么信息时使用。在查询集加载时,只在你不能确定是否需要额外的字段时才使用defer()。如果你经常使用加载数据模型的一些特定字段。建议把不常加载的字段放入一个其他的模型或者数据库表中,如果不常用列和常用列必须在同一个数据表中,可以创建一个带有Meta.managed = False的模型包含所有常加载的字段,将不常用字段调用defer()。这会使代码更清晰,在Python处理过程中也会消耗更少内存,效率也更高。实例如下:
class CommonlyUsedModel(models.Model): f1 = models.CharField(max_length=10) class Meta: managed = False db_table = 'app_largetable' class ManagedModel(models.Model): f1 = models.CharField(max_length=10) f2 = models.CharField(max_length=10) class Meta: db_table = 'app_largetable'
#Two equivalent QuerySets:以下两个方法是一样的
CommonlyUsedModel.objects.all() ManagedModel.objects.all().defer('f2')
注意,如果调用实例的save()方法,只有加载的字段会被存储。
(18)only(*fields): 和defer()相反,only()里面的字段是不需要被延迟加载的字段。和defer()不同的是,only()方法会立刻替代即将加载的字段集,因此对only()进行链式操作只有最后一个only方法调用生效。比如:
# This will defer all fields except the headline.Entry.objects.only("body", "rating").only("headline")
当然,也可以把defer()和only()结合起来使用,但是注意,only()覆盖一切。
# Final result is that everything except "headline" is deferred.Entry.objects.only("headline", "body").defer("body")
# Final result loads headline and body immediately (only() replaces any# existing set of fields).Entry.objects.defer("body").only("headline", "body")
(19)using(alias): 当你使用超过一个数据库的时候,该方法控制QuerySet该使用哪个数据库进行查询。比如:
# queries the database with the 'default' alias.>>> Entry.objects.all()
# queries the database with the 'backup' alias>>> Entry.objects.using('backup')
(20)select_for_update(nowait=False, skip_locked=False, of=()):返回一个会锁定处理行的查询集。当查询集被计算时,所有匹配的条目在查询结束前都会被锁定(禁止改变和访问)。通常,如果另一个查询早就锁定了本次查询的某一行,那么本次查询就会中断,直到锁定释放。
(21)raw(raw_query, params=None, translations=None): 选择一个原生SQL查询语句执行,返回一个django.db.models.query.RawQuerySet实例。这个RawQuerySet实例可以像常用QuerySet一样迭代,提供对象实例。注意:该方法会覆盖之前的过滤操作。
三、常见问题总结
问题1:当queryset非常巨大时,cache会成为问题。
处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法来获取数据,处理完数据就将其丢弃。
objs = BookInfo.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs: print(obj.title)
#但再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in objs: print(obj.title)
注:
(1) 使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。
(2) queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。
标签:Part,objects,数据库,QuerySet,查询,ORM,values,参数 From: https://www.cnblogs.com/kevin-zsq/p/16835960.html