关于Django的查询

Django ORM 提供了很多查询方法,满足多种查询的需求,例如 filterexcludevaluesannotate等等,也有很多 WHERE 字段条件查询语法,例如gtstartswith(前缀匹配)contains等等,还有aggregate聚合方法。 详细可参考文档

这里不一一介绍,下面简单说说一些我觉得平时有用的小技巧:

QuerySets are lazy

QuerySets are lazy – the act of creating a QuerySet doesn’t involve any database activity. You can stack filters together all day long, and Django won’t actually run the query until the QuerySet is evaluated.

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)

例如,这里等到print执行,QuerySet才会执行 评估查询计划,转化成对应的select,从db里获取数据

使用F表达式引用字段值

例如查询某个字段大于某个字段值的记录

# 查询字段n_comments 大于字段n_pingbacks的记录
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

QuerySet 的Cache使用

执行查询,获取QuerySet变量,遍历过一次之后,再次使用,则不会再从数据库中获取数据。说白了,可以理解为查询了的内容,作为变量内容,缓存于内存中。但是以下情况没使用cache

# 没有遍历过的情况下,使用索引或分片访问QuerySet对象,这个会出发 sql limit查询
>>> queryset = Entry.objects.all()
>>> print(queryset[5]) # Queries the database
>>> print(queryset[5]) # Queries the database again


# 这样子则使用了cache
>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database
>>> print(queryset[5]) # Uses cache
>>> print(queryset[5]) # Uses cache

面对复杂的查询,可以使用Q表达式

例如,查询 OR查询,虽然这个用起来,很容易会降低查询性能(不能匹配到索引)。支持多个Q表达式组合使用,通过符合&, |, ~ 来表示SQl中的 AND,OR,NOT。例如:

from django.db.models import Q
# 相当于SELECT * from polls WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
Poll.objects.get(
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

要注意:如何Q表达式和普通的查询一起使用,则必须先使用Q表达式:

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

主要访问外键

Django查询外键记录的值时候,会再次访问Db,如果再遍历QuerySet的是否,获取外键记录的值时候,会再次访问Db,这样就造成了遍历查询,这个十分影响查询效率。

可以使用select_related方法,在获取QuerySet的时候,就获取外键的记录值

# Hits the database.
e = Entry.objects.select_related('blog').get(id=5)

# Doesn't hit the database, because e.blog has been prepopulated
# in the previous query.
b = e.blog

# 多个QuerySet
from django.utils import timezone
# Find all the blogs with entries scheduled to be published in the future.
blogs = set()

for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
    # Without select_related(), this would make a database query for each
    # loop iteration in order to fetch the related blog for each entry.
    blogs.add(e.blog)

参考

例子使用官方文档