Django之ORM跨表查询、join查询、聚合查询、分组查询
ORM跨表查询、join查询、聚合查询、分组查询、FQ查询
跨表查询分为两类: 基于对象查询(子查询) 基于双下划线查询(join查询)
1、基于对象的跨表查询(sql语句:子查询)
子查询: 基于一个查询结果作为另一个查询的条件
1.1 一对多
"""
正向查询:多找一,按字段
反向查询:一找多,按表名称小写_set,其中set表示集合的意思
"""
- 正向查询(按字段)
# 查询西游记出版社的名字和邮箱
book = Book.objects.get(title='西游记')
print(book.publish.name)
print(book.publish.email)
本质上翻译两条sql如下:
(0.000) SELECT "book_book"."id", "book_book"."title", "book_book"."pub_date", "book_book"."price", "book_book"."publish_id" FROM "book_book" WHERE "book_book"."title" = '西游记' LIMIT 21; args=('西游记',)
# 小橘子出版社
(0.000) SELECT "book_publish"."id", "book_publish"."name", "book_publish"."city", "book_publish"."email" FROM "book_publish" WHERE "book_publish"."id" = 3 LIMIT 21; args=(3,)
# 222@666.com
- 反向查询(按表名:book_set,按 表名称小写_set, set是集合的意思,返回queryset集合)
# 查询小橘子出版社的所有书籍
pub = Publish.objects.get(name='小橘子出版社')
print(pub.book_set.all()) # 与这个出版社关联的所有书籍,即返回一个queryset
# <QuerySet [<Book: 西游记>, <Book: 赳赳老秦>]>
print(pub.book_set.values('title', 'price'))
# <QuerySet [{'title': '西游记', 'price': Decimal('199.00')}, {'title': '赳赳老秦', 'price': Decimal('110.00')}]>
1.2 多对多
"""
正向查询:按字段
反向查询:按表名称小写_set,其中set表示集合的意思
"""
- 正向查询(按字段)
# 查询西游记所有作者的名字
book = Book.objects.get(title='西游记')
ret = book.authors.all().values('name')
print(ret) # <QuerySet [{'name': '强子'}, {'name': '乖乖快回家'}]>
- 反向查询(按表名:book_set,按 表名称小写_set, set是集合的意思,返回queryset集合)
# 查询 强子 所有出版过的书籍名称
author_obj = Author.objects.get(name='强子')
ret = author_obj.book_set.all()
print(ret) # <QuerySet [<Book: 西游记>, <Book: 三国志>]>
1.3 一对一
"""
正向查询:按字段,返回model对象,属性取值
反向查询:按表名称小写,不用加_set;一对一查询,仅返回一个对象;返回model对象,属性取值
"""
- 正向查询(按字段)
# 查询强子的手机号
author_obj = Author.objects.get(name='强子')
ret = author_obj.author_detail.telephone
print(ret) # 111
- 反向查询(注意返回一个model对象,所以不用加 _set !!!,查到之后对象的属性取值)
# 查询手机号为111的作者名字
tel_obj = AuthorDetail.objects.get(telephone='111')
ret = tel_obj.author.name
print(ret) # 强子
1.4 related_name 覆写 FOO_set
可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Book model 中做一下更改
publish = ForeignKey(Book, related_name='bookList')
接下来如下骚操作:
# 查询 人民出版社出版过的所有书籍
publish=Publish.objects.get(name="人民出版社")
book_list=publish.bookList.all() # 与人民出版社关联的所有书籍对象集合
2、基于双划线的跨表查询(sql:join语句)
join查询:按照哪两个字段拼表,两张表拼成一张大表,一定程度下会影响查询效率
2.0 join简介
- Publish表
id name email addr
1 北京出版社 123@qq.com bj
2 南京出版社 223@qq.com nj
- Book表
id title price pub_date publish_id
1 西游记 123 2012-12-12 1
2 三国演义 234 2012-12-12 1
3 三体 45 2012-3-12 1
4 水壶 45 2012-3-12 2
- join之后的大表 Book
id title price pub_date publish_id Publish.id Publish.name email addr
1 西游记 123 2012-12-12 1 1 北京出版社 123@qq.com bj
2 三国演义 234 2012-12-12 1 1 北京出版社 123@qq.com bj
3 三体 45 2012-3-12 1 1 北京出版社 123@qq.com bj
4 水壶 45 2012-3-12 2 2 南京出版社 223@qq.com nj
正跨:关联字段publish 所在 表Book 进行查询 其所关联的表Publish的记录
反跨:关联表Publish 查询 其关联字段publish所在的表Book的记录
2.1 一对多
"""
正向跨表(由关联字段所在表查询其关联的表):按关联字段__查询字段
反向跨表:按表名称小写__查询字段
以谁为基表 没所谓
"""
- 正跨查询,按字段;格式:外键字段__跨表字段;返回一个集合列表 [{},{}]
ret = Book.objects.filter(title='西游记').values('publish__name', 'publish__email')
print(ret) # <QuerySet [{'publish__name': '小橘子出版社', 'publish__email': '222@666.com'}]>
ret = Book.objects.filter(publish__name='小橘子出版社').values('title')
print(ret) # <QuerySet [{'title': '西游记'}, {'title': '赳赳老秦'}]>
- 反跨查询,按表名称;格式:小写表名称__跨表字段;返回一个集合列表 [{},{}]
ret = Publish.objects.filter(book__title='西游记').values('name', 'email')
print(ret) # <QuerySet [{'name': '小橘子出版社', 'email': '222@666.com'}]>
ret = Publish.objects.filter(name='小橘子出版社').values('book__title')
print(ret) # <QuerySet [{'book__title': '西游记'}, {'book__title': '赳赳老秦'}]>
2.2 多对多
关于多对多的join,sql中是三张表(Book、Author、book_authors第三张关联id表)拼接而成一张大表
- 正跨查询
"""
正向跨表(由关联表字段所在表查询其关联的表):按关联表字段__查询字段
反向跨表:按表名称小写__查询字段
以谁为基表 没所谓
"""
# 查询西游记所有作者的名字
ret = Book.objects.filter(title='西游记').values('authors__name')
print(ret) # <QuerySet [{'authors__name': '强子'}, {'authors__name': '乖乖快回家'}]>
ret = Author.objects.filter(book__title='西游记').values('name')
print(ret) # <QuerySet [{'name': '强子'}, {'name': '乖乖快回家'}]>
- 反跨查询
# 查询 强子 所有出版过的书籍名称
ret = Author.objects.filter(name='强子').values('book__title')
print(ret) # <QuerySet [{'book__title': '西游记'}, {'book__title': '三国志'}]>
ret = Book.objects.filter(authors__name='强子').values('title')
print(ret) # <QuerySet [{'title': '西游记'}, {'title': '三国志'}]>
2.3 一对一
"""
正向跨表(由关联字段所在表查询其关联的表):按关联字段__查询字段
反向跨表:按表名称小写__查询字段
以谁为基表 没所谓
"""
- 正跨查询
# 查询强子的手机号
ret = Author.objects.filter(name='强子').values('author_detail__telephone')
print(ret) # <QuerySet [{'author_detail__telephone': 111}]>
ret = AuthorDetail.objects.filter(telephone='111').values('author__name')
print(ret) # <QuerySet [{'author__name': '强子'}]>
- 反跨查询
# 查询手机号为111的作者名字
ret = AuthorDetail.objects.filter(author__name='强子').values('telephone')
print(ret) # <QuerySet [{'telephone': 111}]>
ret = Author.objects.filter(author_detail__telephone='111').values('name')
print(ret) # <QuerySet [{'name': '强子'}]>
2.4 related_name 覆写 FOO_set
反向查询时,如果定义了related_name ,则用related_name替换表名,例如:
# 练习: 查询人民出版社出版过的所有书籍的名字与价格(一对多)
# 反向查询 不再按表名:book,而是 related_name:bookList
queryResult=Publish.objects
.filter(name="人民出版社")
.values_list("bookList__title","bookList__price")
3、 跨表查询进阶
3.1 多个跨表嵌套
- 正向跨表
# 跨表查询小橘子出版社出版过的所有书籍名字以及作者的姓名
ret = Book.objects.filter(publish__name='小橘子出版社').values('title', 'authors__name')
print(ret)
# <QuerySet [{'title': '西游记', 'authors__name': '强子'}, {'title': '西游记', 'authors__name': '乖乖快回家'}, {'title': '赳赳老秦', 'authors__name': '大猩猩'}, {'title': '赳赳老秦', 'authors__name': '马大哈'}]>
- 反向跨表
# 跨表查询小橘子出版社出版过的所有书籍名字以及作者的姓名
ret = Publish.objects.filter(name='小橘子出版社').values('book__title', 'book__authors__name')
print(ret)
# <QuerySet [{'book__title': '西游记', 'book__authors__name': '强子'}, {'book__title': '西游记', 'book__authors__name': '乖乖快回家'}, {'book__title': '赳赳老秦', 'book__authors__name': '大猩猩'}, {'book__title': '赳赳老秦', 'book__authors__name': '马大哈'}]>
3.2 连续跨表查询
- 查询手机号以111开头的作者出版过的所有书籍名称以及出版社名称
# 方式1:
query_ret = Book.objects.filter(authors__author_detail__telephone__regex=r'^111').\
values_list('title', 'publish__name')
print(query_ret) # <QuerySet [('西游记', '小橘子出版社'), ('三国志', '小苹果出版社')]>
# 方式2:
ret = Author.objects\
.filter(author_detail__telephone__regex=r'^111')\
.values('book__title', 'book__publish__name')
print(ret)
# <QuerySet [{'book__title': '西游记', 'book__publish__name': '小橘子出版社'}, {'book__title': '三国志', 'book__publish__name': '小苹果出版社'}]>
4、F查询 与 Q查询
4.0 Book表模型
class Book(models.Model):
# id = models.AutoField(primary_key=True)
title = models.CharField(max_length=32)
pub_date = models.DateTimeField()
price = models.DecimalField(max_digits=9, decimal_places=2) # 9999999.99
keep_nums = models.IntegerField(default=0) # 收藏数
comment_nums = models.IntegerField(default=0) # 评论数
# 与Publish建立一对多的关系,外键字段ForeignKey会生成publisher_id建立在多的一方
# publish = models.ForeignKey(to="Publish", to_field="pk", on_delete=models.CASCADE) # 级联删除
publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE) # 级联删除
# 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表book_authors实现多对多的关系
authors = models.ManyToManyField(to='Author')
def __str__(self):
return self.title
4.1 F 查询
表内的两个字段的值如何进行比较??且看Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
F(‘字段名’) 表示字段含义并非命名空间内变量的意思
from django.db.models import F
# 查询 评论数 大于 收藏数 的书籍
books = Book.objects.filter(comment_nums__gt=F('keep_nums'))
print(books) # <QuerySet [<Book: 赳赳老秦>]>
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作
# 查询评论数大于收藏数2倍的书籍
Book.objects.filter(comment_nums__gt=F('keep_nums')*2)
修改操作也可以使用F函数,比如将每一本书的价格提高30元:
Book.objects.all().update(price=F("price")+30)
4.2 Q 查询
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。
Q(字段条件) 进行 与或非 的 多条件过滤
与: &
或: |
非: ~
from django.db.models import Q
Q(title__startswith='Py')
Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
# 查询 价格 大于 100 或 评论数 大于 3000
books = Book.objects.filter(Q(price__gt=100)|Q(comment_nums__gt=3000))
print(books.query)
print(books) # <QuerySet [<Book: 三国志>, <Book: 西游记>, <Book: 赳赳老秦>]>
等同于下面的SQL WHERE 子句:
WHERE price > 100 OR comment_nums =3000
可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用 ~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询
bookList=Book.objects.filter(Q(authors__name="yuan") & ~Q(pub_date__year=2020)).values_list("title")
查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将”AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:
bookList=Book.objects.filter(Q(pub_date__year=2016) | Q(pub_date__year=2017),
title__icontains="python"
)
5、聚合与分组查询
5.1 聚合查询
aggregate(*args, **kwargs)
# 计算所有图书的平均价格
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}
如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
>>> from django.db.models import Avg, Max, Min
>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
5.2 分组查询
5.2.1 分组介绍
###################################--单表分组查询--#######################################################
查询每一个部门名称以及对应的员工数
emp:
id name age salary dep
1 alex 12 2000 销售部
2 egon 22 3000 人事部
3 wen 22 5000 人事部
sql语句:
select dep,Count(*) from emp group by dep;
ORM:
emp.objects.values("dep").annotate(c=Count("id")
###################################--多表分组查询--###########################
多表分组查询:
查询每一个部门名称以及对应的员工数
emp:
id name age salary dep_id
1 alex 12 2000 1
2 egon 22 3000 2
3 wen 22 5000 2
dep
id name
1 销售部
2 人事部
emp-dep:
id name age salary dep_id id name
1 alex 12 2000 1 1 销售部
2 egon 22 3000 2 2 人事部
3 wen 22 5000 2 2 人事部
sql语句:
select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id
ORM:
dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c")
5.2.2 分组 类 实现
class Emp(models.Model):
name=models.CharField(max_length=32)
age=models.IntegerField()
salary=models.DecimalField(max_digits=8,decimal_places=2)
dep=models.CharField(max_length=32)
province=models.CharField(max_length=32)
annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。
5.2.3 查询练习
- (1) 统计每一个出版社的最便宜的书
publishList=Publish.objects.annotate(MinPrice=Min("book__price"))
for publish_obj in publishList:
print(publish_obj.name,publish_obj.MinPrice)
annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:
queryResult= Publish.objects
.annotate(MinPrice=Min("book__price"))
.values_list("name","MinPrice")
print(queryResult)
'''
SELECT "app01_publish"."name", MIN("app01_book"."price") AS "MinPrice" FROM "app01_publish"
LEFT JOIN "app01_book" ON ("app01_publish"."nid" = "app01_book"."publish_id")
GROUP BY "app01_publish"."nid", "app01_publish"."name", "app01_publish"."city", "app01_publish"."email"
'''
- (2) 练习:统计每一本书的作者个数
ret=Book.objects.annotate(authorsNum=Count('authors__name'))
- (3) 统计每一本以py开头的书籍的作者个数
queryResult=Book.objects
.filter(title__startswith="Py")
.annotate(num_authors=Count('authors'))
- (4) 统计不止一个作者的图书
queryResult=Book.objects
.annotate(num_authors=Count('authors'))
.filter(num_authors__gt=1)
- (5) 根据一本图书作者数量的多少对查询集 QuerySet进行排序
Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
- (6) 查询各个作者出的书的总价格
# 按author表的所有字段 group by
queryResult=Author.objects
.annotate(SumPrice=Sum("book__price"))
.values_list("name","SumPrice")
print(queryResult)
附录 git
https://github.com/sunny-future/BMS-demo.git