注意事项:
- 在同一个应用中重新跟新表时,建议删除该应用下migrations文件夹下对应的文件!
- 跨表分组查询之后可以取到任意字段
1.聚合函数
表模型请参考多表操作
数据准备以及路径配置等等请参考 基于对象的跨表查询
其他配置请参考前面几小节的博客
app01/views.py
from django.shortcuts import render,HttpResponse
from app01.models import *
def query(request):
"""跨表查询
1 基于对象查询
2 基于双下划线查询
3 聚合和分组查询
4 F 与 Q 查询
"""
# ================ 聚合查询 ================
"""
aggregate聚合函数:是QuerySet 的一个终止子句,返回值是一个字典,不再是QuerySet
* 它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。
* 键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。
* 如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一写参数。
"""
"""查询所有书籍的平均价格 原生SQL
select avg(price) from app01_book;
"""
# 需要导入相关的聚合函数
from django.db.models import Avg,Max,Min,Count
book_price_avg = Book.objects.all().aggregate(Avg("price"))
print(book_price_avg) # {\'price__avg\': 613.14}
# 可以自己定义一个查询结果的名字,如下
bpav = Book.objects.all().aggregate(avg__price=Avg("price"))
print(bpav)
# 可以同时查询多个聚合函数
aggre = Book.objects.all().aggregate(avg__price=Avg("price"),
max__price=Max(\'price\'), min_price=Min("price"), total_price=Count(\'price\'))
print(aggre)
# {\'avg__price\': 613.14, \'max__price\': Decimal(\'999.99\'), \'min_price\': Decimal(\'222.55\'), \'total_price\': 4}
return HttpResponse("OK")
2.单表下的分组查询
准备工作(详细步骤略,如果需要请参照Django博客其他文章)
- 终端Mysql新建一个数据库,配置setting.py数据库的名称
- 在app01/models.py创建Emp表并进行表迁移(python manage.py makemigrations / python manage.py migrate)
- 在Emp表插入数据。
app01/models.py
from django.db import models
class Emp(models.Model):
id = models.AutoField(primary_key=True)
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)
app01/views.py
from django.shortcuts import render,HttpResponse
from app01.models import * # 最好这样引入视图函数,避免models调用重名覆盖!
from django.db.models import Avg,Max,Min,Count
def query(request):
"""跨表查询
1 基于对象查询
2 基于双下划线查询
3 聚合和分组查询
4 F 与 Q 查询
"""
# ================== 分组查询 ===================
# annotate,返回值依然是queryset
"""
annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
而 aggregate() 是聚合所有的对象??
"""
# ======= 单表的分组查询
"""
app01_emp:
id name age salary dep province
1 alex 12 2000 教学部 四川
2 egon 22 3000 讲师部 上海
3 peiqi 22 5000 讲师部 北京
插入数据
Emp.objects.create(name="alex", age=12, salary=2000, dep="教学部", province="四川")
Emp.objects.create(name="egon", age=22, salary=3000, dep="讲师部", province="上海")
Emp.objects.create(name="peiqi", age=22, salary=5000, dep="讲师部", province="北京")
查询每一个部门名称以及对应的员工数 sql:
select Count(id) from app01_emp group by dep;
如何用ORM 语法进行分组查询:
单表模型.objects.values("group by的字段").annotate(聚合函数("统计字段"))
注意:在单表下按照主键进行group by 是没有任何意义的。
"""
# 示例一:查询每一个部门的名称以及员工的平均薪水
# select dep,Avg(salary) from app01_emp group by dep;
# 可以为分组自定义名字avg_salary ;返回值是QuerySet
res = Emp.objects.values("dep").annotate(avg_salary=Avg(\'salary\'))
print(res)
# <QuerySet [{\'dep\': \'教学部\', \'avg_salary\': 2000.0}, {\'dep\': \'讲师部\', \'avg_salary\': 4000.0}]>
# 示例二:查询每一个省份的名称以及对应的员工数
res = Emp.objects.values("province").annotate(count_number=Count("id"))
print(res)
# <QuerySet [{\'province\': \'四川\', \'count_number\': 1}, {\'province\': \'上海\', \'count_number\': 1}, {\'province\': \'北京\', \'count_number\': 1}]>
# 补充知识点
ret = Emp.objects.all()
# select * from app01_emp;
ret = Emp.objects.all().values(\'name\')
# 等同于 Emp.objects.values(\'name\')
# select name from app01_emp;
return HttpResponse("OK")
3.多表下的分组查询
表模型请参考多表操作
数据准备以及路径配置等等请参考 基于对象的跨表查询
其他配置请参考前面几小节的博客
app01/views.py
from django.shortcuts import render,HttpResponse
from app01.models import * # 最好这样引入视图函数,避免models调用重名覆盖!
from django.db.models import Avg,Max,Min,Count
def query(request):
"""跨表查询
1 基于对象查询
2 基于双下划线查询
3 聚合和分组查询
4 F 与 Q 查询
"""
# ================= 多表(跨表)分组查询 ================================
"""
思路:先join然后group py
跨表分组查询的实用模型
1:被分组字段所在的基表模型.objects.values("pk").annotate(聚合函数(\'关联表__统计字段\')).value("取其他字段"...)
其他玩法:被分组字段所在的基表模型.objects.annotate(聚合函数(\'关联表__统计字段\')).value("取其他字段"...)
-----.all().-----
"""
# 使用之前的orm_multi数据库中的数据
# 单表:查询每一个出版社出版的书籍个数
res = Book.objects.values("publish_id").annotate(Count(\'nid\'))
print(res)
# ===示例一:查询每一个出版社的名称以及出版社的书籍个数
"""原生SQL
select app01_publish.name,Count(app01_book.publish_id)
from app01_book
inner join app01_publish
on app01_book.publish_id = app01_publish.nid
group by app01_publish.name;
"""
# ORM实现跨表分组查询 (这里的values完成了select 和 join的功能)
res = Book.objects.values("publish__name").annotate(Count("publish_id"))
# 或者(好几没有写SQL,之前把Count搞错了,聚合函数是统计个数,不需被统计字段的值是一样的,因为它统计的是整个该对象下的该字段的个数)
res = Publish.objects.values("name").annotate(c_count=Count("book__title"))
print(res)
# <QuerySet [{\'publish__name\': \'成都中医药大学出版社\', \'publish_id__count\': 2}, {\'publish__name\': \'电子科技大学出版社\', \'publish_id__count\': 2}]>
"""有个小问题: 跨表分组查询之后可以取到任意字段
对于两个多对一关联表分组统计过后,如果想显示任意一张表的其他字段,也是可以的!()非严格模式,但是注意:这样操作
对于一对多查询,显示与 分组条件 在同一张表的其他字段是有意义的
但是如果显示 多关系 字段 与 聚合结果,使用跨表分组查询是多此一举的
如下:这样是多此一举"""
res = Publish.objects.values("nid").annotate(c_count=Count("book__title")).values("c_count","book__title")
print(res)
# ===示例二:查询每一个作者的名字以及出版过的书籍的最高价格
"""SQL
select app01_author.name,Max(app01_book.price)
from app01_book
inner join app01_book_authors
on app01_book.nid = app01_book_authors.book_id
inner join app01_author
on app01_author.nid = app01_book_authors.author_id
group by app01_author.nid;
"""
# 方式一
res = Book.objects.values("authors__nid").annotate(max_price=Max("price")).values("authors__name",\'max_price\')
print(res)
"""
<QuerySet [{\'authors__name\': \'alex\', \'max_price\': Decimal(\'999.99\')},
{\'authors__name\': \'egon\', \'max_price\': Decimal(\'999.99\')},
{\'authors__name\': \'peiqi\', \'max_price\': Decimal(\'888.88\')},
{\'authors__name\': None, \'max_price\': Decimal(\'222.55\')}]>
"""
# 方式二
res = Author.objects.values("nid").annotate(max_price=Max("book__price")).values("name","max_price")
print(res)
"""
<QuerySet [ {\'name\': \'alex\', \'max_price\': Decimal(\'999.99\')},
{\'name\': \'egon\', \'max_price\': Decimal(\'999.99\')},
{\'name\': \'peiqi\', \'max_price\': Decimal(\'888.88\')},
{\'name\': \'yuan\', \'max_price\': None}]>
"""
# === 示例三:查询每个书籍的名称以及对应的作者个数
res = Book.objects.values("title").annotate(count_author=Count("authors__nid"))
print(res)
"""{\'name\': \'peiqi\', \'max_price\': Decimal(\'888.88\')}, {\'name\': \'yuan\', \'max_price\': None}]>
<QuerySet [ {\'title\': \'Python\', \'count_author\': 2},
{\'title\': \'JavaScript\', \'count_author\': 0},
{\'title\': \'Vue\', \'count_author\': 1},
{\'title\': \'Java\', \'count_author\': 1}]>
原生SQL:
select app01_book.title,Count(app01_author.nid)
from app01_book
inner join app01_book_authors
on app01_book.nid = app01_book_authors.book_id
inner join app01_author
on app01_author.nid = app01_book_authors.author_id
group by app01_book.title
;
"""
# ======跨表分组查询的另一种玩儿法,可以根据多个字段组合进行分组
# 示例
res = Publish.objects.annotate(c_count=Count("book__title")).values("c_count","book__title")
# ===== 小练习
# 统计每一本以Py开头的书籍的作者的个数。
res = Book.objects.filter(title__startswith="Py").annotate(count_author=Count("authors__nid")).values("title",\'count_author\')
print(res)
# <QuerySet [{\'title\': \'Python\', \'count_author\': 2}]>
# 统计不止一个作者的书籍名称
res = Book.objects.values("pk").annotate(count_book=Count("authors__name")).filter(count_book__gt=1).values("title")
print(res) # <QuerySet [{\'title\': \'Python\'}]>
"""原生SQL
SELECT `app01_book`.`title`
FROM `app01_book`
LEFT OUTER JOIN `app01_book_authors`
ON (`app01_book`.`nid` = `app01_book_authors`.`book_id`)
LEFT OUTER JOIN `app01_author`
ON (`app01_book_authors`.`author_id` = `app01_author`.`nid`)
GROUP BY `app01_book`.`nid`
HAVING COUNT (`app01_author`.`name`) > 1
ORDER BY NULL
LIMIT 21;
"""
return HttpResponse("OK")
4.F查询与Q查询
F查询
- Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
- Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
- 修改操作也可以使用F函数,比如将每一本书的价格提高1元:
Q查询
- Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象
- Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询
- 查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。
app01/views.py
from django.shortcuts import render,HttpResponse
from app01.models import * # 最好这样引入视图函数,避免models调用重名覆盖!
from django.db.models import Avg,Max,Min,Count
def query(request):
# === F查询与Q查询 ===
# 为Book表出插入两个字段comment_num read_num (最好在终端中添加)
# 场景:比如要查询一个字段的值大于另一个字段值,查询Book 表comment_num > read_num 记录
# 荒唐的写法:Book.objects.filter(comment_num__gt=read_num) 不支持!
# F查询 将字段当成一个变量,可以查询
from django.db.models import F
res = Book.objects.filter(comment_num__gt=F("read_num")).values("title",\'comment_num\',\'read_num\')
print(res)
# 场景:把所有的书籍价格都提升1块钱
Book.objects.all().update(price=F("price") + 1)
# Q查询 将字段当成一个条件,可以嵌套
from django.db.models import Q
# 查询 title为Python 且 publish_id 为5 的nid
res = Book.objects.filter(title="Python", publish_id=5).values(\'nid\')
res = Book.objects.filter(Q(title="Python") & Q(publish_id=5)).values(\'nid\')
# 查询 title为Python 或者 publish_id 为5 的nid
res = Book.objects.filter(Q(title="Python") | Q(publish_id=5)).values(\'nid\')
# 查询 title不等于 Python的书籍 。~ 非操作
res = Book.objects.filter(~Q(title="Python")).values(\'title\')
print(res)
# 如果想过滤多个条件,要吧Q语句放在最前面
res = Book.objects.filter(~Q(title="Python"),price__gt=200).values(\'title\')
return HttpResponse("OK")