django学习之Model(四)MakingQuery

时间:2021-07-09 14:32:29

上一篇写到MakingQuey中的filter,本篇接着来。

10)-扩展多值的关系

如果对一个ManyToManyField或ForeignKey的表进行filter过滤查询的话,有2中方法可以用。分别是:

#
Blog.objects.filter(entry__headline__contains='Lennon',
entry__pub_date__year=2008) #
Blog.objects.filter(entry__headline__contains='Lennon').filter(
entry__pub_date__year=2008)

假设,现有一个blog表,它对应了很多的entry的表,而且这个blog既有含有“Lennon”的entry,也有含有2008的entry,但是没有那种2者都有的entry,那么第一个方法过滤的是2者都有的entry,所以没有符合条件的blog返回。第一种方法可以理解为“并且”的意思。第二种是第一个filter返回含有“Lennon”的blog,然后在这个blog集合中,再选有2008的,所以会有blog返回。开始理解起来很费劲,抓住特点:一个blog对应多个entry。例如,blog1对应了entry1,entry2,entry3,其中entry1只有“Lennon”,没有2008;entry2只有2008,没有“Lennon”;entry3则什么都没有。那么用第一种方法来filter的时候,则没有满足既有Lennon又有2008的entry;第二种方法的第一个filter,因为entry1有Lennon,所以blog1为返回值,然后对返回值blog1进行filter,条件为有2008,而blog1中的entry2是有2008的,所以blog1作为返回值。

这个也适用于exlude().

11)-filters可以引用model中的fields

class F

目前为止的例子中,都是用field中的值来作为filter的条件来进行数据查询。但是如果需要把一个field的值与他所在的model的其他field的值呢?

django提供F()表达式来实现这种比较。F()的实例是在一个query中来对model中的field进行引用。这些引用可以作为filter的条件来进行过滤。

例如,要查询blog的entry,comments满足的数量比pingbacks的多,可以用F()来作用在pingbacks上,然后把结果作为filter的条件:

>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

注意:F()表达式中是n_comments比大n_pingbacks。所以,也有倍数、加、减这样的操作可以来比较:

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)#
n_comments比n_pingbacks的2倍多

 >>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))#n_comments与n_pingbacks的和比rating多

gt是多理解为>,lt理解为<.

而下面的例子则是两个字符串一样:

>>> Entry.objects.filter(authors__name=F('blog__name'))

12)-pk

是primary key的意思:

>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # pk implies id__exact
# Get blogs entries with id 1, 4 and 7
>>> Blog.objects.filter(pk__in=[1,4,7]) # Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)

同样应该有这些符号的组合,分别是__双下划线,lt,gt,exact等等。

如果查询含有%符号的,下例给出:

>>> Entry.objects.filter(headline__contains='%')

同样的_下划线符号也是,django会自动来处理的,就像上边这行程序这样写就行了。

5-QuerySet的缓存

每个QuerySet都有缓存来降低对数据库database的访问。弄明白了之后,可以写出高效率的代码。

在新创建的QuerySet中,缓存是空的。当QuerySet第一次 被计算的时候,django把QuerySet放在其缓存中,然后把具体要查询的数据项作为结果返回。把QuerySet这种缓存的行为时刻记住,有的时候很容易使用不当的。例如下面,建立了2个QuerySet,分别使用后,就丢掉了,这样造成了2次访问database,而且第二个e很可能不是第一个e,因为在第二个发生之前,database很可能已经被写入新数据或删除什么东西了:

>>> print([e.headline for e in Entry.objects.all()])
>>> print([e.pub_date for e in Entry.objects.all()])

正确的做法如下,建立个变量来存放QuerySet,让它一直放在内存中,然后每次用的时候从内存中去读取:

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

注意,第一行并没有touch the database,访问数据库发生在第二行代码。

QuerySet也并不是总缓存结果的,当计算queryset的一部分的时候,要缓存,但是如果只是用一个下一层的queryset来访问结果的时候,并没有缓存。这也意味着,可以用数组分片或者一个索引来限制queryset而不产生缓存,例如下面的例子,显示只定义了一个queryset来存放objects,然后用数组来访问其中的某一个,这样就没有缓存,每次都要重新访问数据库:

>>> queryset = Entry.objects.all()
>>> print queryset[5] # Queries the database
>>> print queryset[5] # Queries the database again

然而,如果整个queryset都被计算了,那么就会产生缓存:

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print queryset[5] # Uses cache
>>> print queryset[5] # Uses cache

还有一些例子也是整个querset都被计算了,当然产生了缓存:

>>> [entry for entry in queryset]
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

6-用Q对象来进行复杂的查询

在filter()方法中,用关键字来查询,这些关键字是AND的关系,即”并且“,如果需要进行更复杂的查询,例如OR关系,可以用Q对象这种方法。

Q对象(django.db.models.Q)是用来封装关键字的集合的。这些关键字包括上文的lookup查询中所提到的关键字。例如,下面这个Q对象封装了LIKE查询:

from django.db.models import Q
Q(question__startswith='What')

Q对象可以用逻辑符号&或者|来连接,结果返回一个Q对象:

Q(question__startswith='Who') | Q(question__startswith='What')

同等于SQL语句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

可以把Q对象组合成复杂的表达,也可以用~符号,代表”NOT,非“的意思:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每个查询函数(filter(), get(), exclude())都需要有关键字,Q对象就可以作为这些查询函数的关键字:

Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

翻译成SQL语句就相当痛苦了:

SELECT * from polls WHERE question LIKE 'Who%'
AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

查询函数也可以混合使用keywords和Q object,但是要注意,Q object必须在keyword之前定义:

#正确的
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who')
#错误的
Poll.objects.get(
question__startswith='Who',
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))

7-比较对象

用python标准的比较操作符==就可以进行model实例的比较。而这实际上实在比较primarykey的值:

>>> some_entry == other_entry
>>> some_entry.id == other_entry.id#实际上

如果model的primarykey不叫id,没关系,照样还是在比较primarykey,例如一个model的primarykey是name,则下面2行代码是同样意思的:

>>> some_obj == other_obj
>>> some_obj.name == other_obj.name

8-删除对象

delete()用来进行对象的删除,而且是立即生效,并且没有返回值:

e.delete()

也可以批量来进行删除操作,每个QuerySet都有delete()操作,可以把QuerySet中的所有对象都删掉:

Entry.objects.filter(pub_date__year=2005).delete()

delete是Manager的一个方法,但是由于怕用户误操作而没有显式的显现出来。因为delete是纯粹的在SQL中执行的方法,所以不需要用户在过程中去认为的调用。如果你在一个model的class定义中自己定义了一个delete()的话,那么就相当于覆盖掉了Manager提供的delete,这样就需要用户自己去调用了,而且这种自己定义的delete()还不能批量的来操作,也就是不能作为QuerySet中的参数来调用,而是需要自己对每个object单独进行delete一次。

django提供的delete()方法,会删除由foreignkey来关联到一起的object:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

这种串联的用法是通过 on_delete 参数传递给Foreignkey的。

delete是Manager的一个方法,为了防止用户误操作,所以不用显式调用,但是如果确实需要调用delete()来删除所有的objects的话,应该明确的指出来这个QuerySet:

Entry.objects.all().delete()

9-复制model实例

尽管没有built-in的方法来实现model 实例的复制,但是可以用model的所有的fields的值来创建一个新的实例,最简单的情况就是,先把pk设置成None,然后再save:

 blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1 blog.pk = None
blog.save() # blog.pk == 2

思考:也就是,第1行代码只是把Blog内容放在了blog这个变量中,并没写进去数据库,只有调用save()方法时,才发生写的操作。而且每当调用save(),都相当于要把这个数据(blog)写进数据库。而数据库的pk是唯一而不能重复的,所有先要把blog.pk设成None,然后再操作,由SQL语句把pk由依次递增为2了。

如果有model的继承时,会略复杂一些:

class ThemeBlog(Blog):
theme = models.CharField(max_length=200) django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

因为继承的工作原理,这次需要把pk和id都设成None了:

django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4

这个过程并没有复制相关联的objects,如果想要复制这种关系,需要多写一些代码了。在本文的例子中,Entry和Author是多对多的关系(ManyToManyField):

 entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors = old_authors # saves new many2many relations

今天到这,明天继续!