Django ORM操作

最常用的ORM方法

方法 描述
all() 返回所有结果 返回QuerySet对象
get(**kwargs) 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误 返回具体对象
filter(**kwargs) 它包含了与所给筛选条件相匹配的对象 返回QuerySet对象
exclude(**kwargs) 它包含了与所给筛选条件不匹配的对象 返回QuerySet对象
values(*field) 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列 返回一个可迭代的字典序列
values_list(*field) 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 返回一个可迭代的元祖序列
order_by(*field) 对查询结果排序 返回QuerySet对象
reverse() 对查询结果反向排序,请注意reverse()通常只能在具有已定义顺序的QuerySet上调用(在model类的Meta中指定ordering或调用order_by()方法) 返回QuerySet对象
distinct() 从返回结果中剔除重复纪录(如果你查询跨越多个表,可能在计算QuerySet时得到重复的结果。此时可以使用distinct(),注意只有在PostgreSQL中支持按字段去重。) 返回QuerySet对象
count() 返回数据库中匹配查询(QuerySet)的对象数量。 返回数值
first() 返回第一条记录 返回具体对象
last() 返回最后一条记录 返回具体对象
exists() 如果QuerySet包含数据,就返回True,否则返回False 返回布尔值

单表查询之神奇的双下划线

举例 解释
objects.filter(id__in=[11, 22, 33]) 获取id等于11、22、33的数据
objects.exclude(id__in=[11, 22, 33]) 获取id not in 11,22,33的数据
objects.filter(name__contains="ven") 获取name字段包含”ven”的
objects.filter(name__icontains="ven") icontains大小写不敏感
objects.filter(id__range=[1, 3]) id范围是1到3的
objects.filter(first_day__year=2017) first_day字段为2017年的数据
objects.filter(brith_year=2018,name_contains='ve') 包含’ve’的name且birth为2018年的数据
objects.filter(name__isstartwith="ven") True/False
objects.filter(name__startwith="ven") 获取name以ven开头的数据
objects.filter(name__isendswith="ven") True/False
objects.filter(name__endswith="ven") 获取name以ven结尾的数据
objects.filter(id__lt=10, id__gt=1) 获取id大于1 且 小于10的值

外键操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.db import models

class Publisher(models.Model):
name = models.CharField(max_length=32, unique=True)

class Book(models.Model):
name = models.CharField(max_length=32, unique=True)
pubs = models.ForeignKey('Publisher')

class Person(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
birth = models.DateField()
books = models.ManyToManyField('Book')

正向查找:多对一(设置了foreignKey一方开始查找另一表)

1
2
3
4
5
6
# 通过对象查找(跨表) ==> 对象.外键关联字段.字段
book_obj = models.Book.objects.first() #查找第一本书对象
book_obj = book_obj.pubs.name #获取出版社对象的名称

# 字段查找(跨表) ==> 关联字段__字段
models.Book.objects.values_list("pubs__name")

反向查找:一对多

1
2
3
4
5
6
7
8
9
10
11
# 对象查找 ==> obj.表名_set 注:表名小写_set 即关联到对应表
ret_obj = models.Publisher.objects.filter(id=4).first()
ret = ret_obj.book_set.all() # 出版社关联的所有对象书籍

#字段查找 ==> 表名__字段
ret = models.Publisher.objects.filter(book__id = 5)

扩展:
如反向查询,不想book_set等写法,可以在models的类中属性约束加入`related_name`
pubs = models.ForeignKey('Publisher', related_name='books')
之后反向查询就不用表名关联了,需要也只能用配置的别名来操作。

多表查询之ManyToManyField

“关联管理器” 是在一对多或者多对多的关联上下文中使用的管理器。

它存在于下面两种情况:

  1. 外键关系的反向查询

  2. 多对多关联关系

简单来说就是当 点后面的对象 可能存在多个的时候就可以使用以下的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
create() # ==> 返回新创建的对象
# 为序号为第一个人添加写的书的对象,添加一本书‘活着’
models.Person.objects.first().book_set.create(name='活着')

add() # ==> 把指定的model对象添加到关联对象集中

# 查询id号小于3的人的对象
per_obj = models.Person.objects.filter(id__lt=3)
# 因为 books = models.ManyToManyField('Book') 所以是反向,需要 人表_set.add
ret = models.Book.objects.last().person_set.add(per_obj)
ret = models.Book.objects.first().person_set.add(*[1,2,3]) #一次添加多个
# Person类里面有books字段,所以 直接表名.add
ret = models.Person.objects.filter(id=3).first().books.add()

set() # ==> 更新model对象的关联对象
models.Book.objects.first().person_set.set([2,3,4])

remove() # ==> 从关联对象集中移除执行的model对象
book_obj = models.Book.objects.get(id=3).person_set.remove(3)

clear() # ==> 从关联对象集中移除一切对象,清空。
book_obj= models.Book.objects.first().person_set().clear()
  1. 对于外键对象,clear()和remove() 方法仅在null=True时存在。
  2. 对于所有类型的关联字段,add()、create()、remove()、clear() 、set() 都会马上更新数据库,换句话说,在关联的任何一端,都不需要再调用save()方法。
  3. 对于改变单表未关联其它表的字段,需要使用save()一下。
      per_obj.name = new_name
      per_obj.save()

聚合查询和分组查询

聚合查询:即mysql中的聚合函数sum(),count(),avg()…

aggregate()是querySet的一个终止子句,意思即,它返回一个包含一些键值对的字典。
键的名称是聚合值的标识符,值是计算出来得聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。

1
2
3
4
from django.db.models import Avg,Sum,Max,Min,Count
# 求平均值 最大值,最小值
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')}

分组:即mysql中的group_by,使用annotate()方法

1
2
3
4
5
6
7
8
9
10
11
from django.db.models import Avg
# 单表 部门分组求平均工资
Employee.objects.values("dept").annotate(avg=Avg("salary")).values("dept", "avg")
# ==> select dept,AVG(salary) from employee group by dept;

# 链表 查询的分组 求平均工资
models.Dept.objects.all().annotate(avg=Avg("employee__salary")).values("name", "avg")
# ==> select dept.name,AVG(salary) from employee
inner join dept
on (employee.dept_id=dept.id)
group by dept_id;

统计每一本书的作者个数

1
2
book_list = models.Book.objects.all().annotate(author_num=Count('author'))
# 先通过count里的author字段进行分组,然后再调用count方法计数

统计出每个出版社买的最便宜的书的价格

1
2
3
4
> # 正向
> pub_list = models.Publisher.objects.annotate(min_price=Min('book__price'))
> # 反向
> models.Book.objects.values('publisher__name').annotate(min_price=Min('price'))

统计不止一个作者的图书

1
models.Book.objects.annotate(author_num=Count('author')).filter(author_num__gt=1)

根据一本图书作者数量的多少对查询集QuerSet进行排序

1
models.Book.objects.annotate(author_num=Count('author')).order_by('author_num')

查询各个作者出的书的总价格

1
models.Author.objects.annotate(sum_price=Sum('book__price')).values('name','sum_price')

F查询和Q查询

F查询

Django提供F()方法来做这样的比较

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


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

1
2
3
4
5
6
7
8
9
from django.db.models import F

models.Book.objects.filter(commnet_num__gt=F('keep_num'))

# Django 支持F()对象之间以及F()对象和常数之间的加减乘除和取模的操作
models.Book.objects.filter(comment_num__lt=F('keep_num')*2)

#修改操作也可以使用F函数,比如将每一本数书的价格提高30元
models.Book.objects.all().update(price=F('price')+30)

详解增加表数据

1
2
3
4
5
6
7
# save 方法增加数据
author = Author.objects.get(id=5)
author.name='jony'
author.save()

# update 方法更新数据
Publisher.objects.filter(id=5).update(name='American publisher') #不能用get(id=2)

注意:update方法更新数据修改不能用get的原因是:update是QuerySet对象的方法,get返回的是一个具体model对象,它没有update方法

而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合,比如name=’jony’,可能有两个name=’jony’的行数据)。

models模型的save()方法,这个方法会更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列,update效率更高。

此外,update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录update()方法会返回一个整型数值,表示受影响的记录条数。

注意,这里因为update返回的是一个整形,所以没法用query属性;对于每次创建一个对象,想显示对应的raw sql,需要在settings加上日志记录部分

Q查询

filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象

查询id大于1并且评论数大于100的书

1
2
3
models.Book.objects.filter(nid__gt=1,commentNum__gt=100)
models.Book.objects.filter(nid__gt=1).filter(commentNum__gt=100)
models.Book.objects.filter(Q(nid__gt=1)&Q(commentNum__gt=100))

查询评论数大于100或者阅读数小于200的书

1
2
# Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。 
models.Book.objects.filter(Q(commentNum__gt=100)|Q(readNum__lt=200))

查询年份等于2017年或者价格大于200的书

1
models.Book.objects.filter(Q(publishDdata__year=2017)|Q(price__gt=200)) 

查询年份不是2017年或者价格大于200的书

1
models.Book.objects.filter(~Q(publishDdata__year=2017)&Q(price__gt=200))

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

1
bookList=models.Book.objects.filter(Q(publishDateyear=2016) | Q(publishDateyear=2017),title__icontains="python") 

ORM表自关联

内自关联是指表内数据相关联的对象和表是相同字段,这样我们就直接用表内关联将外键关联设置成自身表的字段。同样表内关联也分一对多字段和多对多字段

创建自关联表(一对多情景)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 对于微博评论,每条评论都可能有子评论,但每条评的字段内容应该都是相同的,并且每条评论都只有一个父评论,这就满足了,一对多的情形。父评论为关联字段,可以对应多个子评论,这就是一对多的自关联。
from django.db import models

#评论表
class Comment(models.Model):
#评论的内容字段
content=models.CharField(max_length=255)
#评论的发布时间
push_time=models.DateTimeField(auto_now_add=True)
#关联父评论的id,可以为空
pcomment = models.ForeignKey(to='self',null=True)
def __str__(self):
return self.content

添加数据,第一条数据关联字段为空,说明是父评论

第二条数据关联了第一条数据说明是第一条数据的字评论,同样后两条数据是是第二条数据的子评论

id content push_time pcomment_id
1 你好啊 2019-08-10 06:25:33 Null
2 兄弟你好 2019-08-10 06:26:13 1
3 老虎,你先上 2019-08-10 06:25:53 2
4 跟老虎还称兄道弟 2019-08-10 06:27:33 2

通过筛选父评论关联字段等于1的对象

1
Comment.objects.filter(pcomment_id=1)

根据子评论关联id正在查找父评论的内容

1
Comment.objects.filter(pcomment_id=2).values('pcomement__content')

根据父评论的id 反向查找子评论

1
Comment.objects.filter(id=1).values('comment__id'')

注意:外键关联是在子评论中,有关联字段的是子评论,子评论查父评论是正向,父评论查子评论是反向。

多对多表自关联

1
2
3
4
5
6
7
8
9
# 建立一张表,表字段为姓名以及朋友多对多字段,一个人可有多个朋友,也可以是多个人的朋友,找一个人的朋友是正向查找,找是某个人的朋友是反向查找。

from django.db import models

class Person(models.Model):
name = models.CharField(max_length=12)
friends = models.ManyToManyField(to='self',symmetrical=False,related_name='ship')
def __str__(self):
return self.name

正向查找关联字段__name 查找沁阳的基友

1
Person.objects.filter(name='沁阳').values('friends__name')

反向查找 沁阳的基友

1
Person.objects.all().filter(ship__name='沁阳')

正向查找 王帅的朋友

1
Person.objects.get(name='王帅').friends.all()

反向查找 是王帅朋友的人

1
Person.objects.filter(ship__name='王帅')

基友是沁阳的那个人反向查找

1
Person.objects.get(name='沁阳').ship.all()

基友是王帅的那个人反向查找

1
Person.objects.get(name='王帅').ship.all()

基友含有沁阳的人反向

1
Person.objects.filter(name='沁阳').values('ship__name')

注意:查找某人的朋友是正向,查找朋友里面有某人的人是反向查找

Django终端打印SQL语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 在Django项目的settings.py文件中,在最后复制粘贴如下代码:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
# 即为你的Django项目配置上一个名为django.db.backends的logger实例即可查看翻译后的SQL语句。

在Python脚本中调用Django环境

1
2
3
4
5
6
7
8
9
10
11
#  在应用的目录下建立一个py文件,将下面的代码写入
import os

if __name__ == '__main__':
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目的名字.settings")
import django
django.setup()

from 应用的名字 import models

'''所有的orm操作写在此处''

参考:ORM操作

ORM表自关联