ORM查询
Django ORM 使用的一些优化.
https://pythondjango.cn/django/advanced/6-queryset-advanced/
https://blog.csdn.net/zy010101/article/details/120816954
https://www.cnblogs.com/aaronthon/p/9435185.html
# 测试环境的搭建
若只想测试Django中某个py文件的内容,不想书写前后端交互的形式,就需要用到测试脚本!
脚本代码无论是写在应用下的test.py中,还是自己单独开设py文件都可以!
可以在Django项目中右键直接运行该tests.py文件
app01下的tests.py
测试环境的准备: 去manage.py中拷贝前四行代码!然后自己再写两行!
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
# -- 通过环境变量DJANGO_SETTINGS_MODULE来告知Django使用哪个配置
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
# -- 若独立运行的python脚本是需要依赖django中的一些配置,那么就需要用到django.setup()
# 运行到django.setup()该行代码才表明测试环境已经准备好了!!
django.setup()
# -- !!所有的测试代码必须都写在测试环境准备好后,否则不生效!
# 也就是写在django.setup()这行代码之后呗!
# ... ... ...
# ... ... ...
# ... ... ...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ORM单表常用方法
filter all first last get
values、values_list、distinct、order_by、reverse、count、exclude
只要是queryset对象,就可以通过.
query查看内部的sql语句!!这里主要是对单表的操作!!!
app01下的models.py
注意,需要执行两条命令进行数据库的迁移!!
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32, verbose_name='姓名')
age = models.IntegerField()
# create_time = models.DateField(auto_now=True, auto_now_add=True)
create_time = models.DateField() # -- 这里我们不传参,自己指定时间
"""
models.DateTimeField -- 年月日 时分秒
models.DateField -- 年月日
auto_now : 若值为True,只要本条数据有修改,就会将该字段的值更新
auto_now_add : 若值为True,会自动帮忙添加本条数据的入库时间,若后续想要更改,需要手动
"""
def __str__(self):
return self.name
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Ps: 在pychrm查看sqlite数据库时,下载对应的驱动可能失败,换一个低点版本试试!!
app01下的tests.py
先来回顾下前面已经学习了的单表的CURD!!
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
# -- 增加
# models.User.objects.create(name='egon', age=18, create_time='2021-08-13')
# models.User.objects.create(name='egon2', age=20, create_time='2021-09-13')
# user_obj = models.User(name='egon1', age=19, create_time='2022-02-22')
# user_obj.save()
# -- 修改
# models.User.objects.update(name='EGON') # -- 这样就全改了!!一定要注意!
# -- pk --> primary key --> 1
models.User.objects.filter(pk=1).update(name='EGON')
user_obj = models.User.objects.filter(pk=1).first()
user_obj.age = 25
user_obj.save()
# -- 删除
# -- 这里是物理删除,我们往往使用的是软删除
# models.User.objects.filter(pk=1).delete()
# -- 查询
models.User.objects.all()
# <QuerySet [<User: EGON>, <User: egon1>, <User: egon2>]>
print(models.User.objects.filter())
# -- print打印对象,触发了对象的__str__方法!
print(models.User.objects.filter().last()) # egon2
print(models.User.objects.filter().first()) # EGON
print(models.User.objects.filter(pk=1)) # <QuerySet [<User: EGON>]>
print(models.User.objects.filter(pk=10)) # <QuerySet []>
# -- 这里models.User.objects.filter(pk=2).get()一样的效果
# get和first的区别,若QuerySet对象为空,前者报错,后者返回None
# 所以get不建议用,非要用,需要try..except捕捉异常!
res = models.User.objects.filter(pk=2).first()
print(res) # egon1
print(res.name) # egon1
print(res.age) # 19
print(res.create_time) # 2022-02-22
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
ORM的其它方法
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
# -- values ==> 指定查询字段!结果是列表套字典.
# select name,age from user
# 字段、数据量少的时候可以all"否则内存会爆",字段多or数据量大的时候用value!!
res = models.User.objects.values('name', 'age')
# <QuerySet [{'name': 'EGON', 'age': 25}, {'name': 'egon1', 'age': 19}, {'name': 'egon2', 'age': 20}]>
print(res)
# SELECT "app01_user"."name", "app01_user"."age" FROM "app01_user"
print(res.query)
# -- values_list ==> 指定查询字段!结果是列表套元祖.
res = models.User.objects.values_list('name', 'age')
# <QuerySet [('EGON', 25), ('egon1', 19), ('egon2', 20)]>
print(res)
# -- distinct 去重查询!
# 强调,去重的前提的是数据一摸一样!千万别加id!
# 若需要去重的查询数据中带有主键,去重就没有意义啦!所以我们要指定字段去重!
res = models.User.objects.values('name').distinct()
print(res)
# -- order_by 排序
# select * from user order by age asc
models.User.objects.order_by('age') # -- 默认升序
# 先按照age倒序,age相同的情况按照create_time正序排.
# select name,age from user order by age desc,create_time asc
res = models.User.objects.values_list('name', 'age').order_by('-age', 'create_time')
# <QuerySet [('EGON', 25), ('egon2', 20), ('egon1', 19)]>
print(res)
# -- reverse 翻转
# 强调!reverse反转有个前提,必须先排序!!
res = models.User.objects.order_by('age').reverse()
print(res)
# -- count 数量,统计当前数据的个数
# 当然可以结合filter先进行筛选,然后再统计数量!
res = models.User.objects.count()
print(res) # 3
# -- exclude 排除在外
res = models.User.objects.exclude(name='egon1')
# <QuerySet [<User: EGON>, <User: egon2>]>
print(res)
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 数据准备
app01下的models.py
注意,需要执行两条命令进行数据库的迁移!!并在数据库可视化界面添加模拟数据!!
"""
book 书籍表 -- id title price "publish_id" "authors_id" maichu kucun
1,西游记,600,1,1,1000,50
2,红楼梦,700,2,1,1000,2000
3,水浒传,300,1,2,1000,3000
4,三国演义,610,1,3,1000,60
publish 出版社表 -- id title addr
1,南京出版社,南京
2,东京出版社,东京
author 作者表 -- id name "detail_id" age create_time
1,egon,1,32,2020-10-01
2,jason,2,18,2021-09-01
3,kevin,3,40,2020-09-01
author_detail 作者详情表 -- id phone email
1,110,[email protected]
2,120,[email protected]
3,130,[email protected]
★ 分析表与表之间的关系:
book:publish = n:1
book:author = n:n
author:author_detail = 1:1
"""
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
publish = models.ForeignKey(to='Publish')
authors = models.ManyToManyField(to='Author')
maichu = models.IntegerField(default=1000) # -- 卖出图书数
kucun = models.IntegerField(default=1000) # -- 库存图书数
def __str__(self):
return self.title
class Publish(models.Model):
title = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
def __str__(self):
return self.title
class Author(models.Model):
name = models.CharField(max_length=32)
detail = models.OneToOneField(to='AuthorDetail')
age = models.IntegerField(default=1)
create_time = models.DateField(null=True)
def __str__(self):
return self.name
class AuthorDetail(models.Model):
phone = models.CharField(max_length=32)
email = models.EmailField()
def __str__(self):
return self.phone
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 基于双下划线查询
age_ _gt、age_ _lt、age_ _gte、age_ _lte
age_ _in
age_ _range
name_ _contains、name_ _icontains
name_ _startswith、name_ _endswith
create_time_ _month、create_time_ _year
app01下的tests.py
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
# -- ■ age__gt、age__lt、age__gte、age__lte
# gte --> greater than equal; lte --> less than equal.
# -- 年龄大于35岁的数据
# select * from table where age > 35
res = models.Author.objects.filter(age__gt=35)
# <QuerySet [<Author: kevin>]>
print(res)
# -- 同理.小于、大于等于、小于等于
models.Author.objects.filter(age__lt=35)
models.Author.objects.filter(age__gte=35)
models.Author.objects.filter(age__lte=35)
# -- ■ age__in、
# -- 查询年龄是32 18的数据
# select * from table where age in (32,18);
# in可以理解成or age=1 or age=3 or age=5, not in不走索引
models.Author.objects.filter(age__in=[32, 18])
# -- ■ age__range
# -- 查询年龄在18到40之间的数据,闭区间首尾都要
# select * from table where age between in 18 and 40;
models.Author.objects.filter(age__range=[18, 40])
# -- ■ name__contains、name__icontains
# -- 查询出名字中含有s的数据,<模糊查询>
# select * from table where name like '%s%';
models.Author.objects.filter(name__contains='s') # -- 区分大小写
models.Author.objects.filter(name__icontains='S') # -- 忽略大小写
# -- ■ name__startswith、name__endswith
# -- 查询表中以t开头的名字
models.Author.objects.filter(name__startswith='t')
# -- 查询表中以t结尾的名字
models.Author.objects.filter(name__endswith='t')
# -- ■ create_time__month、create_time__year、create_time__day
# 在sqlite数据库添加字段类型为DateField的数据时会转成时间戳,这可能是个bug.
# 这里我们用命令添加.
models.Author.objects.filter(pk=1).update(create_time='2020-10-01')
models.Author.objects.filter(pk=2).update(create_time='2021-09-01')
models.Author.objects.filter(pk=3).update(create_time='2020-09-01')
# -- 查询9月份的数据! (该需求很常见!)
# 原生sql需要用到data_format方法!!具体的可以查查.
models.Author.objects.filter(create_time__month='09')
# -- 查询2020年的数据!
models.Author.objects.filter(create_time__year='2020')
# -- 查询2020年9月份的数据!
models.Author.objects.filter(create_time__year='2020',create_time__month='09')
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 外键查询
操作表中的外键,CUD操作
R查的操作暂且不管,它涉及到后文的正反向概念..1> 一对多的外键 -- 推荐直接对外键赋值(一般来说,外键关联的是另一张表的主键)
2> 多对多的外键 -- 拿到表的对象,obj.外键虚拟字段.指定方法()
.
# 一对多外键CUD
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
"""
一对多的增删改查
书:出版社 = n:1 -- 操作书表中的外键!!
"""
# -- ■ 查
# 牵扯到正反向概念!详看后文!
# -- ■ 添加 create
# 添加一本书!
# -- 方式一(推荐) 里面是publish_id~
# 注意:publish_id必须存在!!
models.Book.objects.create(title='水浒传', price=100, publish_id=1)
# -- 方式二 里面是publish~
# publish的值为Publish的实例对象
publish_obj = models.Publish.objects.filter(pk=2).first() # -- 拿到出版社对象
models.Book.objects.create(title='三国演义', price=110, publish=publish_obj)
# -- ■ 修改 update
# -- 方式一(推荐)
models.Book.objects.filter(pk=1).update(title='水浒传1', publish_id=2)
# -- 方式二
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.filter(pk=1).update(title='水浒传2', publish=publish_obj)
# -- ■ 删除 delete
# 默认是级联删除的!也就是该出版社对应的book数据也会在Book表中删除!!
models.Publish.objects.filter(pk=1).delete()
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
31
32
33
34
35
36
37
38
39
40
# 多对多外键CUD
add()、set([])、remove()、all()、clear()
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
"""
多对多的增删改查
书:作者 = n:n -- 操作书表中的虚拟外键字段authors
"""
# -- 添加 add => 原来就有的不会重复添加!
# -- 给id为1的书添加一个作者
book_obj = models.Book.objects.filter(pk=1).first()
# app01.Author.None -- 该结果就表明可以通过虚拟字段authors跨到第三张表!
print(book_obj.authors) # -- 跨到第三张表
# -- ★ 给id为1的book添加一个id为1的作者
book_obj.authors.add(1)
# -- ★ 给id为1的book添加两个作者,id为1和id为2的作者
book_obj.authors.add(2, 3)
# Ps:当然也可以在add里写作者对象!!但不推荐这样做,因为要先查找一遍.数据量一多,效率很低.
# book_obj.authors.add(author_obj0, author_obj1)
# -- 修改 set => 没有也不碍事
# 同理,set里的可迭代对象的元素可以放对象,但不推荐!
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.set([2]) # -- 用元祖也可以,就一点,必须为可迭代对象!
# Django会先比对id为1的book,有没有对应的id为2、3的author
# 若有,不动,没有就添加 -- 有查找比对的过程
# -- 这种方式不一定有先删除id为1的book对应的所有author,然后再添加的效率高
book_obj.authors.set([2, 3])
# -- 删除 remove => 没有也不碍事
book_obj = models.Book.objects.filter(pk=1).first()
# -- 在第三张表中,将id为1的书对应的id为2的作者的这条记录删除..
book_obj.authors.remove(2)
# -- 也可以删除多个 若没有对应id为2的作者的记录删除,不会管,不碍事,不会报错
book_obj.authors.remove(2, 3)
# -- 查询 all 涉及到正反向概念,详见后文!!
# 查询id为2的书的所有作者
book_obj = models.Book.objects.filter(pk=2).first()
res = book_obj.authors.all()
# <QuerySet [<Author: egon>, <Author: jason>, <Author: kevin>]>
# 若没有,值为<QuerySet []>
print(res)
# -- 清空 clear
# 在第三张表中清空某个书籍与作者的绑定关系!
book_obj = models.Book.objects.filter(pk=2).first()
res = book_obj.authors.clear()
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# ORM多表查询!!
想拿到 [对象], 想到 [子查询]; 想拿到包含 <跨表字段> 的一条条记录,想到 <联表查询>..
子查询 -- 用对象来跨
先拿到对象obj, obj = models.表名.objects.filter(条件).first()
1> 正向 正1obj.外键字段
; 正nobj.外键字段.all()
2> 反向 反1obj.另一张表表名小写
; 反nobj.另一张表表名小写_set.all()
无论正向还是反向, 正/反1拿到都是另一张表的一个 实例对象/记录;正/反n拿到的都是QuerySet对象.
Ps: QuerySet对象可以简单理解成一个列表,这里的QuerySet对象包含的是一个个实例/一条条记录.
eg: <QuerySet [<Author: egon>, <Author: jason>]>联表查询 -- 用字段来跨
核心重点是根据基表精准指定跨表字段! 所以别那么死板,像子查询里等其它地方都可以指定跨表字段..
1> 正向查询涉及到另一张表的某个字段,外键__另一张表的字段
2> 反向查询涉及到另一张表的某个字段,另一张表的表名小写__另一张表的字段
Ps:外键到另一张表,另一张表的表名小写也到另一张表,可以连续跨多张表
正向的外键/反向的需跨到表的小写__正向的外键/反向的需跨到表的小写__最后跨到的那张表的字段
res = model.表名.objects.filter(条件).values('字段1','字段2')
无论正向还是反向,res的结果都是QuerySet对象
eg: <QuerySet [{'name': 'egon', 'phone': 110},{'name': 'phone', 'phone': 120}]>
# 正反向概念
正向: 外键字段在我手上,我查你,那就是正向查询
eg -- 书:出版社 = n:1 外键字段在书表中,书查出版社就是正向!! 反向: 反之,外键字段在我手上,你查我,那就是反向查询
eg -- 出版社查书就是反向!!
step1: 判断是正向还是反向!
step2: 判断查的是多的那方还是少的那方!
# 子查询
子查询: 基于对象的跨表查询!
正向查询按字段;
反向查询按表名小写
表名小写_set.all()
.
# 正向查询
正向查询按字段;当返回的结果可能为多个时,要加.all()!!
★ 简单来说,正向查询 多对多/无论查哪方都是多的那方 要加.all()!!
其余情况,正向查询的一对多,一对一不加.all()
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
# -- 查询主键为1的书籍的出版社 (即查该书所关联的出版社对象)
# 书:出版社 = n:1
# 书查出版社=>正向
book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.publish) # 南京出版社
# -- 查询主键为2的书籍的作者 (即查该书所关联的作者对象)
# 书:作者 = n:n 设计表时外键在书这
# So,书查作者=>正向
book_obj = models.Book.objects.filter(pk=2).first()
print(book_obj.authors) # app01.Author.None
print(book_obj.authors.all()) # <QuerySet [<Author: egon>, <Author: jason>]>
# -- 查询作者jason的电话号码 (即查该作者所关联的作者详情对象,通过作者详情对象拿到电话号码)
# 作者:作者详情 = 1:1 设计表时外键在作者这
# 作者查作者详情=>正向
author_obj = models.Author.objects.filter(name='egon').first()
print(author_obj.detail.phone) # 110
# Ps: 对字段的跨表!!联表查询里有涉及到! ※※※※※※
# ad_obj = models.AuthorDetail.objects.filter(author__name='egon').first()
# print(ad_obj.phone)
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
31
32
33
34
# 反向查询
反向查询按
表名小写
表名小写_set.all()
.
简单来说,反向查询 查多的那方n表名小写_set.all()
; 查少的那方1表名小写
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
# -- 查询出版社是南京出版社出版的书
# 书:出版社 = n:1 外键字段在书那
# 出版社查书=>反向
publish_obj = models.Publish.objects.filter(title='南京出版社').first()
print(publish_obj.book_set) # -- app01.Book.None
res = publish_obj.book_set.all()
print(res)
# -- 查询作者jason写过的书
# 作者:书 = n:n 设计表时外键在书这
# 作者查书=>反向
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.book_set) # -- app01.Book.None
res = author_obj.book_set.all()
print(res)
# -- 查询手机号是110的作者的姓名 “这里有误,懒得改了.”
# 作者:作者详情 = 1:1 设计表时外键在作者详情这
# 作者详情查作者=>反向
ad_obj = models.AuthorDetail.objects.filter(phone='110').first()
print(ad_obj.author) # egon
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
31
32
33
34
# 联表查询
联表查询: 基于 双下划线 的跨表查询
查询过程中,正反向的概念同样适用!!
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
# -- 查询egon的年龄和手机号
# 作者:作者详情 = 1:1
# 1> 方式一
# 外键在作者表那,作者查作者详情=>正向 (基表是作者表)
# ★★★ 正向查询涉及到另一张表的某个字段, `外键__另一张表的字段`
res = models.Author.objects.filter(name='egon').values('age', 'detail__phone')
print(res) # <QuerySet [{'age': 32, 'detail__phone': '110'}]>
print(res.query)
# 2> 方式二
# 外键在作者表那,作者详情查作者=>反向 (基表是作者详情表)
# ★★★ 反向查询涉及到另一张表的某个字段, `另一张表的表名小写__另一张表的字段`
res = models.AuthorDetail.objects.filter(author__name='egon').values('author__age', 'phone')
print(res) # <QuerySet [{'author__age': 32, 'phone': '110'}]>
# -- 查询主键为1的书的名称和该书对应的出版社名字
# 书:出版社 = n:1
# 1> 方式一
# 外键字段在书那,书查出版社=>正向
res = models.Book.objects.filter(pk=1).values('title', 'publish__title')
print(res) # <QuerySet [{'title': '西游记', 'publish__title': '南京出版社'}]>
# 2> 方式二
# 外键在书那,出版社查书=>反向 (基表是出版社表)
res = models.Publish.objects.filter(book__pk=1).values('book__title', 'title')
print(res) # <QuerySet [{'book__title': '西游记', 'title': '南京出版社'}]>
# -- 查询主键为2的书籍的作者姓名
# 书:作者 = n:n
# 1> 方式一
# 外键字段在书那,书查作者=>正向
res = models.Book.objects.filter(pk=2).values('authors__name')
print(res) # <QuerySet [{'authors__name': 'egon'}, {'authors__name': 'jason'}]>
# 2> 方式二
# 作者查书=>反向
res = models.Author.objects.filter(book__pk=2).values('name')
print(res) # <QuerySet [{'name': 'egon'}, {'name': 'jason'}]>
# -- ** 查询主键为2的书籍的作者的手机号! **
# 牵扯了三张表!! 书:作者 = n:n 作者:作者详情表 = 1:1
# ★★★ 书表中的authors外键跨到作者表__作者表中的detail外键跨到作者详情表__作者详情表的某个字段
res = models.Book.objects.filter(pk=2).values('authors__detail__phone')
# < QuerySet[{'authors__detail__phone': '110'}, {'authors__detail__phone': '120'}] >
print(res)
""" 查询egon的年龄和手机号
SELECT
"app01_author"."age",
"app01_authordetail"."phone"
FROM
"app01_author"
INNER JOIN
"app01_authordetail"
ON
( "app01_author"."detail_id" = "app01_authordetail"."id" )
WHERE
"app01_author"."name" = egon
"""
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# 聚合查询
聚合查询通常情况下是配合着分组一起使用的
只要跟数据库相关的模块:
基本上都在django.db.models
里面;
如果上述没有,那么应该在django.db里面.聚合查询需要加 aggregate !!!
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
from django.db.models import Max, Min, Sum, Count, Avg
# -- 查询所有书的平均价格、最高价格、最低价格、数量
# select avg(price) from book;
res = models.Book.objects.aggregate(Avg('price'), Max('price'))
print(res) # {'price__avg': 127.5, 'price__max': Decimal('200.00')}
# -- 起个别名
res = models.Book.objects.aggregate(age=Avg('price'), max=Max('price'))
print(res) # {'age': 127.5, 'max': Decimal('200.00')}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 分组查询
也就是sql中的group by!!
分组查询需要加 annotate !!!
指定分组的字段可以理解成是通过 笛卡尔积/联表 的字段!!
看这篇博文!!很详细!★★★https://www.freecodecamp.org/chinese/news/introduction-to-django-group/
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
from django.db.models import Max, Min, Sum, Count, Avg
res = models.Book.objects.annotate() # -- ★不指定分组依据,默认按照表的主键进行分组
# <QuerySet [<Book: 西游记>, <Book: 红楼梦>, <Book: 水浒传>, <Book: 三国演义>]>
print(res)
# -- 统计每一本书的作者个数
# count起别名=Count("authors__pk"正向查询))
# 1 <QuerySet [<Book: 西游记>, <Book: 水浒传>, <Book: 三国演义>, <Book: 红楼梦>]>
print('1', models.Book.objects.annotate(count=Count("authors__pk")))
# ★ 分组结合聚合!!
res = models.Book.objects.annotate(count=Count("authors__pk")).values("count")
# <QuerySet [{'count': 0}, {'count': 0}, {'count': 0}, {'count': 2}]>
print(res)
# -- 统计每个出版社卖的最便宜的书的价格
# 书:出版社 = n:1 出版社查书=>反向查询
res = models.Publish.objects.annotate(price_min=Min("book__price")).values("title", "price_min")
print(res)
# -- 统计不止一个作者的图书
# step1:先按照图书分组,求每一本书对应的作者个数.
# Count('authors__pk') Count('authors') 都是一样的效果.
# models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')
# step2:过滤出不止一个作者的图书/筛选出大于1的.
res = models.Book.objects.annotate(author_num=Count('authors__pk')). \
filter(author_num__gt=1).values('title', 'author_num')
print(res) # -- <QuerySet [{'title': '红楼梦', 'author_num': 2}]>
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
31
32
33
34
35
36
37
38
39
40
# F与Q查询
# F查询
要清楚表中的数据实时都是在改变的.. F('字段名') 拿到该字段在这一刻/这一瞬间的值!!
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
from django.db.models import F
from django.db.models import Value
from django.db.models.functions import Concat # -- 拼接
# -- 查出卖出数大于库存数的书籍!
# sql -- select * from book where maichu > kucun;
res = models.Book.objects.filter(maichu__gt=F('kucun'))
print(res) # <QuerySet [<Book: 红楼梦>, <Book: 水浒传>]>
# -- 将<所有>书籍的价格提升500块!
# sql -- update book set price = price + 500; # -- 是涉及到mysql的行锁和表锁的!!
models.Book.objects.update(price=F('price') + 500)
# -- 将<所有>书籍的名称后面加上爆款两个字!
# Ps: 当然可以筛选一部分书籍,models.Book.objects.filter().update()
# 注意:必须这样写,不然就会导致要操作的数据的值变为0..
models.Book.objects.update(title=Concat(F('title'), Value('爆款')))
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
# Q查询
涉及到 not、and、or
from django.test import TestCase
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tem.settings")
import django
django.setup()
from app01 import models
from django.db.models import Q
# -- 查询卖出数大于100以及价格小于600的书籍
# and关系! => ,
res = models.Book.objects.filter(maichu__gt=100, price__lt=600)
print(res)
# -- 查询卖出数大于100或者价格小于600的书籍
# or关系! => Q |
res = models.Book.objects.filter(Q(maichu__gt=100) | Q(price__lt=600))
print(res)
# -- 查询卖出数不大于100或者价格不小于600的书籍
# not+or关系! => ~ Q |
res = models.Book.objects.filter(~Q(maichu__gt=100) | ~Q(price__lt=600))
print(res)
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
# 补充
正向, 无论表之间是什么关系, filter、value、value_list里使用双下划线、select_related里写入外键字段在底层都会实现联表!!
# 一对多 正向
"""
from django.db import models
class Publish(models.Model):
title = models.CharField(verbose_name="出版社名", max_length=32)
class Book(models.Model):
name = models.CharField(verbose_name="书名", max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
"""
from django.test import TestCase
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day05.settings')
import django
django.setup()
from apps.app01 import models
# -- 往Publish表加数据
models.Publish.objects.create(title="四川")
models.Publish.objects.create(title="重庆")
models.Publish.objects.create(title="上海")
models.Publish.objects.create(title="北京")
# -- 往Book表加数据
models.Book.objects.create(name="ABC1", price="25", publish_id=1) # 若publish_id的值不存在,因为外键约束,报错
obj = models.Publish.objects.filter(title="重庆").first()
models.Book.objects.create(name="ABC2", price="30", publish=obj) # 若obj为None,报错
models.Book.objects.create(name="ABC3", price="50", publish_id=obj.id)
models.Book.objects.create(name="ABC4", price="60", publish_id=3)
models.Book.objects.create(name="ABC5", price="20", publish_id=4)
# -- 删除Book表的某条数据
models.Book.objects.filter(pk=2).delete()
# -- 删除Publish表的某条数据,特别注意:会级联删除
models.Publish.objects.filter(pk=1).delete() # 会级联删除
# -- 删除id为2的出版社的所有书籍 (id为2的出版社是不删除的!)
models.Book.objects.filter(publish_id=2).delete()
# -- 删除上海出版社的所有书籍 该方式要写两个sql
obj = models.Publish.objects.filter(title="上海").first()
# 下面这一行语句等同于 models.Book.objects.filter(publish_id=obj.id).delete()
models.Book.objects.filter(publish=obj).delete()
# -- 删除北京出版社的所有书籍 外键__外籍所关联表里的字段,查看sql使用inner join进行了连表
models.Book.objects.filter(publish__title="北京").delete()
models.Book.objects.create(name="ABC6", price="20", publish_id=4)
models.Book.objects.create(name="ABC7", price="20", publish_id=4)
# -- 若查询的结果是一个对象,而该对象对应的表是有外键的.. 简单探究下
obj = models.Book.objects.filter(pk=6).first()
print(obj) # Book object (6)
print(obj.publish) # Publish object (4) 意味着book对象.外键字段得到了book对象所关联的publish对象
print(obj.publish.title) # 北京 So,可以通过点语法查看所关联的publish对象里的内容,但注意此时又查询了一次数据库
print(obj.publish_id) # 4 这就是外键神奇的地点,还可以.publish_id
# -- 查询的结果是queryset对象
# 借助values或values_list通过双下划线的跨表操作,能拿到当前记录以及当前记录的外键所关联的记录的所有字段的值
# 思考,1:n 外键在多的那方,尽管queryset对象里可有多条记录,但每条记录的外键所关联的记录只有一条!!
# 共四种情况!!
# 1> 指定列,连表
res = models.Book.objects.filter(pk__gte=6).values("name", "publish")
print(res) # <QuerySet [{'name': 'ABC6', 'publish': 4}, {'name': 'ABC7', 'publish': 4}]>
res = models.Book.objects.filter(pk__gte=6).values("name", "publish_id")
print(res) # <QuerySet [{'name': 'ABC6', 'publish_id': 4}, {'name': 'ABC7', 'publish_id': 4}]>
res = models.Book.objects.filter(pk__gte=6).values("name", "publish__id")
print(res) # <QuerySet [{'name': 'ABC6', 'publish__id': 4}, {'name': 'ABC7', 'publish__id': 4}]>
res = models.Book.objects.filter(pk__gte=6).values("name", "publish__title")
print(res) # <QuerySet[{'name':'ABC6','publish__title':'北京'},{'name':'ABC7','publish__title':'北京'}]>
# 2> 指定列,连表
res = models.Book.objects.filter(pk__gte=6).values_list("name", "publish__title")
print(res) # <QuerySet [('ABC6', '北京'), ('ABC7', '北京')]>
# 3> 所有列
# 很明显,先查询Book拿到Book表所有记录,然后循环,通过对象.外键.外键字段又进行了两次对Publish表的查询,共3次单表查询
# 而通过"publish__title"的连表查询,只查了一次!!!
res = models.Book.objects.filter(pk__gte=6) # 只是一个单表查询的sql
print(res) # <QuerySet [<Book: Book object (6)>, <Book: Book object (7)>]>
for obj in res:
# ABC6 北京
# ABC7 北京
print(obj.name, obj.publish.title) # 此时不推荐进行obj.publish.title操作
# 4> 所有列,连表
# 若很想 通过对象.外键.外键字段 进行跨表查询. 使用select_related进行优化!!
res = models.Book.objects.filter(pk__gte=6).select_related("publish") # 是一个联表查询的sql,将两张表关联了起来
print(res) # <QuerySet [<Book: Book object (6)>, <Book: Book object (7)>]>
for obj in res:
# 此处的obj.publish.title就不会进行数据库查询啦.
# 因为数据在select_related进行inner join时就封装到对象里了.
print(obj.name, obj.publish.title)
# -- 更新
models.Book.objects.filter(id=6).update(name="CCC6")
# !!连表只能支持做查询功能,其它操作(添加、删除、修改)是不支持的. 添加、删除、修改只能改变当前表的数据!!!跨不了一点.
# 想通过Book表的外键字段修改Publish表,直接报错!!
# models.Book.objects.filter(id=6).update(publish__title="广州")
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# 一对多 反向
情况一
"""
from django.db import models
class Publish(models.Model):
title = models.CharField(verbose_name="出版社名", max_length=32)
class Book(models.Model):
name = models.CharField(verbose_name="书名", max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
"""
from django.test import TestCase
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day05.settings')
import django
django.setup()
from apps.app01 import models
# -- 单表查询
res = models.Publish.objects.filter(title="北京").values("id", "title")
print(res) # <QuerySet [{'id': 4, 'title': '北京'}]>
# 需求:查询北京出版社的所有书籍,字段包括出版社id、出版社名字、书籍名字
# -- 反向
res = models.Publish.objects.filter(title="北京").values("id", "title", "book")
print(res) # <QuerySet [{'id': 4, 'title': '北京', 'book': 6}, {'id': 4, 'title': '北京', 'book': 7}]>
res = models.Publish.objects.filter(title="北京").values("id", "title", "book__name")
# <QuerySet [
# {'id': 4, 'title': '北京', 'book__name': 'ABC6'},
# {'id': 4, 'title': '北京', 'book__name': 'ABC7'}
# ]>
print(res)
# -- 正向
res = models.Book.objects.filter(publish__title="北京").values("publish__id", "publish__title", "name")
# <QuerySet [
# {'publish__id': 4, 'publish__title': '北京', 'name': 'ABC6'},
# {'publish__id': 4, 'publish__title': '北京', 'name': 'ABC7'}
# ]>
print(res)
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
情况二
Book表中有两个外键都是与Publish表关联的.. 那就需要给外键设置related_name参数, 以便于在反向查询时用哪个外键!!!
"""
from django.db import models
class Publish(models.Model):
title = models.CharField(verbose_name="出版社名", max_length=32)
class Book(models.Model):
name = models.CharField(verbose_name="书名", max_length=32)
price = models.DecimalField(max_digits=8, decimal_places=2)
publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE, related_name='b1')
new_publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE, default=5, related_name='b2')
"""
from django.test import TestCase
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day05.settings')
import django
django.setup()
from apps.app01 import models
# -- 单表查询
res = models.Publish.objects.filter(title="北京").values("id", "title")
print(res) # <QuerySet [{'id': 4, 'title': '北京'}]>
# -- 反向
res = models.Publish.objects.filter(title="北京").values("id", "title", "b1")
print(res) # <QuerySet [{'id': 4, 'title': '北京', 'b1': 6}, {'id': 4, 'title': '北京', 'b1': 7}]>
res = models.Publish.objects.filter(title="北京").values("id", "title", "b1__name")
# <QuerySet [
# {'id': 4, 'title': '北京', 'b1__name': 'ABC6'},
# {'id': 4, 'title': '北京', 'b1__name': 'ABC7'}
# ]>
print(res)
# -- 正向
res = models.Book.objects.filter(publish__title="北京").values("publish__id", "publish__title", "name")
# <QuerySet [
# {'publish__id': 4, 'publish__title': '北京', 'name': 'ABC6'},
# {'publish__id': 4, 'publish__title': '北京', 'name': 'ABC7'}
# ]>
print(res)
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 多对多
通常情况下, 我们会手动创建第三张表, 通过第三张表正向进行查询, 这样比较清晰些!!
"""
from django.db import models
class Boy(models.Model):
name = models.CharField(max_length=32)
class Girl(models.Model):
name = models.CharField(max_length=32)
class B2G(models.Model):
bid = models.ForeignKey(to="Boy", on_delete=models.CASCADE)
gid = models.ForeignKey(to="Girl", on_delete=models.CASCADE)
addr = models.CharField(max_length=200)
"""
from django.test import TestCase
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day05.settings')
import django
django.setup()
from apps.app01 import models
# # -- 添加Boy表数据
# models.Boy.objects.create(name='张三')
# models.Boy.objects.create(name='李四')
# models.Boy.objects.create(name='王五')
#
# # -- 添加Girl表数据
# models.Girl.objects.bulk_create(
# objs=[models.Girl(name="小红"), models.Girl(name="小紫"), models.Girl(name="小粉")],
# batch_size=2, # 共需查询数据库的次数: len(objs) / batch_size
# )
#
# # -- 添加B2G表的数据
# models.B2G.objects.create(bid_id=1, gid_id=1, addr="北京")
# models.B2G.objects.create(bid_id=1, gid_id=2, addr="上海")
# models.B2G.objects.create(bid_id=2, gid_id=2, addr="广州")
# b_obj = models.Boy.objects.filter(name="李四").first()
# g_obj = models.Girl.objects.filter(name="小粉").first()
# models.B2G.objects.create(bid=b_obj, gid=g_obj, addr="深圳")
# -- 查询所有人的约会记录
qs = models.B2G.objects.all().select_related('bid', 'gid') # B2G & Boy & Girl
# <QuerySet [<B2G: B2G object (1)>, <B2G: B2G object (2)>, <B2G: B2G object (3)>, <B2G: B2G object (4)>]>
print(qs)
for item in qs:
"""
1 张三 小红 北京
2 张三 小紫 上海
3 李四 小紫 广州
4 李四 小粉 深圳
"""
# 注:因为使用了select_related 所以这里的item.bid.name不会做格外的跨表的查询操作.
print(item.id, item.bid.name, item.gid.name, item.addr)
# -- 查询李四的所有约会记录
qs = models.B2G.objects.filter(bid__name="李四").select_related('gid') # B2G & Boy & Girl
print(qs) # <QuerySet [<B2G: B2G object (3)>, <B2G: B2G object (4)>]>
for item in qs:
"""
3 李四 小紫 广州
4 李四 小粉 深圳
"""
print(item.id, item.bid.name, item.gid.name, item.addr)
# -- 查询李四的所有约会记录,需要指定字段
# value_list同理.
qs = models.B2G.objects.filter(bid__name="李四").values("id", "bid__name", "gid__name", "addr")
"""
<QuerySet [
{'id': 3, 'bid__name': '李四', 'gid__name': '小紫', 'addr': '广州'},
{'id': 4, 'bid__name': '李四', 'gid__name': '小粉', 'addr': '深圳'}
]>
"""
print(qs)
# 若想通过Girl表来查李四的所有约会记录呢?? 反向正向都会涉及,不清晰
# b2g反向到了第三张表B2G,b2g__bid拿到了第三张表的bid字段,该字段是外键,b2g__bid__name正向查询到了Boy表的name字段..
qs = models.Girl.objects.filter(b2g__bid__name="李四"). \
values("b2g__id", "b2g__bid__name", "b2g__gid__name", "b2g__addr")
"""
<QuerySet [
{'b2g__id': 3, 'b2g__bid__name': '李四', 'b2g__gid__name': '小紫', 'b2g__addr': '广州'},
{'b2g__id': 4, 'b2g__bid__name': '李四', 'b2g__gid__name': '小粉', 'b2g__addr': '深圳'}
]>
"""
print(qs)
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# 一对一
一对一的本质就是一对多+unique
"""
from django.db import models
class User(models.Model):
name = models.CharField(max_length=32)
class UserDetail(models.Model):
user = models.OneToOneField(to=User, on_delete=models.CASCADE)
phone = models.CharField(max_length=200)
"""
from django.test import TestCase
import os
if __name__ == "__main__":
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'day05.settings')
import django
django.setup()
from apps.app01 import models
models.User.objects.create(name="张三")
models.User.objects.create(name="李四")
models.UserDetail.objects.create(phone="123456", user_id=1)
obj = models.User.objects.filter(name="李四").first()
models.UserDetail.objects.create(phone="123456", user=obj)
# -- 正向
obj = models.UserDetail.objects.filter(id=2).select_related("user").first()
print(obj.user.name) # 李四
print(obj) # UserDetail object (2)
print(obj.phone) # 123456
res = models.UserDetail.objects.filter(id=2).values("user__name", "phone")
print(res) # <QuerySet [{'user__name': '李四', 'phone': '123456'}]>
# -- 反向
# select_related主动联表,在反向查询时提高效率.
obj = models.User.objects.filter(name="李四").select_related('userdetail').first()
# 对象.与对象所在表有一对一外键关联的表的表名小写 --> 得到关联关系表的对象
# !!! 注:除了1:1,其它的1:n、n:n,`对象.`的反向关联操作 会报错!!!
print(obj.name) # 李四
print(obj.userdetail) # UserDetail object (2)
print(obj.userdetail.phone) # 123456
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44