- F()函数
F()函数的导入
from django.db.models import F
为什么要使用F()函数?
一个 F()对象代表了一个model的字段值或注释列。使用它就可以直接参考model的field和执行数据库操作而不用再把它们(model field)查询出来放到python内存中。
作为代替,Django使用 F()对象生成一个SQL表达式,来描述数据库层级所需要的操作
这些通过一个例子可以很容易的理解。往常,我们会这样做:
# 普通方式
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1 # 放到内存中,使用python计算,然后通过save方法保存
reporter.save()
# 使用F表达式 (简单使用)
from django.db.models import F
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed = F('stories_filed') + 1
reporter.save()
虽然看上去和上面的内存Python操作相似,但事实上这是一个描述数据库操作的sql概念
当django遇到F()实例,它覆盖了标准的Python运算符创建一个封装的SQL表达式。在这个例子中,reporter.stories_filed就代表了一个指示数据库对该字段进行增量的命令。
无论reporter.stories_filed的值是或曾是什么,Python一无所知--这完全是由数据库去处理的。所有的Python,通过Django的F() 类,只是去创建SQL语法参考字段和描述操作
注意:
使用F()函数保存值后,再次使用实例调用并不能拿到新的值.这是因为F()函数是数据库操作,并不是在内存中python进行的,所以之前拿到的实例存储的还是之前的值.所以需要重新载入实例(即重新获取实例)
# 普通方式
>>reporter = Reporters.objects.get(name='Tintin')
>>reporter.stories_filed
10
>>reporter.stories_filed += 1
>>reporter.stories_filed # 普通方式是在内存中执行的,所以就算不保存也能拿到值,值是存储在内存中的
11 # 这个时候数据库中的值还是10,因为没有save
>>reporter.save() # 保存之后才会去更改数据库
>>reporter.stories_filed
11
# F()函数
>>from django.db.models import F
>>reporter = Reporters.objects.get(name='Tintin')
>>reporter.stories_filed
10
>>reporter.stories_filed = F('stories_filed') + 1
>>reporter.stories_filed
10
>>reporter.save()
>>reporter.stories_filed
10
>>reporter = Reporters.objects.get(name='Tintin') # 重新载入实例之后就是最新的数据
>>reporter.stories_filed
11
-
F()函数配合update可以优化效率,不再需要使用get()和save()方法
更新单个实例
reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)更新多个实例
Reporter.objects.all().update(stories_filed=F('stories_filed') + 1)
F()函数避免竞争
竞争出现的原因:
如果两个Python线程执行上面第一个例子中的代码,一个线程可能在另一个线程刚从数据库中获取完字段值后获取、增加、保存该字段值。第二个线程保存的值将会基于原始字段值;第一个线程的工作将会丢失。
F() 通过数据库而不是Python来更新字段值以避免竞态条件 ,可以理解为上面出现竞争的原因是两个线程拿到的值都是放在内存中的,一个线程更改过数据库之后,而另一个线程的对象实例存储的还是没有更改数据库值之前的数据,所以会出现工作丢失.而F()函数是数据库层面的操作,不存在内存记录数据.利用F()函数第一个线程更改完数据之后,第二个线程在更改的时候因为是数据库操作,这个时候数据库的值是已经更新过的,所以是在第一个线程所做的结果之上更新,所以第一个线程的工作不会丢失
- 综上可以看出F()函数的优点:
1.直接通过数据库操作,而不是通过python操作
2.减少数据库查询次数
3.避免竞争
- F()函数的另外作用,用于查询表达式中
一般我们使用filter过滤字段来得到我们想要的实例.但是一般都是与常量进行比较,比如:
>>> Entry.objects.filter(n_comments__gt=2)
拿到n_comments字段值大于2的所有Entry实例
但是如果我们想要与同一行数据的另外字段进行比较该怎么办呢,那就是使用F()函数
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
找出n_comments字段值大于n_pingbacks值的数据(注意是同一条数据的不同字段比较)
比如a和b对象都有两个属性n_comments,n_pingbacks
应该是a.n_comments和a.n_pingbacks比较,b.n_comments和b.n_pingbacks比较
而不能是a.n_comments和b.n_pingbacks比较
Django 支持对F() 对象使用加法、减法、乘法、除法、取模以及幂计算等算术操作,两个操作数可以都是常数和其它F() 对象
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
F()函数还支持跨越关联关系查找,但是关联的字段要在同一个model
中
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __str__(self): # __unicode__ on Python 2
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self): # __unicode__ on Python 2
return self.headline
在F() 对象中使用双下划线标记来跨越关联关系。带有双下划线的F() 对象将引入任何需要的join 操作以访问关联的对象。
例如,如要获取author 的名字与blog 名字相同的Entry,我们可以这样查询:
>>> Entry.objects.filter(authors__name=F('blog__name'))
F() 对象操作时间对象的加减运算
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F()与聚合函数的配合
- 1.首先讲下什么是聚合,比如现在有两个模型类,文章模型Article,和文章对应的分类Category
class Article(models.Model):
title = models.CharField(null=False,blank=False,max_length=30)
category = models.ForeignKey("Category")
class Meta:
db_table = "article"
class Category(models.Model):
category_name = models.CharField(null=False,blank=False,max_length=10)
class Meta:
db_table = "category"
数据库中存储的数据
article表数据
id title category_id
1 haha 1
2 heihei 1
3 hehe 2
category表数据
id category_name
1 A
2 B
article表里面的1 2数据属于A分类 3数据属于B分类
那么首先来讲解django是怎么通过外键关联拿到数据的
比如:想要拿到 haha对应的分类
1.拿到haha对应的Python层面的实例 a1 = Article.objects.get(title="haha") # 精确匹配可以省略不写,默认
2.拿到a1实例的category_id值,再去category表搜寻id与category_id相等的数据在python层面的实例. b1 = a1.category
3.拿到分类名字 name = b1.category_name
合在一起就是 name = Article.objects.get(title="haha").category.category_name
以上就是django拿取关联数据的方式
那么如果要聚合怎么办呢?查询的聚合API annotate
如果想要查看每个分类对应下的文章数量,使用下面的方法
要聚合的类名.管理器名.annotate(自定义的python层面的实例字段名=聚合的方式(关联的小写模型类))
自定义的python层面的实例字段名会作为实例的实例属性存在
from django.db.models.aggregates import Count
object_query_set = Category.objects.annotate(article_num = Count("article")) # 注意django查询集里面是对象(实例)的集合
for object in object_query_set:
print("分类id:{},分类名:{},该分类对应的文章数量:{}".format(object.id,object.category_name,object.article_num))
- F()与聚合函数annotate的配合使用
用于通过将不同字段与算术相结合来在模型上创建动态字段:
- F()与聚合函数annotate的配合使用
companys = Company.objects.annotate(chairs_needed=F('num_employees') - F('num_chairs'))
将Company类的每个实例都进行num_employees与num_chairs字段的相减操作,并把最后结果存储到动态创建的chairs_needed上面.
查询集包含所有的Company实例
for company in companys :
print(company.chairs_needed)
但是需要注意的是如果你组合的字段是不同的类型,你需要告诉django返回什么类型的字段,django的output_field可以设置返回的字段类型,但是F()函数并不支持output_field字段,需要导入ExpressionWrapper类.
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F('active_at') + F('duration'), output_field=DateTimeField()))
F()函数配合FUNC表达式
Func() 表达式是所有表达式的基础类型,包括数据库函数如 COALESCE 和 LOWER, 或者 SUM聚合.用下面方式可以直接使用:
from django.db.models import Func, F
queryset.annotate(field_lower=Func(F('field'), function='LOWER'))
如果觉得麻烦,可以自定义类,继承Func类
from django.db.models import Func, F
class Lower(Func): # 继承Func类
function = 'LOWER' # 使用类属性指定要使用的方法
queryset.annotate(field_lower=Lower(F('field'))) # 调用自己写的类
Func函数API
class Func(*expressions, **extra)
function 描述将生成的函数的类属性,函数将会替template中函数占位符
template 类属性,作为格式字符串,描述此函数生成的sql语句,默认为'%(function)s(%(expressions)s)'
arg_joiner 类属性,表示用于连接表达式列表的字符,默认为","
*expressions参数是函数将要应用与表达式的位置参数列表,列表会与arg_joiner组成字符串(类似join方法)填充到template里面的占位符里面
位置参数可以是表达式也可以是Python基础类型值,字符串值将被假定为列引用放到F()函数中去,其他值会被放到values()表达式中去
**extra额外的键值对,可以被填充到template中去,output_field 字段可以指定返回字段的类型
利用聚合函数做些复杂的运算
from django.db.models import Count
Company.objects.annotate(
managers_required=(Count('num_employees') / 4) + Count('num_managers'))
你也可以通过继承聚合函数,来实现你自己的聚合函数
from django.db.models import Aggregate
class Count(Aggregate):
# supports COUNT(distinct field)
function = 'COUNT'
template = '%(function)s(%(distinct)s%(expressions)s)'
def __init__(self, expression, distinct=False, **extra):
super(Count, self).__init__(
expression,
distinct='DISTINCT ' if distinct else '',
output_field=IntegerField(),
**extra)