groupby多个字段性能_使用SQL理解Django中的Group By

eac5800c991669239e72a6671106296d.png

聚合在任何类型的ORM中都是混乱的根源,Django也不例外。其文档提供了各种示例和备忘表,演示了如何使用ORM对数据进行分组和聚合,但我决定从另一个角度来处理这个问题。

在本文中,我将查询集和SQL并排放在一起。如果你更熟悉SQL,那么这就是你的Django GROUP BY速查表。

29b288bd63c5a48d44e6e88d764bb17b.png

如何在Django中进行分组

为了演示不同的GROUP BY查询,我将使用Django内置Django .contrib.auth应用程序中的模型。

b1675a855672d3a07b8bd538fbd70b19.png

Django ORM会生成带有长别名的SQL语句。为了简单起见,我将展示一个Django所执行操作的清理后的、但等效的版本。

SQL 日志 要查看Django实际执行SQL的过程,你可以在Django设置中打开SQL日志。

如何计数行

我们来看看我们有多少用户:

d0c9e23ca166c4c392e51fe2675a0bcc.png

计数行是如此常见,以至于Django在QuerySet上为它提供了一个函数。与我们接下来将看到的其他QuerySet不同,count会返回一个数字。

如何使用聚合函数

Django还提供了另外两种计数表中行数的方法。

我们将从aggregate开始:

23afddffd8ae67f88c04696c883f8498.png

为了使用aggregate,我们导入了聚合函数Count。该函数接受一个表达式进行计数。在本例中,我们使用主键列id的名称来计数表中的所有行。

聚合NULL 聚合会忽略NULL值。 有关聚合如何处理NULL的更多信息,请参见《SQL中12个常见错误和容易错过的优化机会》一文。

aggregate的结果是一个字典:

d65e5961d0f8d9e174751e9690d7d925.png

键的名称派生自字段名称和聚合名称。在本例中,它是id_count。最好不要依赖这种命名约定,而是提供你自己的名称:

34d25642415a737a091463e707add523.png

要aggregate的参数的名称也就是生成的字典中的键的名称。

如何按字段分组

使用aggregate,我们得到了对整个表应用聚合函数后的结果。这很有用,但通常我们希望对行的分组应用聚合过程。

让我们通过用户的活动状态来对它们进行计数:

d0fcdc8da89cd632d65ef49bf205709b.png

这次我们使用了函数annotate。我们使用values和annotate的一个组合去生成一个GROUP BY :

  • values('is_active'): 对什么进行按字段分组

  • annotate(total=Count('id')): 对什么进行聚合 

顺序很重要:在annotate之前对values的调用失败的话将不会产生聚合结果。

与aggregate一样,要annotate的参数的名称是经过计算的的QuerySet结果中的键。在本例中是total。

如何使用Group By筛选一个QuerySet

要对一个筛选后的查询应用聚合,你可以在查询中的任何位置使用filter。例如,仅根据员工用户的活动状态来对他们进行计数:

78378576ec1f049d72ca621956e4eca9.png

如何使用Group By对一个QuerySet进行排序

就像筛选器一样,你可以在查询的任何地方使用order_by来对一个查询集进行排序:

fde30e7be313d80fd5c91aced4c1ce62.png

注意,你可以按照Group By键和聚合字段对该查询集进行排序。

如何合并多个聚合

要生成同一个分组的多个聚合,请添加多个注解:

f5465a3c59598f79847729c13b54aad9.png

该查询将生成活动用户和非活动用户的数量,以及用户加入每个组的最后日期。

如何按多个字段分组

就像执行多个聚合一样,我们可能也想要按照多个字段进行分组。例如,按活动状态和员工状态进行分组:

b8d0a57a1109fb6911d4e1d71925ab0d.png

这个查询的结果包括is_active、is_staff和每个分组中的用户数量。

如何按一个表达式分组

GROUP BY的另一个常见用例是按照一个表达式进行分组。例如,计数每年加入的用户数:

eb496d34d434f39c96d018304f9b69cf.png

请注意,为了获取数据的年份,我们在第一次values()调用中使用了特别的表达式__year。此查询的结果是一个字典,该字典的键名称将是date_joined__year。

有时,内置表达式还不够,你需要聚合一个更复杂的表达式。例如,按注册后已经登录的用户进行分组:

2c45dbaa0f85a452d9967fa050300659.png

这里的表达式相当复杂。我们首先使用annotate来构建这个表达式,并通过在接下来的values()调用中引用该表达式来将其标记为一个GROUP BY键。从这里开始,一切都是一样的了。

如何使用条件聚合

使用条件聚合,你只能聚合分组的一部分。当你有多个聚合时,条件就派上用场了。例如,通过注册的年份来计数员工和非员工用户:

317d6e725b1c707cca4a142c592dfc41.png

上面的SQL语句来自PostgreSQL,它和SQLite是目前唯一支持FILTER语法快捷方式(正式名称为“选择性聚合”)的数据库后端。对于其他数据库后端,ORM则使用CASE ... WHEN来代替。

提示 我之前写过关于聚合和筛选器的文章。 请参阅我的《处理数据库的9个Django提示》

如何使用Having

HAVING子句用于对一个聚合函数的结果进行筛选。例如,查找超过100个用户加入的年份:

a9d941940d8ba7b22087548ada9a19c5.png

注解后的字段total上的filter在生成的SQL中添加了一个HAVING子句。

如何按照不同之处分组

对于一些聚合函数,比如COUNT,有时最好是只计数不同的出现特征。例如,每个用户的活动状态有多少个不同的姓氏:

30935d601f2192e5919ea4a631c51acb.png

请注意Count调用中distinct=True的使用。

如何使用聚合字段创建表达式

聚合字段通常只是一个更大问题的第一步。例如,不同的姓氏除以用户活动状态的百分比是多少:

634e4ca4dd307f40ffc839600ddbb10d.png

第一个annotate()定义了聚合字段。第二个annotate()使用聚合函数来构造表达式。

如何跨关系分组

到目前为止,我们只在单个模型中使用了数据,但是聚合通常是跨关系使用的。比较简单一点的场景是一对一或外键关系。例如,假设我们有一个UserProfile,它与用户是一对一的关系,我们想要根据profile的类型来计数用户数量:

ad91839e5c8f24801226ea0013a85b56.png

就像GROUP BY表达式一样,在值中使用关系将会根据该关系字段进行分组。请注意,结果中的用户profile类型名称将会是“user_profile_ type”。

如何按多对多关系分组

一个更复杂的关系类型是多对多关系。例如,计数每个用户是多少个分组的成员:

04ca4e0047b7d797472ee9b53a6c31cc.png

一个用户可以是多个分组的成员。为了计数该用户所属分组的数量,我们在User模型中使用了相关的名称“groups”。如果我们没有显式地设置相关名称(也没有显式地禁用),Django将会自动以{related model model}_set的格式生成一个名称。例如,group_set。

延伸阅读

想要更深入地了解ORM和GROUP BY,请查看以下链接:

  • 《如何在Django中使用分组集》:一篇关于高级分组技术的文章,比如按cube分组、按rollup分组和按分组集分组。

  • 《如何在SQL中使用Group By获取一个分组中的第一个或最后一个值》:在PostgreSQL中使用数组的一个小技巧。

  • 《SQL中常见的12个错误和易错过的优化机会》:你在处理数据和编写SQL时需要知道的一些SQL注意事项。

  • 《Django聚合速查表页面》:如何执行常见的聚合查询。

英文原文:https://hakibenita.com/django-group-by-sql
译者:好酒不上头

3bb71d83822145b6277f209cf5678cbc.png

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值