更新
感谢发布的答案,我找到了一种更简单的方法来表述问题。原题可在修改历史中查看。
问题
我正在尝试将 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
You should also note that average_rating has been explicitly included in the list of values to be returned. This is required because of the ordering of the values() and annotate() clause.
If the values() clause precedes the annotate() clause, any annotations will be automatically added to the result set. However, if the values() clause is applied after the annotate() clause, you need to explicitly include the aggregate column.
因此,dbl_price
无法在聚合中找到,因为它是由先前的 annotate
创建的,但未包含在 values()
。
但是,我也不能将它包含在 values
中,因为我想使用 values
(后跟另一个 annotate
)作为分组设备,因为
If the values() clause precedes the annotate(), the annotation will be computed using the grouping described by the values() clause.
Django的基础是怎样的implements SQL GROUP BY
.这意味着我不能在 values()
中包含 dbl_price
,因为这样分组将基于 publisher
和dbl_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
也是用于分组(额外字段会出错)。问题本质上是由于 values
在 Django 中使用的两种非常不同的方式,具体取决于上下文(values
是否后跟 annotate
) - 这是 (1) 值提取(SQL plain SELECT
list)和 (2) 分组 + 对组的聚合(SQL GROUP BY
) - 在这个如果这两种方式似乎冲突。
我的问题是:有什么方法可以解决这个问题(无需回退到原始 sql 之类的东西)吗?
请注意:有问题的具体示例可以通过将所有 annotate
语句移动到 values
之后来解决,这已被多个答案指出。但是,我对将 annotate
语句保留在 values()
之前的解决方案(或讨论)更感兴趣,原因有以下三个:1. 还有更多复杂的示例,其中建议的解决方法不起作用。 2. 我可以想象这样的情况,其中带注释的查询集已传递给另一个函数,该函数实际上执行 GROUP BY,因此我们唯一知道的是带注释的字段的名称集及其类型。 3. 情况似乎很简单,如果以前没有注意到和讨论过 values()
的两种不同用法的冲突,我会感到很惊讶。
最佳答案
更新:从 Django 2.1 开始,一切都开箱即用。无需解决方法,生成的查询是正确的。
这可能有点太晚了,但我找到了解决方案(使用 Django 1.11.1 测试)。
问题是,调用提供分组所需的 .values('publisher')
会删除所有未包含在 .values()
中的注释> 字段参数。
而且我们不能将 dbl_price
包含到 fields 参数中,因为它会添加另一个 GROUP BY
语句。
所有聚合的解决方案,首先需要带注释的字段,然后调用 .values()
并将该聚合包含到 fields 参数(这不会添加GROUP BY
,因为它们是聚合)。
然后我们应该用任何表达式调用 .annotate()
- 这将使 django 使用查询中唯一的非聚合字段将 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 查询中。处理此问题的一种方法是创建您的字段的副本:
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')
关于sql - Django 中 GROUP BY 注释的聚合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43007595/