django QuerySet

掉了的东西就不要捡了,路过的风景也不要打听了,失去的人也别再纠缠了。

查询集

  一旦你建立好数据模型,Django 会自动为你生成一套数据库抽象的API,可以让你创建、检索、更新和删除对象。
  Django 使用一种直观的方式把数据库表中的数据表示成Python 对象:一个模型类代表数据库中的一个表,一个模型类的实例代表这个数据库表中的一条特定的记录。

  所以如果要获取数据库里的一条对象,那么 django 通过模型中的管理器构造一个查询集,来从你的数据库中获取对象。查询集表示从数据库中取出来的对象的集合。它可以含有零个、一个或者多个过滤器。过滤器基于所给的参数限制查询的结果。 从SQL 的角度,查询集和SELECT 语句等价,过滤器是像WHERE 和LIMIT 一样的限制子句。

In [2]: Group.objects.all()
Out[2]: <QuerySet [<Group: devops>, <Group: hr>, <Group: op>, <Group: ops>]>

注意 all()方法返回包含数据库中所有对象的一个查询集。但在通常情况下,你往往想要获取的是完整数据集的一个子集。要创建这样一个子集,你需要在原始的的查询集上增加一些过滤条件。

两个最普遍的途径是:

  • filter(**kwargs)
    返回一个新的查询集,它包含满足查询参数的对象。
  • exclude(**kwargs)
    返回一个新的查询集,它包含不满足查询参数的对象。
In [4]: Group.objects.filter(id=1)
Out[4]: <QuerySet [<Group: devops>]>

In [5]: Group.objects.exclude(id=1)
Out[5]: <QuerySet [<Group: hr>, <Group: op>, <Group: ops>]>

1、查询集是惰性执行的

创建查询集不会带来任何数据库的访问。你可以将过滤器保持一整天,直到查询集 需要求值时,Django 才会真正运行这个查询。看下这个例子:

In [8]: u1 = User.objects.all()
In [9]: u2 = u1.exclude(id__exact=1)
In [10]: u3 = u1.filter(id__exact=1)
In [11]: print(u3)

虽然它看上去有三次数据库访问,但事实上只有在最后一行(print())时才访问一次数据库。一般来说,只有在“请求”查询集 的结果时才会到数据库中去获取它们。

2、缓存和查询集

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

# 下面两行相同的数据库查询将执行两次,显然增加了数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录
In [13]: print([u.username for u in User.objects.all()])
In [14]: print([u.email for u in User.objects.all()])

因为在两次请求期间有可能有不同的条目被添加进来或删除掉。为了避免这个问题,只需保存查询集并重用

# 下面两次 print 都重用了 qs 查询集缓存,实际上只执行了一次数据库查询
In [16]: qs = User.objects.all()
In [17]: print([u.username for u in qs]) # 查询数据库
In [18]: print([u.email for u in qs]) # 使用缓存

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

例如,重复获取查询集对象中一个特定的索引将每次都查询数据库:(这部分直接摘抄官方的例子)

>>> queryset = Entry.objects.all()
>>> print queryset[5] # 查询数据库
>>> print queryset[5] # 仍旧查询数据库

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

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # 查询数据库
>>> print queryset[5] # 使用缓存
>>> print queryset[5] # 使用缓存

下面是一些其它例子,它们会使得全部的查询集被求值并填充到缓存中:

>>> [entry for entry in queryset]	# 查询库
>>> bool(queryset)
>>> entry in queryset
>>> list(queryset)

另外,简单地打印查询集不会填充缓存。因为__repr__()调用只返回全部查询集的一个切片。

3、其他查询集方法

Django 提供了一系列 的QuerySet筛选方法,用于改变 QuerySet 返回的结果类型或者SQL查询执行的方式。

大多数情况下,需要从数据库中查找对象时,你会使用all()get()filter()exclude()。查询集方法的完整列表,请参见查询集API 参考

这里说一下 values()values_list()

  • values():返回一个ValuesQuerySet —— QuerySet 的一个子类,迭代时返回字典而不是模型实例对象。需要注意的是,返回的不是list,不要直接当list来用了。对ValuesQuerySet遍历,每一个元素是“字典”dict。

当不传入参数时,返回这个model的所有字段

In [20]: Group.objects.values()
Out[20]: <QuerySet [{'id': 1, 'name': 'devops'}, {'id': 11, 'name': 'hr'}, {'id': 2, 'name': 'op'}, {'id': 10, 'name': 'ops'}]>

当传入参数时,只会列出你指定的参数

In [22]: Group.objects.values('name')
Out[22]: <QuerySet [{'name': 'devops'}, {'name': 'hr'}, {'name': 'op'}, {'name': 'ops'}]>

也可以加上filter,filter在前或者后面都是一样的

In [24]: Group.objects.filter(pk=1).values('name')
Out[24]: <QuerySet [{'name': 'devops'}]>

下面两种写法是相同的

Blog.objects.values().order_by('id')
Blog.objects.order_by('id').values()
  • 当values() 与distinct() 一起使用时,注意排序可能影响最终的结果。详细信息参见distinct() 中的备注。
  • 如果values() 子句位于extra() 调用之后,extra() 中的select 参数定义的字段必须显式包含在values() 调用中。values() 调用后面的extra() 调用将忽略选择的额外的字段。
  • 在values() 之后调用only() 和defer() 不太合理,所以将引发一个NotImplementedError。
  • values_list():和values一样,只是返回的不是字典而是元组。

官方给了一个例子比较好理解。如果只传递一个字段,你还可以传递flat 参数。如果为True,它表示返回的结果为单个值而不是元组。一个例子会让它们的区别更加清晰:

>>> Entry.objects.values_list('id').order_by('id')
[(1,), (2,), (3,), ...]

>>> Entry.objects.values_list('id', flat=True).order_by('id')
[1, 2, 3, ...]
  • 如果有多个字段,传递flat 将发生错误。
  • 如果你不传递任何值给values_list(),它将返回模型中的所有字段,以它们在模型中定义的顺序。
  • 注意,这个方法返回ValuesListQuerySet。这个类的行为类似列表。大部分时候它足够用了,但是如果你需要一个真实的Python 列表对象,可以对它调用list(),这将会对查询集求值。

最后还有几点注意,摘自涂伟忠大大自强学堂内容

  • (1). 如果只是检查 Entry 中是否有对象,应该用 Entry.objects.all().exists()
  • (2). QuerySet 支持切片 Entry.objects.all()[:10] 取出10条,可以节省内存
  • (3). 用 len(es) 可以得到Entry的数量,但是推荐用 Entry.objects.count()来查询数量,后者用的是SQL:SELECT COUNT(*)
  • (4). list(es) 可以强行将 QuerySet 变成 列表

  

参考阅读