聚合函数、分组查询、F、Q查询、orm查询优化、常见字段、重要参数

今日学习内容总结

聚合查询

五种聚合函数

    1. Avg(Average): 平均值

    2. Max(Maximum): 最大值

    3. Min(Minimum): 最小值

    4. Sum(Summary): 求和

    5. Count: 个数

aggregate()

      aggregate() 是 QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。

      键的名称是聚合值的标识符,值时计算出来的聚合值。

      键的名称是按照字段和聚合函数的名称自动生成出来的。

      如果你想要为聚合值指定一个名称,可以向聚合子句提供它。

    from django.db.models import Avg, Max, Min, Sum, Count  # 导入聚合函数

    res = models.Book.objects.aggregate(Max('price'))
    print(res)

      没有分组也可以使用聚合函数,默认整体就是一组。

    # 求所有数据价钱最高的,最低的,总和,平均价钱,个数
    res = models.Book.objects.aggregate(
        Max('price'),
        Min('price'),
        Sum('price'),
        Avg('price'),
        Count('pk')
    )

    print(res)    # {'price__max': Decimal('56777.98'), 'price__min': Decimal('16987.22'), 'price__sum': Decimal('179408.51'), 'price__avg': 29901.418333, 'pk__count': 6}

分组查询 annotate

      以字段为依据将相同的分为一类,annotate() 内写聚合函数。

分组依据

    1. values() 在 annotate() 之前则表示 group by 字段
      # 默认分组依据
          如果 annotate() 直接跟在 objects 后面,则表示直接以当前的基表为分组依据
          例 : 按书来分组 : odels.Book.objects.annotate(sum_price=Sum)
      # 指定分组依据
          如果 annotate() 跟在 values() 后面,则表示按照values中指定的字段来进行分组
          例 : 按照书中的price进行分组 : models.Book.objects.values('price').annotate()

    2. values() 在 annotate() 之后则表示取字段

    3. filter() 在 annotate() 之前则表示where条件

    3. filter() 在 annotate() 之后则表示having条件

示例

      以表为单位做分组:

  # 统计每本书的作者个数
    res = models.Book.objects.annotate(author_num=Count('authors_pk')).values('title', 'author_num')
    
  # 统计每个出版社卖的最便宜的书的价格
    res = models.Publish.objects.annotate(min_price=Min('book_price')).values('name', 'min_price')

  # 统计不止一个作者的图书
    res = models.Book.objects.annotate(author_num=Count('authors_pk')).filter(author_num__gt=1).values('title', 'author_num')

  # 统计每个作者出的书的总价格
    res = models.Author.objects.annotate(book_sum_price=Sum('book_price')).values('name', 'book_sum_price')
  

      以表中的某个字段分组:

  # 统计每个出版社主键值对应的书籍个数
    res = models.Book.objects.values('publish_id').annotate(book_num=Count('pk')).values('publish_id', 'book_num')

F 与 Q 查询

F 查询介绍

      我们之前的例子中对一些字段的过滤和操作都是与一个常量进行比较大小, 如果我们想让字段与字段的值进行比较就无法简单实现, 于是就可以使用 F 查询了。

      作用 : 取出某个字段对应的值

      使用示例

  # 查询库存大于销量的书籍
    from django.db.models import F
    res = models.Book.objects.filter(kucun__gt=F('maichu'))

  # 将所有书的价格提升1000块
    res = models.Book.objects.update(price=F('price') + 1000)

  # 将所有书的名称后面加上_爆款后缀(如果要修改char字段,需要使用下列两个方法)
    from django.db.models.functions import Concat
    from django.db.models import Value
    res = models.Book.objects.update(name=Concat(F('title'), Value('_爆款')))

Q查询介绍

      filter 的字段筛选条件如果指定多个, 默认是and连接多个条件, 如果想要使用or或者not,则需要Q查询。

      作用 : 构造 或 or、与 &、非 ~ 条件

      使用示例

  # 查询价格大于20000或者卖出大于1000的书籍
    from django.db.models import Q
    res = models.Book.objects.filter(Q(price__gt=20000), Q(maichu__gt=1000))  # 逗号是and关系
    res = models.Book.objects.filter(Q(price__gt=20000) | Q(maichu__gt=1000))  # 管道符是or关系
    res = models.Book.objects.filter(~Q(price__gt=20000))  # ~是not操作

  # Q对象进阶用法: 当我们需要编写一个搜索功能 并且条件是由用户指定 这个时候左边的数据就是一个字符串
    q_obj = Q()
    q_obj.connector = 'or'  # 默认是and 可以改为or
    q_obj.children.append(('price__gt',20000))
    q_obj.children.append(('maichu__gt',1000))
    res = models.Book.objects.filter(q_obj)
    print(res.query)

ORM查询优化

      针对数据库,需要尽量做到,能不'麻烦'它就不'麻烦'它。

    # 1.orm查询默认都是惰性查询(能不消耗数据库资源就不消耗)
        光编写orm语句并不会直接指向SQL语句 只有后续的代码用到了才会执行

    # 2.orm查询默认自带分页功能(尽量减轻单次查询数据的压力)
    # 单个结果还是以对象的形式展示 可以直接通过句点符操作
        res = models.Book.objects.values('title','price')
        for i in res:
            print(i.get('title'))
        
        res = models.Book.objects.only('title', 'price')
        fro obj in res:
            print(obj.title)
            print(obj.price)
            print(obj.publish_time)

      only会产生对象结果集,对象点括号内出现的字段不会再走数据库查询。但是如果点击了括号内没有的字段也可以获取到数据,但是每次都会走数据库查询。

    res = models.Book.objects.defer('title','price')
    for obj in res:
       print(obj.title)
       print(obj.price)
       print(obj.publish_time)

      defer与only刚好相反,对象点括号内出现的字段会走数据库查询。如果点击了括号内没有的字段也可以获取到数据,每次都不会走数据库查询。

      select_related和prefetch_related

    res = models.Book.objects.select_related('publish')
        for obj in res:
            print(obj.title)
            print(obj.publish.name)
            print(obj.publish.addr)

      select_related括号内只能传一对一和一对多字段,不能传多对多字段。效果是内部直接连接表(inner join) 然后将连接之后的大表中所有的数据全部封装到数据对象中,后续对象通过正反向查询跨表,内部不会再走数据库查询。

   res = models.Book.objects.prefetch_related('publish')
   for obj in res:
       print(obj.title)
       print(obj.publish.name)
       print(obj.publish.addr)

      将多次查询之后的结果封装到数据对象中,后续对象通过正反向查询跨表,内部不会再走数据库查询。

ORM常见字段

ORM常用字段类型介绍

常用字段 描述 与MySQL字段对应关系
AutoField 必须指定参数primary_key=True指定主键. 如果没有设置主键, 默认创建并以id名作为主键 integer auto_increment
IntegerField 整型字段. 存储宽度4Bytes. 无符号: 0~2^32 有符号: -232/2~232-1 int 或 integer
BigIntegerField 整型字段. 存储宽度8Bytes. 无符号: 0~2^64 有符号: -264/2~264-1 bigint
DeciamlField 浮点字段. 必须指定参数max_digits设置总长度. decimal_places设置小数位长度 numeric(%(max_digits)s, %(decimal_places)s)
EmailField 字符字段. Django Admin以及ModelForm中提供验证机制
CharField 字符字段. 必须指定参数max_length参数设置字符存储个数. Django中的CharField对应的MySQL数据库中的varchar类型,没有设置对应char类型的字段,但是Django允许我们自定义新的字段. varchar(%(max_length)s)
DateField 日期字段. 格式: 年-月-日. 一般指定参数auto_now=Ture更新记录的时间, 或者auto_now_add=True插入记录的时间 date
DateTimeField 日期字段. 格式: 年-月-日 时:分:秒 一般指定参数auto_now=Ture更新记录的时间, 或者auto_now_add=True插入记录的时间 datetime

自定义字段类型

      我们知道Django中CharField字段类型对应的mysql中默认就是varchar类型,如果想让Django支持Char类型,那么我们可以重写一个类来支持Char字段。

      在pycharm中先 Ctrl+点击 CharField 字段查看其源码,按照它的模板改写。

from django.db import models

class MyCharField(models.Field):
    # 自定义参数
    def __init__(self,max_length,*args,**kwargs):
        self.max_length = max_length
        super().__init__(max_length=max_length,*args,**kwargs)
    
    # 定义存储类型及约束条件 
    def db_type(self,connection):
        return 'char(%s)' % self.max_length

重要参数

通用字段参数

      所有字段类型都具备的参数。

    # 更改字段名          
    db_colum=''
        
    # 设置主键           
    primary_key=True,默认为False
        
    # 给字段设置别名(备注) 
    verbose_name=''
        
    # 为字段设置默认值
    default
        
    # 字段的唯一键属性    
    unique=True,设置之后,该字段在此表中必须是唯一的 
        
    # 允许字段为空        
    null=True(数据库中字段可以为空)
    blank=True(网页表单提交内容可以为空)
    # 不可以将null设置为Fasle的同时还把blank设置为True,否则会报错的
        
    # 给字段建立索引      
    db_index=True
        
    # 在表单中显示说明    
    help_text=''
        
    # 字段值不允许更改
    editable=False,默认是True,可以更改

auto_now 和 auto_now_add 参数

      一般作用于 DateField 和 DateTimeField 字段类型。

    auto_now (更新时间)
    # 配置上auto_now=True,每次更新数据记录的时候会更新该字段
    # [对象].[属性],[对象].save() 有效
    # QuerySet对象.update() 无效,可以在update()里面进行手动传值更新

    auto_now_add (创建时间)
    # 配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库

max_length 参数

      CharField 字段类型才有的参数。

    CharField(max_length=32)
    # 表示字段长度为utf8编码的32个字符串,对应的是mysql中的varchar类型

unique_for_date 参数

      作用于 DateField 字段类型。

    DateField(unique_for_date=True)
    # 表示该字段的时间必须唯一

max_digits 与 decimal_places 参数

      一般同时作用于 DecimalField 字段类型

    DecimalField(max_digits=8, decimal_places=2)
    # 表示该小数总位数为8位,小数位占2位

choices 参数

      提供可被选择的参数, 传入的参数是个双元组, 内层元组的第一个参数是choices的可选参数, 第二个是对这个参数的说明。

      针对可以预测到结果的字段数据, 可以为该字段加上choices参数, 比如 : 用户的性别。

      class User(models.Model):
          username = models.CharField(max_length=32)
          password = models.IntegerField()
          gender_choice = (
              (1,'男性'),
              (2,'女性'),
              (3,'变性')
          )
          gender = models.IntegerField(choices=gender_choice)
	  user_obj.get_gender_display()  
        # 有对应关系就拿 没有还是本身

事务操作

    from django.db import transaction
        try:
            with transaction.atomic():
                pass
        except Exception:
            pass

ORM执行原生SQL

    # 方式1
    from django.db import connection, connections
    cursor = connection.cursor()  
    cursor = connections['default'].cursor()
    cursor.execute("""SELECT * from auth_user where id = %s""", [1])
    cursor.fetchone()

    # 方式2
    models.UserInfo.objects.extra(
                select={'newid':'select count(1) from app01_usertype where id>%s'},
                select_params=[1,],
                where = ['age>%s'],
                params=[18,],
                order_by=['-age'],
                tables=['app01_usertype']
        )

多对多三种创建方式

自动创建(常见)

    1. 优点 : 第三张表以及对应的外键关联字段不需要书写

    2. 缺点 : 可扩展性差, 无法对ORM自动生成的中间表进行增加字段的操作

    3. 注意 : 可以使用ORM提供给多对多关系表操作API以及正方向和双下划线查询

    4. 第三张表中没有其他字段
  class Author(models.Model):
      name = models.CharField(max_length=32,verbose_name='作者名')

  # 通过ORM自带的ManyToManyField自动创建第三张表
  class Book(models.Model):
      title = models.CharField(max_length=32,verbose_name='书名')
      authors = models.ManyToManyField(to='Author',related_name='authors')

全手动创建(使用频率最低)

    1. 优点 : 可扩展性强, 第三张表的内容完全取决于自己

    2. 缺点 : ORM提供给多对多关系表之间的API以及正反向和双下划线查询都不支持, 并且代码也会多一些
  class Book(models.Model):
      title = models.CharField(max_length=32)
  class Author(models.Model):
      name = models.CharField(max_length=32)
  class Book2Author(models.Model):
      book_id = models.ForeignKey(to='Book')
      author_id = models.ForeignKey(to='Author')

半自动(常见)

    1. 优点 : 既可以对第三张表进行字段的添加, 也能使用ORM提供的正反向和双下划线查询

    2. 缺点 : 无法使用ORM提供的多对多表关系之间的API (add, set, remove, clear)
  class Book(models.Model):
      title = models.CharField(max_length=32)
      authors = models.ManyToManyField(
        	to='Author',
      	through='Book2Author',  # 指定表
        	through_fields=('book','author')  # 指定字段
      )

   class Author(models.Model):
      name = models.CharField(max_length=32)
      '''多对多建在任意一方都可以 如果建在作者表 字段顺序互换即可'''
      books = models.ManyToManyField(
        	to='Author',
      	through='Book2Author',  # 指定表
        	through_fields=('author','book')  # 指定字段
      )

  class Book2Author(models.Model):
      book = models.ForeignKey(to='Book')
      author = models.ForeignKey(to='Author')
posted @ 2022-05-18 21:33  くうはくの白  阅读(187)  评论(0)    收藏  举报