掌握Django(五)

前一篇介绍了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)

再执行, 就不会提示出错了.


附加字段小练习

题目:

编写代码得到以下数据

  1. 包含最新订单ID的顾客列表
  2. 分类列表以及各分类包含的商品种数
  3. 超过5个订单的顾客列表
  4. 列出各顾客并包含花费总额
  5. 最畅销的5种商品以及各自销售总数.

答案:

  1. queryset = Customer.objects.annotate(last_order_id=Max(‘order__id’)
  2. queryset = Collection.objects.annotate(products_count=Count(‘product’)
  3. queryset = Customer.objects.annotate(orders_count=Count(‘order’)).filter(orders_count__gt=5)
  4. queryset = Customer.objects.annotate(total_spent=Sum(F(‘order__orderitem__unit_price’)*F(‘order__orderitem__quantity’)))
  5. 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的管理。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注