Django笔记:ORM模型

内容摘要
Django中操作数据库的方式有两种,一种是使用ORM模型,另一种是直接执行SQL,推荐使用ORM模型的方式来管理数据库,因为当需要执行的数据库操作过多时,第二种方式产生的SQL会非常难于
文章正文

Django中操作数据库的方式有两种,一种是使用ORM模型,另一种是直接执行SQL,推荐使用ORM模型的方式来管理数据库,因为当需要执行的数据库操作过多时,第二种方式产生的SQL会非常难于管理和维护,而第一种ORM模型的方式因为是操作的类,并没有和SQL本身直接打交道,而且并没有针对具体的数据库类型,只要是Django支持的数据库,都可以使用已有的ORM模型,所以相比之下会更加方便一点,当然,具体使用哪种方式还是看个人和工作需要了。
注:本文中涉及到数据库实际案例和说明的时候,如具体的SQL描述等,都是使用的MySQL数据库。

一、ORM简介

ORM(Object Relational Mapping),即对象关系映射,ORM模型指的就是Django中的模型类,通过ORM模型,我们可以通过操作类的方式去操作数据库,而不用写原生的SQL语句。ORM模型会把类映射为表,把类的一个实例映射为表中的一行数据(即一条记录),把类的属性映射为字段。另外需要说的是,虽然我们操作的是类,但是ORM模型底层还是会把我们的这些操作转换为原生的SQL语句去执行,相当于是简化了我们编程人员的工作,不用我们自己考虑原生SQL相关的问题。
使用ORM的优点:

  • 易用性:使用ORM模型做数据库的开发可以有效的减少重复SQL的编写,写出来的模型也更加直观、清晰。
  • 性能损耗小:ORM模型转换为底层数据库操作指令确实存在一些开销,但是这种开销其实很少,而且从实际情况来看,相对于ORM模型带来的好处是远大于它的损耗的。
  • 设计灵活:可以轻松写出复杂的查询。
  • 可移植性:ORM模型并不是针对某种数据库来开发的,它的开发设计是无关于具体的数据库的,Django封装了底层的数据库实现,可以支持多种数据库,包括MySQL、PostgreSQL和SQLite,可以非常轻松的实现数据库的切换。

注:代码中可以通过打印django.db.connection.queries来查看Django最近执行的SQL。在开发中,如果想要了解在执行了某个ORM模型的操作后,Django在底层具体执行了什么样的SQL,就可以通过这个方式来查看。

二、创建和使用ORM模型

下面的描述中的models都是来自from django.db import models,就不再特别说明了。

1. 创建ORM模型

通常会使用命令python manage.py startapp [app名称]创建一个子app,在此app文件夹中会默认生成一个models.py文件,一般我们就在这个文件中编写ORM模型类。编写一个ORM模型类有一些基本点:

  • 模型定义:ORM模型类必须继承自models.Model或其子类,一个模型类对应于数据库中的一张表。
  • 表名:如果没有指定表名,则以“app名称_类名”的全小写作为数据库中的表名。想要自定义表名,可以定义一个名为Meta的内部类,在类中指定db_table属性作为自定义表名。
  • 属性字段定义:属性字段是直接定义在类中的,而不是定义在__init__()等方法中的,一个属性对应于表中的一个字段,并且每种属性都有其对应Field类,不同的Field类对应于数据库中的不同数据类型。
  • 字段名:如果在属性定义中没有指定字段名,则默认以属性名的全小写作为数据库中的字段名。想要自定义字段名,可以在定义属性字段时,给相应的Field指定db_column参数作为自定义的字段名。

示例:在models.py中创建一个Book模型

from django.db import models


# ORM模型类必须继承自models.Model或其子类
class Book(models.Model):
    # 如果没有指定表名,则以“app名称_类名”的全小写作为表名
    # 如果在属性定义中没有指定字段名,则默认以属性名的全小写作为字段名

    # 属性(字段)类型都在models中,
    # models.AutoField表示自动增长类型,primary_key参数设置是否为True
    # 如果一个ORM模型类没有定义主键,则会自动创建一个叫id的自动增长类型的主键字段,
    # 即当前这个id属性不定义也是可以的,底层会自动生成
    id = models.AutoField(primary_key=True)
    # models.CharField表示可变长类型,max_length参数设置最大长度,
    # null设置是否允许为空,默认为False
    name = models.CharField(max_length=100, null=False)
    author = models.CharField(max_length=100, null=False)
    # models.FloatField表示浮点类型,default参数设置为空时的默认值
    price = models.FloatField(null=False, default=0)

2. 映射ORM模型到数据库

编写好模型后,就需要将它映射到数据库中,将代码中的类变成数据库中的表。首先需要确保该app在settings.pyINSTALLED_APPS中已配置(将app名称添加到此列表中即可),然后在命令窗口cd到项目根目录(确保已进入对应的Python环境),按顺序执行以下两个命令就可以在数据库中生成对应的表了:

  1. python manage.py makemigrations:根据Python中的模型类生成对应的迁移脚本。执行完此命令会在项目的app目录下的migrations文件夹下生成一些脚本文件,此时还没有映射到数据库。通常执行此命令也够了,不需要指定别的参数,但这里还是介绍下此命令的常用参数选项:
    • app_labelmakemigrations后面可以跟app名称,表示此次只会针对指定的app生成迁移脚本,如果没有指定这个参数,则默认为INSTALLED_APPS中所有的app。
    • --name:给此次生成的迁移脚本指定名称,这个参数通常是在默认的名称不能正确表达此次迁移所做的事的时候使用。
    • --empty:表示生成一个空的迁移文件,然后自己在里面写迁移脚本。(当你对迁移脚本比较熟的时候可以自己编写,但是也用的较少)
  2. python manage.py migrate:将迁移脚本映射到数据库中。执行完此命令后数据库中就会生成对应的表了。通常执行此命令也够了,不需要指定别的参数,但这里还是介绍下此命令常用的参数选项:
    • app_labelmigrate可以跟app名称,表示将指定app的迁移脚本映射到数据库中,如果不指定,默认为INSTALLED_APPS中所有的app。
    • app_label migrationname:将某个app下指定的迁移文件映射到数据库中,注意,迁移文件不用写后缀名。
    • --fake:会将指定的迁移脚本名称添加到数据库中,但是不执行对应的数据库映射操作,默认是全部迁移脚本。每次使用migrate命令时都会把执行过的迁移脚本存储在数据库的django_migrations表(自动生成的表)中,并且执行时会检查已经在数据库中的迁移脚本就不会再去执行了。
    • --fake-initial:使用这个参数时,通常需要先手动删除数据库中django_migrations表的所有迁移脚本记录以及对应app下所有迁移脚本文件,然后执行makemigrations命令生成一个迁移脚本文件,再执行此命令时,就会在数据库中将此迁移脚本记录进去,并标记为一个初始状态的迁移脚本,表示以后的迁移脚本都会依赖于此文件,但是不会去执行此迁移脚本对应的数据库映射操作。

3. 增删改查基础操作

对数据库的数据进行增删改查操作,通常都是直接通过ORM模型类的objects属性来进行,objects属性中定义了许多和数据库相关的操作,大部分的数据库操作都可以通过objects属性来完成。示例代码如下:
示例:定义一个视图函数,在其中对Book模型进行简单的增删改查操作

from django.http import HttpResponse
from .models import Book


def index(request):
    # 增:添加一条数据到数据库中,实例化一个模型类后调用save方法即可
    # book = Book(name="了不起的盖茨比", author="弗朗西斯·斯科特·基·菲茨杰拉德", price=29.8)
    # book.save()

    # 查:查询一条数据
    # 查询方式一:根据主键查找,get方法返回的是一个实例对象
    # 参数pk为primary key的缩写,当然也可以传入具体的主键名称,如get(id=1)
    # book = Book.objects.get(pk=1)
    
    # 查询方式二:根据过滤条件查找,注意filter返回的是一个数据集对象,而不是单一的数据对象
    # book = Book.objects.filter(name="了不起的盖茨比").first()

    # 删:删除一条数据,调用对应的delete方法即可
    # book.delete()

    # 改:修改一条数据,直接修改对象的属性值,最后调用save方法同步到数据库中即可
    book = Book.objects.get(pk=1)
    book.price = 40
    book.save()

    return HttpResponse("书籍删除成功!")

三、ORM模型常用字段Field

1. 常用字段Field

模型中一个Field对应于数据库中的一种数据类型,定义模型时根据需要定义相关类型的属性即可,常用的字段Field如下:

  • AutoField:具有自动增长特性的int类型。实际开发中很少用到这个类型, 如果一个表没有指定主键的话,ORM模型会自动为表创建一个名为id的此类型字段。
  • BigAutoField:类似AutoField,为更大范围的64位整型bigint
  • BooleanField:Python代码中接收的是True/False,对应于数据库中的tinyint类型(1/0),注意,此类型是不能指定null参数的,而且是不能为空的,如果想要允许为空,可以使用NullBooleanField字段。
  • NullBooleanField:与BooleanField类似,但是允许为空。
  • CharField:Python代码中接收的是字符串类型,对应于数据库中的varchar,使用时必须指定max_length参数,不然会报错。而且如果超过了254个字符,那么就不推荐使用这个类型了,建议使用TextField
  • TextField:文本类型,对应于数据库中的longtext类型。
  • DateTimeField:在Python中为datetime.datetime类型,可以使用datetime.datetime.now或者django.utils.timezone.now/localtime等方法设置该字段值,对应于数据库中的datetime类型,可以记录年月日和时分秒信息。此类型的字段有两个参数需要注意下:
    • auto_now_add:如果设置为True,在第一次添加数据时,如果没有传值,则会自动获取当前时间。
    • auto_now:如果设置为True,在对象每次修改数据调用save()方法时都会用当前时间更新这个字段。
    • 注:在settings.py中如果配置项USE_TZ设置为True,则django.utils.timezone.now/localtime获取的时间就会根据配置项TIME_ZONE设置的时区来处理,默认为UTC时区,也可以将其更改为Asia/Shanghai(即中国东八区)。
  • DateField:在Python中为datetime.date类型,对应于数据库中的date类型,可以记录年月日信息。与DateTimeField字段类似,这个字段也有auto_now_addauto_now可以使用。
  • TimeField:在Python中为datetime.time,对应于数据库中的time类型,可以记录时分秒信息。
  • EmailField:类似于CharField,在数据库中也是varchar类型,默认最大长度max_length为254个字符。需要注意的是,这个类型本身只是个普通的字符串类型,并不会进行邮箱格式的验证,其作用是在后续根据ORM模型生成对应表单对象的时候,会根据这个类型对表单中提交的数据进行邮箱格式验证。
  • FileField:用于存储文件。
  • ImageField:用于存储图片文件。
  • FloatField:浮点类型,对应于数据库中的float类型。
  • IntegerField:整型。
  • BigIntegerField:大整型。
  • PositiveIntegerField:正整型。
  • SmallIntegerField:小整型。
  • PositiveSmallIntegerField:正小整型。
  • UUIDField:只能存储uuid格式的字符串,通常用作主键类型。
  • URLField:类似于CharField,只能存储url格式的字符串,默认的最大长度max_length为200。

2. Field常用参数

不同的Field可能拥有自己特殊的参数,这里就只列举一些大多Field都有的常用参数:

  • null:设置这个字段在数据库中存储时是否允许为空NULL,默认为False。注意,如果是False,而使用时又没有传递值,则Django会默认传递一个空字符串,如果是True,即允许为空,但是如果也没有传递值,则数据库中存储的值就是NULL,而不是空字符串了。
  • blank:设置这个字段在表单验证时是否允许为空,默认为False。这个参数与null的区别在于,null是对应于数据库存储的,而blank是对应于表单验证的。
  • db_column:设置字段名,默认为模型类中对应属性名的全小写作为字段名。
  • default:设置默认值,可以是一个值或函数,但是不支持lambda表达式。
  • primary_key:是否为主键,默认为False。
  • unique:设置此字段是否唯一,通常用于设置手机号、邮箱等字段。

3. Meta内部类

如果需要定义一些模型(表)级别的配置,可以在模型类中定义一个名叫Meta的内部类,然后在类中配置相关属性即可。常用的属性:

  • db_table:表名,如果没有指定的话,默认以app名称_模型类名全小写形式作为表名。
  • ordering:列表类型,元素为字段名,表示提取数据时会根据指定的字段来进行排序,默认为升序,如果想要降序,则在字段名称前加一个负号-即可。

示例:定义一个Author模型,并指定表名为author

"""models.py"""
from django.db import models
from django.utils.timezone import localtime


class Author(models.Model):
    username = models.CharField(max_length=100, null=True)
    age = models.IntegerField(null=True, db_column="author_age")
    create_time = models.DateTimeField(default=localtime)
    
    class Meta:
        # 指定表名
        db_table = "author"
        # 指定查询结果以create_time字段降序排列,然后以age字段升序排列
        ordering = ["-create_time", "age"]

四、模型中的关联关系

1. 一对多

在一对多的关系中,需要使用models.ForeignKey在表示“多”的模型中定义一个外键字段,定义时需要指定表示“一”的模型类名。models.ForeignKey常用参数:

  • to:该属性的第一个参数,表示外键指向的模型类的类名。如果外键指向的模型类在另一个app中,则不用从另一个app中导入该模型类,直接使用app名称.模型类名的格式即可。如果外键指向的模型类为自身(是存在这种情况的),则除了可以使用模型类名外,还可以使用self
  • on_delete:表示外键指向的数据不存在时,本条数据该如何处理。可以指定的处理类型如下:
    • CASCADE:级联操作,如果外键对应的在另一张表中的数据被删除了,那么本条数据也会被跟着删除。
    • PROTECT:外键数据受保护,即只要本条数据还存在,则外键对应的在另一张表中的数据是无法被删除的。
    • SET_NULL:删除时设置为空,如果外键对应的在另一张表中的数据被删除了,那么本条数据中的这个外键值就设置为空。
    • SET_DEFUALT:设置为默认值,如果外键对应的在另一张表中的数据被删除了,那么本条数据中的这个外键值就设置为指定的默认值,但前提是此字段有指定的默认值。
    • SET(obj/func):设置默认值,如果外键对应的在另一张表中的数据被删除了,那么本条数据中的这个外键值就设置为指定的值obj或者指定函数或方法func的返回值。
    • DO_NOTHING:不做任何操作,一切只看数据库本身的约束。
  • related_name:使用models.ForeignKey再表示“多”的模型中定义外键后,Django会在表示“一”的模型中自动生成一个表示“多”的模型类名_set属性,如果不想使用这种默认的命名方式,就可以使用此参数指定自己需要的名称了。

示例:创建“分类”模型和“文章”模型表示一对多关系,即一个分类下可以有多篇文章,此时就需要在“文章”模型中定义一个外键。

"""models.py"""
from django.db import models


class Category(models.Model):
    """此模型用于存储:文章分类的名称"""
    name = models.CharField(max_length=100)
    
    
class Article(models.Model):
    """此模型用于存储:文章信息"""
    title = models.CharField(max_length=100)
    content = models.TextField()
    # 定义一个外键:指向Category模型,采用级联删除(相应分类被删除的话,本文章也会被删除)
    category = models.ForeignKey("Category", on_delete=models.CASCADE)
    # 如果引用的外键在另一个app中,则不用导入相关模型类,直接使用“app名称.模型类名”即可
    author = models.ForeignKey("frontuser.FrontUser", on_delete=models.CASCADE, null=True)
"""views.py"""
from django.http import HttpResponse
from .models import Category, Article


def index(request):
    # ORM模型中不会自动调用save()方法,所有实例化的对象如果还未保存在数据库中,都需要调用一次save()方法。
    category = Category(name="python")
    category.save()
    
    article = Article(title="Python基础", content="列表、元组、字典类型详解……")
    # 可以直接将外键对象赋给对应的外键属性即可
    article.category = category
    article.save()
    
    category = Category.objects.first()
    article = Article.objects.first()
    
    # 如果想要使用外键对象的属性,也可以通过article.category.name的方式直接访问
    print(article.category.name)
    
    # 定义了外键之后,会在外键对应的模型中自动生成一个“模型类名_set”的属性,也可以使用related_name参数进行自定义
    # 例如此处的article_set表示该category下的所有article对象集合
    articles = category.article_set.all()
    
    return HttpResponse("success!")

2. 一对一

在其中一个模型中使用models.OneToOneField指定即可,定义方法和参数使用与models.ForeignKey类似(因为models.OneToOneField就是通过models.ForeignKey来实现的,只不过是添加了一个“唯一”的约束),不同之处在于,在另一个外键对应的模型中自动生成的属性为类名的小写形式,当然,你不想用默认生成的属性,也可以通过related_name参数来指定。

3. 多对多

在其中一个模型中使用models.ManyToManyField指定即可(定义多对多关系时,Django会在数据库中自动生成一个关联两张表的中间表,但是在Django中不用我们自己去定义),同样的,也会在另一个模型中自动生成一个形如类名_set的属性。

五、查询操作

1. 查询条件表达式

Django中的ORM查询操作,通常使用filter(返回值为QuerySet对象)、excludeget(返回值为实例对象)等方法来实现,查询时使用形如Field__Condition,用双下划线将查询字段和条件表达式分隔。如果字段Field涉及到其他引用,也是使用双下划线分隔即可,不能使用点号.来表达。查询条件如果有多个,也是使用使用双下划线分隔。见具体示例。
常用的查询条件:

  • exact:SQL中的等号=,提供精确的查询条件,如果提供的是一个None,则对应于数据库中的null
article = Article.objects.get(id__exact=14)
# 等价于
article = Article.objects.get(id=14)  # 这种方式更常用
  • iexact:SQL中的LIKE,因为转换为SQL后Django并不会在判断条件中添加通配符,所以大部分情况下其实是和exact是等价的。
article = Article.objects.filter(title__iexact="hello")
# 转换为SQL后就是:... WHERE "article"."title" LIKE hello 
# 虽然LIKE具有模糊查找的功能,但是因为Django并没有自动添加通配符,
# 所以转换之后的SQL就相当于是精确匹配了,与使用等号是一致的
  • contains:SQL中的LIKE BINARY %xxx%,判断某个字符串是否被包含在指定字段中,区分大小写。如articles = Article.objects.filter(title__contains="hello")
  • icontains:与contains用法类似,区别是SQL中少了BINARY,即不区分大小写。
  • in:SQL中的in,判断某个字段是否在指定的容器中,这个容器可以是列表、元组或其他可迭代对象,包括QuerySet对象。
articles = Article.objects.filter(id__in=[1, 2, 3])
# 相当于:SELECT ... WHERE id IN (1, 2, 3)
  • gt/gte/lt/lte:大于、大于等于、小于、小于等于。
  • startswith:SQL中的... LIKE BINARY xxx%,判断某个字段值是否以指定字符串开头,大小写敏感。
  • istartswith:与startswith用法类似,区别是SQL中少了BINARY,即不区分大小写。
  • endswith:SQL中的... LIKE BINARY %xxx,判断某个字段值是否以指定字符串结尾,大小写敏感。
  • iendswith:与endswith用法类似,区别是SQL中少了BINARY,即不区分大小写。
  • range:判断字段是否在指定时间范围之内。
from datetime import datetime
form django.utils.timezone import make_aware

# make_aware会根据Django中TIME_ZONE设置的时区信息将一个navie类型的时间(不包含时区信息)转换为aware类型的时间(包含时区信息)
start_time = make_aware(datetime(year=2020, month=10, day=12, hour=21, minute=0, second=0))
end_time = make_aware(datetime(year=2020, month=10, day=12, hour=21, minute=0, second=0))
articles = Articles.objects.filter(create_time__range=(start_time, end_time))
  • date:根据年月日信息查找数据,可以传入datetime.date类型或datetime.datetime类型的参数。如articles = Article.objects.filter(create_time__date=dateime(year=2020, month=10, day=12))
  • year:根据年份查找数据。
  • month:根据月份查找数据。
  • day:根据天数查找数据。
  • week_day:根据星期查找数据,注意,1表示星期天,7表示星期六。
  • time:根据时分秒查找数据,可以传入datetime.time类型的参数,如果要精确到秒最好使用range条件一起判断一个范围,因为数据库中的秒可能有“小数”,即毫秒。
  • isnull:根据字段是否为空值进行查找。如articles = Article.objects.filter(create_time__isnull=False)
  • regex和iregex:根据正则表达式查找数据,regex大小写敏感,iregex大小写不敏感。如articles = Article.objects.filter(title__regex=r"^hello")

2. 聚合函数

聚合函数都在django.db.models中,直接导入使用即可。聚合函数的执行必须放在支持聚合函数的方法中执行,如:

  • objects.aggregate:执行指定的聚合函数,返回值为字典类型,key为字段名__聚合函数名,value为聚合函数的执行结果。如果不想使用默认的key的命名方式,可以将自定义的命名当做关键字参数传入即可,如Book.objects.aggregate(my_price=Avg("price")),此时key就是my_price了。
  • objects.annotate:也是执行聚合函数,但是会先根据外键关系进行分组,再对分组结果执行聚合函数,返回值为QuerySet类型,QuerySet中的元素为对应数据的实例对象,聚合函数的执行结果存储在实例对象的一个新添加的属性当中,这个属性的命名也是可以通过关键字参数来指定。
# 模型Book表示每一种书的信息,如作者
# 模型BookOrder表示具体每一本书的真实订单信息,如价格
# 模型BookOrder中有一个外键对应于模型Book中的主键,所以可以直接这样查询,表示查询每一种书的平均每本售卖价格
books = Book.objects.annotate(order_avg=Avg("bookorder_price"))
for book in books:
    # order_avg就是新添加的用于存储聚合函数结果的属性
    print(book.name, book.order_avg)
  • 注:这两个方法都可以传入多个聚合函数,如Book.objects.aggregate(max=Max("price"), min=Min("price))

常用的聚合函数:

  • Avg:求平均值。
from django.db.models import Avg
# 求book模型中price字段的平均值
result = Book.objects.aggregate(Avg("price"))
  • Count:统计个数,可以指定参数distinctTrue过滤重复的数据。
from django.db.models import Count
# 过滤重复数据使用book_nums=Count("id", distinct=True)
result = Book.objects.aggregate(book_nums=Count("id"))
  • Max/Min:求指定字段的最大值或最小值。
  • Sum:求某个字段的和。

3. F表达式和Q表达式

F表达式和Q表达式都在from django.db.models import F, Q中,这两种表达式都是用来优化数据库操作的,即不用这两种表达式也可以完成同样的功能,但是效率会很低。优化原理见具体示例。
F表达式
当涉及到的数据过多时,如果不想直接获取模型实例来操作数据库,而是需要直接动态执行SQL的时候,就可以考虑F表达式。通常我们的数据库操作是先获取到对应的模型实例,然后操作模型实例,如果有修改则执行对应的save等方法,相当于是先把数据库中的结果加载到内存中,然后有修改的话再映射回数据库。而F表达式并不会马上去执行对应的数据库操作,而是在触发具体执行的方法的时候再将这个表达式的条件添加上去一起执行。具体见示例:

from django.db.models import F

# 下面两个例子如果不使用F表达式,可能需要先使用xxx.objects.all()的方式获取全部实例对象,然后再对每个实例进行相应的修改操作,如此的话,每修改一次实例对象就会执行一次SQL

# 参数price表示要修改的字段,F表达式中的price表示要使用的字段
# 在book模型中给所有数据的price字段增加10,此处就只会执行一次SQL
Book.objects.update(price=F("price")+10)

# 查找出所有用户名和邮箱一样的用户,此处也是只会执行一次SQL
authors = Author.objects.filter(name=F("email"))

Q表达式
通过objects.filter传入多个条件参数时,多个条件之间默认为“与”的关系,如果想要变成“或”、“非”等其他关系,就可以使用Q表达式。Q表达式可以使用&AND)、|OR)、~NOT)等运算符。

# 不使用Q表达式,则默认多个条件之间是“与”的关系
books = Book.objects.filter(price_gte=100, rating_gte=9)

# 使用Q表达式,将多个条件之间使用“或”的关系进行过滤
from django.db.models import Q
books = Book.objects.filter(Q(price_gte=100) | Q(rating_gte=9))

六、QuerySet API

对于模型类的objects属性,如Book.objects,根据它的源码可以看出,它其实是一个“空壳”类,它的所有方法都拷贝自QuerySet类,所以QuerySet类可以使用的方法,在objects上也可以直接调用。
以下是一些常用的方法:

  • filter:根据传入的过滤条件查询数据。返回值为QuerySet对象,QuerySet中存储的是模型对象。
  • exclude:排除满足条件的数据。返回值为QuerySet对象,QuerySet中存储的是模型对象。
  • annotate:给QuerySet中的每个对象在执行SQL时添加上一个查询表达式(聚合函数、F表达式、Q表达式、Func表达式等),表达式的查询结果会以新字段的方式添加到结果对象中。返回值为QuerySet对象,QuerySet中存储的是模型对象。
  • order_by:将查询的结果按照指定规则进行排序,默认升序排列,如果要降序排列,在对应字段前添加一个负号-即可,如果传入多个字段参数,则按传入字段的先后顺序进行排序。如果使用了多个order_by方法,则只会根据最后一个order_by方法来进行排序,如objects.order_by(xxx).order_by(xxx)。返回值为QuerySet对象,QuerySet中存储的是模型对象。
# 升序排列
articles = Article.objects.order_by("create_time")
# 降序排列
articles = Article.objects.order_by("-create_time")
  • values:指定提取哪些字段,如果不传入任何参数,则会返回所有的字段,也可以使用聚合函数形成新的字段名。返回值为QuerySet对象,QuerySet中存储的是字典。
# 只提取id和name两个字段
books = Book.objects.values("id", "name")
for book in books:
    print(book)  # 打印结果形如{"id": 1, "name": "xxx"}
    
# 如果想要给提取出来的字段命名,使用关键字参数传入`F`表达式即可,如`values("book_id"=F("id"), "book_name"=F("name"))`,但是自定义的名称不能和模型上已经有的字段名称相同。
# 使用F表达式自定义字段名,以及使用聚合函数形成新的字段
books = Book.objects.values("id", book_name=F("name"), order_nums=Count("bookorder"))
  • values_list:类似values。返回值为QuerySet对象,QuerySet中存储的是字段的值的元组。
  • all:返回模型对应的所有数据。返回值为QuerySet对象,QuerySet中存储的是模型对象。
  • select_related:在具有关联关系的模型中,用于多对一一对一关系的这一端的模型去关联这一端的模型,即传入的关联模型只能是本模型中定义的外键模型。可以传入多个关联模型,即一个表中有多个外键。使用这个方法只会产生一次查询,可以优化查询效率。返回值为QuerySet对象,QuerySet中存储的是模型对象。示例:
# 关联关系:一个作者可以有多本书,外键定义在Book模型中
books = Book.objects.all()  # 这里会执行一次SQL查询
for book in books:
    print(book.author.name)  # 打印关联模型Author中的name属性时每次遍历都会执行一次SQL查询
    
# 使用关联查询后,会一次性将Book和Author模型中的字段都查询出来
books = Book.objects.select_related("author")
  • prefetch_related:类似select_related,用于多对多一对多关系中获取那一端的数据(当然,在使用外键关系关联的模型中,也是可以使用这个方法获取的,但是外键关联关系还是建议使用select_related,因为它只会产生一次查询,而这个方法会产生两次查询)。这个方法会产生两次查询,当然,“两次查询”也是优化查询效率的解决方案。注意,如果对查询结果再进行过滤的话,“两次查询”的优化就没了,还是会执行多次查询,这时候想要将查询和过滤一起执行的话,可以使用Prefetch,并传入queryset过滤调用即可。返回值为QuerySet对象,QuerySet中存储的是模型对象。示例:
# Book模型表示每种书的信息
# BookOrder模型表示每一本书的真实订单信息

# 以下代码还是会执行多次查询
# 查询每一种书的具体订单信息
books = Book.objects.prefetch_related("bookorder_set")
for book in books:
    # 因为这里又使用filter方法过滤price大于等于90的条件,
    # 所以还是会在这里“遍历一次查询一次”
    orders = book.bookorder_set.filter(price_gte=90)
    for order in orders:
        print(order.id)
        
        
# 使用Prefetch后,就只会执行两次查询了
prefetch = Prefetch("bookorder_set", queyset=BookOrder.objects.filter(price_gte=90))
# 只会在这里产生两次查询,之后的遍历都不会产生查询了
books = Book.objects.prefetch_related(prefetch)
for book in books:
    # 注意这里就不能在调用filter了,不然还是会产生多次查询
    orders = book.bookorder_set.all()
    for order in orders:
        print(order.id)
  • defer:排除指定字段。如Book.objects.defer("name)提取出来的模型就没有name这个字段了。注意不能排除主键。返回值为QuerySet对象,QuerySet中存储的是模型对象。
  • only:与defer相反,只提取指定字段,同样的,主键是默认提取的。返回值为QuerySet对象,QuerySet中存储的是模型对象。
  • get:获取一条数据,注意如果查询到了多条数据就会报错,只能提取一条数据。返回值为模型对象。如Book.objects.get(id=1)是可以的,但是Book.objects.get(id__gte=1)查询id大于等于1的数据不只一条了,这就不可以了。
  • create:创建一条数据并存入数据库中,相当于book = Book(title="xxx", ...);book.save()两个语句的合并。
  • get_or_create:获取一条数据,如果在数据库中不存在,则会在数据库中创建并返回,返回值为两个元素的元组,第一个元素为模型对象,第二个元素为True/False,如果获取的数据在数据库中存在,则为False,否则创建对应数据存入数据库并返回此值为True
  • bulk_create:一次性创建多条数据,传入一个列表,列表中为需要创建的模型对象。优点在于只会执行一次SQL。
  • count:获取提取的数据的条数,底层SQL使用的是slect count(*)的方式来实现的,虽然使用如len(articles)的方式也能获取到数据条数,但是count方法会更高效。
  • first:返回QuerySet中的第一条数据。
  • last:返回QuerySet中的最后一条数据。
  • aggregate:用于执行聚合函数。
  • exists:判断某个条件的数据是否存在,如Book.objects.filter(title="xxx").exists()
  • distinct:去除重复的数据。注意,此方法判断的是一条数据的全部字段。
  • update:批量更新数据,如Book.objects.update(price=F("price")+10)
  • delete:批量删除满足条件的数据,删除时要注意是否会影响到其他表(使用on_delete参数指定的级联操作关系)。
  • 切片操作:QueySet对象还支持切片操作,如books = Book.objects.all()[1:3]

以下情况Django会将QuerySet对象转换为SQL去执行(某个Python语句执行之后是否触发了SQL的执行,打印connection.queries就可以看到了):

  • 迭代:在遍历QuerySet对象的时候,会先执行这个SQL,再对SQL结果进行遍历。
  • 切片时使用了步长:切片操作本身并不会执行SQL,但是切片时如果提供了步长,就会立即执行SQL。
  • 调用len函数:调用len函数获取QuerySet对象中的数据条数时,也会执行SQL。
  • 调用list:调用list函数将QuerySet对象转换为list对象时,也会执行SQL。
  • 判断:对QuerySet对象进行判断时,也会执行SQL对象。

注:本文为学习笔记,发现错误欢迎指出。

代码注释
[!--zhushi--]

作者:喵哥笔记

IDC笔记

学的不仅是技术,更是梦想!