django2.2-模型层详解

系列文章目录

文章目录

一、ORM常用字段及参数

1. 字段类型

  • AutoField

    数字类型字段,会自动增长。通常由ORM自动添加,用来充当主键。不需要我们手动添加。

  • BooleanField

    布尔类型字段,默认为None。传入布尔值True或False,会在数据库内保存为1或0。

  • CharField

    字符串类型的字段,适用于较小的字符串。对于大量的文本,应该使用 TextField

    一个必须的参数:

    max_length,指定该字段的最大长度,单位为字符。

  • DateField

    日期字段。

    两个可选的参数:

    • auto_now=布尔值:在每次save()对象时,将该字段的值设置为当前日期,对update()没有效果;

    • auto_now_add=布尔值:只在第一次创建对象时,将该字段设置为当前日期。

    auto_now_addauto_nowdefault 参数是相互排斥的,只能传入一个。

  • DateTimeField

    日期和时间字段,多了时间部分,其他与DateField一样。

  • DecimalField

    固定精度的十进制数字段,

    两个必须的参数:

    • max_digits:数字的总位数;
    • decimal_places:小数所占的位数。
  • EmailField

    属于CharField,但它会检查值是否是有效的电子邮件地址。

  • FileField

    文件上传字段。

    一个可选参数:upload_to='路径',它会将文件保存到该路径下,并将文件的路径信息保存到数据库。

  • IntegerField

    整数类型字段,范围从 -21474836482147483647

  • TextField

    一个文本字段,没有字数限制。

  • 外键字段:

    参考之前的文章:传送门

更多字段类型参考官方文档:传送门

2. 字段通用参数

  • null=布尔值

    字段是否可以为空。

  • unique=布尔值

    字段的值是否唯一。

  • db_index=布尔值

    如果db_index=True,则代表着为此字段设置索引。

  • default=默认值

    为该字段设置默认值。

  • max_length=正整数

    字段值的最大字符数。

  • primary_key=布尔值

    是否设置当前字段为主键。

3. choices参数的基本用法

choices参数,用来指定一个序列。这个序列实现写好了一些选项和说明,目的是让用户选择我们写好的选项,以便规范用户输入和优化性能。

  • 基本语法:

    选项是什么类型,字段就选择什么类型。

    选项不在选项序列中,只要与字段类型符合就可以保存。

    选项序列 = (
        (选项值, '选项别名'),
        ……
    )
    
    字段名 = models.字段类型(choices=选项序列)
    

    比如:

    class User(models.Model):
        GENDER_CHOICES = (
            (1, '男'),
            (2, '女'),
            (3, '保密')
        )
    
        gender = models.IntergerField(choices=GENDER_CHOICES)
    
  • 获取选项别名:

    对象.get_字段名_display()
    

    比如:

    print(user_obj.get_gender_display())
    # 打印:男
    

    获取选项别名时,如果保存的选项不在选项序列中,那么get_字段名_display()返回保存的选项值

更多字段参数参考官方文档:传送门

二、单表操作

1. 增删改查

  • 新增:

    方法一:

    模型类名.object.create(属性=值,……)
    

    方法二:

    对象 = 模型类名(属性=值,……)  # 生成对象
    对象.save()  # 保存到数据库
    
  • 查询:

    查询集也称查询结果集、QuerySet,表示从数据库中获取的对象集合,它可以包含0个,1个或者多个对象(记录)

    以下方法若非特殊说明,均返回一个查询集:

    • 查询全部对象:

      模型类名.object.all()
      
    • 查询满足条件的对象:

      模型类名.object.filter(条件)
      
    • 查询单个对象:

      模型类名.object.get(条件)
      

      直接返回对象如果没有满足查询条件的结果, get()会抛出一个 DoesNotExist 异常。如果有多个记录满足 get()的查询条件时,会抛出 MultipleObjectsReturned异常

    • 返回查询集匹配的第一个对象

      模型类名.object.first()
      

      如果没有匹配的对象,则返回 None

    • first()同理,但返回查询集的最后一个对象

      模型类名.object.last()
      
    • 以类似列表套字典的形式,返回查询到的所有对象:

      模型类名.object.filter(条件).values()
      # 或者查询所有对象
      模型类名.object.values()
      

      一个对象就对应一个字典,键为属性名,值为属性值。

      如果只想查询某些字段的值,则可以将属性名传入values()

    • 以类似列表套元组的形式,返回查询到的所有对象:

      模型类名.object.filter(条件).values_list()
      模型类名.object.values_list()
      
    • 去重:

      模型类名.object.all(条件).distinct()
      

      注意:要在查询集中将主键排除,然后再去重 。因为只有所有字段都一样,才会被distinct()判定为重复。

    • 排序:

      模型类名.object.filter(条件).order_by('字段1', '字段2'……)
      

      先按1排序,再按2排序。默认为升序,加-改为降序。

    • 反转顺序:

      模型类名.object.filter(条件).order_by('字段1', '字段2'……).reverse()
      

      只有有序的数据才能反转。

    • 返回一个整数,表示查询到的对象数量:

      模型类名.object.count()
      
    • 返回不符合条件的对象,返回值为查询集:

      模型类名.object.exclude(条件)
      
    • 判断对象是否存在,返回布尔值:

      模型类名.object.filter(条件).exists()
      

    上述这些查询方法,只要返回值是queryset对象,就可以紧接着链式调用(一个方法后面紧接着调用另一个方法)其他方法

  • 删除:

    模型类名.object.filter(属性=值,……).delete()
    
  • 修改:

    方法一:

    查询集.update(属性=值,……)
    

    update()会修改查询集中的所有记录。

    方法二:

    模型类对象.属性 = 值
    模型类对象.save()
    

    上述方法二,在修改字段值的时候,会将所有字段(包括没有修改过的字段)都更新一遍。因此,当记录的字段非常多的时候,效率会十分低。

2. 双下划线查询(Field查询):

双下划线查询在官方文档中被称为Field查找,是指定 SQL WHERE 子句的方法。它们被指定为 filter()exclude()get() 方法的关键字参数。

比如:查询年龄大于35岁的记录,可以在字段名age后面跟一个__gt=值

filter(age__gt=35)

其他常用的有:

  • 大于:__gt=值

  • 小于:__lt=值

  • 大于等于: __gte=值

  • 小于等于:__lte=值

  • 字段值在1、2、3中的所有记录:__in=[1,2,3]

  • 字段值在18到35之间的所有记录,包括18和35__range=[18, 35]

  • 名字包含“n”的,区分大小写:name__contains='n'

  • 名字包含“n”的,不区分大小写:name__icontains='n'

  • 是否为null__isnull

  • h开头:__startswith='h'

  • h结尾:__endswith='h'

  • 时间字段在3月的记录: __month='3'

    • 年:__year
    • 月:__day
    • 日:__week_day
  • 字段命名:

    由于多下划线在django中有专门的用途,所以字段名称不能包含连续的多个下划线

3. 主键 (pk) 查询快捷方式

出于方便的目的,Django 提供了一种 pk 查询快捷方式,使用 pk 就可以表示主键 “primary key”。

假设在 Blog 模型中,主键是 id 字段,则下面这 3 个语句是等效的:

Blog.objects.get(id__exact=14)
Blog.objects.get(id=14)
Blog.objects.get(pk=14)

pk 的使用并不仅限于 __exact 查询,任何的查询项都能接在 pk 后面,执行对模型主键的查询:

# 查找主键值为1、4、7的
Blog.objects.filter(pk__in=[1,4,7])

# 查找主键值大于14的
Blog.objects.filter(pk__gt=14)

三、外键字段的增、删、改

1. 一对多和一对一模型

注意:外键定义在多的一方!

  • 新增:

    第一种:直接写关联模型的主键值,但外键字段的名称必须是数据库中的真实名称,而不是定义时写的名称。

    比如:外键定义的名称为publisher,而在数据库中,该字段的名称为publisher_id

模型类.objects.create(数据库中的外键字段名称=被参照表的主键字段,……)
```

第二种:写一个关联对象,此时,就可以用在模型类中定义的名称了。

```python
模型类.objects.create(外键字段名称=关联对象,……)
```
  • 删除:

    模型类.objects.filter(条件).delete()
    
  • 修改:

    和新增类似,既可以传主键值,也可以传对象。

    模型类.objects.filter(条件).update(数据库中的外键字段名称=被参照表的主键字段,……)
    

    传对象:

    模型类.objects.filter(条件).update(外键字段名称=关联对象,……)
    

2. 多对多模型

多对多模型外键的修改,其实就是在修改连接表。要访问连接表,就要通过定义了外键的模型对象来访问:对象.外键,在此基础上进行修改

  • 新增:

    对象.外键.add(关联对象的主键或关联对象,……)
    
  • 删除:

    对象.外键.remove(关联对象的主键或关联对象,……)
    
  • 修改:

    set()方法必须传可迭代对象

    对象.外键.set([关联对象的主键或关联对象,……])
    

    内部原理是先删除后新增。

  • 清空:

    对象.外键.clear()  # 括号内不要写任何东西
    

四、多表查询

  • 正、反向查询的概念:

    看外键字段在哪一方定义。假设外键在A中定义,则A查B为正向查询,B查A反向查询。

1. 子查询(基于对象的跨表查询)

  • 正向查询:

    获取关联对象时,直接通过外键字段进行获取:

    关联对象 = 定义了外键的对象.外键.all()  # all()看情况加
    

    注意:如果查询到的关联对象有多个,此时就需要通过all()方法获取

  • 反向查询:

    获取关联对象时,通过全小写模型类名_set进行获取,此处的模型类就是定义了外键的模型类。比如模型类名为Book,则应该写book_set

    定义了外键的对象 = 关联对象.全小写模型类名_set.all()  # all()看情况加
    

2. 联表查询(基于双下划线的跨表查询)

  • 正向查询:

    通过外键__关联模型的字段注意是双下划线)来获取关联对象的字段:

    模型类.objetcs.filter(条件).values('外键__关联模型的字段',……)
    
  • 反向查询:

    通过全小写模型类名__字段获取关联模型的字段,模型类就是外键所关联的模型类。

    模型类.objects.filter(条件).values('全小写模型类名__字段')
    
  • 注意:

    联表查询语句可以当作条件使用。

    联表查询语句可以连续写多个,如:外键__关联模型的字段外键__字段

五、聚合查询

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

模型类.objects.aggregate(别名1=聚合函数1('字段'), 别名2=聚合函数2('字段'),……)
  • aggregate()的返回值为dict类型,其中键是根据字段名和聚合函数而自动生成的,有别名就使用别名;值是计算出来的结果。

  • 别名是可选的。

  • 比如,查询书籍中的最高价:

    Book.objects.all().aggregate(high_price=Max('price'))
    # 返回:{'high_price': 184.35}
    

以上是聚合函数单独使用时的语法,需要在aggregate()方法中使用。而实际上,聚合查询通常都是配合下面的分组查询进行使用。

六、分组查询

在MySQL中,分组之后默认只能获取到分组的依据字段,其他字段都不能直接获取。所以,在必要的情况下,需要修改MySQL的严格模式。

# 统计每一本书的作者
Book.objects.annotate(zuozheshu=Count('author')).values('title','zuozheshu')


# 统计每个出版社最便宜的书的价格
publisher.objects.annotate(zuipianyi=('book__price')).values('publisher_name','zuipianyi')

七、F查询与Q查询

  • F查询:

    F查询用来直接获取表中某个字段的值。

    from django.db.models import F
    
    # 查询销售量大于库存数量的书籍
    Book.objects.filter(sales_volume__gt(F('inventory')))
    
    # 将书的价格提升10块
    Book.objects.update(price=F('price') + 10)  # 不支持字符串的拼接
    

    如果需要拼接字符串,则:

    from django.db.models import F, Value
    from django.db.models.functions import Concat
    
    # 在书名后面追加“真滴好”
    Book.objects.update(title=Concat(F('title'), Value('真滴好')))
    
  • Q查询:

    Q查询可以使用&(逻辑与),|(逻辑或),~(逻辑非)。

    # 查询销售量小于100或价格小于500的书籍
    Book.objects.filter(~Q(sales_volume__gt=100)|Q(price__lt=500))  # ~和__gt组合,表示小于
    

    Q查询也支持以字符串形式传入field查询:

    # 创建Q对象
    q = Q()
    
    # 给Q对象追加条件
    # 销售量小于100,价格小于500
    q.children.append(('sales_volume__gt', 100), ('price__lt', 500))
    
    # 查询销售量小于100且价格小于500的书籍
    Book.objects.filter(q)
    

    默认情况下,多个条件是and关系,如果需要修改,则需要在查询语句之前添加

    q.connector = 'or'  # 改为or关系
    

八、多对多模型的三种建表方式

我们已经学过一种多对多模型的建表方法,但是它有很多限制。因此,我们需要其他方法来进行弥补。

  • 方法一:

    我们之前学过的,使用ManyToManyField()字段创建。

    class Book(models.Model):
        author = models.ManyToManyField(to='Author')
        ……
    

    优点:简单方便,可以使用django提供的各种方法。

    缺点:不能直接操作连接表,且连接表不能扩展其他字段。

  • 方法二(不推荐):

    不使用ManyToManyField()字段,自己创建连接表。

    class BookAuthor(models.Model):
        book = models.ForeignKey(to='Book')
        author = models.ForeignKey(to='Author')
        ……
    

    优点:连接表可以按照我们的意愿随意修改。

    缺点:不能使用django提供的各种方法。

  • 方法三:

    还是使用ManyToManyField()字段,但是使用参数指定连接表和字段。

    class Book(models.Model):
        author = models.ManyToManyField(to='Author', 
                           through='BookAuthor',  # 指定连接表
                           through_fields=('book', 'author'))  # 指定字段
        ……
        
        
    class BookAuthor(models.Model):
        book = models.ForeignKey(to='Book')
        author = models.ForeignKey(to='Author')
        ……
    

    注意:由于ManyToManyField()字段定义在Book模型类,所以,through_fields的元组参数的第一个元素为'book'。这个顺序不能搞错!

    优点:可以使用django提供的部分方法;能够*扩展连接表。

    缺点:代码比较繁琐,不能使用addsetremoveclear方法。

九、数据库查询优化

  • QuerySet 是惰性的:

    QuerySet 是惰性的,创建 QuerySet并不会引发任何数据库活动。Django 只会在 QuerySet 中的对象被使用时,才会执行实际的数据库操作

  • 模型类.objects.defer('字段',……)

    如果某张表有很多字段,但其中一部分字段不确定是否会用到或者根本用不到的时候,就可以用defer()方法,将它们从检索目标中排除,以优化性能。

    defer()内指定的字段,不会被django从数据库中检索出来。这些字段在被访问时,需要从数据库中查询。

    注意:传入主键字段不会生效。

  • 模型类.objects.only('字段',……)

    defer()正好相反,django只会从数据库中检索出在only()内指定的字段。

  • 模型类.objects.select_related(外键字段)

    select_related()会将外键字段所关联的记录也一块查询出来,合并成一条记录保存到查询集中。其内部使用的是内连接INNER JOIN语句。

    外键字段只能放一对多关系和一对一关系的外键字段,不能放多对多关系的外键字段。

    可以通过双下划线__的方法,连续查询多个关联的表:

    模型类.objects.select_related(外键字段1__外键字段2__外键字段3……)
    
    
  • 模型类.objects.prefetch_related(外键字段)

    prefetch_related()的作用和select_related()一样,只不过前者内部使用的是子查询的方法,查询次数比后者多一次,并且还支持多对多的关系模型。

上一篇:【Java】412. Fizz Buzz---时间复杂度O(N),快速解决问题!!!


下一篇:10.1 ES6 的新增特性以及简单语法