前言
在此记录一下Django ORM的常用操作
常用字段
AutoField自增字段- 可设置主键
primary_key=True
- 可设置主键
CharField字符串max_length必填
BooleanField布尔类型integerField整形DateField日期- 与python里的
datetime.date相同,数据库字段内容为:2018-08-30
- 与python里的
DateTimeField日期+时间- 与python里的
datetime.datetime相同, 数据库字段内容为:2018-08-30 16:31:00 - 最后两个时间字段有两个参数
auto_new = true新增和编辑时保存当前时间auto_now_add = true新增时保存当前时间
- 与python里的
TextField文本类型
常用字段参数
null数据库可以为空blank用户输入可以为空unique唯一约束verbose_name提示信息choice让用户选择的数据,参数值为:choices=((1,'男'), (2, '女'))default默认值db_column自定义列名
查询常用方法
ALL 方法
查询所有数据,返回所有QuerySet 对象列表
1 | In [9]: from curd.models import Author |
GET 方法
获取一个唯一的数据对象,如果没有或者存在多个就会报错
1 | In [1]: from curd.models import Author |
filter 方法
获取满足条件的所有数据, 如果没有满足条件,则返回空对象列表
1 | In [4]: ret = Author.objects.filter(age=30) |
多个参数为and条件
1 | In [8]: ret = Author.objects.filter(age=96, name="王五") |
exclude 方法
获取不满足条件的所有对象
1 | In [54]: ret = Author.objects.exclude(pk=1) |
order_by 方法
排序, 带上-号是降序
1 | In [2]: ret = Author.objects.all().order_by("age") |
可以加多个排序规则,如果第一个排序遇到相同的,则以后的排序规则继续处理
1 | 比如 age=11岁的pkID为4 5,我们在以降序的方式排序,变成5,4 |
reverse 方法
对已经排序的对象列表进行翻转
1 | ret = Author.objects.all().order_by("age").reverse() |
values 方法
默认获取所有字段的值, 如果指定字段,则获取指定字段的值, 返回queryset
1 | In [12]: ret = Author.objects.all().values() |
values_list 方法
方法和values一样,只不过一个返回列表字典,一个返回元组
1 | In [16]: ret = Author.objects.all().values_list("name","age") |
distinct 方法
去重,需要用valuses 指定去重的字段
1 | In [38]: ret = Author.objects.all().values('age').distinct() |
count 方法
计数
1 | In [41]: ret = Author.objects.all().count() |
fist 方法
获取第一条对象
1 | In [44]: ret = Author.objects.all().first() |
last 方法
获取最后一条数据
1 | In [46]: ret = Author.objects.all().last() |
exists
判断是否有结果,数据是否存在. 返回Ture或者False
1 | In [50]: ret = Author.objects.filter(pk=10).exists() |
总结:
返回对象列表. (QuerySet)的方法
注意, 下列方法可在QuerySet基础上可多次调用- all
- filter
- exclude
- order_by
- reverse
- values [{}, {}]
- values_list [(), ()]
- distinct
返回对象
- get
- fister
- last
返回数字
- count
返回布尔
- exists
模糊查找,单表双下划线
小于:
ltless than大于:
gtgreater than小于等于
lteless than equal大于等于
gtegreater than equal范围:
range查找范围 如: age__range=[3, 20]成员判断
in如: age__in=[1,2,3]包含
contains如: name__contains=‘三’忽略带小写包含
icontains如: name__contains=‘三’以什么开头
startswith如 name__startswith=’aaa’忽略大小写,以什么开头
istartswith如 name__startswith=’aaa’以什么结尾
endswith忽略大小写,
iendswith
- 时间年
year如: time__year=’2020’ - 时间月
month - 时间天
day
- 查找是否为
nullis_null name__isnull=True name__isnull=False
例子:
1 | 比如查找pk小于 39的 |
关系管理对象
关系管理对象
多对一字段
ForeignKey1
2
3
4
5
6
7
8from django.db import models
class Blog(models.Model):
# ...
pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, null=True)关系管理对象
blog.entry_set. 默认为类名小写_setManyToManyField多对多关系:1
2
3
4
5
6class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
toppings = models.ManyToManyField(Topping)topping.pizza_set和pizza.toppings中均可用
关系管理对象方法
add(*objs, bulk=True, through_defaults=None)
将指定的模型对象加入关联对象集。
举例
1 | >>> b = Blog.objects.get(id=1) |
set(objs, bulk=True, clear=False, through_defaults=None)
替换一组关联对象:
1 | >>> new_list = [obj1, obj2, obj3] |
关系字段 on_delete 常用字段
当一个由引用的对象被删除时,Django 将模拟 on_delete 参数所指定的 SQL 约束的行为。例如,如果你有一个可空的 ForeignKey,并且你希望当被引用的对象被删除时,它被设置为空:
1 | user = models.ForeignKey( |
CASCADE级联删除。Django 模拟了 SQL 约束 ON DELETE CASCADE 的行为,也删除了包含 ForeignKey 的对象。
PROTECT通过引发 ProtectedError,即 django.db.IntegrityError 的子类,防止删除被引用对象。
SET_NULL设置 ForeignKey 为空;只有当 null 为 True 时,才有可能。
SET_DEFAULT将 ForeignKey 设置为默认值,必须为 ForeignKey 设置一个默认值。
外键,多对一关系字段
我们创建一个出版社的表和一个书籍的表,其中书籍的pub字段为外键
1 | class Book(models.Model): |
基于对象的查询
正向查询
通过book,查询到出版社
1 | # 获取到图书 |
反向查询通过出版社,拿到图书
如果没有指定 related_name 则使用反向查询时 需要,类名小写_set,如果指定了,则使用自定义的名称
pub = models.ForeignKey(‘Publisher’, on_delete=models.SET_NULL, null=True, related_name=”aaa”)
1 | book_obj = models.Publisher.objects.get(pk=1) |
基于字段的查询,给出版社,查询书
1 | ret = models.Book.objects.filter(pub__name='新欢出版社') |
基于外键字段,已双下滑线的方式去关联到对应的表中进行查询,可以继续以双下划线的方式调用方法
1 | ret = models.Book.objects.filter(pub__name__contains='新欢出') |
给出书的名字,在出版社表中查询出对应的出版社
1 | ret = models.Publisher.objects.filter(book__name="只狼") |
总结
外键的查询方式分为
正向查询和反向查询- 正向查询: 语法:
对象.关联字段.字段 - 反向查询: 表中不存在外键字段,但是在其他表中存在被关联的行为. 此时,我们需要通过 外键字段表名小写 + _set的方式进行查询
obj.表名_set.all()同理,可以在_set后继续调用queryset方法.
- 正向查询: 语法:
字段查询方法
正向查询: 直接查询外键字段,并且通过双下划线的方式去指定另一张表的字段,如:
models.Book.objects.filter(pub__name='新欢出版社'),此方法可以使用上一节单表双下划线继续模糊查询如:pub__pk__gte=124查询出版社主键大于等于124的书籍反向查询: 表内没有外键字段,直接引用另一张表关联的表名:如
(book__name="只狼")其中book为另一张表,表名小写
自定义反向查询的字段
- 通过在外键字段中,加入参数:
related_name="aaa",后续查询则直接使用book.aaa即可
- 通过在外键字段中,加入参数:
多对多
定义Models类,使用ManyToManyField
1 | from django.db import models |
多对多的方法大致与外键的方法一致
如:
根据作者查书
1 | author = models.Author.objects.get(pk=1) |
根据书,查作者
1 | book_obj = models.Book.objects.get(pk=1) |
根据字段查询
在书表里面通过作者查询
1 | ret = models.Book.objects.filter(author__name='张三') |
在作者表内通过book查询
1 | ret = models.Author.objects.filter(books__name="元龙") |
设置多对多关系
通过关系管理对象 方法
set 设置多对多关系,给出列表,book_ID,分别设置author 4:4, 4:2
1 | # 拿到主键ID为4的作者对象 |
add 方法 添加多对多关系
1 | # 添加对应的书籍ID |
remove 移除多对多关系
1 | author.books.remove(*models.Book.objects.filter(id__in=[3, 4])) |
clear 清空多对多关系
1 | author.books.clear() |
总结
- 正向反向查询与外键无太大差异
- 需要注意的是,
set方法在设置多对多关系时,会把之前的内容先清空.而add方法则不会清空
聚合查询和分组查询
聚合
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
用到的内置函数
1 | from django.db.models import Avg, Sum, Max, Min, Count |
示例:
1 | >>> from django.db.models import Avg, Sum, Max, Min, Count |
返回是一个字典,键值的名称是根据聚合字段__聚合函数自动生成出来的
如果想自定义键的名称,可向着聚合函数提供参数
1 | >>> models.Book.objects.aggregate(average_price=Avg('price')) |
如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
1 | >>> models.Book.objects.all().aggregate(Avg("price"), Max("price"), Min("price")) |
分组
我们在这里先复习一下SQL语句的分组。
假设现在有一张公司职员表:
我们使用原生SQL语句,按照部门分组求平均工资:
1 | select dept,AVG(salary) from employee group by dept; |
ORM查询:
1 | from django.db.models import Avg |
连表查询的分组:
1 | select dept.name,AVG(salary) from employee inner join dept on (employee.dept_id=dept.id) group by dept_id; |
ORM查询:
1 | from django.db.models import Avg |
- queryset对象.annotate()
- annotate进行分组统计,按前面select 的字段进行 group by
- annotate() 返回值依然是 queryset对象,增加了分组统计后的键值对
一些例子:
根据name分组,查询相同名字的年龄平均值
1 | models.Author.objects.values("name").annotate(avg=Avg("age")).values("name", "avg") |
示例1:统计每一本书的作者个数
1 | >>> book_list = models.Book.objects.all().annotate(author_num=Count("author")) |
示例2:统计出每个出版社买的最便宜的书的价格
1 | publisher_list = models.Publisher.objects.annotate(min_price=Min("book__price")) |
方法二:
1 | >>> models.Book.objects.values("publisher__name").annotate(min_price=Min("price")) |
示例3:统计不止一个作者的图书
1 | >>> models.Book.objects.annotate(author_num=Count("author")).filter(author_num__gt=1) |
示例4:根据一本图书作者数量的多少对查询集 QuerySet进行排序
1 | >>> models.Book.objects.annotate(author_num=Count("author")).order_by("author_num") |
示例5:查询各个作者出的书的总价格
1 | >>> models.Author.objects.annotate(sum_price=Sum("book__price")).values("name", "sum_price") |
F查询和Q查询
F查询
在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
示例1:
查询评论数大于收藏数的书籍
1 | from django.db.models import F |
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
1 | models.Book.objects.filter(commnet_num__lt=F('keep_num')*2) |
修改操作也可以使用F函数,比如将每一本书的价格提高30元
1 | models.Book.objects.all().update(price=F("price")+30) |
Q查询
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。
示例1:
查询作者名是小仙女或小魔女的
1 | models.Book.objects.filter(Q(authors__name="小仙女")|Q(authors__name="小魔女")) |
你可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询。
示例:查询作者名字是小仙女并且不是2018年出版的书的书名。
1 | >>> models.Book.objects.filter(Q(author__name="小仙女") & ~Q(publish_date__year=2018)).values_list("title") |
查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将”AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。
例如:查询出版年份是2017或2018,书名中带物语的所有书。
1 | >>> models.Book.objects.filter(Q(publish_date__year=2018) | Q(publish_date__year=2017), title__icontains="物语") |