Django ORM – 多表实例(聚合与分组查询)

聚合查询(aggregate)

聚合查询函数是对一组值执行计算,并返回单个值。

有表结构如下:

id title price publish_id pub_date
3 菜鸟教程 200.00 1 2010-10-10
4 冲灵剑法 100.00 1 2004-04-04

detail.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{ res }}
</body>
</html>

计算所有图书的平均价格:

from django.db.models import Avg,Max,Min,Count,Sum  #   引入函数
...
def add_book(request):
    res = models.Book.objects.aggregate(Avg("price"))
    return render(request, "detail.html", {"res": res});

// 输出:{'price__avg': Decimal('150.000000')}

计算所有图书的数量、最贵价格和最便宜价格:

res=models.Book.objects.aggregate(c=Count("id"),max=Max("price"),min=Min("price"))
// 输出:{'c': 2, 'max': Decimal('200.00'), 'min': Decimal('100.00')}

小结:

  1. 聚合查询返回值的数据类型是字典。不能再使用 QuerySet 数据类型的一些 API 了。
  2. 聚合函数 aggregate() 是 QuerySet 的一个终止子句, 生成的一个汇总值,相当于 count()。日期数据类型(DateField)可以用 Max 和 Min。
  3. 返回的字典中:键的名称默认是(属性名称加上__聚合函数名),值是计算出来的聚合值。
    如果要自定义返回字典的键的名称,可以起别名:
    aggregate(别名 = 聚合函数名("属性名称"))

分组查询(annotate)

分组查询一般会用到聚合函数。

返回值:

  1. 分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典
  2. 分组后,用values_list取值,则返回值是 QuerySet 数据类型里面为一个个元组
    MySQL 中的 limit 相当于 ORM 中的 QuerySet 数据类型的切片。

annotate 里面放聚合函数。

  1. values 或者 values_list 放在 annotate 前面:values 或者 values_list 是声明以什么字段分组,annotate 执行分组。
  2. values 或者 values_list 放在annotate后面: annotate 表示直接以当前表的pk执行分组,values 或者 values_list 表示查询哪些字段, 并且要将 annotate 里的聚合函数起别名,在 values 或者 values_list 里写其别名。

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

res = models.Publish.objects.values("name").annotate(in_price = Min("book__price"))

// 输出:<QuerySet [{'name': '华山出版社', 'in_price': Decimal('100.00')}, {'name': '明教出版社', 'in_price': None}]>

统计每一本书的作者个数1:

res = models.Book.objects.annotate(c = Count("authors__name")).values("title","c")
// 输出:<QuerySet [{'title': '菜鸟教程', 'c': 2}, {'title': '冲灵剑法', 'c': 1}]>

统计每一本书的作者个数2:values_list

res = models.Book.objects.annotate(c = Count("authors__name")).values_list("title","c")
// 输出:<QuerySet [('菜鸟教程', 2), ('冲灵剑法', 1)]>

统计每一本以"菜"开头的书籍的作者个数:

models.Book.objects.filter(title__startswith="菜").annotate(c = Count("authors__name")).values("title","c")
// 输出:<QuerySet [{'title': '菜鸟教程', 'c': 2}]>

统计不止一个作者的图书名称:

res = models.Book.objects.annotate(c = Count("authors__name")).filter(c__gt=1).values("title","c")
// 输出:<QuerySet [{'title': '菜鸟教程', 'c': 2}]>

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

res = models.Book.objects.annotate(c = Count("authors__name")).order_by("-c").values("title","c")
// 输出:<QuerySet [{'title': '菜鸟教程', 'c': 2}, {'title': '冲灵剑法', 'c': 1}]>

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

res = models.Author.objects.annotate(all = Sum("book__price")).values("name","all")
// 输出:<QuerySet [{'name': '令狐冲', 'all': Decimal('300.00')}, {'name': '任我行', 'all': None}, {'name': '任盈盈', 'all': Decimal('200.00')}]>

小结:

  1. 使用 values => 字典,values_list => 元祖。
  2. value(或 value_list)在前,以values声明的字段分组,在后以pk分组。

F() 查询

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

F 动态获取对象字段的值,可以进行运算。
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取余的操作。
修改操作(update)也可以使用 F() 函数。

查询工资大于年龄的人:

from django.db.models import F
...
res = models.Emp.objects.filter(salary__gt=F("age")).values("name","age")
...

将每一本书的价格提高100元:

res = models.Book.objects.update(price=F("price")+100)

Q() 查询

Q(条件判断)
Q 对象可以使用 & | ~ (与 或 非)操作符进行组合。

查询价格小于 150 或者名称以菜开头的书籍的名称和价格。

rom django.db.models import Q
...
res=models.Book.objects.filter(Q(price__lt=150)|Q(title__startswith="菜")).values("title","price")
// 输出:<QuerySet [{'title': '菜鸟教程', 'price': Decimal('200.00')}, {'title': '冲灵剑法', 'price': Decimal('100.00')}]>

查询以"菜"结尾或者不是 2010 年 10 月份的书籍:

res = models.Book.objects.filter(Q(title__endswith="菜") | ~Q(Q(pub_date__year=2010) & Q(pub_date__month=10)))
// 输出:<QuerySet [{'title': '冲灵剑法', 'price': Decimal('100.00')}]>

查询出版日期是 2004 或者 1999 年,并且书名中包含有"菜"的书籍。
Q 对象和关键字混合使用,Q 对象要在所有关键字的前面:

res = models.Book.objects.filter(Q(pub_date__year=2004) | Q(pub_date__year=1999), title__contains="菜")
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容