系列文章目录
文章目录
一、ORM常用字段及参数
1. 字段类型
-
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=布尔值
:是否设置当前字段为主键。
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提供的部分方法;能够*扩展连接表。
缺点:代码比较繁琐,不能使用
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()
一样,只不过前者内部使用的是子查询的方法,查询次数比后者多一次,并且还支持多对多的关系模型。