- 系列文章目录
- 一、ORM常用字段及参数
- 1. 字段类型
- 2. 字段通用参数
- 3. `choices`参数的基本用法
- 二、单表操作
- 1. 增删改查
- 2. 双下划线查询(Field查询):
- 3. 主键 (`pk`) 查询快捷方式
- 三、外键字段的增、删、改
- 1. 一对多和一对一模型
- 2. 多对多模型
- 四、多表查询
- 1. 子查询(基于对象的跨表查询)
- 2. 联表查询(基于双下划线的跨表查询)
- 五、聚合查询
- 六、分组查询
- 七、F查询与Q查询
- 八、多对多模型的三种建表方式
- 九、数据库查询优化
-
AutoField:
数字类型字段,会自动增长。通常由ORM自动添加,用来充当主键。不需要我们手动添加。
-
BooleanField:
布尔类型字段,默认为None。传入布尔值True或False,会在数据库内保存为1或0。
-
CharField:
字符串类型的字段,适用于较小的字符串。对于大量的文本,应该使用 TextField。
一个必须的参数:
max_length,指定该字段的最大长度,单位为字符。
-
DateField:
日期字段。
两个可选的参数:
-
auto_now=布尔值:在每次save()对象时,将该字段的值设置为当前日期,对update()没有效果;
-
auto_now_add=布尔值:只在第一次创建对象时,将该字段设置为当前日期。
auto_now_add、auto_now 和 default 参数是相互排斥的,只能传入一个。
-
-
DateTimeField:
日期和时间字段,多了时间部分,其他与DateField一样。
-
DecimalField:
固定精度的十进制数字段,
两个必须的参数:
- max_digits:数字的总位数;
- decimal_places:小数所占的位数。
-
EmailField:
属于CharField,但它会检查值是否是有效的电子邮件地址。
-
FileField:
文件上传字段。
一个可选参数:upload_to='路径',它会将文件保存到该路径下,并将文件的路径信息保存到数据库。
-
IntegerField:
整数类型字段,范围从 -2147483648 到 2147483647。
-
TextField:
一个文本字段,没有字数限制。
-
外键字段:
参考之前的文章:传送门
2. 字段通用参数更多字段类型参考官方文档:传送门
-
null=布尔值:
字段是否可以为空。
-
unique=布尔值:
字段的值是否唯一。
-
db_index=布尔值:
如果db_index=True,则代表着为此字段设置索引。
-
default=默认值:
为该字段设置默认值。
-
max_length=正整数:
字段值的最大字符数。
-
primary_key=布尔值:
是否设置当前字段为主键。
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()
上述方法二,在修改字段值的时候,会将所有字段(包括没有修改过的字段)都更新一遍。因此,当记录的字段非常多的时候,效率会十分低。
双下划线查询在官方文档中被称为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中有专门的用途,所以字段名称不能包含连续的多个下划线。
出于方便的目的,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(外键字段名称=关联对象,……)
多对多模型外键的修改,其实就是在修改连接表。要访问连接表,就要通过定义了外键的模型对象来访问:对象.外键,在此基础上进行修改。
-
新增:
对象.外键.add(关联对象的主键或关联对象,……)
-
删除:
对象.外键.remove(关联对象的主键或关联对象,……)
-
修改:
set()方法必须传可迭代对象。
对象.外键.set([关联对象的主键或关联对象,……])
内部原理是先删除后新增。
-
清空:
对象.外键.clear() # 括号内不要写任何东西
-
正、反向查询的概念:
看外键字段在哪一方定义。假设外键在A中定义,则A查B为正向查询,B查A反向查询。
-
正向查询:
获取关联对象时,直接通过外键字段进行获取:
关联对象 = 定义了外键的对象.外键.all() # all()看情况加
注意:如果查询到的关联对象有多个,此时就需要通过all()方法获取。
-
反向查询:
获取关联对象时,通过全小写模型类名_set进行获取,此处的模型类就是定义了外键的模型类。比如模型类名为Book,则应该写book_set:
定义了外键的对象 = 关联对象.全小写模型类名_set.all() # all()看情况加
-
正向查询:
通过外键__关联模型的字段(注意是双下划线)来获取关联对象的字段:
模型类.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提供的部分方法;能够自由扩展连接表。
缺点:代码比较繁琐,不能使用add、set、remove、clear方法。
-
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()一样,只不过前者内部使用的是子查询的方法,查询次数比后者多一次,并且还支持多对多的关系模型。



