3.2 查 询
一旦数据模型创建完成,Django会自动为模型提供一个数据库抽象API,允许创建、检索、更新和删除对象。在本节,我们会学习如何使用这些API,同时学习Django执行查询语句的过程。
3.2.1 保存对象
为了在Python对象中表示数据表数据,Django使用了一个直观的系统:模型类表示数据库表,该类的实例表示数据库表中的特定记录。
在创建对象后,通过调用save( )方法可以将对象存到数据库,如下面的示例代码:
Django会将上面的代码转换成一条INSERT语句。在显式调用save( )方法前,Django不会访问数据库。
要保存对对象的修改,同样使用save( )方法,示例代码如下:
上面的代码会被Django转换成一条UPDATE SQL语句。同样的,在调用save()方法前,Django不会访问数据库。
3.2.2 获取对象
要从数据库中检索对象,需要通过模型类的管理器构造查询集合。
QuerySet表示数据库中的对象集合。可以在一个QuerySet上应用零个、一个或多个过滤器,过滤器根据给定的参数缩小查询结果的范围。在SQL术语中,QuerySet等同于SELECT语句,而过滤器是限制子句,如WHERE或LIMIT。
每个模型至少有一个Manager,可以使用Manger来获取QuerySet。默认情况下通过调用模型类的objects来获取Manager,Manager是获取QuerySet的主要来源。示例代码如下:
>>> Product.objects <django.db.models.manager.Manager object at ...>
可以通过调用Manager的all( )方法来获取数据表中的所有对象,代码如下:
>>> all_products = Product.objects.all()
通常情况下,我们只需要获取全部对象的子集,这就要用到过滤器。例如,获取2018年创建的商品:
>>> Product.objects.filter(date_created__year=2018)
对QuerySet加上过滤器返回的也是一个QuerySet,因此可以将过滤器连在一起。例如,下面的查询将返回2018年所有非12月份创建的商品:
>>> Product.objects.filter(date_created__year=2018).exclude(date_created__month=12)
使用Python的数组切片语法可以对QuerySet的结果数量进行限制,就像SQL语法中的LIMIT和OFFSET子句一样。代码如下:
>>> Product.objects.all()[5:10] # 相当于OFFSET 5 LIMIT 5
有时候我们也需要连表查询,如我们想知道用户名为zhangpeng的人的所有订单,查询语句如下:
>>> Order.objects.filter(user__username="zhangpeng")
在这个例子中,Django会将代码转成一条JOIN语句,联合User模型和Order模型对应的数据表查到我们想要的数据。
上面使用的filter( )和exclude( )方法,对应SQL中的AND查询。如果想使用其他查询语法(如OR查询),则可以使用Q对象。
Q对象是用于封装关键字参数集合的对象。可以使用&和|来组合Q对象,结果也是一个Q对象,代码如下:
以上代码会被大致翻译成下面的SQL语句:
SELECT * from product WHERE title LIKE 'Women%' AND (date_created = '2005-05-02' OR date_created = '2005-05-06')
3.2.3 懒加载和缓存
QuerySet采用了懒加载的机制。创建QuerySet不会访问数据库,只有在用到结果的时候才去访问数据库,下面列出使用查询结果的情况。
- 遍历:QuerySet是可迭代的,在第一次迭代的时候执行数据库查询。
- 切片:对未评估的QuerySet进行切片会返回另一个未评估的QuerySet,但如果使用切片语法的step参数,则Django将执行数据库查询,并返回一个列表。
- 使用repr( )方法:对QuerySet调用该方法会让Django执行数据库查询。
- 使用len( )方法:同repr( )。
- 使用list( )方法:同repr( )。
- 使用bool( )方法:同repr( )。
为了减少对数据库的负载,每个QuerySet都会保留一份缓存。理解这个缓存的工作过程对于编码是很重要的。
在新创建的QuerySet中,缓存为空。在第一次评估QuerySet且因此发生数据库查询后,Django会将查询结果保存在QuerySet的缓存中,并返回已明确请求的结果。例如,下面的代码会查询两次数据库:
>>> print([p.date_created for p in Product.objects.all()]) >>> print([p.title for p in Product.objects.all()])
注意,这两次查询的结果可能不同。要想使用缓存,可以这样写:
>>> queryset = Product.objects.all() >>> print([p.date_created for p in queryset]) >>> print([p.title for p in queryset])
QuerySet并不总是缓存其结果。当仅评估部分查询集时,其将会检查缓存;但如果评估的那部分没有缓存,则后续查询返回的项目不会缓存。具体地说,使用数组切片或索引限制查询数量将不会填充缓存。具体来看一个例子,涉及的代码如下:
>>> queryset = Product.objects.all() >>> print(queryset[5]) # 查询数据库 >>> print(queryset[5]) # 再次查询数据库
不过,如果QuerySet已经被评估了,则缓存将被使用。代码如下:
>>> queryset = Product.objects.all() >>> [product for product in queryset] # 查询数据库 >>> print(queryset[5]) # 使用缓存 >>> print(queryset[5]) # 使用缓存
3.2.4 聚合查询
在实际的应用场景中,经常需要对查到的数据集做一些简单运算并返回结果,这称为聚合查询。Django的ORM是支持聚合查询的,如统计查询结果的数量:
>>> Product.objects.count()
对某个字段求平均值,如对所有购物篮的每一条记录中的商品数量求平均值,在实际中,这样查询基本没有意义,我们在这里只是为了说明求平均值的用法:
>>> from django.db.models import Avg >>> Line.objects.all().aggregate(Avg('quantity'))
在日常业务中,还有求最大值、最大值与平均值的差等统计需求,这些都可以用QuerySet来实现:
aggregate( )子句的参数描述了想要计算的聚合值。
也可以对QuerySet中的每个项生成聚合,这里需要用到annotate( )方法。annotate( )的使用方法和aggregate( )的使用方法相同,每个参数都描述了要计算的聚合。例如,下面的代码统计每个品类商品的总数:
>>> from django.db.models import Count >>> q = Category.objects.annotate(Count('products'))