更新
多亏了张贴的答案,我找到了一个更简单的方法来表述这个问题。原始问题可以在修订历史中看到。
问题所在
我正在尝试将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
,因为分组将基于字段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普通SELECT
列表)和(2)分组+组上的聚合(SQL GROUP BY
) -在这种情况下,这两种方式似乎是冲突的。
我的问题是:有没有办法解决这个问题(不用退回到原始的sql)?
请注意:问题中的具体示例可以通过将所有annotate
语句移到values
后面来解决,这在几个答案中都有提到。但是,我更感兴趣的是将annotate
语句放在values()
之前的解决方案(或讨论),原因有三: 1.还有更复杂的示例,其中建议的解决方法不起作用。2.我可以想象这样的情况,带注释的查询集被传递给另一个函数,该函数实际上是GROUP BY的,因此我们唯一知道的就是带注释的字段的名称集及其类型。3.情况似乎很简单,如果之前没有注意到和讨论过values()
的两种截然不同的用法的冲突,我会感到惊讶。
发布于 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')
发布于 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(我希望!),一些甚至复杂的注释也可以在这个解决方案中使用。
发布于 2017-03-25 05:20:43
你的问题来自values()
,然后是annotate()
。顺序很重要。在有关order of annotate and values clauses文档中对此进行了说明
.values('pub_id')
使用pub_id
限制查询集字段。所以你不能在income
上注解
()方法接受可选的位置参数*,它们指定应将选择限制为的字段名称。
https://stackoverflow.com/questions/43007595
复制相似问题