UPDATE
感谢发布的答案,我找到了一种更简单的方法来制定问题。原始问题可以在修订历史中看到。
我正在尝试将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
查询看起来像:
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
的基础。这意味着我不能在dbl_price
中包含values()
,因为那时分组将基于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
也用于分组(这将是一个额外的字段错误)。问题主要是由于在Django中使用values
的两种截然不同的方式,取决于上下文(values
是否跟随annotate
) - 这是(1)值提取(SQL plain SELECT
list)和(2)对组进行分组+聚合(SQL GROUP BY
) - 在这种情况下,这两种方式似乎发生冲突。
我的问题是:有没有办法解决这个问题(没有回到原始sql之类的东西)?
请注意:有问题的具体示例可以通过在annotate
之后移动所有values
语句来解决,这些语句由几个答案注明。但是,我对在annotate
之前保留values()
语句的解决方案(或讨论)更感兴趣,原因有三:1。还有更复杂的例子,建议的解决方法不起作用。 2.我可以想象这样的情况,其中已注释的查询集已传递给另一个实际执行GROUP BY的函数,因此我们唯一知道的是注释字段的名称集及其类型。 3.情况似乎非常简单,如果之前没有注意到并讨论过values()
的两种不同用途的冲突,我会感到惊讶。
这可能有点太晚了,但我找到了解决方案(用Django 1.11.1测试)。
问题是,调用.values('publisher')
(需要提供分组),删除所有注释,这些注释不包含在.values()
字段param中。
我们不能将dbl_price
包含在字段param中,因为它将添加另一个GROUP BY
语句。
要进行所有聚合的解决方案,首先需要带注释的字段,然后调用.values()
并将聚合包含到字段param(这不会添加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查询中。处理此问题的一种方法是创建您的字段的副本:
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')
这是从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(我希望!),这个解决方案中也可以容纳一些复杂的注释。
你的问题来自values()
跟随annotate()
。订单很重要。这在关于[注释和值子句的顺序]的文档中有解释(https://docs.djangoproject.com/en/1.10/topics/db/aggregation/#order-of-annotate-and-values-clauses)
.values('pub_id')
使用pub_id
限制queryset字段。所以你不能在income
上注释
values()方法采用可选的位置参数* fields,它们指定SELECT应限制的字段名称。
@alexandr的这个解决方案正确地解决了它。
https://stackoverflow.com/a/44915227/6323666
你需要的是这个:
from django.db.models import Sum
Title.objects.values('publisher').annotate(tot_dbl_prices=2*Sum('price'))
理想情况下,我通过先将它们相加然后将其加倍来扭转这种情况。你试图将它加倍然后总结。希望这很好。