django查询之F函数

  • 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))
    1. 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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,928评论 2 89
  • 注:本文参考文献有书籍《Spark大数据处理:技术、应用与性能优化》、RDD的Paper《Resilient Di...
    SiyueLin阅读 6,203评论 2 15
  • 遇见不论早晚,真心才能相伴; 朋友不论远近,懂得才有温暖。 轰轰烈烈的,未必是真心; 默默无声的,未必是无心。 把...
    阡陌望你阅读 259评论 0 2
  • 逻辑在代码中描述很清楚了,若是有比笔者更好的方法,希望一起讨论
    Draper阅读 501评论 2 0
  • 一. 一个黑色袋子中装有5个红球,5个蓝球,5个黄球,从中抽取三次,每次抽一个球,取完不放回,则每种颜色球各得一个...
    林大鹏阅读 4,401评论 0 8