聚合查询,分组查询,F查询与Q查询,ORM查询优化,ORM字段类型和choice参数,ORM事务,ORM执行原生SQL,多对多创建第三张表

数据库:
#书籍
class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8,decimal_places=2)
    # 一对多
    publish = models.ForeignKey(to='Publish')
    # 多对多
    authors = models.ManyToManyField(to='Author')  # 自动创建书籍和作者的第三张关系表

#出版
class Publish(models.Model):
    title = models.CharField(max_length=32)

#作者
class Author(models.Model):
    name = models.CharField(max_length=32)
    # 一对一
    author_detail = models.OneToOneField(to='AuthorDetail')

#作者信息
class AuthorDetail(models.Model):
    addr = models.CharField(max_length=32)
    phone = models.BigIntegerField()

聚合查询(aggregate)

聚合查询返回值的数据类型是字典。
返回的字典中:键的名称默认是(属性名称加上__聚合函数名),值是计算出来的聚合值。
MySQL聚合函数:max\min\sum\count\avg
如果要自定义返回字典的键的名称,可以起别名:
  aggregate(别名 = 聚合函数名("属性名称"))

使用时需要导入对应模块
from django.db.models import Max, Min, Sum, Avg, Count

查找书籍中最便宜书籍的价格
    from django.db.models import Max, Min, Sum, Avg, Count
    res = models.Book.objects.aggregate(price_min = Min('price'))
    print(res) # {'price_min': Decimal('11111.00')}
'''没有分组也可以使用聚合函数 默认整体就是一组'''

分组查询(annotate)

MySQL分组操作:group by
ORM执行分组操作 如果报错 可能需要去修改sql_mode 移除only_full_group_by

返回值:
  分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典;
  分组后,用 values_list 取值,则返回值是 QuerySet 数据类型里面为一个个元组。

annotate 里面放聚合函数。
    values 或者 values_list 放在 annotate 前面:values 或者 values_list 是声明以什么字
  段分组,annotate 执行分组。
    values 或者 values_list 放在annotate后面: annotate 表示直接以当前表的pk执行分组,
  values 或者 values_list 表示查询哪些字段, 并且要将 annotate 里的聚合函数起别名,在 
  values 或者 values_list 里写其别名。

1.统计每本书的作者个数
    from django.db.models import Min,Min,Avg,Sum,Count
    res = models.Book.objects.annotate(munb = Count('authors__pk')).values('title','munb')
    print(res) 
    # <QuerySet [{'title': 'Python编程', 'munb': 2}, {'title': 'JAVA', 'munb': 1}, {'title': 'JS', 'munb': 2}]>

2.统计每个出版社卖的最便宜的书的价格
    from django.db.models import Min
    res = models.Publish.objects.annotate(book_min = Min('book__price')).values('title','book_min')
    print(res)
    # <QuerySet [{'title': '上海出版社', 'book_min': Decimal('100000.00')}, {'title': '浙江出版社', 'book_min': Decimal('11111.00')}]>

3.统计不止一个作者的图书
    from django.db.models import Count
    res = models.Book.objects.annotate(count = Count('authors__id')).filter(count__gt=1).values('title')
    print(res) # <QuerySet [{'title': 'Python编程'}, {'title': 'JS'}]>

4.统计每个出版社出版的书籍个数
    from django.db.models import Count
    res = models.Publish.objects.annotate(count = Count('book__id')).values('title','count')
    print(res) 
    # <QuerySet [{'title': '上海出版社', 'count': 2}, {'title': '浙江出版社', 'count': 1}]>

当表中已有数据的情况下,为表重新添加字段

为书籍表添加2个字段,销量和库存字段
#书籍
class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8,decimal_places=2)
    #销量
    sales = models.IntegerField(verbose_name='销量')
    #库存
    inventory = models.IntegerField(verbose_name='库存')
    # 一对多
    publish = models.ForeignKey(to='Publish')
    # 多对多
    authors = models.ManyToManyField(to='Author')  # 自动创建书籍和作者的第三张关系表

修改了模型代码后,一定要进行数据迁移。
执行迁移命令后会发现不成功
它给了两个选项:
  1)立即提供一次性默认值(在所有现有行上都设置为空值)
  2)退出,让我在Models.py中添加一个默认值。

解决方法:
  当表中已经有数据的情况下 添加额外的字段 需要指定默认值或者可以为null
	方式1
		IntegerField(verbose_name='销量',default=1000)
	方式2
		IntegerField(verbose_name='销量',null=True)
	方式3
		在迁移命令提示中直接给默认值

F查询和Q查询

F查询

如果想要对同一张表中的两个字段的值做比较,就需要用到 F()。
F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
用法:
  F("字段名称")

F 动态获取对象字段的值,可以进行运算。
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取余的操作。
修改操作(update)也可以使用 F() 函数。

首先需要导入模块
from django.db.models import F

1.查询库存大于销量的书籍
    from django.db.models import F
    res = models.Book.objects.filter(inventory__gt=F('sales'))
    print(res) # <QuerySet [<Book: Book object>, <Book: Book object>]>
    print(res[0].title,res[1].title,end='   ') # Python编程 JS  

2.将所有书的价格提升1000块
    from django.db.models import F
    res = models.Book.objects.update(price = F('price') + 1000)
    print(res) # 3 返回受影响的行数

在已有的char数据类型的值后面添加内容

'''如果要修改char字段咋办(千万不能用上面对数值类型的操作!!!) 需要使用下列两个方法'''
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Book.objects.update(title=Concat(F('title'), Value('爆款')))

Q查询

用法:
  Q(条件判断)

之前构造的过滤器里的多个条件的关系都是 and,如果需要执行更复杂的查询(例如 or 语句),就可以使用 Q 。
Q 对象可以使用 & | ~ (与 或 非)操作符进行组合。
优先级从高到低:~ & |。

可以混合使用 Q 对象和关键字参数,Q 对象和关键字参数是用"and"拼在一起的(即将逗号看成 
and ),但是 Q 对象必须位于所有关键字参数的前面。
使用时需要导入模块
  from django.db.models import Q

1.查询价格销量大于900或者名称以P开头的书籍的名称和价格。
    from django.db.models import Q
    res = models.Book.objects.filter(Q(sales__gt=900) | Q(title__startswith='P')).values('title','price')
    print(res) 
    # <QuerySet [{'title': 'Python编程', 'price': Decimal('102000.00')}, {'title': 'JAVA', 'price': Decimal('125456.00')}]>

Q对象的第二种用法

    from django.db.models import Q
    q = Q()  # 创建一个Q对象q
    q.connector = 'OR'  # 默认是and 可以改为or
    # 传入条件进行查询:
    q.children.append(('price__lt', 100000))
    q.children.append(('sales__gt', 600))
    res = models.Book.objects.filter(q) 
    # 等于models.Book.objects.filter(Q(price__lt=100000) | Q(sales__gt=600))
    print(res) # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>

ORM查询优化

# 惰性查询:如果只是书写了orm语句,在后面根本没有用到该语句所查询出来的参数,那么orm会自动识别出来,直接不执行。

# 举例:
    res = models.Book.objects.all()  # 这时orm是不会走数据库的
    print(res)   # 只有当要用到的上述orm语句的结果时,才回去数据库查询。

only

res = models.Book.objects.only('title')     # 括号内查询的字段可以有多个
print(res)        # 查询一次,打印一条sql查询语句
for i in res:
    print(i.title)  # 查询一次,打印一条sql查询语句
    print(i.price)  # 有几个对象,就查询几次,打印几条sql查询语句

  only会把括号内字段对应的值,封装到查询返回的对象中,通过对象点括号字段,不需要再走数据库
  查询,直接返回结果,一旦你点了不是括号内的字段 就会频繁的去走数据库查询
  '''
      only英语的意思就是只有,那我们可以理解为通过only查询出来的对象内只会有小括号内的字
      段的值,如果想要获取其他字段的值,就还得在查一次数据库或者说是在执行一次SQL语句。
  '''

defer

res = models.Book.objects.defer('title')
# print(res)
for i in res:
    print(i.price) # 只会执行一次SQL语句
    print(i.title) # 每查询一次执行一次SQL语句

  和 only相反,defer会将括号内的字段排除之外将其他字段对应的值, 直接封装到返回给你的对象
  中, 点其他字段 不需要再走数据库查询,一旦你点了括号内的字段就会有多少值,就会查询几次

  '''
      defer刚好和only相反,defer小括号里写了啥,它反倒不给你,它会给你除了括号内的所有
      字段值。当你获取小括号内的字段值时,会去查询,获取不在小括号内的字段值时反倒还不用去查
      询。
  '''
    res = models.Book.objects.select_related('publish')
    res = models.Book.objects.select_related('publish')
    print(res) # <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>
    print(res[0].title)  # Python编程
    print(res[0].publish.title) # 上海出版社

  select_related括号内放外键字段,并且外键字段的类型只能是一对一和一对多,不能是多对多,

  内部自动做联表操作,会将括号内外键字段所关联的表与当前表自动拼接成一张表,然后将表中的
  数据一个个查询出来封装成一个个的对象。 这样做 就不会重复的走数据库,减轻数据库的压力。

  select_related括号内可以放多个外键字段,用逗号隔开,会将多个外键字段关联的表拼接成一张大表
  
  后续对象通过正反向查询跨表 内部不会再走数据库查询
  res = models.Book.objects.prefetch_related('publish','authors')
  for i in res:
      print(i.publish)

  prefetch_related内部是子查询,会自动按照步骤查询多张表,然后将查询的结果封装到对象中,这样给用户的感觉还是联表操作。

  括号内支持传多个外键字段,并且没有类型限制。

  每放一个外键字段,就会多走一条sql语句,多查询一张表

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

ORM常见字段

属性名 = models.字段类型,定义属性时需要指定字段类型, 通过字段类型的参数指定选项

属性名:
  不允许使用python的保留关键字
  不允许使用mysql的保留关键字
  不允许使用连续的下划线,因为Django的查询语法就是连续的下划线

AutoField:自动增长的IntegerField, 不指定时Django会自动创建属性名为id的自动增长属性

BooleanField:布尔字段,值为True或False

NullBooleanField:支持Null、True、False三种值

CharField(max_length=20):字符串
参数max_length表示最大字符个数

TextFiled:大文本字段,一般超过4000个字符时使用

IntegerField:整数

DecimalField(max_digits=None, decimal_places=None):可以指定精度的十进制浮点数
参数max_digits表示总位数
参数decimal_places表示小数位数

FloatField():浮点数 

DateField[auto_now=False, auto_now_add=False]):日期
参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它
总是使用当前日期,默认为false
参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当
前日期,默认为false
参数auto_now_add和auto_now是相互排斥的,组合将会发生错误

TimeField:参数和DateField一样

DateTimeField:日期时间,参数同DateField

FileField:上传文件字段,以二进制的形式

ImageField:继承于FileField,对上传的内容进行校验,确保是有效的图片

自定义字段类型

class FixedCharField(models.Field):
    """
    自定义的char类型的字段类    固定长度
    """
    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):
        """
        限定生成数据库表的字段类型为char,长度为max_length指定的值
        """
        return 'char(%s)' % self.max_length

使用:
  class Class(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=25)
    # 使用上面自定义的char类型的字段
    cname = FixedCharField(max_length=25)

choice参数

用户表	
	性别
	学历
	工作经验
	是否结婚
	是否生子
	客户来源
	...
针对某个可以列举完全的可能性字段,我们应该如何存储

只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数

class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
	
    # 性别
    gender_choices = (
        (1,'男'),
        (2,'女'),
        (3,'其他'),
    )
    gender = models.IntegerField(choices=gender_choices)
    
    # 分数
    score_choices = (
        ('A','优秀'),
        ('B','良好'),
        ('C','及格'),
        ('D','不合格'),
    )
    # 保证字段类型跟列举出来的元祖第一个数据类型一致即可
    score = models.CharField(choices=score_choices,null=True)


添加数据:
  models.User.objects.create(username='111',age=18,gender=1)
  models.User.objects.create(username='222',age=85,gender=2)
  models.User.objects.create(username='333',age=40,gender=3)
  models.User.objects.create(username='444',age=45,gender=4)
'''
  添加数据时,如果添加了没有列举出来的数字也能存(范围是按照字段类型决定)
'''

取值:
  只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()
  user_obj = models.User.objects.filter(pk=1).first()
  print(user_obj.get_gender_display())
  '''
    有对应关系就拿对应的内容 没有还是本身
  '''

ORM事务

在Django中可以通过django.db.transaction模块提供的atomic来定义一个事务,atomic提供两种用法
1.装饰器方法:
from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # 这些代码会在一个事务中执行
    ...   

2.with 语句:
from django.db import transaction

def viewfunc(request):
    # 这部分代码不在事务中,会被Django自动提交
    ...

    with transaction.atomic():
        # 这部分代码会在事务中执行
    # 创建回滚点
    save_id = transaction.savepoint() 
    #一旦异常,则回滚代码
    transaction.savepoint_rollback(save_id)
        ...

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("原生SQL语句",[1]) # []中的内容会按位置传值给%s

# 查询结果
row = cursor.fetchone()
'''类似于pycharm操作'''

(2)
models.UserInfo.objects.extra(
                    select={'newid':'select count(1) from app01_usertype where id>%s'},
                    select_params=[1,], # 按位置传值给select中的%s
                    where = ['age>%s'],
                    params=[18,], # 按位置传值给where中的%s
                    order_by=['-age'],# 指定按这个字段降序,升序把"-"号去掉即可
                    tables=['app01_usertype'] # 指定表
                )

多对多三种创建方式

# 全自动(常见)
	orm自动创建第三张表 但是无法扩展第三张表的字段
	authors = models.ManyToManyField(to='Author')
# 全手动(使用频率最低)
	优势在于第三张表完全自定义扩展性高 劣势在于无法使用外键方法和正反向
	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')
# 半自动(常见)
	正反向还可以使用 并且第三张表可以扩展 唯一的缺陷是不能用
  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 22:23  春游去动物园  阅读(37)  评论(0编辑  收藏  举报