前言

在此记录一下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]>

外键

我们创建一个出版社的表和一个书籍的表,其中书籍的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,如果指定了,则使用自定义的名称

1
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: 橘子物语>]>