首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Django中GROUP BY中批注的聚合

Django中GROUP BY中批注的聚合
EN

Stack Overflow用户
提问于 2017-03-25 03:23:46
回答 4查看 19.5K关注 0票数 40

更新

多亏了张贴的答案,我找到了一个更简单的方法来表述这个问题。原始问题可以在修订历史中看到。

问题所在

我正在尝试将SQL查询转换为Django,但得到了一个我不理解的错误。

这是我的Django模型:

class Title(models.Model):
  title_id = models.CharField(primary_key=True, max_length=12)
  title = models.CharField(max_length=80)
  publisher = models.CharField(max_length=100)
  price = models.DecimalField(decimal_places=2, blank=True, null=True)

我有以下数据:

publisher                    title_id      price  title
---------------------------  ----------  -------  -----------------------------------
New Age Books                PS2106         7     Life Without Fear
New Age Books                PS2091        10.95  Is Anger the Enemy?
New Age Books                BU2075         2.99  You Can Combat    Computer Stress!
New Age Books                TC7777        14.99  Sushi, Anyone?
Binnet & Hardley             MC3021         2.99  The Gourmet Microwave
Binnet & Hardley             MC2222        19.99  Silicon Valley   Gastronomic Treats
Algodata Infosystems         PC1035        22.95  But Is It User Friendly?
Algodata Infosystems         BU1032        19.99  The Busy Executive's   Database Guide
Algodata Infosystems         PC8888        20     Secrets of Silicon Valley

这是我想要做的:引入一个带注释的字段dbl_price,它的价格是价格的两倍,然后用publisher对结果查询集进行分组,并为每个出版商计算该出版商发布的所有图书的所有dbl_price值的总和。

执行此操作的SQL查询如下所示:

SELECT SUM(dbl_price) AS total_dbl_price, publisher
FROM (
  SELECT price * 2 AS dbl_price, publisher
  FROM title
) AS A 
GROUP BY publisher

期望的输出将是:

publisher                    tot_dbl_prices
---------------------------  --------------
Algodata Infosystems                 125.88
Binnet & Hardley                      45.96
New Age Books                         71.86 

Django查询

查询将如下所示:

Title.objects
 .annotate(dbl_price=2*F('price'))
 .values('publisher')
 .annotate(tot_dbl_prices=Sum('dbl_price'))

但是给出了一个错误:

KeyError: 'dbl_price'. 

这表明它在查询集中找不到字段dbl_price

错误的原因

下面是这个错误发生的原因:the documentation says

您还应该注意到,average_rating已显式包含在要返回的值列表中。这是必需的,因为values()和annotate()子句的顺序。

如果values()子句在annotate()子句之前,则所有批注都将自动添加到结果集中。但是,如果在annotate()子句之后应用values()子句,则需要显式包含聚合列。

因此,在聚合中找不到dbl_price,因为它是由先前的annotate创建的,但没有包含在values()中。

但是,我也不能将它包含在values中,因为我想使用values (后面跟着另一个annotate)作为分组设备,因为

如果values()子句在annotate()之前,则将使用values()子句所描述的分组来计算批注。

这是Django implements SQL GROUP BY的基础。这意味着我不能在values()中包含dbl_price,因为分组将基于字段publisherdbl_price的唯一组合,而我只需要按publisher分组。

因此,下面的查询与上面的不同之处只在于我聚合了模型的price字段,而不是带注释的dbl_price字段,它实际上是有效的:

Title.objects
 .annotate(dbl_price=2*F('price'))
 .values('publisher')
 .annotate(sum_of_prices=Count('price'))

因为price字段在模型中,而不是带注释的字段,所以我们不需要在values中包含它来将它保留在查询集中。

问题是

所以,这里我们有了它:我需要将带注释的属性包含到values中,以将其保留在查询集中,但我不能这样做,因为values也用于分组(如果使用额外的字段,这将是错误的)。问题本质上是由于在Django中使用values的两种截然不同的方式,取决于上下文( values后面是否跟着annotate) -即(1)值提取(SQL普通SELECT列表)和(2)分组+组上的聚合(SQL GROUP BY) -在这种情况下,这两种方式似乎是冲突的。

我的问题是:有没有办法解决这个问题(不用退回到原始的sql)?

请注意:问题中的具体示例可以通过将所有annotate语句移到values后面来解决,这在几个答案中都有提到。但是,我更感兴趣的是将annotate语句放在values()之前的解决方案(或讨论),原因有三: 1.还有更复杂的示例,其中建议的解决方法不起作用。2.我可以想象这样的情况,带注释的查询集被传递给另一个函数,该函数实际上是GROUP BY的,因此我们唯一知道的就是带注释的字段的名称集及其类型。3.情况似乎很简单,如果之前没有注意到和讨论过values()的两种截然不同的用法的冲突,我会感到惊讶。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2017-07-05 07:39:02

更新:从Django2.1开始,一切都开箱即用。不需要解决方法,并且生成的查询是正确的。

这可能有点晚了,但我已经找到了解决方案(使用Django 1.11.1测试)。

问题是,调用提供分组所需的.values('publisher')会删除.values() 字段参数中未包含的所有注释。

而且我们不能将dbl_price包含到字段参数中,因为它会添加另一个GROUP BY语句。

中的解决方案是进行所有聚合,这首先需要带注释的字段,然后调用.values()并将聚合包含到字段的参数中(这不会添加GROUP BY,因为它们是聚合)。然后,我们应该使用任何表达式调用.annotate() -这将使django使用query - publisher中唯一的非聚合字段将GROUP BY语句添加到SQL查询中。

Title.objects
    .annotate(dbl_price=2*F('price'))
    .annotate(sum_of_prices=Sum('dbl_price'))
    .values('publisher', 'sum_of_prices')
    .annotate(titles_count=Count('id'))

这种方法唯一的缺点是-如果除了带有注释字段的聚合之外,不需要任何其他聚合-那么无论如何都必须包含一些聚合。如果没有最后一次调用.annotate() (它应该至少包含一个表达式!),Django就不会将GROUP BY添加到SQL query中。解决这个问题的一种方法是创建字段的副本:

Title.objects
    .annotate(dbl_price=2*F('price'))
    .annotate(_sum_of_prices=Sum('dbl_price')) # note the underscore!
    .values('publisher', '_sum_of_prices')
    .annotate(sum_of_prices=F('_sum_of_prices')

另外,请注意,您应该小心进行QuerySet排序。你最好在不带参数的情况下调用.order_by()来清除排序,或者使用GROUP BY字段调用。如果结果查询将包含按任何其他字段排序,则分组将是错误的。https://docs.djangoproject.com/en/1.11/topics/db/aggregation/#interaction-with-default-ordering-or-order-by

此外,您可能希望从输出中删除该伪注释,因此再次调用.values()。因此,最终的代码如下所示:

Title.objects
    .annotate(dbl_price=2*F('price'))
    .annotate(_sum_of_prices=Sum('dbl_price'))
    .values('publisher', '_sum_of_prices')
    .annotate(sum_of_prices=F('_sum_of_prices'))
    .values('publisher', 'sum_of_prices')
    .order_by('publisher')
票数 39
EN

Stack Overflow用户

发布于 2017-03-30 02:10:06

这在Django中的group_by works方式中是意料之中的。所有带注释的字段都添加到GROUP BY子句中。然而,我无法评论为什么它是这样写的。

你可以让你的查询像这样工作:

Title.objects
  .values('publisher')
  .annotate(total_dbl_price=Sum(2*F('price'))

这将生成以下SQL:

SELECT publisher, SUM((2 * price)) AS total_dbl_price
FROM title
GROUP BY publisher

只是碰巧在你的情况下有效。

我知道这可能不是您想要的完整解决方案,但是通过使用CombinedExpressions(我希望!),一些甚至复杂的注释也可以在这个解决方案中使用。

票数 4
EN

Stack Overflow用户

发布于 2017-03-25 05:20:43

你的问题来自values(),然后是annotate()。顺序很重要。在有关order of annotate and values clauses文档中对此进行了说明

.values('pub_id')使用pub_id限制查询集字段。所以你不能在income上注解

()方法接受可选的位置参数*,它们指定应将选择限制为的字段名称。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43007595

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档