queryset 的F( )表达式

GavinGuan 2017-04-18

class F

一个 F()对象代表了一个model的字段值或注释列。使用它就可以直接参考model的field和执行数据库操作而不用再把它们查询出来放到python内存中。作为代替,Django使用 F()对象生成一个SQL表达式,来描述数据库层级所需要的操作,这些通过一个例子可以很容易的理解。往常,在数据库中获取一个数据会这样做:

(终端实验)

>>>q = OB.objects.filter(name='Tintin')
>>>q[0].number += 1
(值不变)

或者:

q = OB.objects.get(name='Tintin')
q.number += 1(值增加,但是并未保存进数据库,加上q.save()即可)

这里,我们把 q 的 number 属性值从数据库取出放到内存中并用我们熟悉的python运算符操作它,最后再把它保存到数据库。然而,还可以这样做:

from django.db.models import F
 
q = OB.objects.get(name='Tintin')
q.number = F('number') + 1

虽然 q[0].number = F('number') + 1 看起来像一个正常的Python分配值赋给一个实例属性,事实上这是一个描述数据库操作的SQL概念。

>>>q.number
<CombinedExpression(联合表达): F(number) + Value(1)>
下面有具体例子

当Django遇到 F()实例,它覆盖了标准的Python运算符创建一个封装的SQL表达式。在这个例子中,q[0].number就代表了一个指示数据库对该字段进行增量的命令。

无论q[0].number的值是或曾是什么,Python完全不知道,这完全是由数据库去处理的。所有的Python,通过Django的F() 类,只是去创建SQL语法参考字段和描述操作。

为了获得用这种方法保存的新值,此对象应重新加载:

q = OB.objects.get(name='Tintin')

和上面单独实例的操作一样, F()配合 update()可以应用于对象实例的 QuerySets这减少了我们上面使用的两个查询 get() 和 save() 只需要使用如下方式:

>>> q = UserItem.objects.filter(name='雪碧')
>>> q.update(number=F('number')+1)
1
>>> q[0].number
Decimal('6')

我们可以使用update()方法批量地增加多个对象的字段值。这比先从数据库查询后,通过循环一个个增加,并一个个保存要快的很多。

UserItem.objects.all().update(number=F('number')+1)

F()表达式的效率上的优点主要体现在

1.直接通过数据库操作而不是Python

2.减少数据库查询次数

使用 F() 的另一个好处是通过数据库而不是Python来更新字段值以避免竞态条件。

如果两个Python线程执行上面第一个例子中的代码,一个线程可能在另一个线程刚从数据库中获取完字段值后获取、增加、保存该字段值。第二个线程保存的值将会基于原始字段值;第一个线程的工作将会丢失。如果让数据库对更新字段负责,这个过程将变得更稳健:它将只会在 save() 或 update()执行时根据数据库中该字段值来更新字段,而不是基于实例之前获取的字段值。同时F()在查询集和过滤器中也十分有用,它使得使用条件通过字段值而不是Python值过滤一组对象变得可能,例如:

q = OB.objects.filter(number=F('pro_number'))

直接获取了实际数量大于优惠数量的商品信息,不需要获取两个数量之后比较再存入新数组,同时搭配查询集的选择器能进行更多方便的操作。

F()可用于通过将不同字段与算术相结合来在模型上创建动态字段:

q = OB.objects.annotate(new_number=F('number') - F('pro_number')){annotate方法添加注释}

如果你组合的字段是不同类型,你需要告诉Django将返回什么类型的字段。由于F()不直接支持output_field,您需要使用Expression Wrapper

{Expression Wrapper简单地包围另一个表达式,并提供对其他表达式可能不可用的属性(例如output_field)的访问。当对 F() 的 annotations 中描述的不同类型的F()表达式使用算术时,必须使用 Expression Wrapper, class ExpressionWrapper(expression, output_field) }

from django.db.models import DateTimeField, ExpressionWrapper, F
 
OB.objects.annotate(
    expires=ExpressionWrapper(
        F('active_a') + F('active_b'), output_field=DateTimeField()))

相关推荐