ORM跨表查询、join查询、聚合查询、分组查询、FQ查询
跨表查询分为两类: 基于对象查询(子查询) 基于双下划线查询(join查询)
1、基于对象的跨表查询(sql语句:子查询)
子查询: 基于一个查询结果作为另一个查询的条件
1.1 一对多
"""
正向查询:多找一,按字段
反向查询:一找多,按表名称小写_set,其中set表示集合的意思
"""
本质上翻译两条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')}]>
"""
正向查询:按字段
反向查询:按表名称小写_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对象,属性取值
"""
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 一对一
"""
正向跨表(由关联字段所在表查询其关联的表):按关联字段__查询字段
反向跨表:按表名称小写__查询字段
以谁为基表 没所谓
"""
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(字段条件) 进行 与或非 的 多条件过滤
与: &
或: |
非: ~
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')) 1 (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)