Model 查询

单表查询

链式过滤

QeurySet的筛选结果本身还是QeurySet,所以可以将筛选语句链接在一起。像这样:

>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime(2005, 1, 30)
... )

这个例子最开始获取数据库中所有对象的一个QeurySet,之后增加一个过滤器,然后又增加一个排除,再之后又是另外一个过滤器。最后的结果仍然是一个QeurySet,它包含标题以”What“开头、发布日期在2005年1月30日至当天之间的所有记录。

 

过滤后的QeurySet是独立的

每次你筛选一个QeurySet,得到的都是全新的另一个QeurySet,它和之前的QeurySet之间没有任何绑定关系。每次筛选都会创建一个独立的QeurySet,它可以被存储及反复使用。

例如:

>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

这三个QeurySet都是独立的。第一个是一个基础的QeurySet,包含所有标题以“What”开头的记录。第二个QeurySet是第一个的子集,它增加另外一个限制条件,排除pub_date 为今天和将来的记录。第三个QeurySet同样是第一个的子集,它增加另外一个限制条件,只选择pub_date 为今天或将来的记录。原始的QeurySet(q1)不会受到筛选过程的影响。

 

限制查询集

可以使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMITOFFSET 子句。

例如,下面的语句返回前面5 个对象(LIMIT 5):

>>> Entry.objects.all()[:5]

下面这条语句返回第6 至第10 个对象(OFFSET 5 LIMIT 5):

>>> Entry.objects.all()[5:10]

不支持负的索引(例如Entry.objects.all()[-1])。

通常,查询集 的切片返回一个新的查询集 —— 它不会执行查询。有一个例外,是如果你使用Python 切片语法中"step"参数。例如,下面的语句将返回前10 个对象中每隔2个对象,它将真实执行查询:

>>> Entry.objects.all()[:10:2]

若要获取一个单一的对象而不是一个列表(例如,SELECT foo FROM bar LIMIT 1),可以简单地使用一个索引而不是切片。例如,下面的语句返回数据库中根据标题排序后的第一条Entry

>>> Entry.objects.order_by('headline')[0]

它大体等同于:

>>> Entry.objects.order_by('headline')[0:1].get()

然而请注意,如果没有对象满足给定的条件,第一条语句将引发IndexError而第二条语句将引发DoesNotExist更多细节参见get()

 

字段查询

字段查询是指如何指定SQL WHERE 子句的内容。它们通过查询集方法filter()exclude()get() 的关键字参数指定。

查询的关键字参数的基本形式是field__lookuptype=value(中间是两个下划线)。例如:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')

翻译成SQL(大体)是:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

这是如何实现的

Python 定义的函数可以接收任意的键/值对参数,这些名称和参数可以在运行时求值。更多信息,参见Python 官方文档中的关键字参数

查询条件中指定的字段必须是模型字段的名称。但有一个例外,对于ForeignKey你可以使用字段名加上_id 后缀。在这种情况下,该参数的值应该是外键的原始值。例如:

>>> Entry.objects.filter(blog_id=4)

如果你传递的是一个不合法的参数,查询函数将引发 TypeError

这些数据库API 支持大约二十多种查询的类型;字段查询参考 中可以找到完整的参考。为了让你尝尝鲜,下面是一些你可能用到的常见查询:

exact

“精确”匹配。例如:

>>> Entry.objects.get(headline__exact="Man bites dog")

将生成下面的SQL:

SELECT ... WHERE headline = 'Man bites dog';

如果你没有提供查询类型 —— 即如果你的关键字参数不包含双下划线 —— 默认假定查询类型是exact

例如,下面的两条语句相等:

>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)         # __exact is implied

这是为了方便,因为exact 查询是最常见的情况。

iexact

大小写不敏感的匹配。所以,查询:

>>> Blog.objects.get(name__iexact="beatles blog")

将匹配标题为"Beatles Blog""beatles blog" 甚至"BeAtlES blOG"Blog

contains

大小写敏感的包含关系测试。例如:

Entry.objects.get(headline__contains='Lennon')

大体可以翻译成下面的SQL:

SELECT ... WHERE headline LIKE '%Lennon%';

注意,这将匹配'Today Lennon honored' 但不能匹配'today lennon honored'

还有一个大小写不敏感的版本,icontains

startswith, endswith
分别表示以XXX开头和以XXX结尾。当然还有大小写不敏感的版本,叫做istartswithiendswith

同样,这里只是表面。完整的参考可以在字段查询参考中找到。

 

跨关联关系的查询

Django 提供一种强大而又直观的方式来“处理”查询中的关联关系,它在后台自动帮你处理JOIN若要跨越关联关系,只需使用关联的模型字段的名称,并使用双下划线分隔,直至你想要的字段:

下面这个例子获取所有Blogname'Beatles Blog'Entry 对象:

>>> Entry.objects.filter(blog__name='Beatles Blog')

这种跨越可以是任意的深度。它还可以反向查询若要引用一个“反向”的关系,只需要使用该模型的小写的名称。

下面的示例获取所有的Blog 对象,它们至少有一个Entry 对象headline 包含'Lennon'

>>> Blog.objects.filter(entry__headline__contains='Lennon')

如果你在多个关联关系直接过滤而且其中某个中介模型没有满足过滤条件的值,Django 将把它当做一个空的(所有的值都为NULL)但是合法的对象。这意味着不会有错误引发。例如,在下面的过滤器中:

Blog.objects.filter(entry__authors__name='Lennon')

(如果有一个相关联的Author 模型),如果Entry 中没有找到对应的author,那么它将当作其没有name,而不会因为没有author 引发一个错误。通常,这就是你想要的。唯一可能让你困惑的是当你使用isnull 的时候。因此:

Blog.objects.filter(entry__authors__name__isnull=True)

返回的Blog 对象包括author __name 为空的Blog对象,以及author__name不为空但author__name关联的entry __author 为空的对象。如果你不需要后者,你可以这样写:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

 

缓存和查询集

每个查询集都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。

在一个新创建的查询集中,缓存为空。首次对查询集进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果(例如,如果正在迭代查询集,则返回下一个结果)。接下来对该查询集 的求值将重用缓存的结果。

请牢记这个缓存行为,因为对查询集使用不当的话,它会坑你的。例如,下面的语句创建两个查询集,对它们求值,然后扔掉它们:

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

这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Entry被添加进来或删除掉。

为了避免这个问题,只需保存查询集并重新使用它:

>>> 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.

何时查询集不会被缓存(部分查询)

查询集不会永远缓存它们的结果。当只对查询集的部分进行求值时会检查缓存, 但是如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。特别地,这意味着使用切片或索引来限制查询集将不会填充缓存。

例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:

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

然而,如果已经对全部查询集求值过,则将检查缓存:

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

 

一对多关系

正向查询

如果一个模型具有ForeignKey,那么该模型的实例将可以通过属性访问关联的(外部)对象。

例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog # Returns the related Blog object.

你可以通过外键属性获取和设置。和你预期的一样,对外键的修改不会保存到数据库中直至你调用save()例如:

>>> e = Entry.objects.get(id=2)
>>> e.blog = some_blog
>>> e.save()

一对多关联关系的前向访问在第一次访问关联的对象时被缓存。以后对同一个对象的外键的访问都使用缓存。例如:

>>> e = Entry.objects.get(id=2)
>>> print(e.blog)  # Hits the database to retrieve the associated Blog.
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

注意select_related() QuerySet方法递归地预填充所有的一对多关系到缓存中。例如:

>>> e = Entry.objects.select_related().get(id=2)
>>> print(e.blog)  # Doesn't hit the database; uses cached version.
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

反向查询

如果模型I有一个ForeignKey,那么该ForeignKey 所指的模型II实例可以通过一个Manager返回前面有ForeignKey的模型I的所有实例。默认情况下,这个Manager的名字为foo_set,其中foo 是源模型的小写名称。Manager返回的查询集可以用上一节提到的方式进行过滤和操作。

例如:

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() 
# Returns all Entry objects related to Blog. # b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon') >>> b.entry_set.count()

你可以在ForeignKey 定义时设置related_name 参数来覆盖foo_set 的名称。例如,如果Entry 模型改成blog = ForeignKey(Blog, related_name='entries'),那么上面的示例代码应该改成这样:

>>> b = Blog.objects.get(id=1)
>>> b.entries.all() 
# Returns all Entry objects related to Blog. # b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon') >>> b.entries.count()

 

多对多关系

多对多关系的两端都会自动获得访问另一端的API。这些API 的工作方式与上面提到的“方向”一对多关系一样。

唯一的区别在于属性的名称:定义 ManyToManyField 的模型使用该字段的属性名称,而“反向”模型使用源模型的小写名称加上'_set' (和一对多关系一样)。

一个例子可以让它更好理解:

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

未完待续。。。。。。。。。。。。




posted @ 2017-05-19 18:02  Vincen_shen  阅读(182)  评论(0)    收藏  举报