更新
多亏了发布的答案,我找到了解决问题的简单得多的方法。原始问题可以在修订历史记录中看到。
我正在尝试将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发布者发布的所有标题的所有值的总和。
dbl_price
publisher
执行此操作的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集中找到该字段。
这就是发生此错误的原因:文档说
您还应注意,average_rating已明确包含在要返回的值列表中。这是必需的,因为values()和annotate()子句的顺序。 如果values()子句位于annotate()子句之前,则所有注释将自动添加到结果集中。但是,如果values()子句在annotate()子句之后应用,则需要显式包括聚合列。
您还应注意,average_rating已明确包含在要返回的值列表中。这是必需的,因为values()和annotate()子句的顺序。
如果values()子句位于annotate()子句之前,则所有注释将自动添加到结果集中。但是,如果values()子句在annotate()子句之后应用,则需要显式包括聚合列。
因此,dbl_price无法在聚合中找到,因为它是由Prior创建的annotate,但未包含在中values()。
annotate
values()
但是,我也不能将其包括在内values,因为我想使用values(紧随其后的annotate)作为分组设备,因为
values
如果values()子句位于annotate()之前,则将使用values()子句描述的分组来计算注释。
这是Django实现SQLGROUP BY的基础。这意味着我不能包含dbl_priceinside values(),因为这样分组将基于字段publisher和的唯一组合dbl_price,而我publisher仅需按分组。
GROUP BY
因此,下面的查询实际上与上面的查询不同,因为我在模型的price字段而不是带注释的dbl_price字段上进行汇总:
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简单SELECT列表)和(2)分组+聚合组(SQL GROUP BY)-在这种情况下,这两种方式似乎冲突。
SELECT
我的问题是 :有什么办法可以解决此问题(没有回落到原始sql之类的东西)?
请注意: 可以通过将所有annotate语句移到后面来解决有问题的特定示例values,这已由多个答案指出。但是,由于以下三个原因,我对将annotate声明保留在前面的解决方案(或讨论)更感兴趣values():1.还有一些更复杂的示例,其中建议的解决方法不起作用。2.我可以想象一下这样一种情况,其中带注释的查询集已传递给另一个函数,该函数实际上执行GROUP BY,因此我们唯一了解的是带注释的字段的名称集及其类型。3.情况似乎非常简单,如果values()以前没有注意到和讨论过两种不同用法的冲突,那将令我感到惊讶。
更新:自Django 2.1起,一切均可用。 不需要任何解决方法,并且生成的查询是正确的。
这可能为时已晚,但是我找到了解决方案(已在Django 1.11.1上进行了测试)。
问题是,.values('publisher')提供分组所需的对的调用会删除.values() 字段 参数中未包括的所有注释。
.values('publisher')
.values()
而且我们不能dbl_price将param包含到 字段中 ,因为它将添加另一个GROUP BY语句。
进行所有聚合的解决方案,首先需要带注释的字段,然后调用.values()该聚合并将其包含到 字段 param中(这不会添加GROUP BY,因为它们是聚合)。然后,我们应该.annotate()使用ANY表达式进行调用-这将使django addGROUP BY语句使用query- publisher中 唯一的非聚合字段进行SQL查询。
.annotate()
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/zh- CN/1.11/topics/db/aggregation/#interaction-with-default-ordering-or-order- by
.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')