前一篇介绍了Django ORM 的部分知识,本文我们继续介绍选择关系对象、聚合对象、附加对象、调用数据库函数、数据分组、查询通用关系等知识点,并介绍如何创建对象、更新对象、删除对象、创建事务以及如何执行原始SQL语句等。
选择关系的对象
有时我们需要加载关联的对象。看例子。
queryset = Product.objects.all()
再修改模板 hello.html:
…
<li> {{ product.title }} – {{ product.collection.title }} </li>
…
刷新浏览器,出现了与之前一样加载慢的问题,每次迭代都向数据库发送查询语句,有1000多个SQL语句,因为queryset只会加载product表,每一次查collection的title都向数据发送查询指令.
所以,需要预加载product的关系表,用的就是select_related方法.
queryset = Product.objects.select_related(‘collection’).all()
我们在调用all之前调用 select_related 方法将关系表 collection 预加载.
再刷新网页,发现网页已变快了.django后台也在product和collection间建立了连接.
关系表还可以再扩展,比如再扩展collection的关系表.
queryset = Product.objects.select_related(‘collection__someOtherField’).all()
除了select_related 方法,还有 prefetch_related 方法.
关系的另一端是1,就用select_related;
关系的另一端是多个,则用prefetch_related.
’
比如product 与 promotion 的关系.
queryset = Product.objects.prefetch_related(‘promotions’).all()
可以连接多个关系组成复杂的语句.
queryset = Product.objects.prefetch_related(‘promotions’).select_related(‘collection’).all()
小练习:
得到最后5个订单以及他们的客户以及订单的商品.
答:
queryset = order.objects.select_related(‘customer’).prefetch_related(‘orderitem_set__product’).order_by(‘-placed_at’)[0:5]
如上,orderitem_set是自动生成的反向字段,多关系侧约定加 _set,然后再从这个扩展到product
聚合对象
聚合对象有Count,Max,Min,Avg,Sum等.
from django.db.models.aggregates import Count,Max,Min,Avg,Sum
…
result = Product.objects.aggregate(Count(‘id’))
return render(request, ‘hello.html’, { ‘name’:’kelemi’, ‘result’:result })
…
再修改hello.html:
…
{{ result }}
…
保存,刷新网页,能看到输出是:
{‘id__count’: 1000}
键值 id_count 有点丑,我们可以修改.
result = Product.objects.aggregate(count=Count(‘id’))
这样就将键改成了 count.刷新网页输出是:
{‘count’: 1000}
可以进行多个聚合.
result = Product.objects.aggregate(count=Count(‘id’), min_price=Min(‘unit_price’))
聚合方法aggregate是查询集的方法, 可以在之前进行任何的查询后再来聚合, 如:
…objects.filter(collection__id=1).aggregate(XXX)
附加对象
有时需要增加一个附加的字段, 可以用annotate方法.
queryset = Customer.objects.annotate(is_new=Value(True))
增加一列 is_new, 不能直接传递True, 需要一个表达式. 这里要先导入Value:
from django.db.models import Value
再增加一列 new_id:
queryset = Customer.objects.annotate(new_id=F(‘id’)+1)
调用数据库函数
queryset = Customer.objects.annotate(full_name=Func(F(‘first_name’), Value(‘ ‘), F(‘last_name’), function=’CONCAT’))
Func 用来调用数据库函数, 这里的 CONCAT 是数据库原生的函数.
还有更简单及常用的调用数据库函数的方法, 就是使用Django的通用引擎, 再通过它来调用底层数据库函数, 比如对于上面这个连接, 可以使用Concat类.
…
from django.db.models.functions import Concat
…
queryset = Customer.objects.annotate(full_name=Concat(‘first_name’, Value(‘ ‘), ‘last_name’))
…
通过谷歌搜索 django database functions 查看到更多的django数据库函数的用法.
数据分组
比如我们想查看每个客户下的订单数量, 就需要在每个客户下增加一个字段 order_count
queryset = Customer.objects.annotate(order_count=Count(‘order’))
表达式包装器
Django查询集的各参数需要提供的是表达式 Expression.
该Expression类有以下派生类:
- Value
- F
- Func
- Aggregate
这几个我们前面都介绍过了, 本节介绍Expression的另一个派生类 ExpressionWrapper, 在用到较复杂的计算的时候, 需要用到这个包装器类.
举个例子, 我们需要增加一个折扣价格的字段,如下:
queryset = Product.objects.annotate(discounted_price=F(‘unit_price’)*0.8)
执行之后, 提示出错, 说数字与浮点数相乘出错, 需要确认输出的字段类型.
我们用ExpressionWrapper包装一下.
…
from django.db.models import ExpressionWrapper, DecimalField
…
dicounted_price = ExpressionWrapper(F(‘unit_price’)*0.8, output_field = DecimalField())
queryset = Product.objects.annotate(discounted_price=dicounted_price)
…
再执行, 就不会提示出错了.
附加字段小练习
题目:
编写代码得到以下数据
- 包含最新订单ID的顾客列表
- 分类列表以及各分类包含的商品种数
- 超过5个订单的顾客列表
- 列出各顾客并包含花费总额
- 最畅销的5种商品以及各自销售总数.
答案:
- queryset = Customer.objects.annotate(last_order_id=Max(‘order__id’)
- queryset = Collection.objects.annotate(products_count=Count(‘product’)
- queryset = Customer.objects.annotate(orders_count=Count(‘order’)).filter(orders_count__gt=5)
- queryset = Customer.objects.annotate(total_spent=Sum(F(‘order__orderitem__unit_price’)*F(‘order__orderitem__quantity’)))
- queryset = Product.objects.annotate(total_sales=Sum(F(‘orderitem__unit_price’)*F(‘orderitem__quantity’))).order_by(‘-total_sales’)[:5]
查询通用关系
可以在数据表django_content_type中查看已安装的应用和模型.
先导入:
from django.contrib.contenttypes.models import ContentType
from store.models import Product
from tags.models import TaggedItem
…
然后取得模型:
…
content_type = ContentType.objects.get_for_model(Product)
…
get_for_model 是 ContentType管理器独有的方法.
再获取查询集:
…
queryset = TaggedItem.objects.select_related(‘tag’).filter(content_type=content_type, object_id=1)
…
传递给hello.html:
…
{…, ‘tags’:list(queryset) }
…
自定义管理器
如上一节,tags应用与store有关联.
每次访问tag时, 如果都需要按上节的方法是有点烦的, 可以自定义管理器,实现类似这下的功能:
TaggedItem.objects.get_tags_for(Product, 1)
下面是实现:
tags应用–>models.py, 添加类.
class TaggedItemManager(models.Manager):
def get_tags_for(self, obj_type, obj_id):
content_type = ContentType.objects.get_for_model(obj_type)
return TaggedItem.objects
.select_related(‘tag’)
.filter(
content_type=content_type,
object_id=obj_id
)
然后:
在模型TaggedItem中添加刚定义的管理器的实例
class TaggedItem(models.Model):
objects = TaggedItemManager()
…
再回到playground的say_hello, 就可以调用 get_tags_for 了:
…
TaggedItem.objects.get_tags_for(Product, 1)
…
理解查询集的缓存机制
在计算queryset 时, 会向数据库发送请求取数.
这是个昂贵的操作, 所以django会缓存, 下次不会访问数据库而是直接从缓存取数, 作优化作用.
创建对象
假设我们要创建一个collection对象
collection = Collection()
collection.title = ‘Video Games’
collection.feautured_product = Product(id=1)
feautured_product的 id=1 也可以用 pk=1 代替,或者用下划线(注意是下划线,不是双下划线)的方法:
collecttion.feautured_product_id = 1
上面创建collectio对象的3句也可以合起来用一句用构造函数:
collection = Collection(title=’Video Games’, collection.feautured_product = Product(id=1))
但用构造函数的方式有个缺点是 IDE 不会自动感知, 还是传统方式更好.
再就是保存, 将数据插入数据库:
collection.save()
此外, 调用管理器方法也可以创建 对象. 注意不用再调用 save 方法.
Collection.objects.create(title=’a’, feature_product_id=1)
更新对象
collection = Collection.objects.get(pk=11)
collection.featured_product = None
collection.save()
或者使用管理器:
Collection.objects.update(featured_product=None)
Collection.objects.filter(pk=11).update(featured_product=None)
删除对象
删除单个对象:
collection = Collection(pk=11)
collection.delete()
批量删除对象:
Collection.objects.filter(id__gt=5).delete()
事务
需要导入transaction
from django.db import transaction
使用:
@transaction.atomic()
def say_hello(request):
…
这样将整个say_hello打包成原子事务.
也可以用with包装部分或全部.
def say_hello(request):
…
with transaction.atomic():
…
…
with下缩进的部分被打包成了原子事务.
执行原始SQL语句
用的是查询集的 raw 方法
queryset = Product.objects.raw(‘SELECT * FROM store_product’)
返回queryset 查询集, 但这个查询集没有 filter 等方法, 与之前的查询集是不同的, 会映射到对象的字段中.
也可以选择列, 比如
queryset = Product.objects.raw(‘SELECT id,title FROM store_product’)
这里选择了id,title 列, 如果后续用到其他列, 将进行延迟查询.
如果我们不想与模型匹配, 可以直接与数据库交流.
…
from django.db import connection
cursor = connection.cursor()
cursor.execute(‘XXX’)
cursor.close()
…
cursor.execute(‘XXX’)可以执行任何SQL语句, 包括SELECT,UPDATE,DELETE,INSERT 等.
打开的游标需要关闭以释放资源,并需要捕捉错误.
所以可以with 简化.
…
with connection.cursor() as cursor:
cursor.execute(‘XXX’)
cursor.callproc(‘get_customers’, [1, 2, ‘a’])
…
…
这里的callproc是执行数据库的存储过程, 对于直接执行数据库而言, 存储过程可能更清晰, 更推荐.
小结
本文我介绍选择关系对象、聚合对象、附加对象、调用数据库函数、数据分组、查询通用关系等知识点,并介绍如何创建对象、更新对象、删除对象、创建事务以及如何执行原始SQL语句等。下一篇开始介绍Django的管理。