momo's Blog.

Django相关ORM操作总结

字数统计: 4k阅读时长: 17 min
2020/09/20 Share

前言

在此记录一下Django ORM的常用操作

常用字段

  • AutoField 自增字段
    • 可设置主键primary_key=True
  • CharField 字符串
    • max_length 必填
  • BooleanField 布尔类型
  • integerField 整形
  • DateField 日期
    • 与python里的datetime.date 相同,数据库字段内容为: 2018-08-30
  • DateTimeField 日期+时间
    • 与python里的 datetime.datetime 相同, 数据库字段内容为: 2018-08-30 16:31:00
    • 最后两个时间字段有两个参数
      auto_new = true 新增和编辑时保存当前时间
      auto_now_add = true 新增时保存当前时间
  • TextField 文本类型

常用字段参数

  • null 数据库可以为空
  • blank 用户输入可以为空
  • unique 唯一约束
  • verbose_name 提示信息
  • choice 让用户选择的数据,参数值为: choices=((1,'男'), (2, '女'))
  • default 默认值
  • db_column 自定义列名

查询常用方法

ALL 方法

查询所有数据,返回所有QuerySet 对象列表

1
2
3
4
In [9]: from curd.models import Author
In [10]: ret = Author.objects.all()
In [11]: print(ret)
<QuerySet [<Author: Author object (1)>, <Author: Author object (2)>, <Author: Author object (3)>]>

GET 方法

获取一个唯一的数据对象,如果没有或者存在多个就会报错

1
2
3
4
In [1]: from curd.models import Author
In [2]: ret = Author.objects.get(pk=1)
In [3]: print(ret)
张三

filter 方法

获取满足条件的所有数据, 如果没有满足条件,则返回空对象列表

1
2
3
4
5
6
In [4]: ret = Author.objects.filter(age=30)
In [5]: print(ret)
<QuerySet []>
In [6]: ret = Author.objects.filter(age=96)
In [7]: print(ret)
<QuerySet [<Author: 王五>]>

多个参数为and条件

1
2
3
In [8]: ret = Author.objects.filter(age=96, name="王五")
In [9]: print(ret)
<QuerySet [<Author: 王五>]>

exclude 方法

获取不满足条件的所有对象

1
2
3
In [54]: ret = Author.objects.exclude(pk=1)
In [55]: ret
Out[55]: <QuerySet [2 --- 李四 --- 25, 3 --- 王五 --- 96, 4 --- 王五 --- 11, 5 --- 赵六 --- 11]>

order_by 方法

排序, 带上-号是降序

1
2
3
4
5
6
In [2]: ret = Author.objects.all().order_by("age")
In [3]: print(ret)
<QuerySet [1 --- 张三 --- 6, 4 --- 王五 --- 11, 2 --- 李四 --- 25, 3 --- 王五 --- 96]>
In [8]: ret = Author.objects.all().order_by("-age")
In [9]: print(ret)
<QuerySet [3 --- 王五 --- 96, 2 --- 李四 --- 25, 4 --- 王五 --- 11, 5 --- 赵六 --- 11, 1 --- 张三 --- 6]>

可以加多个排序规则,如果第一个排序遇到相同的,则以后的排序规则继续处理

1
2
3
4
比如 age=11岁的pkID为4 5,我们在以降序的方式排序,变成5,4
In [10]: ret = Author.objects.all().order_by("-age", "-pk")
In [11]: print(ret)
<QuerySet [3 --- 王五 --- 96, 2 --- 李四 --- 25, 5 --- 赵六 --- 11, 4 --- 王五 --- 11, 1 --- 张三 --- 6]>

reverse 方法

对已经排序的对象列表进行翻转

1
ret = Author.objects.all().order_by("age").reverse()

values 方法

默认获取所有字段的值, 如果指定字段,则获取指定字段的值, 返回queryset

1
2
3
4
5
6
7
In [12]: ret = Author.objects.all().values()
In [13]: print(ret)
<QuerySet [{'id': 1, 'name': '张三', 'age': 6}, {'id': 2, 'name': '李四', 'age': 25}, {'id': 3, 'name': '王五', 'age': 96}, {'id': 4, 'name': '王五', 'age': 11}, {'id': 5, 'name': '赵六', 'age': 11}]>

In [14]: ret = Author.objects.all().values("name","age")
In [15]: print(ret)
<QuerySet [{'name': '张三', 'age': 6}, {'name': '李四', 'age': 25}, {'name': '王五', 'age': 96}, {'name': '王五', 'age': 11}, {'name': '赵六', 'age': 11}]>

values_list 方法

方法和values一样,只不过一个返回列表字典,一个返回元组

1
2
3
In [16]: ret = Author.objects.all().values_list("name","age")
In [17]: print(ret)
<QuerySet [('张三', 6), ('李四', 25), ('王五', 96), ('王五', 11), ('赵六', 11)]>

distinct 方法

去重,需要用valuses 指定去重的字段

1
2
3
In [38]: ret = Author.objects.all().values('age').distinct()
In [39]: print(ret)
<QuerySet [{'age': 6}, {'age': 25}, {'age': 96}, {'age': 11}]>

count 方法

计数

1
2
3
4
In [41]: ret = Author.objects.all().count()

In [42]: ret
Out[42]: 5

fist 方法

获取第一条对象

1
2
3
In [44]: ret = Author.objects.all().first()
In [45]: ret
Out[45]: 1 --- 张三 --- 6

last 方法

获取最后一条数据

1
2
3
In [46]: ret = Author.objects.all().last()
In [47]: ret
Out[47]: 5 --- 赵六 --- 11

exists

判断是否有结果,数据是否存在. 返回Ture或者False

1
2
3
4
5
6
7
8
9
In [50]: ret = Author.objects.filter(pk=10).exists()

In [51]: ret
Out[51]: False

In [52]: ret = Author.objects.filter(pk=1).exists()

In [53]: ret
Out[53]: True

总结:

  • 返回对象列表. (QuerySet)的方法
    注意, 下列方法可在QuerySet基础上可多次调用

    • all
    • filter
    • exclude
    • order_by
    • reverse
    • values [{}, {}]
    • values_list [(), ()]
    • distinct
  • 返回对象

    • get
    • fister
    • last
  • 返回数字

    • count
  • 返回布尔

    • exists

模糊查找,单表双下划线

  • 小于: lt less than

  • 大于:gt greater than

  • 小于等于 lte less than equal

  • 大于等于 gte greater 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
  • 查找是否为 null is_null name__isnull=True name__isnull=False

例子:

1
2
3
4
比如查找pk小于 39
In [2]: ret = models.Author.objects.filter(pk__lt=39)
In [3]: ret
Out[3]: <QuerySet [1 --- 张三 --- 6, 2 --- 李四 --- 25, 3 --- 王五 --- 96, 4 --- 王五 --- 11, 5 --- 赵六 --- 11]>

关系管理对象

关系管理对象

  • 多对一字段 ForeignKey

    1
    2
    3
    4
    5
    6
    7
    8
    from 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. 默认为 类名小写_set

  • ManyToManyField 多对多关系:

    1
    2
    3
    4
    5
    6
    class Topping(models.Model):
    # ...
    pass

    class Pizza(models.Model):
    toppings = models.ManyToManyField(Topping)

    topping.pizza_setpizza.toppings 中均可用

关系管理对象方法

add(*objs, bulk=True, through_defaults=None)

将指定的模型对象加入关联对象集。

举例

1
2
3
>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.add(e) # Associates Entry e with Blog b.

set(objs, bulk=True, clear=False, through_defaults=None)

替换一组关联对象:

1
2
>>> new_list = [obj1, obj2, obj3]
>>> e.related_set.set(new_list)

关系字段 on_delete 常用字段

当一个由引用的对象被删除时,Django 将模拟 on_delete 参数所指定的 SQL 约束的行为。例如,如果你有一个可空的 ForeignKey,并且你希望当被引用的对象被删除时,它被设置为空:

1
2
3
4
5
6
user = models.ForeignKey(
User,
models.SET_NULL,
blank=True,
null=True,
)
  • CASCADE

    级联删除。Django 模拟了 SQL 约束 ON DELETE CASCADE 的行为,也删除了包含 ForeignKey 的对象。

  • PROTECT

    通过引发 ProtectedError,即 django.db.IntegrityError 的子类,防止删除被引用对象。

  • SET_NULL

    设置 ForeignKey 为空;只有当 null 为 True 时,才有可能。

  • SET_DEFAULT

    将 ForeignKey 设置为默认值,必须为 ForeignKey 设置一个默认值。

官方文档

外键,多对一关系字段

我们创建一个出版社的表和一个书籍的表,其中书籍的pub字段为外键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Book(models.Model):
name = models.CharField(max_length=30, verbose_name="书名")
pub = models.ForeignKey('Publisher', on_delete=models.SET_NULL, null=True)

def __str__(self):
return "{}---{}".format(self.pk, self.name)
__repr__ = __str__


class Publisher(models.Model):
name = models.CharField(max_length=32, verbose_name="出版社名称")

def __str__(self):
return "{}---{}".format(self.pk, self.name)
__repr__ = __str__

基于对象的查询

正向查询

通过book,查询到出版社

1
2
3
4
5
6
7
8
9
10
11
12
13
# 获取到图书
In [2]: book_obj = models.Book.objects.get(pk=1)
In [3]: book_obj
Out[3]: 1---只狼

# 根据外键,拿到出版社
In [4]: book_obj.pub
Out[4]: 4---新欢出版社4


# 拿到出版社ID
In [5]: book_obj.pub.id
Out[5]: 4

反向查询
通过出版社,拿到图书

如果没有指定 related_name 则使用反向查询时 需要,类名小写_set,如果指定了,则使用自定义的名称

pub = models.ForeignKey(‘Publisher’, on_delete=models.SET_NULL, null=True, related_name=”aaa”)

1
2
3
4
5
6
7
8
9
book_obj = models.Publisher.objects.get(pk=1)
print(book_obj)
print(book_obj.book_set) # 类名小写_set 关系管理对象
# 调用all()方法,拿到该出版社所有的书
print(book_obj.book_set.all())

1---新欢出版社
curd.Book.None
<QuerySet [3---斗破苍穹, 4---武动乾坤]>

基于字段的查询,给出版社,查询书

1
2
ret = models.Book.objects.filter(pub__name='新欢出版社')
print(ret)

基于外键字段,已双下滑线的方式去关联到对应的表中进行查询,可以继续以双下划线的方式调用方法

1
2
ret = models.Book.objects.filter(pub__name__contains='新欢出')
print(ret)

给出书的名字,在出版社表中查询出对应的出版社

1
2
ret = models.Publisher.objects.filter(book__name="只狼")
print(ret)

总结

  • 外键的查询方式分为正向查询反向查询

    • 正向查询: 语法:对象.关联字段.字段
    • 反向查询: 表中不存在外键字段,但是在其他表中存在被关联的行为. 此时,我们需要通过 外键字段表名小写 + _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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from django.db import models


# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
books = models.ManyToManyField('Book')

def __str__(self):
return "{} --- {} --- {}".format(self.pk, self.name, self.age)

__repr__ = __str__


class Book(models.Model):
name = models.CharField(max_length=30, verbose_name="书名")
pub = models.ForeignKey('Publisher', on_delete=models.SET_NULL, null=True)

def __str__(self):
return "{}---{}".format(self.pk, self.name)
__repr__ = __str__


class Publisher(models.Model):
name = models.CharField(max_length=32, verbose_name="出版社名称")

def __str__(self):
return "{}---{}".format(self.pk, self.name)
__repr__ = __str__

多对多的方法大致与外键的方法一致

如:

根据作者查书

1
2
3
author = models.Author.objects.get(pk=1)
print(author.books) # 关系管理对象
print(author.books.all()) # 关系管理对象集合

根据书,查作者

1
2
3
book_obj = models.Book.objects.get(pk=1)
# 其中多对多字段设置在author中,所以在book中需要反向查询. author_set 方法
print(book_obj.author_set.all())

根据字段查询

在书表里面通过作者查询

1
2
ret = models.Book.objects.filter(author__name='张三')
print(ret)

在作者表内通过book查询

1
2
ret = models.Author.objects.filter(books__name="元龙")
print(ret)

设置多对多关系

通过关系管理对象 方法
set 设置多对多关系,给出列表,book_ID,分别设置author 4:4, 4:2

1
2
3
4
# 拿到主键ID为4的作者对象
author = models.Author.objects.get(pk=4)
# 设置多对多对应关系,分别对应书籍主键4,2
author.books.set([4, 2])

add 方法 添加多对多关系

1
2
3
4
# 添加对应的书籍ID
author.books.add(1, 2)
# 也可以通过对象的方式来添加. 注意,需要将列表拆包
author.books.add(*models.Book.objects.filter(id__in=[3, 4]))

remove 移除多对多关系

1
author.books.remove(*models.Book.objects.filter(id__in=[3, 4]))

clear 清空多对多关系

1
author.books.clear()

总结

  1. 正向反向查询与外键无太大差异
  2. 需要注意的是,set方法在设置多对多关系时,会把之前的内容先清空.而add方法则不会清空

聚合查询和分组查询

聚合

aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。

用到的内置函数

1
from django.db.models import Avg, Sum, Max, Min, Count

示例:

1
2
3
>>> from django.db.models import Avg, Sum, Max, Min, Count
>>> models.Book.objects.all().aggregate(Avg("price"))
{'price__avg': 13.233333}

返回是一个字典,键值的名称是根据聚合字段__聚合函数自动生成出来的

如果想自定义键的名称,可向着聚合函数提供参数

1
2
>>> models.Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 13.233333}

如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:

1
2
>>> models.Book.objects.all().aggregate(Avg("price"), Max("price"), Min("price"))
{'price__avg': 13.233333, 'price__max': Decimal('19.90'), 'price__min': Decimal('9.90')}

分组

我们在这里先复习一下SQL语句的分组。

假设现在有一张公司职员表:

我们使用原生SQL语句,按照部门分组求平均工资:

1
select dept,AVG(salary) from employee group by dept;

ORM查询:

1
2
from django.db.models import Avg
Employee.objects.values("dept").annotate(avg=Avg("salary").values(dept, "avg")

连表查询的分组:

1
select dept.name,AVG(salary) from employee inner join dept on (employee.dept_id=dept.id) group by dept_id;

ORM查询:

1
2
from django.db.models import Avg
models.Dept.objects.annotate(avg=Avg("employee__salary")).values("name", "avg")
  1. queryset对象.annotate()
  2. annotate进行分组统计,按前面select 的字段进行 group by
  3. annotate() 返回值依然是 queryset对象,增加了分组统计后的键值对

一些例子:
根据name分组,查询相同名字的年龄平均值

1
models.Author.objects.values("name").annotate(avg=Avg("age")).values("name", "avg")

示例1:统计每一本书的作者个数

1
2
3
4
5
6
7
>>> book_list = models.Book.objects.all().annotate(author_num=Count("author"))
>>> for obj in book_list:
... print(obj.author_num)
...
2
1
1

示例2:统计出每个出版社买的最便宜的书的价格

1
2
3
4
5
6
>>> publisher_list = models.Publisher.objects.annotate(min_price=Min("book__price"))
>>> for obj in publisher_list:
... print(obj.min_price)
...
9.90
19.90

方法二:

1
2
>>> models.Book.objects.values("publisher__name").annotate(min_price=Min("price"))
<QuerySet [{'publisher__name': '沙河出版社', 'min_price': Decimal('9.90')}, {'publisher__name': '人民出版社', 'min_price': Decimal('19.90')}]>

示例3:统计不止一个作者的图书

1
2
>>> models.Book.objects.annotate(author_num=Count("author")).filter(author_num__gt=1)
<QuerySet [<Book: 番茄物语>]>

示例4:根据一本图书作者数量的多少对查询集 QuerySet进行排序

1
2
>>> models.Book.objects.annotate(author_num=Count("author")).order_by("author_num")
<QuerySet [<Book: 香蕉物语>, <Book: 橘子物语>, <Book: 番茄物语>]>

示例5:查询各个作者出的书的总价格

1
2
>>> models.Author.objects.annotate(sum_price=Sum("book__price")).values("name", "sum_price")
<QuerySet [{'name': '小精灵', 'sum_price': Decimal('9.90')}, {'name': '小仙女', 'sum_price': Decimal('29.80')}, {'name': '小魔女', 'sum_price': Decimal('9.90')}]>

F查询和Q查询

F查询

在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。

示例1:

查询评论数大于收藏数的书籍

1
2
from django.db.models import F
models.Book.objects.filter(commnet_num__gt=F('keep_num'))

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
2
>>> models.Book.objects.filter(Q(author__name="小仙女") & ~Q(publish_date__year=2018)).values_list("title")
<QuerySet [('番茄物语',)]>

查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将”AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。

例如:查询出版年份是2017或2018,书名中带物语的所有书。

1
2
>>> models.Book.objects.filter(Q(publish_date__year=2018) | Q(publish_date__year=2017), title__icontains="物语")
<QuerySet [<Book: 番茄物语>, <Book: 香蕉物语>, <Book: 橘子物语>]>
CATALOG
  1. 1. 前言
  2. 2. 常用字段
  3. 3. 常用字段参数
  4. 4. 查询常用方法
    1. 4.1. ALL 方法
    2. 4.2. GET 方法
    3. 4.3. filter 方法
    4. 4.4. exclude 方法
    5. 4.5. order_by 方法
    6. 4.6. reverse 方法
    7. 4.7. values 方法
    8. 4.8. values_list 方法
    9. 4.9. distinct 方法
    10. 4.10. count 方法
    11. 4.11. fist 方法
    12. 4.12. last 方法
    13. 4.13. exists
    14. 4.14. 总结:
  5. 5. 模糊查找,单表双下划线
  6. 6. 关系管理对象
    1. 6.1. 关系管理对象
    2. 6.2. 关系管理对象方法
      1. 6.2.1. add(*objs, bulk=True, through_defaults=None)
      2. 6.2.2. set(objs, bulk=True, clear=False, through_defaults=None)
  7. 7. 关系字段 on_delete 常用字段
  8. 8. 外键,多对一关系字段
    1. 8.1. 基于对象的查询
    2. 8.2. 总结
  9. 9. 多对多
    1. 9.1. 根据字段查询
    2. 9.2. 设置多对多关系
    3. 9.3. 总结
  10. 10. 聚合查询和分组查询
  11. 11. F查询和Q查询